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.
This commit is contained in:
Red Bear OS
2026-07-09 10:47:53 +03:00
parent 7370d6a818
commit e818909712
2 changed files with 32 additions and 3 deletions
+18 -2
View File
@@ -39,6 +39,7 @@ pub struct Rule {
pub dev: Rc<str>,
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<IpAddress> {
+14 -1
View File
@@ -92,7 +92,20 @@ fn parse_route(value: &str, route_table: &RouteTable) -> SyscallResult<Rule> {
.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::<u32>()
.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(