rlldpctl/model.rs
1//! The ergonomic, safe-Rust view of what lldpd knows: interfaces and the
2//! neighbors discovered on them.
3
4use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5use std::sync::Arc;
6
7/// One local interface, as returned by [`crate::Client::interfaces`].
8///
9/// This is the cheap, no-neighbor-query view (`GET_INTERFACES`); pass its
10/// [`name`](Interface::name) to [`crate::Client::interface`] to get full
11/// details including discovered neighbors.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Interface {
14 pub name: String,
15 pub alias: Option<String>,
16}
17
18/// Full state of one local interface (`GET_INTERFACE`): its own identity plus
19/// every neighbor lldpd has heard from on it.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct InterfaceDetails {
22 pub name: String,
23 pub alias: Option<String>,
24 pub mac_address: [u8; 6],
25 /// The chassis lldpd advertises as *us* on this interface (i.e. what a
26 /// neighbor would see if it looked back at this machine).
27 pub local_chassis: Option<Arc<Chassis>>,
28 pub neighbors: Vec<Neighbor>,
29}
30
31/// One neighbor device seen on an interface.
32///
33/// `chassis` is reference-counted because lldpd deduplicates chassis
34/// internally: two neighbor entries reached via different protocols (say,
35/// LLDP and CDP) but originating from the same physical device share one
36/// `Chassis`. It's an `Arc` rather than a plain `Rc` so that values
37/// containing a `Neighbor` (e.g. `Vec<InterfaceDetails>`) stay `Send` -
38/// needed to `.await` calls that return them from inside a `Send`-bound
39/// future (an `#[async_trait]` method, a `tokio::spawn`ed task, ...), which
40/// is the whole point of this crate's `tokio`-feature async API.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Neighbor {
43 pub chassis: Arc<Chassis>,
44 pub port_id_subtype: PortIdSubtype,
45 /// Raw port identifier bytes; interpretation depends on `port_id_subtype`
46 /// (e.g. 6 raw bytes for `MacAddress`, a printable string for `IfName`).
47 pub port_id: Vec<u8>,
48 pub port_description: Option<String>,
49 /// Time-to-live announced for this neighbor entry, in seconds.
50 pub ttl: u16,
51}
52
53impl Neighbor {
54 /// Convenience accessor for the common case where `port_id` is a
55 /// printable string (true for `IfAlias`, `IfName`, `Local`, and often in
56 /// practice for others too). Returns `None` on invalid UTF-8.
57 pub fn port_id_str(&self) -> Option<&str> {
58 std::str::from_utf8(&self.port_id).ok()
59 }
60}
61
62/// One neighbor-table change, as pushed by [`crate::Subscription`]
63/// (`NOTIFICATION`, after a `SUBSCRIBE`).
64///
65/// lldpd sends one of these per changed neighbor on *any* interface - the
66/// protocol has no per-interface filtering, so check `interface` yourself if
67/// you only care about some of them.
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct NeighborChange {
70 pub interface: String,
71 pub interface_alias: Option<String>,
72 pub kind: NeighborChangeKind,
73 /// The neighbor this change is about. Upstream always populates this in
74 /// practice (see `src/daemon/lldpd.c`'s three call sites of
75 /// `levent_ctl_notify`), but nothing in the protocol *requires* it, so
76 /// this stays an `Option` rather than asserting it's always present.
77 pub neighbor: Option<Neighbor>,
78}
79
80/// What happened to a neighbor entry (`NEIGHBOR_CHANGE_*` in
81/// `src/lldpd-structs.h`).
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum NeighborChangeKind {
84 /// A new neighbor was discovered.
85 Added,
86 /// An existing neighbor's advertised information changed.
87 Updated,
88 /// A neighbor aged out or its port went down.
89 Deleted,
90 /// A value this crate doesn't have a name for.
91 Other(i32),
92}
93
94impl From<i32> for NeighborChangeKind {
95 fn from(v: i32) -> Self {
96 match v {
97 1 => Self::Added,
98 0 => Self::Updated,
99 -1 => Self::Deleted,
100 other => Self::Other(other),
101 }
102 }
103}
104
105/// A neighbor (or local) chassis: the "this whole box" identity, as opposed
106/// to one of its ports.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct Chassis {
109 pub id_subtype: ChassisIdSubtype,
110 /// Raw chassis identifier bytes; interpretation depends on `id_subtype`.
111 pub id: Vec<u8>,
112 pub name: Option<String>,
113 pub description: Option<String>,
114 pub capabilities_available: Capabilities,
115 pub capabilities_enabled: Capabilities,
116 pub management_addresses: Vec<ManagementAddress>,
117 /// LLDP-MED inventory TLVs, if the neighbor sent any (all fields are
118 /// `None` when it didn't, rather than this being wrapped in an `Option`).
119 pub med: MedInventory,
120}
121
122impl Chassis {
123 /// Convenience accessor for the common case where `id` is a printable
124 /// string (true for `ChassisComponent`, `IfAlias`, `IfName`, `Local`).
125 pub fn id_str(&self) -> Option<&str> {
126 std::str::from_utf8(&self.id).ok()
127 }
128}
129
130/// LLDP-MED "Inventory" TLVs (IEC/TR 61936, TIA-1057 ยง12.3). Populated only if
131/// the neighbor implements LLDP-MED and advertises them.
132#[derive(Debug, Clone, Default, PartialEq, Eq)]
133pub struct MedInventory {
134 pub hardware_revision: Option<String>,
135 pub firmware_revision: Option<String>,
136 pub software_revision: Option<String>,
137 pub serial_number: Option<String>,
138 pub manufacturer: Option<String>,
139 pub model: Option<String>,
140 pub asset_id: Option<String>,
141}
142
143/// A management address advertised by a chassis (IEEE 802.1AB Management
144/// Address TLV).
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ManagementAddress {
147 family: u32,
148 octets: Vec<u8>,
149 pub interface_index: u32,
150}
151
152impl ManagementAddress {
153 pub(crate) fn new(family: u32, octets: Vec<u8>, interface_index: u32) -> Self {
154 Self {
155 family,
156 octets,
157 interface_index,
158 }
159 }
160
161 /// Decodes the address as an [`IpAddr`], if it is one lldpd recognizes as
162 /// IPv4 or IPv6 (lldpd can in principle carry other address families, in
163 /// which case this returns `None` - use [`Self::raw_octets`] instead).
164 pub fn ip(&self) -> Option<IpAddr> {
165 match self.family {
166 1 if self.octets.len() >= 4 => Some(IpAddr::V4(Ipv4Addr::new(
167 self.octets[0],
168 self.octets[1],
169 self.octets[2],
170 self.octets[3],
171 ))),
172 2 if self.octets.len() >= 16 => {
173 let mut b = [0u8; 16];
174 b.copy_from_slice(&self.octets[..16]);
175 Some(IpAddr::V6(Ipv6Addr::from(b)))
176 }
177 _ => None,
178 }
179 }
180
181 /// The raw address bytes as lldpd stored them (network byte order),
182 /// regardless of address family.
183 pub fn raw_octets(&self) -> &[u8] {
184 &self.octets
185 }
186}
187
188/// `IEEE 802.1AB` chassis ID subtype (`LLDP_CHASSISID_SUBTYPE_*` in
189/// `lldp-const.h`).
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum ChassisIdSubtype {
192 ChassisComponent,
193 IfAlias,
194 PortComponent,
195 MacAddress,
196 NetworkAddress,
197 IfName,
198 Local,
199 /// A value this crate doesn't have a name for (future TLV extension).
200 Other(u8),
201}
202
203impl From<u8> for ChassisIdSubtype {
204 fn from(v: u8) -> Self {
205 match v {
206 1 => Self::ChassisComponent,
207 2 => Self::IfAlias,
208 3 => Self::PortComponent,
209 4 => Self::MacAddress,
210 5 => Self::NetworkAddress,
211 6 => Self::IfName,
212 7 => Self::Local,
213 other => Self::Other(other),
214 }
215 }
216}
217
218/// `IEEE 802.1AB` port ID subtype (`LLDP_PORTID_SUBTYPE_*` in `lldp-const.h`).
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum PortIdSubtype {
221 Unknown,
222 IfAlias,
223 PortComponent,
224 MacAddress,
225 NetworkAddress,
226 IfName,
227 AgentCircuitId,
228 Local,
229 /// A value this crate doesn't have a name for (future TLV extension).
230 Other(u8),
231}
232
233impl From<u8> for PortIdSubtype {
234 fn from(v: u8) -> Self {
235 match v {
236 0 => Self::Unknown,
237 1 => Self::IfAlias,
238 2 => Self::PortComponent,
239 3 => Self::MacAddress,
240 4 => Self::NetworkAddress,
241 5 => Self::IfName,
242 6 => Self::AgentCircuitId,
243 7 => Self::Local,
244 other => Self::Other(other),
245 }
246 }
247}
248
249/// System capabilities bitmap (IEEE 802.1AB Table 8-4). Wraps the raw `u16`
250/// lldpd sends; use [`Capabilities::contains`] to test individual bits.
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
252pub struct Capabilities(pub u16);
253
254impl Capabilities {
255 pub const OTHER: u16 = 0x0001;
256 pub const REPEATER: u16 = 0x0002;
257 pub const BRIDGE: u16 = 0x0004;
258 pub const WLAN_ACCESS_POINT: u16 = 0x0008;
259 pub const ROUTER: u16 = 0x0010;
260 pub const TELEPHONE: u16 = 0x0020;
261 pub const DOCSIS_CABLE_DEVICE: u16 = 0x0040;
262 pub const STATION_ONLY: u16 = 0x0080;
263 pub const CVLAN_COMPONENT: u16 = 0x0100;
264 pub const SVLAN_COMPONENT: u16 = 0x0200;
265 pub const TWO_PORT_MAC_RELAY: u16 = 0x0400;
266
267 pub fn contains(self, bit: u16) -> bool {
268 self.0 & bit == bit
269 }
270}