Skip to main content

net_lattice_model/
interface.rs

1use std::fmt;
2
3use net_lattice_core::Id;
4
5use crate::mac::MacAddress;
6
7/// Identifies an [`Interface`].
8pub type InterfaceId = Id<Interface>;
9
10/// The kind of a network interface.
11///
12/// Platforms expose different classifications (Linux `ARPHRD_*`, Windows
13/// `IfType`, BSD/macOS `IFT_*`); this enum carries the subset that maps
14/// cleanly across all of them. It is `#[non_exhaustive]` so adding
15/// platform-specific kinds later is not a breaking change — see
16/// ARCHITECTURE.md's note on model extensibility.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[non_exhaustive]
19pub enum InterfaceKind {
20    /// Ethernet and other IEEE 802 MAC-layer interfaces.
21    Ethernet,
22    /// Wi-Fi / 802.11 wireless.
23    Wireless,
24    /// Loopback (`lo` / `Loopback Pseudo-Interface`).
25    Loopback,
26    /// Point-to-point tunnels and virtual links without a broadcast domain.
27    PointToPoint,
28    /// A bridge or other software switch aggregating member interfaces.
29    Bridge,
30    /// Anything not yet classified — the raw OS-supplied type code, when
31    /// available, is carried alongside for diagnostics.
32    Other(u32),
33}
34
35impl fmt::Display for InterfaceKind {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            InterfaceKind::Ethernet => write!(f, "ethernet"),
39            InterfaceKind::Wireless => write!(f, "wireless"),
40            InterfaceKind::Loopback => write!(f, "loopback"),
41            InterfaceKind::PointToPoint => write!(f, "point-to-point"),
42            InterfaceKind::Bridge => write!(f, "bridge"),
43            InterfaceKind::Other(code) => write!(f, "other({code})"),
44        }
45    }
46}
47
48/// The administrative state of an interface, as configured by the operator.
49///
50/// Distinct from [`OperationalState`]: an interface can be administratively
51/// up but operationally down (cable unplugged), or administratively down
52/// (and therefore always operationally down).
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54#[non_exhaustive]
55pub enum AdminState {
56    Up,
57    Down,
58    /// The platform does not expose a separate administrative state (e.g.
59    /// BSD/macOS route-socket interfaces report a single combined state).
60    Unknown,
61}
62
63/// The operational state of an interface, as observed from the link layer.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65#[non_exhaustive]
66pub enum OperationalState {
67    Up,
68    Down,
69    /// The interface is up but has no carrier / lower-layer connectivity.
70    NoCarrier,
71    /// The platform does not expose a separate operational state.
72    Unknown,
73}
74
75/// A network interface.
76///
77/// `#[non_exhaustive]`: platforms carry different interface fields (Linux
78/// exposes `carrier`, `txqueuelen`, `type`; Windows exposes `MediaType`,
79/// `OperationalStatus`; BSD/macOS expose a combined flags word). Marking
80/// this non-exhaustive now means adding platform-specific fields later is
81/// not a breaking change for consumers who construct an `Interface` via
82/// [`Interface::new`] rather than a struct literal — see ARCHITECTURE.md's
83/// note on model extensibility.
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85#[non_exhaustive]
86pub struct Interface {
87    pub id: InterfaceId,
88    /// The OS-level interface index (`ifindex` on Linux, `InterfaceIndex` on
89    /// Windows, `if_index` on BSD/macOS). This is the same raw value
90    /// `Route::interface_index` carries — kept here as the canonical home
91    /// now that the `interface` domain exists (Stage 0.4).
92    pub index: u32,
93    pub name: String,
94    pub kind: InterfaceKind,
95    pub mac: Option<MacAddress>,
96    pub mtu: Option<u32>,
97    pub admin_state: AdminState,
98    pub operational_state: OperationalState,
99}
100
101impl Interface {
102    pub fn new(id: InterfaceId, index: u32, name: impl Into<String>, kind: InterfaceKind) -> Self {
103        Self {
104            id,
105            index,
106            name: name.into(),
107            kind,
108            mac: None,
109            mtu: None,
110            admin_state: AdminState::Unknown,
111            operational_state: OperationalState::Unknown,
112        }
113    }
114
115    pub fn with_mac(mut self, mac: MacAddress) -> Self {
116        self.mac = Some(mac);
117        self
118    }
119
120    pub fn with_mtu(mut self, mtu: u32) -> Self {
121        self.mtu = Some(mtu);
122        self
123    }
124
125    pub fn with_admin_state(mut self, state: AdminState) -> Self {
126        self.admin_state = state;
127        self
128    }
129
130    pub fn with_operational_state(mut self, state: OperationalState) -> Self {
131        self.operational_state = state;
132        self
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn new_interface_has_no_optional_fields() {
142        let iface = Interface::new(InterfaceId::new(1), 1, "eth0", InterfaceKind::Ethernet);
143        assert!(iface.mac.is_none());
144        assert!(iface.mtu.is_none());
145        assert_eq!(iface.admin_state, AdminState::Unknown);
146        assert_eq!(iface.operational_state, OperationalState::Unknown);
147    }
148
149    #[test]
150    fn builder_methods_set_optional_fields() {
151        let mac = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
152        let iface = Interface::new(InterfaceId::new(1), 1, "eth0", InterfaceKind::Ethernet)
153            .with_mac(mac)
154            .with_mtu(1500)
155            .with_admin_state(AdminState::Up)
156            .with_operational_state(OperationalState::Up);
157        assert_eq!(iface.mac, Some(mac));
158        assert_eq!(iface.mtu, Some(1500));
159        assert_eq!(iface.admin_state, AdminState::Up);
160        assert_eq!(iface.operational_state, OperationalState::Up);
161    }
162
163    #[test]
164    fn kind_displays() {
165        assert_eq!(InterfaceKind::Ethernet.to_string(), "ethernet");
166        assert_eq!(InterfaceKind::Wireless.to_string(), "wireless");
167        assert_eq!(InterfaceKind::Loopback.to_string(), "loopback");
168        assert_eq!(InterfaceKind::PointToPoint.to_string(), "point-to-point");
169        assert_eq!(InterfaceKind::Bridge.to_string(), "bridge");
170        assert_eq!(InterfaceKind::Other(42).to_string(), "other(42)");
171    }
172}