mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
fix: use ip route replace to avoid connectivity gap on gateway changes
Replace the flush+add cycle in apply_policy_routing with ip route replace for each desired route, then delete stale routes. This eliminates the window where the per-interface routing table is empty, which caused temporary connectivity loss on other gateways.
This commit is contained in:
@@ -1018,18 +1018,16 @@ async fn apply_policy_routing(
|
|||||||
})
|
})
|
||||||
.copied();
|
.copied();
|
||||||
|
|
||||||
// Flush and rebuild per-interface routing table.
|
// Rebuild per-interface routing table using `ip route replace` to avoid
|
||||||
// Clone all non-default routes from the main table so that LAN IPs on
|
// the connectivity gap that a flush+add cycle would create. We replace
|
||||||
// other subnets remain reachable when the priority-75 catch-all overrides
|
// every desired route in-place (each replace is atomic in the kernel),
|
||||||
// default routing, then replace the default route with this interface's.
|
// then delete any stale routes that are no longer in the desired set.
|
||||||
Command::new("ip")
|
|
||||||
.arg("route")
|
// Collect the set of desired non-default route prefixes (the first
|
||||||
.arg("flush")
|
// whitespace-delimited token of each `ip route show` line is the
|
||||||
.arg("table")
|
// destination prefix, e.g. "192.168.1.0/24" or "10.0.0.0/8").
|
||||||
.arg(&table_str)
|
let mut desired_prefixes = BTreeSet::<String>::new();
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
if let Ok(main_routes) = Command::new("ip")
|
if let Ok(main_routes) = Command::new("ip")
|
||||||
.arg("route")
|
.arg("route")
|
||||||
.arg("show")
|
.arg("show")
|
||||||
@@ -1044,11 +1042,14 @@ async fn apply_policy_routing(
|
|||||||
if line.is_empty() || line.starts_with("default") {
|
if line.is_empty() || line.starts_with("default") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if let Some(prefix) = line.split_whitespace().next() {
|
||||||
|
desired_prefixes.insert(prefix.to_owned());
|
||||||
|
}
|
||||||
let mut cmd = Command::new("ip");
|
let mut cmd = Command::new("ip");
|
||||||
cmd.arg("route").arg("add");
|
cmd.arg("route").arg("replace");
|
||||||
for part in line.split_whitespace() {
|
for part in line.split_whitespace() {
|
||||||
// Skip status flags that appear in route output but
|
// Skip status flags that appear in route output but
|
||||||
// are not valid for `ip route add`.
|
// are not valid for `ip route replace`.
|
||||||
if part == "linkdown" || part == "dead" {
|
if part == "linkdown" || part == "dead" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1058,10 +1059,11 @@ async fn apply_policy_routing(
|
|||||||
cmd.invoke(ErrorKind::Network).await.log_err();
|
cmd.invoke(ErrorKind::Network).await.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add default route via this interface's gateway
|
|
||||||
|
// Replace the default route via this interface's gateway.
|
||||||
{
|
{
|
||||||
let mut cmd = Command::new("ip");
|
let mut cmd = Command::new("ip");
|
||||||
cmd.arg("route").arg("add").arg("default");
|
cmd.arg("route").arg("replace").arg("default");
|
||||||
if let Some(gw) = ipv4_gateway {
|
if let Some(gw) = ipv4_gateway {
|
||||||
cmd.arg("via").arg(gw.to_string());
|
cmd.arg("via").arg(gw.to_string());
|
||||||
}
|
}
|
||||||
@@ -1075,6 +1077,40 @@ async fn apply_policy_routing(
|
|||||||
cmd.invoke(ErrorKind::Network).await.log_err();
|
cmd.invoke(ErrorKind::Network).await.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete stale routes: any non-default route in the per-interface table
|
||||||
|
// whose prefix is not in the desired set.
|
||||||
|
if let Ok(existing_routes) = Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("show")
|
||||||
|
.arg("table")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.and_then(|b| String::from_utf8(b).with_kind(ErrorKind::Utf8))
|
||||||
|
{
|
||||||
|
for line in existing_routes.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with("default") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(prefix) = line.split_whitespace().next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if desired_prefixes.contains(prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("route")
|
||||||
|
.arg("del")
|
||||||
|
.arg(prefix)
|
||||||
|
.arg("table")
|
||||||
|
.arg(&table_str)
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure global CONNMARK restore rules in mangle PREROUTING (forwarded
|
// Ensure global CONNMARK restore rules in mangle PREROUTING (forwarded
|
||||||
// packets) and OUTPUT (locally-generated replies). Both are needed:
|
// packets) and OUTPUT (locally-generated replies). Both are needed:
|
||||||
// PREROUTING handles DNAT-forwarded traffic, OUTPUT handles replies from
|
// PREROUTING handles DNAT-forwarded traffic, OUTPUT handles replies from
|
||||||
|
|||||||
Reference in New Issue
Block a user