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()), ); }