nmstate/
lldp.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use serde::{de::IgnoredAny, Deserialize, Deserializer, Serialize};
4
5const LLDP_SYS_CAP_OTHER: u16 = 1;
6const LLDP_SYS_CAP_REPEATER: u16 = 2;
7const LLDP_SYS_CAP_MAC_BRIDGE: u16 = 3;
8const LLDP_SYS_CAP_AP: u16 = 4;
9const LLDP_SYS_CAP_ROUTER: u16 = 5;
10const LLDP_SYS_CAP_TELEPHONE: u16 = 6;
11const LLDP_SYS_CAP_DOCSIS: u16 = 7;
12const LLDP_SYS_CAP_STATION_ONLY: u16 = 8;
13const LLDP_SYS_CAP_CVLAN: u16 = 9;
14const LLDP_SYS_CAP_SVLAN: u16 = 10;
15const LLDP_SYS_CAP_TWO_PORT_MAC_RELAY: u16 = 11;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18#[repr(u8)]
19#[serde(into = "u8")]
20pub enum LldpNeighborTlvType {
21    ChassisId = 1,
22    Port = 2,
23    SystemName = 5,
24    SystemDescription = 6,
25    SystemCapabilities = 7,
26    ManagementAddress = 8,
27    OrganizationSpecific = 127,
28}
29
30impl From<LldpNeighborTlvType> for u8 {
31    fn from(value: LldpNeighborTlvType) -> Self {
32        value as Self
33    }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
37#[repr(u8)]
38#[serde(into = "u8")]
39pub enum LldpOrgSubtype {
40    Vlan = 3,
41    MacPhyConf = 1,
42    Ppvids = 2,
43    MaxFrameSize = 4,
44}
45
46impl From<LldpOrgSubtype> for u8 {
47    fn from(value: LldpOrgSubtype) -> Self {
48        value as Self
49    }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
53pub enum LldpOrgOiu {
54    #[serde(rename = "00:80:c2")]
55    Vlan,
56    #[serde(rename = "00:12:0f")]
57    MacPhyConf,
58    #[serde(rename = "00:80:c2")]
59    Ppvids,
60    #[serde(rename = "00:12:0f")]
61    MaxFrameSize,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
65#[non_exhaustive]
66#[serde(deny_unknown_fields)]
67pub struct LldpConfig {
68    #[serde(deserialize_with = "crate::deserializer::bool_or_string")]
69    pub enabled: bool,
70    #[serde(
71        default,
72        deserialize_with = "skip",
73        skip_serializing_if = "Vec::is_empty"
74    )]
75    pub neighbors: Vec<Vec<LldpNeighborTlv>>,
76}
77
78// The serde is treating skipped value as unknown field which trigger
79// `serde(deny_unknown_fields)`, so we manually skip this field.
80fn skip<'de, D, T>(deserializer: D) -> Result<T, D::Error>
81where
82    D: Deserializer<'de>,
83    T: Default,
84{
85    // Ignore the data in the input.
86    IgnoredAny::deserialize(deserializer)?;
87    Ok(T::default())
88}
89
90impl LldpConfig {
91    pub(crate) fn sanitize(&mut self) {
92        // Remove since it is for query only
93        self.neighbors = Vec::new();
94    }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
98#[serde(untagged)]
99#[non_exhaustive]
100pub enum LldpNeighborTlv {
101    SystemName(LldpSystemName),
102    SystemDescription(LldpSystemDescription),
103    SystemCapabilities(LldpSystemCapabilities),
104    ChassisId(LldpChassisId),
105    PortId(LldpPortId),
106    Ieee8021Vlans(LldpVlans),
107    Ieee8023MacPhyConf(LldpMacPhy),
108    Ieee8021Ppvids(LldpPpvids),
109    ManagementAddresses(LldpMgmtAddrs),
110    Ieee8023MaxFrameSize(LldpMaxFrameSize),
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
114#[non_exhaustive]
115#[serde(rename_all = "kebab-case", deny_unknown_fields)]
116pub struct LldpSystemName {
117    #[serde(rename = "type")]
118    pub ty: LldpNeighborTlvType,
119    pub system_name: String,
120}
121
122impl LldpSystemName {
123    pub fn new(value: String) -> Self {
124        Self {
125            system_name: value,
126            ty: LldpNeighborTlvType::SystemName,
127        }
128    }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
132#[non_exhaustive]
133#[serde(rename_all = "kebab-case", deny_unknown_fields)]
134pub struct LldpSystemDescription {
135    #[serde(rename = "type")]
136    pub ty: LldpNeighborTlvType,
137    pub system_description: String,
138}
139
140impl LldpSystemDescription {
141    pub fn new(value: String) -> Self {
142        Self {
143            system_description: value,
144            ty: LldpNeighborTlvType::SystemDescription,
145        }
146    }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
150#[non_exhaustive]
151#[serde(rename_all = "kebab-case", deny_unknown_fields)]
152pub struct LldpChassisId {
153    #[serde(rename = "type")]
154    pub ty: LldpNeighborTlvType,
155    pub chassis_id: String,
156    pub chassis_id_type: LldpChassisIdType,
157    #[serde(rename = "_description")]
158    pub description: String,
159}
160
161impl LldpChassisId {
162    pub fn new(chassis_id: String, chassis_id_type: LldpChassisIdType) -> Self {
163        Self {
164            chassis_id,
165            chassis_id_type: chassis_id_type.clone(),
166            description: chassis_id_type.into(),
167            ty: LldpNeighborTlvType::ChassisId,
168        }
169    }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
173#[repr(u8)]
174#[serde(into = "u8")]
175pub enum LldpChassisIdType {
176    Reserved = 0,
177    ChassisComponent = 1,
178    InterfaceAlias = 2,
179    PortComponent = 3,
180    MacAddress = 4,
181    NetworkAddress = 5,
182    InterfaceName = 6,
183    LocallyAssigned = 7,
184}
185
186impl From<LldpChassisIdType> for u8 {
187    fn from(value: LldpChassisIdType) -> Self {
188        value as Self
189    }
190}
191
192impl From<u8> for LldpChassisIdType {
193    fn from(v: u8) -> LldpChassisIdType {
194        if v == LldpChassisIdType::ChassisComponent as u8 {
195            LldpChassisIdType::ChassisComponent
196        } else if v == LldpChassisIdType::InterfaceAlias as u8 {
197            LldpChassisIdType::InterfaceAlias
198        } else if v == LldpChassisIdType::PortComponent as u8 {
199            LldpChassisIdType::PortComponent
200        } else if v == LldpChassisIdType::MacAddress as u8 {
201            LldpChassisIdType::MacAddress
202        } else if v == LldpChassisIdType::NetworkAddress as u8 {
203            LldpChassisIdType::NetworkAddress
204        } else if v == LldpChassisIdType::InterfaceName as u8 {
205            LldpChassisIdType::InterfaceName
206        } else if v == LldpChassisIdType::LocallyAssigned as u8 {
207            LldpChassisIdType::LocallyAssigned
208        } else {
209            LldpChassisIdType::Reserved
210        }
211    }
212}
213
214impl From<LldpChassisIdType> for String {
215    fn from(v: LldpChassisIdType) -> String {
216        match v {
217            LldpChassisIdType::Reserved => "Reserved",
218            LldpChassisIdType::ChassisComponent => "Chasis compontent",
219            LldpChassisIdType::InterfaceAlias => "Interface alias",
220            LldpChassisIdType::PortComponent => "Port component",
221            LldpChassisIdType::MacAddress => "MAC address",
222            LldpChassisIdType::NetworkAddress => "Network address",
223            LldpChassisIdType::InterfaceName => "Interface name",
224            LldpChassisIdType::LocallyAssigned => "Locally assigned",
225        }
226        .to_string()
227    }
228}
229
230impl Default for LldpChassisIdType {
231    fn default() -> Self {
232        Self::Reserved
233    }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
237#[non_exhaustive]
238#[serde(rename_all = "kebab-case", deny_unknown_fields)]
239pub struct LldpSystemCapabilities {
240    #[serde(rename = "type")]
241    pub ty: LldpNeighborTlvType,
242    pub system_capabilities: Vec<LldpSystemCapability>,
243}
244
245impl LldpSystemCapabilities {
246    pub fn new(system_capabilities: Vec<LldpSystemCapability>) -> Self {
247        Self {
248            system_capabilities,
249            ty: LldpNeighborTlvType::SystemCapabilities,
250        }
251    }
252}
253
254impl From<u16> for LldpSystemCapabilities {
255    fn from(caps: u16) -> Self {
256        let mut ret = Vec::new();
257        if (caps & (1 << (LLDP_SYS_CAP_OTHER - 1))) > 0 {
258            ret.push(LldpSystemCapability::Other);
259        }
260        if (caps & (1 << (LLDP_SYS_CAP_REPEATER - 1))) > 0 {
261            ret.push(LldpSystemCapability::Repeater);
262        }
263        if (caps & (1 << (LLDP_SYS_CAP_MAC_BRIDGE - 1))) > 0 {
264            ret.push(LldpSystemCapability::MacBridgeComponent);
265        }
266        if (caps & (1 << (LLDP_SYS_CAP_AP - 1))) > 0 {
267            ret.push(LldpSystemCapability::AccessPoint);
268        }
269        if (caps & (1 << (LLDP_SYS_CAP_ROUTER - 1))) > 0 {
270            ret.push(LldpSystemCapability::Router);
271        }
272        if (caps & (1 << (LLDP_SYS_CAP_TELEPHONE - 1))) > 0 {
273            ret.push(LldpSystemCapability::Telephone);
274        }
275        if (caps & (1 << (LLDP_SYS_CAP_DOCSIS - 1))) > 0 {
276            ret.push(LldpSystemCapability::DocsisCableDevice);
277        }
278        if (caps & (1 << (LLDP_SYS_CAP_STATION_ONLY - 1))) > 0 {
279            ret.push(LldpSystemCapability::StationOnly);
280        }
281        if (caps & (1 << (LLDP_SYS_CAP_CVLAN - 1))) > 0 {
282            ret.push(LldpSystemCapability::CVlanComponent);
283        }
284        if (caps & (1 << (LLDP_SYS_CAP_SVLAN - 1))) > 0 {
285            ret.push(LldpSystemCapability::SVlanComponent);
286        }
287        if (caps & (1 << (LLDP_SYS_CAP_TWO_PORT_MAC_RELAY - 1))) > 0 {
288            ret.push(LldpSystemCapability::TwoPortMacRelayComponent);
289        }
290        Self::new(ret)
291    }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
295#[non_exhaustive]
296pub enum LldpSystemCapability {
297    Other,
298    Repeater,
299    #[serde(rename = "MAC Bridge component")]
300    MacBridgeComponent,
301    #[serde(rename = "802.11 Access Point (AP)")]
302    AccessPoint,
303    Router,
304    Telephone,
305    #[serde(rename = "DOCSIS cable device")]
306    DocsisCableDevice,
307    #[serde(rename = "Station Only")]
308    StationOnly,
309    #[serde(rename = "C-VLAN component")]
310    CVlanComponent,
311    #[serde(rename = "S-VLAN component")]
312    SVlanComponent,
313    #[serde(rename = "Two-port MAC Relay component")]
314    TwoPortMacRelayComponent,
315}
316
317impl Default for LldpSystemCapability {
318    fn default() -> Self {
319        Self::Other
320    }
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
324#[non_exhaustive]
325#[serde(rename_all = "kebab-case", deny_unknown_fields)]
326pub struct LldpPortId {
327    #[serde(rename = "type")]
328    pub ty: LldpNeighborTlvType,
329    pub port_id: String,
330    pub port_id_type: LldpPortIdType,
331    #[serde(rename = "_description")]
332    pub description: String,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
336#[repr(u8)]
337#[serde(into = "u8")]
338pub enum LldpPortIdType {
339    Reserved = 0,
340    InterfaceAlias = 1,
341    PortComponent = 2,
342    MacAddress = 3,
343    NetworkAddress = 4,
344    InterfaceName = 5,
345    AgentCircuitId = 6,
346    LocallyAssigned = 7,
347}
348
349impl From<LldpPortIdType> for u8 {
350    fn from(value: LldpPortIdType) -> Self {
351        value as Self
352    }
353}
354
355impl From<LldpPortIdType> for String {
356    fn from(v: LldpPortIdType) -> String {
357        match v {
358            LldpPortIdType::Reserved => "Reserved",
359            LldpPortIdType::InterfaceAlias => "Interface alias",
360            LldpPortIdType::PortComponent => "Port component",
361            LldpPortIdType::MacAddress => "MAC address",
362            LldpPortIdType::NetworkAddress => "Network address",
363            LldpPortIdType::InterfaceName => "Interface name",
364            LldpPortIdType::AgentCircuitId => "Agent circuit ID",
365            LldpPortIdType::LocallyAssigned => "Locally assigned",
366        }
367        .to_string()
368    }
369}
370
371impl From<u8> for LldpPortIdType {
372    fn from(v: u8) -> LldpPortIdType {
373        if v == LldpPortIdType::InterfaceName as u8 {
374            LldpPortIdType::InterfaceName
375        } else if v == LldpPortIdType::PortComponent as u8 {
376            LldpPortIdType::PortComponent
377        } else if v == LldpPortIdType::MacAddress as u8 {
378            LldpPortIdType::MacAddress
379        } else if v == LldpPortIdType::NetworkAddress as u8 {
380            LldpPortIdType::NetworkAddress
381        } else if v == LldpPortIdType::AgentCircuitId as u8 {
382            LldpPortIdType::AgentCircuitId
383        } else if v == LldpPortIdType::LocallyAssigned as u8 {
384            LldpPortIdType::LocallyAssigned
385        } else {
386            LldpPortIdType::Reserved
387        }
388    }
389}
390
391impl LldpPortId {
392    pub fn new(port_id: String, port_id_type: LldpPortIdType) -> Self {
393        Self {
394            port_id,
395            port_id_type: port_id_type.clone(),
396            description: port_id_type.into(),
397            ty: LldpNeighborTlvType::Port,
398        }
399    }
400}
401
402impl Default for LldpPortIdType {
403    fn default() -> Self {
404        Self::Reserved
405    }
406}
407
408#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
409#[non_exhaustive]
410#[serde(rename_all = "kebab-case", deny_unknown_fields)]
411pub struct LldpVlans {
412    #[serde(rename = "type")]
413    pub ty: LldpNeighborTlvType,
414    pub ieee_802_1_vlans: Vec<LldpVlan>,
415    pub oui: LldpOrgOiu,
416    pub subtype: LldpOrgSubtype,
417}
418
419impl LldpVlans {
420    pub fn new(ieee_802_1_vlans: Vec<LldpVlan>) -> Self {
421        Self {
422            ieee_802_1_vlans,
423            oui: LldpOrgOiu::Vlan,
424            subtype: LldpOrgSubtype::Vlan,
425            ty: LldpNeighborTlvType::OrganizationSpecific,
426        }
427    }
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
431#[non_exhaustive]
432pub struct LldpVlan {
433    pub name: String,
434    pub vid: u32,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
438#[non_exhaustive]
439#[serde(rename_all = "kebab-case")]
440pub struct LldpMacPhy {
441    #[serde(rename = "type")]
442    pub ty: LldpNeighborTlvType,
443    pub ieee_802_3_mac_phy_conf: LldpMacPhyConf,
444    pub oui: LldpOrgOiu,
445    pub subtype: LldpOrgSubtype,
446}
447
448#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
449#[non_exhaustive]
450#[serde(rename_all = "kebab-case")]
451pub struct LldpMacPhyConf {
452    pub autoneg: bool,
453    pub operational_mau_type: u16,
454    pub pmd_autoneg_cap: u16,
455}
456
457impl LldpMacPhy {
458    pub fn new(
459        autoneg: bool,
460        operational_mau_type: u16,
461        pmd_autoneg_cap: u16,
462    ) -> Self {
463        Self {
464            ieee_802_3_mac_phy_conf: LldpMacPhyConf {
465                autoneg,
466                operational_mau_type,
467                pmd_autoneg_cap,
468            },
469            oui: LldpOrgOiu::MacPhyConf,
470            subtype: LldpOrgSubtype::MacPhyConf,
471            ty: LldpNeighborTlvType::OrganizationSpecific,
472        }
473    }
474}
475
476#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
477#[non_exhaustive]
478#[serde(rename_all = "kebab-case", deny_unknown_fields)]
479pub struct LldpPpvids {
480    #[serde(rename = "type")]
481    pub ty: LldpNeighborTlvType,
482    pub ieee_802_1_ppvids: Vec<u32>,
483    pub oui: LldpOrgOiu,
484    pub subtype: LldpOrgSubtype,
485}
486
487impl LldpPpvids {
488    pub fn new(ieee_802_1_ppvids: Vec<u32>) -> Self {
489        Self {
490            ieee_802_1_ppvids,
491            oui: LldpOrgOiu::Ppvids,
492            subtype: LldpOrgSubtype::Ppvids,
493            ty: LldpNeighborTlvType::OrganizationSpecific,
494        }
495    }
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
499#[non_exhaustive]
500#[serde(rename_all = "kebab-case")]
501pub struct LldpMgmtAddrs {
502    #[serde(rename = "type")]
503    pub ty: LldpNeighborTlvType,
504    pub management_addresses: Vec<LldpMgmtAddr>,
505}
506
507impl LldpMgmtAddrs {
508    pub fn new(management_addresses: Vec<LldpMgmtAddr>) -> Self {
509        Self {
510            management_addresses,
511            ty: LldpNeighborTlvType::ManagementAddress,
512        }
513    }
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
517#[non_exhaustive]
518#[serde(rename_all = "kebab-case")]
519pub struct LldpMgmtAddr {
520    pub address: String,
521    pub address_subtype: LldpAddressFamily,
522    pub interface_number: u32,
523    pub interface_number_subtype: u32,
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
527#[non_exhaustive]
528pub enum LldpAddressFamily {
529    Unknown,
530    #[serde(rename = "IPv4")]
531    Ipv4,
532    #[serde(rename = "IPv6")]
533    Ipv6,
534    #[serde(rename = "MAC")]
535    Mac,
536}
537
538impl Default for LldpAddressFamily {
539    fn default() -> Self {
540        Self::Unknown
541    }
542}
543
544const ADDRESS_FAMILY_IP4: u16 = 1;
545const ADDRESS_FAMILY_IP6: u16 = 2;
546const ADDRESS_FAMILY_MAC: u16 = 6;
547
548impl From<u16> for LldpAddressFamily {
549    fn from(i: u16) -> Self {
550        match i {
551            ADDRESS_FAMILY_IP4 => Self::Ipv4,
552            ADDRESS_FAMILY_IP6 => Self::Ipv6,
553            ADDRESS_FAMILY_MAC => Self::Mac,
554            _ => Self::Unknown,
555        }
556    }
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
560#[non_exhaustive]
561#[serde(rename_all = "kebab-case", deny_unknown_fields)]
562pub struct LldpMaxFrameSize {
563    #[serde(rename = "type")]
564    pub ty: LldpNeighborTlvType,
565    pub ieee_802_3_max_frame_size: u32,
566    pub oui: LldpOrgOiu,
567    pub subtype: LldpOrgSubtype,
568}
569
570impl LldpMaxFrameSize {
571    pub fn new(ieee_802_3_max_frame_size: u32) -> Self {
572        Self {
573            ieee_802_3_max_frame_size,
574            oui: LldpOrgOiu::MaxFrameSize,
575            subtype: LldpOrgSubtype::MaxFrameSize,
576            ty: LldpNeighborTlvType::OrganizationSpecific,
577        }
578    }
579}