From a209fc65ae79a21654a249922191f4d7ec4b6478 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 4 Apr 2026 18:55:42 -0500 Subject: [PATCH] Restrict enrichment enqueue to T-Mobile CGNAT subnet (172.56.0.0/13) Store remote_ip in session, check subnet in LiveView mount before enqueuing weather/HRRR/terrain/solar enrichment jobs. --- .../live/contact_live/show.ex | 37 ++++++++++++++++--- lib/microwaveprop_web/router.ex | 6 +++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 7affd5c7..3b49a3cf 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -18,10 +18,14 @@ defmodule MicrowavepropWeb.ContactLive.Show do require Logger @earth_radius_m 6_371_000.0 + # T-Mobile CGNAT range — only enqueue enrichment jobs from this subnet + @enqueue_subnet {172, 56, 0, 0} + @enqueue_mask 13 @impl true - def mount(%{"id" => id}, _session, socket) do + def mount(%{"id" => id}, session, socket) do contact = id |> Radio.get_contact!() |> Radio.ensure_positions!() + can_enqueue = internal_network?(session) if connected?(socket) do Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:#{contact.id}") @@ -31,13 +35,16 @@ defmodule MicrowavepropWeb.ContactLive.Show do end weather = load_weather(contact) - solar = maybe_enqueue_solar(contact) + solar = if can_enqueue, do: maybe_enqueue_solar(contact), else: load_solar(contact) hrrr_path = Weather.hrrr_profiles_for_path(contact) hrrr = List.first(hrrr_path) - {hrrr, contact} = maybe_enqueue_hrrr(hrrr, contact) + + {hrrr, contact} = + if can_enqueue, do: maybe_enqueue_hrrr(hrrr, contact), else: {hrrr, contact} + terrain = Terrain.get_terrain_profile(contact.id) - contact = maybe_enqueue_terrain(terrain, contact) - contact = maybe_enqueue_weather(weather, contact) + contact = if can_enqueue, do: maybe_enqueue_terrain(terrain, contact), else: contact + contact = if can_enqueue, do: maybe_enqueue_weather(weather, contact), else: contact iemre = load_iemre(contact) elevation_profile = compute_elevation_profile(contact, hrrr_path, weather.soundings) propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, weather.soundings) @@ -389,6 +396,26 @@ defmodule MicrowavepropWeb.ContactLive.Show do |> Map.new() end + defp internal_network?(session) do + case session["remote_ip"] do + nil -> + false + + ip_str -> + case :inet.parse_address(String.to_charlist(ip_str)) do + {:ok, {a, b, c, d}} -> + ip_int = Bitwise.bsl(a, 24) + Bitwise.bsl(b, 16) + Bitwise.bsl(c, 8) + d + {sa, sb, sc, sd} = @enqueue_subnet + subnet_int = Bitwise.bsl(sa, 24) + Bitwise.bsl(sb, 16) + Bitwise.bsl(sc, 8) + sd + mask = Bitwise.bsl(0xFFFFFFFF, 32 - @enqueue_mask) + Bitwise.band(ip_int, mask) == Bitwise.band(subnet_int, mask) + + _ -> + false + end + end + end + defp load_solar(contact) do contact.qso_timestamp |> DateTime.to_date() diff --git a/lib/microwaveprop_web/router.ex b/lib/microwaveprop_web/router.ex index c72cc179..7bc12d41 100644 --- a/lib/microwaveprop_web/router.ex +++ b/lib/microwaveprop_web/router.ex @@ -4,12 +4,18 @@ defmodule MicrowavepropWeb.Router do pipeline :browser do plug :accepts, ["html"] plug :fetch_session + plug :store_remote_ip plug :fetch_live_flash plug :put_root_layout, html: {MicrowavepropWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end + defp store_remote_ip(conn, _opts) do + ip_str = conn.remote_ip |> :inet.ntoa() |> to_string() + Plug.Conn.put_session(conn, :remote_ip, ip_str) + end + pipeline :api do plug :accepts, ["json"] end