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:
parent
ac084b461a
commit
7e91de1956
1 changed files with 42 additions and 24 deletions
|
|
@ -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) {
|
||||
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])
|
||||
.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()),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue