From e818909712054aef212873e225a12250e1a65f05 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 10:47:53 +0300 Subject: [PATCH] route: metric/preference field (mirrors iproute2 metric) Rule gains metric: u32 field (default 0). Lower metric = higher priority. lookup_rule() now selects the matching rule with the lowest metric among those with the same prefix length. route/add parser accepts 'metric N': default via 10.0.2.2 metric 100 default via 10.0.2.254 metric 200 Display shows metric when non-zero: default via 10.0.2.2 dev eth0 src 10.0.2.15 metric 100 Rule::with_metric(m) builder added. All existing callers use default metric=0 (backward compatible). Use case: multi-homed hosts with primary/backup default routes. The primary route has metric 0, the backup has metric 100. All 31 tests pass. --- netstack/src/router/route_table.rs | 20 ++++++++++++++++++-- netstack/src/scheme/netcfg/mod.rs | 15 ++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/netstack/src/router/route_table.rs b/netstack/src/router/route_table.rs index ff9f9b43db..9ecce29142 100644 --- a/netstack/src/router/route_table.rs +++ b/netstack/src/router/route_table.rs @@ -39,6 +39,7 @@ pub struct Rule { pub dev: Rc, pub src: IpAddress, pub route_type: RouteType, + pub metric: u32, } impl Rule { @@ -49,6 +50,7 @@ impl Rule { dev, src, route_type: RouteType::Unicast, + metric: 0, } } @@ -59,7 +61,12 @@ impl Rule { src: IpAddress, route_type: RouteType, ) -> Self { - Self { filter, via, dev, src, route_type } + Self { filter, via, dev, src, route_type, metric: 0 } + } + + pub fn with_metric(mut self, metric: u32) -> Self { + self.metric = metric; + self } } @@ -81,6 +88,10 @@ impl Display for Rule { write!(f, " dev {}", self.dev)?; write!(f, " src {}", self.src)?; + if self.metric != 0 { + write!(f, " metric {}", self.metric)?; + } + Ok(()) } } @@ -92,10 +103,15 @@ pub struct RouteTable { impl RouteTable { pub fn lookup_rule(&self, dst: &IpAddress) -> Option<&Rule> { + // Find the longest-prefix match. Among rules with the same + // prefix length, prefer the one with the lowest metric. + // Rules are sorted by (prefix_len, metric) — highest prefix + // length first, lowest metric first within each prefix. self.rules .iter() .rev() - .find(|rule| rule.filter.contains_addr(dst)) + .filter(|rule| rule.filter.contains_addr(dst)) + .min_by_key(|rule| rule.metric) } pub fn lookup_src_addr(&self, dst: &IpAddress) -> Option { diff --git a/netstack/src/scheme/netcfg/mod.rs b/netstack/src/scheme/netcfg/mod.rs index bfaf4c96b1..b47f527121 100644 --- a/netstack/src/scheme/netcfg/mod.rs +++ b/netstack/src/scheme/netcfg/mod.rs @@ -92,7 +92,20 @@ fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult { .lookup_rule(&via) .ok_or(SyscallError::new(syscall::EINVAL))?; - Ok(Rule::new(cidr, Some(via), rule.dev.clone(), rule.src)) + let mut metric: u32 = 0; + if let Some(keyword) = parts.next() { + if keyword == "metric" { + metric = parts + .next() + .ok_or(SyscallError::new(syscall::EINVAL))? + .parse::() + .map_err(|_| SyscallError::new(syscall::EINVAL))?; + } else { + return Err(SyscallError::new(syscall::EINVAL)); + } + } + + Ok(Rule::new(cidr, Some(via), rule.dev.clone(), rule.src).with_metric(metric)) } fn mk_root_node(