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(