Skip to main content

net_lattice_model/
ifaddr.rs

1use net_lattice_core::Id;
2
3use crate::IpAddress;
4use crate::address::Network;
5use crate::interface::InterfaceId;
6use net_lattice_ip::Ipv4Address;
7
8/// Identifies an [`InterfaceAddress`].
9pub type InterfaceAddressId = Id<InterfaceAddress>;
10
11/// Intent to assign an IP address to an interface.
12///
13/// This is intentionally distinct from [`InterfaceAddress`]: callers do not
14/// manufacture an `InterfaceAddressId`, and the backend may report derived
15/// attributes after creation. `broadcast` is meaningful only for IPv4; when
16/// omitted, the platform selects its normal broadcast behaviour. Point-to-
17/// point peers, lifetimes, scope, and platform-specific flags are reserved
18/// for a future extension rather than silently guessed here.
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20#[non_exhaustive]
21pub struct NewInterfaceAddress {
22    /// The interface receiving the address.
23    pub interface_id: InterfaceId,
24    /// The requested address and prefix length.
25    pub address: Network,
26    /// An optional explicit IPv4 broadcast address.
27    pub broadcast: Option<Ipv4Address>,
28}
29
30impl NewInterfaceAddress {
31    pub fn new(interface_id: InterfaceId, address: Network) -> Self {
32        Self {
33            interface_id,
34            address,
35            broadcast: None,
36        }
37    }
38
39    pub fn with_broadcast(mut self, broadcast: Ipv4Address) -> Self {
40        self.broadcast = Some(broadcast);
41        self
42    }
43}
44
45/// An IP address assigned to a network interface, plus the prefix length it
46/// was assigned with (e.g. `192.168.1.10/24`).
47///
48/// Named `ifaddr`, not `address`, to avoid colliding with the `address`
49/// module's `IpAddress`/`Network` primitives — this is a distinct domain
50/// concept (an address *bound to an interface*), not another address
51/// representation.
52///
53/// `#[non_exhaustive]`: platforms carry different address fields (Linux
54/// exposes address `scope` and lifetime/deprecation timers via
55/// `IFA_CACHEINFO`; Windows exposes per-address `DadState`/`SkipAsSource`;
56/// BSD/macOS expose a combined flags word). Marking this non-exhaustive now
57/// means adding platform-specific fields later is not a breaking change for
58/// consumers who construct an `InterfaceAddress` via
59/// [`InterfaceAddress::new`] rather than a struct literal — see
60/// ARCHITECTURE.md's note on model extensibility.
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub struct InterfaceAddress {
64    pub id: InterfaceAddressId,
65    /// The OS-level interface index this address is assigned to, the same
66    /// raw value `Interface::index`/`Route::interface_index` carry.
67    pub interface_index: u32,
68    /// The assigned address and the prefix length it carries.
69    pub address: Network,
70    /// The IPv4 broadcast address for this subnet, if the platform reports
71    /// one. Always `None` for IPv6, which has no broadcast concept.
72    pub broadcast: Option<IpAddress>,
73}
74
75impl InterfaceAddress {
76    pub fn new(id: InterfaceAddressId, interface_index: u32, address: Network) -> Self {
77        Self {
78            id,
79            interface_index,
80            address,
81            broadcast: None,
82        }
83    }
84
85    pub fn with_broadcast(mut self, broadcast: IpAddress) -> Self {
86        self.broadcast = Some(broadcast);
87        self
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use net_lattice_ip::{Ipv4Address, Ipv4Network, Ipv4PrefixLength};
95
96    fn sample_network() -> Network {
97        Network::from(Ipv4Network::new(
98            Ipv4Address::new(192, 168, 1, 10),
99            Ipv4PrefixLength::new(24).unwrap(),
100        ))
101    }
102
103    #[test]
104    fn new_address_has_no_broadcast() {
105        let addr = InterfaceAddress::new(InterfaceAddressId::new(1), 1, sample_network());
106        assert!(addr.broadcast.is_none());
107    }
108
109    #[test]
110    fn with_broadcast_sets_the_field() {
111        let broadcast = IpAddress::from(Ipv4Address::new(192, 168, 1, 255));
112        let addr = InterfaceAddress::new(InterfaceAddressId::new(1), 1, sample_network())
113            .with_broadcast(broadcast);
114        assert_eq!(addr.broadcast, Some(broadcast));
115    }
116
117    #[test]
118    fn new_address_intent_keeps_interface_identity_separate_from_output_id() {
119        let intent = NewInterfaceAddress::new(InterfaceId::new(7), sample_network())
120            .with_broadcast(Ipv4Address::new(192, 168, 1, 255));
121        assert_eq!(intent.interface_id, InterfaceId::new(7));
122        assert_eq!(intent.broadcast, Some(Ipv4Address::new(192, 168, 1, 255)));
123    }
124}