Skip to main content

net_lattice_model/
neighbor.rs

1use net_lattice_core::Id;
2
3use crate::IpAddress;
4use crate::mac::MacAddress;
5
6/// Identifies a [`NeighborEntry`].
7pub type NeighborId = Id<NeighborEntry>;
8
9/// The reachability state of a neighbor entry, per the Neighbor Unreachability
10/// Detection state machine shared (with minor naming differences) by Linux's
11/// `NUD_*` states, BSD/macOS's route-socket `RTF_*` flags on an ARP/NDP
12/// entry, and Windows's `NL_NEIGHBOR_STATE`.
13///
14/// `#[non_exhaustive]`: not every platform exposes every state (BSD/macOS's
15/// route-socket view collapses several of these into a single flags word),
16/// and `Unknown` covers whatever a platform can't map cleanly.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[non_exhaustive]
19pub enum NeighborState {
20    /// Address resolution is in progress; no link-layer address yet.
21    Incomplete,
22    /// Confirmed reachable within the last reachable-time window.
23    Reachable,
24    /// Reachability is unconfirmed but the entry has not yet been probed.
25    Stale,
26    /// About to send a reachability probe after a delay.
27    Delay,
28    /// Actively probing to reconfirm reachability.
29    Probe,
30    /// Address resolution failed.
31    Failed,
32    /// Manually configured; never expires or is probed.
33    Permanent,
34    /// The platform does not expose a separate reachability state.
35    Unknown,
36}
37
38/// A single entry in the system's neighbor table (ARP for IPv4, NDP for
39/// IPv6): the mapping from an on-link IP address to a link-layer (MAC)
40/// address, as observed or configured on a given interface.
41///
42/// `#[non_exhaustive]`: platforms carry different neighbor fields (Linux
43/// exposes NUD state and a routing-protocol-style entry `kind`; BSD/macOS
44/// expose only a flags word; Windows exposes `IsRouter`/reachability
45/// timestamps). Marking this non-exhaustive now means adding
46/// platform-specific fields later is not a breaking change for consumers who
47/// construct a `NeighborEntry` via [`NeighborEntry::new`] rather than a
48/// struct literal — see ARCHITECTURE.md's note on model extensibility.
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50#[non_exhaustive]
51pub struct NeighborEntry {
52    pub id: NeighborId,
53    /// The OS-level interface index this entry was observed on, the same raw
54    /// value `Interface::index`/`Route::interface_index` carry.
55    pub interface_index: u32,
56    pub address: IpAddress,
57    /// Absent for an entry still being resolved (`NeighborState::Incomplete`)
58    /// or one the platform reports without a link-layer address at all.
59    pub mac: Option<MacAddress>,
60    pub state: NeighborState,
61}
62
63impl NeighborEntry {
64    pub fn new(id: NeighborId, interface_index: u32, address: IpAddress) -> Self {
65        Self {
66            id,
67            interface_index,
68            address,
69            mac: None,
70            state: NeighborState::Unknown,
71        }
72    }
73
74    pub fn with_mac(mut self, mac: MacAddress) -> Self {
75        self.mac = Some(mac);
76        self
77    }
78
79    pub fn with_state(mut self, state: NeighborState) -> Self {
80        self.state = state;
81        self
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use net_lattice_ip::Ipv4Address;
89
90    #[test]
91    fn new_entry_has_no_mac_and_unknown_state() {
92        let entry = NeighborEntry::new(
93            NeighborId::new(1),
94            1,
95            IpAddress::from(Ipv4Address::new(192, 168, 1, 1)),
96        );
97        assert!(entry.mac.is_none());
98        assert_eq!(entry.state, NeighborState::Unknown);
99    }
100
101    #[test]
102    fn builder_methods_set_optional_fields() {
103        let mac = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
104        let entry = NeighborEntry::new(
105            NeighborId::new(1),
106            1,
107            IpAddress::from(Ipv4Address::new(192, 168, 1, 1)),
108        )
109        .with_mac(mac)
110        .with_state(NeighborState::Reachable);
111        assert_eq!(entry.mac, Some(mac));
112        assert_eq!(entry.state, NeighborState::Reachable);
113    }
114}