From 7e91de195606bb9fef02ef57d4849257b14b499e Mon Sep 17 00:00:00 2001 From: Fimeg Date: Sat, 1 Aug 2026 14:58:34 -0400 Subject: [PATCH] =?UTF-8?q?sessiond:=20never=20reapply=20clat=20=E2=80=94?= =?UTF-8?q?=20it=20destroys=20the=20carrier's=20own=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on the phone. After one PreferLink the clat device was up with its 192.0.0.1/32 address and *zero* routes: no default, and no 205.151.11.13/32, which is the only path to the MMS proxy. ip route get 205.151.11.13 went out wlan0. Restored by restarting blueline-clat.service. nmcli device reapply resets a device to its connection's config, so it is destructive to precisely the routes a sidecar daemon owns - and the CLAT daemon owns all of clat's. TASK-49 acceptance #2 says carrier services ride the carrier; this broke it on the first action it ever took. The gsm connection was the wrong target too. The modem is v6-only and the v4 default for cellular is installed by the CLAT daemon as 'default dev clat scope link metric 2048', not by the gsm connection, so a route-metric set there is a number nothing reads. Wifi's metric is the only lever: under 2048 wifi wins, over it the carrier does. One connection modified, one device reapplied, and the carrier's routes left alone. --- src/sessiond/server.rs | 66 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/sessiond/server.rs b/src/sessiond/server.rs index 656d0f2..31ff859 100644 --- a/src/sessiond/server.rs +++ b/src/sessiond/server.rs @@ -378,47 +378,65 @@ fn execute(shared: &Arc, action: Action) { run_executor(shared, program, &args, label, action); } -/// Route metrics for the two bearers. +/// The cellular v4 default's metric, which is a fixed reference point rather +/// than something to set. /// -/// The numbers are NetworkManager's own defaults for the winning case (wifi -/// 600), with the loser pushed clear of NM's +20000 connectivity penalty so a -/// penalised-but-preferred link still beats the demoted one. That penalty is -/// what inverted the metrics on 2026-07-31: wifi went to 20600 and the clat -/// default at 2048 won, on a link that could not resolve. +/// The modem is v6-only; the v4 default for the carrier is installed by the +/// CLAT daemon on the `clat` tun as `default dev clat scope link metric 2048`, +/// not by the `gsm` connection. So the *only* lever needed is wifi's metric: +/// below 2048 it wins, above 2048 the carrier does. Setting anything on the +/// gsm connection would be adjusting a number nothing reads. +const CLAT_DEFAULT_METRIC: u32 = 2048; + +/// Wifi's metric when it should win: NetworkManager's own default, and +/// comfortably under the clat default. const METRIC_PREFERRED: &str = "600"; + +/// Wifi's metric when the carrier should win. Above 2048, and clear of NM's +/// +20000 connectivity penalty so a penalised link cannot accidentally land +/// back under the CLAT default. That penalty is what inverted things on +/// 2026-07-31: wifi went to 20600 and the clat default won on a link that +/// could not resolve. const METRIC_DEMOTED: &str = "30000"; /// Tell NetworkManager which link ordinary traffic should take. /// -/// Both connections are modified every time rather than only the winner: the -/// loser's metric is half the decision, and leaving it at whatever it happened -/// to be is how the original inversion survived. +/// **Only the wifi connection is touched, and only `wlan0` is reapplied.** An +/// earlier version also modified the `gsm` connection and reapplied `clat`, and +/// that reapply wiped every route the CLAT daemon had installed out-of-band — +/// including the `205.151.11.13/32` pin that is the only path to the MMS proxy. +/// Measured on the phone 2026-08-01: after one reapply the clat device was up +/// with an address and *zero* routes, and the carrier path was gone until +/// `blueline-clat.service` was restarted. `nmcli device reapply` resets a +/// device to its connection's config, so it is destructive to exactly the +/// routes a sidecar daemon owns. Do not reapply a device whose routes NM did +/// not write. fn apply_link_preference(shared: &Arc, bearer: crate::sessiond::bearer::Bearer) { use crate::sessiond::bearer::Bearer; - let (wifi_metric, wwan_metric) = match bearer { - Bearer::Wifi => (METRIC_PREFERRED, METRIC_DEMOTED), - Bearer::Cellular => (METRIC_DEMOTED, METRIC_PREFERRED), + debug_assert!(METRIC_PREFERRED.parse::().unwrap() < CLAT_DEFAULT_METRIC); + debug_assert!(METRIC_DEMOTED.parse::().unwrap() > CLAT_DEFAULT_METRIC); + + let metric = match bearer { + Bearer::Wifi => METRIC_PREFERRED, + Bearer::Cellular => METRIC_DEMOTED, }; - for (kind, metric) in [("802-11-wireless", wifi_metric), ("gsm", wwan_metric)] { - for conn in active_connections_of_type(kind) { - let _ = std::process::Command::new("nmcli") - .args(["connection", "modify", &conn, "ipv4.route-metric", metric]) - .status(); - } + for conn in active_connections_of_type("802-11-wireless") { + let _ = std::process::Command::new("nmcli") + .args(["connection", "modify", &conn, "ipv4.route-metric", metric]) + .status(); } // Reapply rather than up/down. Cycling the connection is what made the old // gate's corrections into NM events that re-entered it; `reapply` changes // the live config without a state transition, so nothing observing NM sees // a link flap. - for dev in ["wlan0", "clat"] { - let _ = std::process::Command::new("nmcli") - .args(["device", "reapply", dev]) - .status(); - } + let _ = std::process::Command::new("nmcli") + .args(["device", "reapply", "wlan0"]) + .status(); + shared.lock().device_state.record_error( "bearer", "prefer-link", - &format!("preferred {}", bearer.as_str()), + &format!("preferred {} (wifi route-metric {metric})", bearer.as_str()), ); }