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}