Skip to main content

net_lattice_model/
route.rs

1use net_lattice_core::Id;
2
3use crate::address::{IpAddress, Network};
4
5/// A single routing table entry.
6///
7/// `#[non_exhaustive]`: platforms carry different route fields (Linux adds
8/// table/protocol/scope/type on top of destination/gateway/metric; Windows
9/// and BSD expose a narrower set — see ARCHITECTURE.md's note on model
10/// extensibility). Marking this non-exhaustive now means adding
11/// platform-specific fields later is not a breaking change for consumers
12/// who construct a `Route` via [`Route::new`] rather than a struct literal.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[non_exhaustive]
15pub struct Route {
16    pub id: RouteId,
17    pub destination: Network,
18    pub gateway: Option<IpAddress>,
19    pub metric: Option<u32>,
20    /// The outgoing interface, identified by its raw OS-level index.
21    ///
22    /// This is a raw `u32` rather than a typed `InterfaceId` because the
23    /// `interface` domain module doesn't exist yet (it lands in Stage 0.4
24    /// per ARCHITECTURE.md's Incremental Delivery Plan) — `Route` shouldn't
25    /// block on it just to express what the kernel already gives us
26    /// directly. Many routes are ambiguous or outright rejected by the
27    /// kernel without an explicit output interface (on-link routes,
28    /// multiple interfaces on the same subnet), so this isn't optional
29    /// polish: without it, a meaningful fraction of real routes can't be
30    /// added at all.
31    pub interface_index: Option<u32>,
32}
33
34/// Identifies a [`Route`].
35pub type RouteId = Id<Route>;
36
37impl Route {
38    pub fn new(id: RouteId, destination: Network) -> Self {
39        Self {
40            id,
41            destination,
42            gateway: None,
43            metric: None,
44            interface_index: None,
45        }
46    }
47
48    pub fn with_gateway(mut self, gateway: IpAddress) -> Self {
49        self.gateway = Some(gateway);
50        self
51    }
52
53    pub fn with_metric(mut self, metric: u32) -> Self {
54        self.metric = Some(metric);
55        self
56    }
57
58    pub fn with_interface_index(mut self, interface_index: u32) -> Self {
59        self.interface_index = Some(interface_index);
60        self
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use net_lattice_ip::{Ipv4Address, Ipv4Network, Ipv4PrefixLength};
68
69    fn destination() -> Network {
70        Network::from(Ipv4Network::new(
71            Ipv4Address::new(10, 0, 0, 0),
72            Ipv4PrefixLength::new(24).unwrap(),
73        ))
74    }
75
76    #[test]
77    fn new_route_has_no_gateway_or_metric() {
78        let route = Route::new(RouteId::new(1), destination());
79        assert!(route.gateway.is_none());
80        assert!(route.metric.is_none());
81    }
82
83    #[test]
84    fn builder_methods_set_optional_fields() {
85        let gateway = IpAddress::from(Ipv4Address::new(10, 0, 0, 1));
86        let route = Route::new(RouteId::new(1), destination())
87            .with_gateway(gateway)
88            .with_metric(100)
89            .with_interface_index(2);
90        assert_eq!(route.gateway, Some(gateway));
91        assert_eq!(route.metric, Some(100));
92        assert_eq!(route.interface_index, Some(2));
93    }
94}