Watch
1
0
Fork
You've already forked souveraine
0

sessiond: never reapply clat — it destroys the carrier's own routes

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.
This commit is contained in:
Fimeg 2026-08-01 14:58:34 -04:00
commit 7e91de1956

View file

@ -378,47 +378,65 @@ fn execute(shared: &Arc<Shared>, 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<Shared>, 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::<u32>().unwrap() < CLAT_DEFAULT_METRIC);
debug_assert!(METRIC_DEMOTED.parse::<u32>().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()),
);
}