nmstate/ifaces/
bond.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::collections::HashMap;
4use std::collections::HashSet;
5use std::net::Ipv6Addr;
6
7use serde::{
8    de::IntoDeserializer, Deserialize, Deserializer, Serialize, Serializer,
9};
10
11use crate::{
12    deserializer::NumberAsString, BaseInterface, ErrorKind, Interface,
13    InterfaceState, InterfaceType, MergedInterface, NmstateError,
14};
15
16#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
17#[serde(rename_all = "kebab-case")]
18#[non_exhaustive]
19/// Bond interface.
20///
21/// When serializing or deserializing, the [BaseInterface] will
22/// be flatted and [BondConfig] stored as `link-aggregation` section. The yaml
23/// output [crate::NetworkState] containing an example bond interface:
24/// ```yml
25/// interfaces:
26/// - name: bond99
27///   type: bond
28///   state: up
29///   mac-address: 1A:24:D5:CA:76:54
30///   mtu: 1500
31///   min-mtu: 68
32///   max-mtu: 65535
33///   wait-ip: any
34///   ipv4:
35///     enabled: false
36///   ipv6:
37///     enabled: false
38///   accept-all-mac-addresses: false
39///   link-aggregation:
40///     mode: balance-rr
41///     options:
42///       all_slaves_active: dropped
43///       arp_all_targets: any
44///       arp_interval: 0
45///       arp_validate: none
46///       downdelay: 0
47///       lp_interval: 1
48///       miimon: 100
49///       min_links: 0
50///       packets_per_slave: 1
51///       primary_reselect: always
52///       resend_igmp: 1
53///       updelay: 0
54///       use_carrier: true
55///     port:
56///     - eth1
57///     - eth2
58/// ```
59pub struct BondInterface {
60    #[serde(flatten)]
61    /// Base interface. Flat during serializing.
62    pub base: BaseInterface,
63    #[serde(
64        skip_serializing_if = "Option::is_none",
65        rename = "link-aggregation",
66        alias = "bond"
67    )]
68    /// Bond specific settings.
69    pub bond: Option<BondConfig>,
70}
71
72impl Default for BondInterface {
73    fn default() -> Self {
74        let mut base = BaseInterface::new();
75        base.iface_type = InterfaceType::Bond;
76        Self { base, bond: None }
77    }
78}
79
80impl BondInterface {
81    // * Do not merge bond options from current when bond mode is changing
82    pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
83        if let Some(bond_conf) = self.bond.as_mut() {
84            if let (Some(des_bond_conf), Some(cur_bond_conf)) =
85                (desired.bond.as_ref(), current.bond.as_ref())
86            {
87                if des_bond_conf.mode != cur_bond_conf.mode {
88                    bond_conf.options.clone_from(&des_bond_conf.options);
89                }
90            }
91        }
92    }
93
94    fn drop_empty_arp_ip_target(&mut self) {
95        if let Some(ref mut bond_conf) = self.bond {
96            if let Some(ref mut bond_opts) = &mut bond_conf.options {
97                if let Some(ref mut arp_ip_target) = bond_opts.arp_ip_target {
98                    if arp_ip_target.is_empty() {
99                        bond_opts.arp_ip_target = None;
100                    }
101                }
102                if let Some(ref mut v) = bond_opts.ns_ip6_target {
103                    if v.is_empty() {
104                        bond_opts.ns_ip6_target = None;
105                    }
106                }
107            }
108        }
109    }
110
111    fn sort_ports(&mut self) {
112        if let Some(ports) = self.bond.as_mut().and_then(|b| b.port.as_mut()) {
113            ports.sort_unstable();
114        }
115    }
116
117    fn sort_ports_config(&mut self) {
118        if let Some(port_confs) =
119            self.bond.as_mut().and_then(|b| b.ports_config.as_mut())
120        {
121            port_confs.sort_unstable_by_key(|p| p.name.clone());
122        }
123    }
124
125    pub(crate) fn sanitize(&mut self) -> Result<(), NmstateError> {
126        self.sort_ports();
127        self.sort_ports_config();
128        self.drop_empty_arp_ip_target();
129        self.make_ad_actor_system_mac_upper_case();
130        self.check_overlap_queue_id()?;
131        Ok(())
132    }
133
134    // In kernel code drivers/net/bonding/bond_options.c
135    // bond_option_queue_id_set(), kernel is not allowing multiple bond port
136    // holding the same queue ID, hence we raise error when queue id overlapped.
137    fn check_overlap_queue_id(&self) -> Result<(), NmstateError> {
138        let mut existing_qids: HashMap<u16, &str> = HashMap::new();
139        if let Some(ports_conf) =
140            self.bond.as_ref().and_then(|b| b.ports_config.as_deref())
141        {
142            for port_conf in ports_conf
143                .iter()
144                .filter(|p| p.queue_id.is_some() && p.queue_id != Some(0))
145            {
146                if let Some(queue_id) = port_conf.queue_id {
147                    if let Some(exist_port_name) = existing_qids.get(&queue_id)
148                    {
149                        return Err(NmstateError::new(
150                            ErrorKind::InvalidArgument,
151                            format!(
152                                "Port {} and {} of Bond {} are sharing the \
153                                 same queue-id which is not supported by \
154                                 linux kernel yet",
155                                exist_port_name,
156                                port_conf.name.as_str(),
157                                self.base.name.as_str()
158                            ),
159                        ));
160                    } else {
161                        existing_qids.insert(queue_id, port_conf.name.as_str());
162                    }
163                }
164            }
165        }
166        Ok(())
167    }
168
169    pub(crate) fn ports(&self) -> Option<Vec<&str>> {
170        self.bond.as_ref().and_then(|bond_conf| {
171            bond_conf
172                .port
173                .as_ref()
174                .map(|ports| {
175                    ports.as_slice().iter().map(|p| p.as_str()).collect()
176                })
177                .or_else(|| {
178                    bond_conf.ports_config.as_ref().map(|ports| {
179                        ports
180                            .as_slice()
181                            .iter()
182                            .map(|p| p.name.as_str())
183                            .collect()
184                    })
185                })
186        })
187    }
188
189    pub(crate) fn get_port_conf(
190        &self,
191        port_name: &str,
192    ) -> Option<&BondPortConfig> {
193        self.bond
194            .as_ref()
195            .and_then(|bond_conf| bond_conf.ports_config.as_ref())
196            .and_then(|port_confs| {
197                port_confs
198                    .iter()
199                    .find(|port_conf| port_conf.name == port_name)
200            })
201    }
202
203    pub(crate) fn mode(&self) -> Option<BondMode> {
204        self.bond.as_ref().and_then(|bond_conf| bond_conf.mode)
205    }
206
207    pub fn new() -> Self {
208        Self::default()
209    }
210
211    fn is_mac_restricted_mode(&self) -> bool {
212        self.bond
213            .as_ref()
214            .and_then(|bond_conf| {
215                if self.mode() == Some(BondMode::ActiveBackup) {
216                    bond_conf.options.as_ref()
217                } else {
218                    None
219                }
220            })
221            .and_then(|bond_opts| bond_opts.fail_over_mac)
222            == Some(BondFailOverMac::Active)
223    }
224
225    fn is_not_mac_restricted_mode_explicitly(&self) -> bool {
226        (self.mode().is_some() && self.mode() != Some(BondMode::ActiveBackup))
227            || ![None, Some(BondFailOverMac::Active)].contains(
228                &self
229                    .bond
230                    .as_ref()
231                    .and_then(|bond_conf| bond_conf.options.as_ref())
232                    .and_then(|bond_opts| bond_opts.fail_over_mac),
233            )
234    }
235
236    fn validate_new_iface_with_no_mode(
237        &self,
238        current: Option<&Interface>,
239    ) -> Result<(), NmstateError> {
240        if self.base.state == InterfaceState::Up
241            && current.is_none()
242            && self.mode().is_none()
243        {
244            let e = NmstateError::new(
245                ErrorKind::InvalidArgument,
246                format!(
247                    "Bond mode is mandatory for new bond interface: {}",
248                    &self.base.name
249                ),
250            );
251            log::error!("{e}");
252            return Err(e);
253        }
254        Ok(())
255    }
256
257    // Remove desired MAC address with warning message when:
258    // * New bond desired in mac restricted mode with mac defined
259    // * Desire mac address with current interface in mac restricted mode with
260    //   desired not changing mac restricted mode
261    // The verification process will fail the apply action when desired MAC
262    // address differ from port MAC.
263    fn sanitize_mac_restricted_mode(&mut self, current: Option<&Interface>) {
264        let warn_msg = format!(
265            "The bond interface {} with fail_over_mac:active and \
266             mode:active-backup is determined by its ports, hence ignoring \
267             desired MAC address",
268            self.base.name.as_str()
269        );
270
271        if let Some(Interface::Bond(current)) = current {
272            if current.is_mac_restricted_mode()
273                && self.base.mac_address.is_some()
274                && !self.is_not_mac_restricted_mode_explicitly()
275            {
276                self.base.mac_address = None;
277                log::warn!("{warn_msg}");
278            }
279        } else if self.is_mac_restricted_mode()
280            && self.base.mac_address.is_some()
281        {
282            self.base.mac_address = None;
283            log::warn!("{warn_msg}");
284        }
285    }
286
287    fn validate_conflict_in_port_and_port_configs(
288        &self,
289    ) -> Result<(), NmstateError> {
290        if let Some(bond_conf) = self.bond.as_ref() {
291            if let (Some(ports), Some(ports_config)) =
292                (&bond_conf.port, &bond_conf.ports_config)
293            {
294                let ports: HashSet<&str> =
295                    ports.iter().map(String::as_str).collect();
296                let port_confs: HashSet<&str> =
297                    ports_config.iter().map(|p| p.name.as_str()).collect();
298
299                if ports != port_confs {
300                    let missing_in_config: Vec<&str> =
301                        ports.difference(&port_confs).copied().collect();
302                    let missing_in_ports: Vec<&str> =
303                        port_confs.difference(&ports).copied().collect();
304
305                    let mut error_msg_detail = String::new();
306
307                    if !missing_in_config.is_empty() {
308                        error_msg_detail.push_str(&format!(
309                            "Ports in `port` but not in `ports_config`: {}. ",
310                            missing_in_config.join(", ")
311                        ));
312                    }
313                    if !missing_in_ports.is_empty() {
314                        error_msg_detail.push_str(&format!(
315                            "Ports in `ports_config` but not in `port`: {}. ",
316                            missing_in_ports.join(", ")
317                        ));
318                    }
319
320                    let e = NmstateError::new(
321                        ErrorKind::InvalidArgument,
322                        format!(
323                            "The port names specified in `port` conflict with \
324                             the port names specified in `ports-config` for \
325                             bond interface: {}. {}",
326                            &self.base.name, error_msg_detail
327                        ),
328                    );
329                    log::error!("{e}");
330                    return Err(e);
331                }
332            }
333        }
334
335        Ok(())
336    }
337
338    pub(crate) fn is_options_reset(&self) -> bool {
339        if let Some(bond_opts) = self
340            .bond
341            .as_ref()
342            .and_then(|bond_conf| bond_conf.options.as_ref())
343        {
344            bond_opts == &BondOptions::default()
345        } else {
346            false
347        }
348    }
349
350    fn make_ad_actor_system_mac_upper_case(&mut self) {
351        if let Some(mac) = self
352            .bond
353            .as_mut()
354            .and_then(|c| c.options.as_mut())
355            .and_then(|o| o.ad_actor_system.as_mut())
356        {
357            mac.make_ascii_uppercase();
358        }
359    }
360
361    pub(crate) fn remove_port(&mut self, port_to_remove: &str) {
362        if let Some(index) = self.bond.as_ref().and_then(|bond_conf| {
363            bond_conf.port.as_ref().and_then(|ports| {
364                ports
365                    .iter()
366                    .position(|port_name| port_name == port_to_remove)
367            })
368        }) {
369            self.bond
370                .as_mut()
371                .and_then(|bond_conf| bond_conf.port.as_mut())
372                .map(|ports| ports.remove(index));
373        }
374    }
375
376    pub(crate) fn change_port_name(
377        &mut self,
378        origin_name: &str,
379        new_name: String,
380    ) {
381        if let Some(port_name) = self
382            .bond
383            .as_mut()
384            .and_then(|bond_conf| bond_conf.port.as_mut())
385            .and_then(|ports| {
386                ports
387                    .iter_mut()
388                    .find(|port_name| port_name.as_str() == origin_name)
389            })
390        {
391            *port_name = new_name.clone();
392        }
393
394        if let Some(port_conf) = self
395            .bond
396            .as_mut()
397            .and_then(|bond_conf| bond_conf.ports_config.as_mut())
398            .and_then(|port_confs| {
399                port_confs
400                    .iter_mut()
401                    .find(|port_conf| port_conf.name == origin_name)
402            })
403        {
404            port_conf.name = new_name;
405        }
406    }
407
408    pub(crate) fn get_config_changed_ports(&self, current: &Self) -> Vec<&str> {
409        let mut ret: Vec<&str> = Vec::new();
410        let mut des_ports_index: HashMap<&str, &BondPortConfig> =
411            HashMap::new();
412        let mut cur_ports_index: HashMap<&str, &BondPortConfig> =
413            HashMap::new();
414        if let Some(port_confs) = self
415            .bond
416            .as_ref()
417            .and_then(|bond_conf| bond_conf.ports_config.as_ref())
418        {
419            for port_conf in port_confs {
420                des_ports_index.insert(port_conf.name.as_str(), port_conf);
421            }
422        }
423
424        if let Some(port_confs) = current
425            .bond
426            .as_ref()
427            .and_then(|bond_conf| bond_conf.ports_config.as_ref())
428        {
429            for port_conf in port_confs {
430                cur_ports_index.insert(port_conf.name.as_str(), port_conf);
431            }
432        }
433
434        for (port_name, port_conf) in des_ports_index.iter() {
435            if let Some(cur_port_conf) = cur_ports_index.get(port_name) {
436                if port_conf.is_changed(cur_port_conf) {
437                    ret.push(port_name);
438                }
439            }
440        }
441        ret
442    }
443}
444
445#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
446#[non_exhaustive]
447#[serde(remote = "BondMode")]
448/// Bond mode
449#[derive(Default)]
450pub enum BondMode {
451    #[serde(rename = "balance-rr", alias = "0")]
452    /// Deserialize and serialize from/to `balance-rr`.
453    /// You can use integer 0 for deserializing to this mode.
454    #[default]
455    RoundRobin,
456    #[serde(rename = "active-backup", alias = "1")]
457    /// Deserialize and serialize from/to `active-backup`.
458    /// You can use integer 1 for deserializing to this mode.
459    ActiveBackup,
460    #[serde(rename = "balance-xor", alias = "2")]
461    /// Deserialize and serialize from/to `balance-xor`.
462    /// You can use integer 2 for deserializing to this mode.
463    XOR,
464    #[serde(rename = "broadcast", alias = "3")]
465    /// Deserialize and serialize from/to `broadcast`.
466    /// You can use integer 3 for deserializing to this mode.
467    Broadcast,
468    #[serde(rename = "802.3ad", alias = "lacp", alias = "4")]
469    /// Deserialize and serialize from/to `802.3ad`.
470    /// You can use integer 4, or the alias "lacp" for deserializing to this
471    /// mode.
472    LACP,
473    #[serde(rename = "balance-tlb", alias = "5")]
474    /// Deserialize and serialize from/to `balance-tlb`.
475    /// You can use integer 5 for deserializing to this mode.
476    TLB,
477    /// Deserialize and serialize from/to `balance-alb`.
478    /// You can use integer 6 for deserializing to this mode.
479    #[serde(rename = "balance-alb", alias = "6")]
480    ALB,
481    #[serde(rename = "unknown")]
482    Unknown,
483}
484
485impl<'de> Deserialize<'de> for BondMode {
486    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
487    where
488        D: Deserializer<'de>,
489    {
490        BondMode::deserialize(
491            NumberAsString::deserialize(deserializer)?
492                .as_str()
493                .into_deserializer(),
494        )
495    }
496}
497
498impl Serialize for BondMode {
499    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
500    where
501        S: Serializer,
502    {
503        BondMode::serialize(self, serializer)
504    }
505}
506
507impl std::fmt::Display for BondMode {
508    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
509        write!(
510            f,
511            "{}",
512            match self {
513                BondMode::RoundRobin => "balance-rr",
514                BondMode::ActiveBackup => "active-backup",
515                BondMode::XOR => "balance-xor",
516                BondMode::Broadcast => "broadcast",
517                BondMode::LACP => "802.3ad",
518                BondMode::TLB => "balance-tlb",
519                BondMode::ALB => "balance-alb",
520                BondMode::Unknown => "unknown",
521            }
522        )
523    }
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
527#[serde(rename_all = "kebab-case", deny_unknown_fields)]
528#[non_exhaustive]
529pub struct BondConfig {
530    #[serde(skip_serializing_if = "Option::is_none")]
531    /// Mode is mandatory when create new bond interface.
532    pub mode: Option<BondMode>,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    /// When applying, if defined, it will override current port list.
535    /// The verification will not fail on bond options miss-match but an
536    /// warning message.
537    /// Please refer to [kernel documentation](https://www.kernel.org/doc/Documentation/networking/bonding.txt) for detail
538    pub options: Option<BondOptions>,
539    #[serde(skip_serializing_if = "Option::is_none", alias = "ports")]
540    /// Deserialize and serialize from/to `port`.
541    /// You can also use `ports` for deserializing.
542    /// When applying, if defined, it will override current port list.
543    pub port: Option<Vec<String>>,
544    #[serde(skip_serializing_if = "Option::is_none")]
545    /// Deserialize and serialize from/to `ports-config`.
546    /// When applying, if defined, it will override current ports
547    /// configuration. Note that `port` is not required to set with
548    /// `ports-config`. An error will be raised during apply when the port
549    /// names specified in `port` and `ports-config` conflict with each
550    /// other.
551    pub ports_config: Option<Vec<BondPortConfig>>,
552}
553
554impl BondConfig {
555    pub fn new() -> Self {
556        Self::default()
557    }
558}
559
560#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
561#[non_exhaustive]
562#[serde(remote = "BondAdSelect", rename_all = "kebab-case")]
563/// Specifies the 802.3ad aggregation selection logic to use.
564pub enum BondAdSelect {
565    /// Deserialize and serialize from/to `stable`.
566    #[serde(alias = "0")]
567    Stable,
568    /// Deserialize and serialize from/to `bandwidth`.
569    #[serde(alias = "1")]
570    Bandwidth,
571    /// Deserialize and serialize from/to `count`.
572    #[serde(alias = "2")]
573    Count,
574}
575
576impl<'de> Deserialize<'de> for BondAdSelect {
577    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
578    where
579        D: Deserializer<'de>,
580    {
581        BondAdSelect::deserialize(
582            NumberAsString::deserialize(deserializer)?
583                .as_str()
584                .into_deserializer(),
585        )
586    }
587}
588
589impl Serialize for BondAdSelect {
590    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
591    where
592        S: Serializer,
593    {
594        BondAdSelect::serialize(self, serializer)
595    }
596}
597
598impl std::fmt::Display for BondAdSelect {
599    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600        write!(
601            f,
602            "{}",
603            match self {
604                Self::Stable => "stable",
605                Self::Bandwidth => "bandwidth",
606                Self::Count => "count",
607            }
608        )
609    }
610}
611
612#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
613#[serde(rename_all = "kebab-case", remote = "BondLacpRate")]
614#[non_exhaustive]
615/// Option specifying the rate in which we'll ask our link partner to transmit
616/// LACPDU packets in 802.3ad mode
617pub enum BondLacpRate {
618    /// Request partner to transmit LACPDUs every 30 seconds.
619    /// Serialize to `slow`.
620    /// Deserialize from 0 or `slow`.
621    #[serde(alias = "0")]
622    Slow,
623    /// Request partner to transmit LACPDUs every 1 second
624    /// Serialize to `fast`.
625    /// Deserialize from 1 or `fast`.
626    #[serde(alias = "1")]
627    Fast,
628}
629
630impl<'de> Deserialize<'de> for BondLacpRate {
631    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
632    where
633        D: Deserializer<'de>,
634    {
635        BondLacpRate::deserialize(
636            NumberAsString::deserialize(deserializer)?
637                .as_str()
638                .into_deserializer(),
639        )
640    }
641}
642
643impl Serialize for BondLacpRate {
644    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
645    where
646        S: Serializer,
647    {
648        BondLacpRate::serialize(self, serializer)
649    }
650}
651
652impl std::fmt::Display for BondLacpRate {
653    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
654        write!(
655            f,
656            "{}",
657            match self {
658                Self::Slow => "slow",
659                Self::Fast => "fast",
660            }
661        )
662    }
663}
664
665#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
666#[serde(rename_all = "kebab-case", remote = "BondAllPortsActive")]
667#[non_exhaustive]
668/// Equal to kernel `all_slaves_active` option.
669/// Specifies that duplicate frames (received on inactive ports) should be
670/// dropped (0) or delivered (1).
671pub enum BondAllPortsActive {
672    /// Drop the duplicate frames
673    /// Serialize to `dropped`.
674    /// Deserialize from 0 or `dropped`.
675    #[serde(alias = "0")]
676    Dropped,
677    /// Deliver the duplicate frames
678    /// Serialize to `delivered`.
679    /// Deserialize from 1 or `delivered`.
680    #[serde(alias = "1")]
681    Delivered,
682}
683
684impl<'de> Deserialize<'de> for BondAllPortsActive {
685    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686    where
687        D: Deserializer<'de>,
688    {
689        BondAllPortsActive::deserialize(
690            NumberAsString::deserialize(deserializer)?
691                .as_str()
692                .into_deserializer(),
693        )
694    }
695}
696
697impl Serialize for BondAllPortsActive {
698    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
699    where
700        S: Serializer,
701    {
702        BondAllPortsActive::serialize(self, serializer)
703    }
704}
705
706impl std::fmt::Display for BondAllPortsActive {
707    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708        write!(
709            f,
710            "{}",
711            match self {
712                Self::Dropped => "dropped",
713                Self::Delivered => "delivered",
714            }
715        )
716    }
717}
718
719impl From<BondAllPortsActive> for u8 {
720    fn from(v: BondAllPortsActive) -> u8 {
721        match v {
722            BondAllPortsActive::Dropped => 0,
723            BondAllPortsActive::Delivered => 1,
724        }
725    }
726}
727
728/// The `arp_all_targets` kernel bond option.
729///
730/// Specifies the quantity of arp_ip_target that must be reachable in order for
731/// the ARP monitor to consider a port as being up. This option affects only
732/// active-backup mode for ports with arp_validation enabled.
733#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
734#[serde(rename_all = "kebab-case", remote = "BondArpAllTargets")]
735#[non_exhaustive]
736pub enum BondArpAllTargets {
737    /// consider the port up only when any of the `arp_ip_target` is reachable
738    #[serde(alias = "0")]
739    Any,
740    /// consider the port up only when all of the `arp_ip_target` are
741    /// reachable
742    #[serde(alias = "1")]
743    All,
744}
745
746impl<'de> Deserialize<'de> for BondArpAllTargets {
747    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748    where
749        D: Deserializer<'de>,
750    {
751        BondArpAllTargets::deserialize(
752            NumberAsString::deserialize(deserializer)?
753                .as_str()
754                .into_deserializer(),
755        )
756    }
757}
758
759impl Serialize for BondArpAllTargets {
760    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
761    where
762        S: Serializer,
763    {
764        BondArpAllTargets::serialize(self, serializer)
765    }
766}
767
768impl std::fmt::Display for BondArpAllTargets {
769    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770        write!(
771            f,
772            "{}",
773            match self {
774                Self::Any => "any",
775                Self::All => "all",
776            }
777        )
778    }
779}
780
781/// The `arp_validate` kernel bond option.
782///
783/// Specifies whether or not ARP probes and replies should be validated in any
784/// mode that supports arp monitoring, or whether non-ARP traffic should be
785/// filtered (disregarded) for link monitoring purposes.
786#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
787#[serde(rename_all = "snake_case", remote = "BondArpValidate")]
788#[non_exhaustive]
789pub enum BondArpValidate {
790    /// No validation or filtering is performed.
791    /// Serialize to `none`.
792    /// Deserialize from 0 or `none`.
793    #[serde(alias = "0")]
794    None,
795    /// Validation is performed only for the active port.
796    /// Serialize to `active`.
797    /// Deserialize from 1 or `active`.
798    #[serde(alias = "1")]
799    Active,
800    /// Validation is performed only for backup ports.
801    /// Serialize to `backup`.
802    /// Deserialize from 2 or `backup`.
803    #[serde(alias = "2")]
804    Backup,
805    /// Validation is performed for all ports.
806    /// Serialize to `all`.
807    /// Deserialize from 3 or `all`.
808    #[serde(alias = "3")]
809    All,
810    /// Filtering is applied to all ports. No validation is performed.
811    /// Serialize to `filter`.
812    /// Deserialize from 4 or `filter`.
813    #[serde(alias = "4")]
814    Filter,
815    /// Filtering is applied to all ports, validation is performed only for
816    /// the active port.
817    /// Serialize to `filter_active`.
818    /// Deserialize from 5 or `filter-active`.
819    #[serde(alias = "5")]
820    FilterActive,
821    /// Filtering is applied to all ports, validation is performed only for
822    /// backup port.
823    /// Serialize to `filter_backup`.
824    /// Deserialize from 6 or `filter_backup`.
825    #[serde(alias = "6")]
826    FilterBackup,
827}
828
829impl<'de> Deserialize<'de> for BondArpValidate {
830    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
831    where
832        D: Deserializer<'de>,
833    {
834        BondArpValidate::deserialize(
835            NumberAsString::deserialize(deserializer)?
836                .as_str()
837                .into_deserializer(),
838        )
839    }
840}
841
842impl Serialize for BondArpValidate {
843    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
844    where
845        S: Serializer,
846    {
847        BondArpValidate::serialize(self, serializer)
848    }
849}
850
851impl std::fmt::Display for BondArpValidate {
852    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
853        write!(
854            f,
855            "{}",
856            match self {
857                Self::None => "none",
858                Self::Active => "active",
859                Self::Backup => "backup",
860                Self::All => "all",
861                Self::Filter => "filter",
862                Self::FilterActive => "filter_active",
863                Self::FilterBackup => "filter_backup",
864            }
865        )
866    }
867}
868
869/// The `fail_over_mac` kernel bond option.
870///
871/// Specifies whether active-backup mode should set all ports to the same MAC
872/// address at port attachment (the traditional behavior), or, when enabled,
873/// perform special handling of the bond's MAC address in accordance with the
874/// selected policy.
875#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
876#[serde(rename_all = "kebab-case", remote = "BondFailOverMac")]
877#[non_exhaustive]
878pub enum BondFailOverMac {
879    /// This setting disables fail_over_mac, and causes bonding to set all
880    /// ports of an active-backup bond to the same MAC address at attachment
881    /// time.
882    /// Serialize to `none`.
883    /// Deserialize from 0 or `none`.
884    #[serde(alias = "0")]
885    None,
886    /// The "active" fail_over_mac policy indicates that the MAC address of the
887    /// bond should always be the MAC address of the currently active port.
888    /// The MAC address of the ports is not changed; instead, the MAC address
889    /// of the bond changes during a failover.
890    /// Serialize to `active`.
891    /// Deserialize from 1 or `active`.
892    #[serde(alias = "1")]
893    Active,
894    /// The "follow" fail_over_mac policy causes the MAC address of the bond to
895    /// be selected normally (normally the MAC address of the first port added
896    /// to the bond). However, the second and subsequent ports are not set to
897    /// this MAC address while they are in a backup role; a port is programmed
898    /// with the bond's MAC address at failover time (and the formerly active
899    /// port receives the newly active port's MAC address).
900    /// Serialize to `follow`.
901    /// Deserialize from 2 or `follow`.
902    #[serde(alias = "2")]
903    Follow,
904}
905
906impl<'de> Deserialize<'de> for BondFailOverMac {
907    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
908    where
909        D: Deserializer<'de>,
910    {
911        BondFailOverMac::deserialize(
912            NumberAsString::deserialize(deserializer)?
913                .as_str()
914                .into_deserializer(),
915        )
916    }
917}
918
919impl Serialize for BondFailOverMac {
920    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
921    where
922        S: Serializer,
923    {
924        BondFailOverMac::serialize(self, serializer)
925    }
926}
927
928impl std::fmt::Display for BondFailOverMac {
929    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
930        write!(
931            f,
932            "{}",
933            match self {
934                Self::None => "none",
935                Self::Active => "active",
936                Self::Follow => "follow",
937            }
938        )
939    }
940}
941
942/// The `primary_reselect` kernel bond option.
943///
944/// Specifies the reselection policy for the primary port. This affects how the
945/// primary port is chosen to become the active port when failure of the active
946/// port or recovery of the primary port occurs. This option is designed to
947/// prevent flip-flopping between the primary port and other ports.
948#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
949#[serde(rename_all = "kebab-case", remote = "BondPrimaryReselect")]
950#[non_exhaustive]
951pub enum BondPrimaryReselect {
952    ///The primary port becomes the active port whenever it comes back up.
953    /// Serialize to `always`.
954    /// Deserialize from 0 or `always`.
955    #[serde(alias = "0")]
956    Always,
957    /// The primary port becomes the active port when it comes back up, if the
958    /// speed and duplex of the primary port is better than the speed and
959    /// duplex of the current active port.
960    /// Serialize to `better`.
961    /// Deserialize from 1 or `better`.
962    #[serde(alias = "1")]
963    Better,
964    /// The primary port becomes the active port only if the current active
965    /// port fails and the primary port is up.
966    /// Serialize to `failure`.
967    /// Deserialize from 2 or `failure`.
968    #[serde(alias = "2")]
969    Failure,
970}
971
972impl<'de> Deserialize<'de> for BondPrimaryReselect {
973    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
974    where
975        D: Deserializer<'de>,
976    {
977        BondPrimaryReselect::deserialize(
978            NumberAsString::deserialize(deserializer)?
979                .as_str()
980                .into_deserializer(),
981        )
982    }
983}
984
985impl Serialize for BondPrimaryReselect {
986    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
987    where
988        S: Serializer,
989    {
990        BondPrimaryReselect::serialize(self, serializer)
991    }
992}
993impl std::fmt::Display for BondPrimaryReselect {
994    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
995        write!(
996            f,
997            "{}",
998            match self {
999                Self::Always => "always",
1000                Self::Better => "better",
1001                Self::Failure => "failure",
1002            }
1003        )
1004    }
1005}
1006
1007/// The `xmit_hash_policy` kernel bond option.
1008///
1009/// Selects the transmit hash policy to use for port selection in balance-xor,
1010/// 802.3ad, and tlb modes.
1011#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)]
1012#[non_exhaustive]
1013#[serde(remote = "BondXmitHashPolicy")]
1014pub enum BondXmitHashPolicy {
1015    #[serde(rename = "layer2", alias = "0")]
1016    /// Serialize to `layer2`.
1017    /// Deserialize from 0 or `layer2`.
1018    Layer2,
1019    #[serde(rename = "layer3+4", alias = "1")]
1020    /// Serialize to `layer3+4`.
1021    /// Deserialize from 1 or `layer3+4`.
1022    Layer34,
1023    #[serde(rename = "layer2+3", alias = "2")]
1024    /// Serialize to `layer2+3`.
1025    /// Deserialize from 2 or `layer2+3`.
1026    Layer23,
1027    #[serde(rename = "encap2+3", alias = "3")]
1028    /// Serialize to `encap2+3`.
1029    /// Deserialize from 3 or `encap2+3`.
1030    Encap23,
1031    #[serde(rename = "encap3+4", alias = "4")]
1032    /// Serialize to `encap3+4`.
1033    /// Deserialize from 4 or `encap3+4`.
1034    Encap34,
1035    #[serde(rename = "vlan+srcmac", alias = "5")]
1036    /// Serialize to `vlan+srcmac`.
1037    /// Deserialize from 5 or `vlan+srcmac`.
1038    VlanSrcMac,
1039}
1040
1041impl<'de> Deserialize<'de> for BondXmitHashPolicy {
1042    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1043    where
1044        D: Deserializer<'de>,
1045    {
1046        BondXmitHashPolicy::deserialize(
1047            NumberAsString::deserialize(deserializer)?
1048                .as_str()
1049                .into_deserializer(),
1050        )
1051    }
1052}
1053
1054impl Serialize for BondXmitHashPolicy {
1055    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1056    where
1057        S: Serializer,
1058    {
1059        BondXmitHashPolicy::serialize(self, serializer)
1060    }
1061}
1062
1063impl std::fmt::Display for BondXmitHashPolicy {
1064    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1065        write!(
1066            f,
1067            "{}",
1068            match self {
1069                Self::Layer2 => "layer2",
1070                Self::Layer34 => "layer3+4",
1071                Self::Layer23 => "layer2+3",
1072                Self::Encap23 => "encap2+3",
1073                Self::Encap34 => "encap3+4",
1074                Self::VlanSrcMac => "vlan+srcmac",
1075            }
1076        )
1077    }
1078}
1079
1080#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
1081#[non_exhaustive]
1082#[serde(deny_unknown_fields)]
1083pub struct BondOptions {
1084    #[serde(
1085        skip_serializing_if = "Option::is_none",
1086        default,
1087        deserialize_with = "crate::deserializer::option_u16_or_string"
1088    )]
1089    /// In an AD system, this specifies the system priority. The allowed range
1090    /// is 1 - 65535.
1091    pub ad_actor_sys_prio: Option<u16>,
1092    #[serde(skip_serializing_if = "Option::is_none")]
1093    /// In an AD system, this specifies the mac-address for the actor in
1094    /// protocol packet exchanges (LACPDUs). The value cannot be NULL or
1095    /// multicast. It is preferred to have the local-admin bit set for this mac
1096    /// but driver does not enforce it. If the value is not given then system
1097    /// defaults to using the controller's mac address as actors' system
1098    /// address.
1099    pub ad_actor_system: Option<String>,
1100    #[serde(skip_serializing_if = "Option::is_none")]
1101    /// Specifies the 802.3ad aggregation selection logic to use. The
1102    /// possible values and their effects are:
1103    pub ad_select: Option<BondAdSelect>,
1104    #[serde(
1105        skip_serializing_if = "Option::is_none",
1106        default,
1107        deserialize_with = "crate::deserializer::option_u16_or_string"
1108    )]
1109    /// In an AD system, the port-key has three parts as shown below -
1110    ///
1111    /// ```text
1112    /// Bits   Use
1113    /// 00     Duplex
1114    /// 01-05  Speed
1115    /// 06-15  User-defined
1116    /// ```
1117    ///
1118    /// This defines the upper 10 bits of the port key. The values can be from
1119    /// 0
1120    /// - 1023. If not given, the system defaults to 0.
1121    ///
1122    /// This parameter has effect only in 802.3ad mode.
1123    pub ad_user_port_key: Option<u16>,
1124    #[serde(skip_serializing_if = "Option::is_none")]
1125    /// Specifies that duplicate frames (received on inactive ports) should be
1126    /// dropped (0) or delivered (1).
1127    ///
1128    /// Normally, bonding will drop duplicate frames (received on inactive
1129    /// ports), which is desirable for most users. But there are some times it
1130    /// is nice to allow duplicate frames to be delivered.
1131    pub all_slaves_active: Option<BondAllPortsActive>,
1132    #[serde(skip_serializing_if = "Option::is_none")]
1133    /// Specifies the quantity of arp_ip_target that must be reachable in
1134    /// order for the ARP monitor to consider a port as being up. This
1135    /// option affects only active-backup mode for ports with
1136    /// arp_validation enabled.
1137    pub arp_all_targets: Option<BondArpAllTargets>,
1138    #[serde(
1139        skip_serializing_if = "Option::is_none",
1140        default,
1141        deserialize_with = "crate::deserializer::option_u32_or_string"
1142    )]
1143    /// Specifies the ARP link monitoring frequency in milliseconds.
1144    ///
1145    /// The ARP monitor works by periodically checking the port devices to
1146    /// determine whether they have sent or received traffic recently (the
1147    /// precise criteria depends upon the bonding mode, and the state of the
1148    /// port). Regular traffic is generated via ARP probes issued for the
1149    /// addresses specified by the arp_ip_target option.
1150    ///
1151    /// This behavior can be modified by the arp_validate option,
1152    /// below.
1153    ///
1154    /// If ARP monitoring is used in an etherchannel compatible mode (modes 0
1155    /// and 2), the switch should be configured in a mode that evenly
1156    /// distributes packets across all links. If the switch is configured to
1157    /// distribute the packets in an XOR fashion, all replies from the ARP
1158    /// targets will be received on the same link which could cause the other
1159    /// team members to fail. ARP monitoring should not be used in conjunction
1160    /// with miimon. A value of 0 disables ARP monitoring. The default value
1161    /// is 0.
1162    /// Invalid for setting `arp_interval` bigger than 0 for bond in mode:
1163    ///  * `802.3ad`
1164    ///  * `balance-tlb`
1165    ///  * `balance-alb`
1166    pub arp_interval: Option<u32>,
1167    #[serde(skip_serializing_if = "Option::is_none")]
1168    /// Specifies the IP addresses to use as ARP monitoring peers when
1169    /// arp_interval is > 0. These are the targets of the ARP request sent to
1170    /// determine the health of the link to the targets. Specify these values
1171    /// in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by a
1172    /// comma. At least one IP address must be given for ARP monitoring to
1173    /// function. The maximum number of targets that can be specified is 16.
1174    /// The default value is no IP addresses.
1175    pub arp_ip_target: Option<String>,
1176    #[serde(skip_serializing_if = "Option::is_none")]
1177    /// Specifies whether or not ARP probes and replies should be validated in
1178    /// any mode that supports arp monitoring, or whether non-ARP traffic
1179    /// should be filtered (disregarded) for link monitoring purposes.
1180    pub arp_validate: Option<BondArpValidate>,
1181    #[serde(
1182        skip_serializing_if = "Option::is_none",
1183        default,
1184        deserialize_with = "crate::deserializer::option_u32_or_string"
1185    )]
1186    /// Specifies the time, in milliseconds, to wait before disabling a port
1187    /// after a link failure has been detected. This option is only valid for
1188    /// the miimon link monitor. The downdelay value should be a multiple of
1189    /// the miimon value; if not, it will be rounded down to the nearest
1190    /// multiple. The default value is 0.
1191    pub downdelay: Option<u32>,
1192    #[serde(skip_serializing_if = "Option::is_none")]
1193    /// Specifies whether active-backup mode should set all ports to the same
1194    /// MAC address at enportment (the traditional behavior), or, when enabled,
1195    /// perform special handling of the bond's MAC address in accordance with
1196    /// the selected policy.
1197    pub fail_over_mac: Option<BondFailOverMac>,
1198    #[serde(skip_serializing_if = "Option::is_none")]
1199    /// Option specifying the rate in which we'll ask our link partner to
1200    /// transmit LACPDU packets in 802.3ad mode.
1201    pub lacp_rate: Option<BondLacpRate>,
1202    #[serde(
1203        skip_serializing_if = "Option::is_none",
1204        default,
1205        deserialize_with = "crate::deserializer::option_u32_or_string"
1206    )]
1207    /// Specifies the number of seconds between instances where the bonding
1208    /// driver sends learning packets to each slaves peer switch.
1209    ///
1210    /// The valid range is 1 - 0x7fffffff; the default value is 1. This Option
1211    /// has effect only in balance-tlb and balance-alb modes.
1212    pub lp_interval: Option<u32>,
1213    #[serde(
1214        skip_serializing_if = "Option::is_none",
1215        default,
1216        deserialize_with = "crate::deserializer::option_u32_or_string"
1217    )]
1218    /// Specifies the MII link monitoring frequency in milliseconds.
1219    /// This determines how often the link state of each port is
1220    /// inspected for link failures. A value of zero disables MII
1221    /// link monitoring. A value of 100 is a good starting point.
1222    /// The use_carrier option, below, affects how the link state is
1223    /// determined. See the High Availability section for additional
1224    /// information. The default value is 0.
1225    pub miimon: Option<u32>,
1226    #[serde(
1227        skip_serializing_if = "Option::is_none",
1228        default,
1229        deserialize_with = "crate::deserializer::option_u32_or_string"
1230    )]
1231    /// Specifies the minimum number of links that must be active before
1232    /// asserting carrier. It is similar to the Cisco EtherChannel min-links
1233    /// feature. This allows setting the minimum number of member ports that
1234    /// must be up (link-up state) before marking the bond device as up
1235    /// (carrier on). This is useful for situations where higher level services
1236    /// such as clustering want to ensure a minimum number of low bandwidth
1237    /// links are active before switchover. This option only affect 802.3ad
1238    /// mode.
1239    ///
1240    /// The default value is 0. This will cause carrier to be asserted (for
1241    /// 802.3ad mode) whenever there is an active aggregator, regardless of the
1242    /// number of available links in that aggregator. Note that, because an
1243    /// aggregator cannot be active without at least one available link,
1244    /// setting this option to 0 or to 1 has the exact same effect.
1245    pub min_links: Option<u32>,
1246    #[serde(
1247        skip_serializing_if = "Option::is_none",
1248        default,
1249        deserialize_with = "crate::deserializer::option_u8_or_string"
1250    )]
1251    /// Specify the number of peer notifications (gratuitous ARPs and
1252    /// unsolicited IPv6 Neighbor Advertisements) to be issued after a
1253    /// failover event. As soon as the link is up on the new port
1254    /// (possibly immediately) a peer notification is sent on the
1255    /// bonding device and each VLAN sub-device. This is repeated at
1256    /// the rate specified by peer_notif_delay if the number is
1257    /// greater than 1.
1258    ///
1259    /// The valid range is 0 - 255; the default value is 1. These options
1260    /// affect only the active-backup mode. These options were added for
1261    /// bonding versions 3.3.0 and 3.4.0 respectively.
1262    ///
1263    /// From Linux 3.0 and bonding version 3.7.1, these notifications are
1264    /// generated by the ipv4 and ipv6 code and the numbers of repetitions
1265    /// cannot be set independently.
1266    pub num_grat_arp: Option<u8>,
1267    #[serde(
1268        skip_serializing_if = "Option::is_none",
1269        default,
1270        deserialize_with = "crate::deserializer::option_u8_or_string"
1271    )]
1272    /// Identical to [BondOptions.num_grat_arp]
1273    pub num_unsol_na: Option<u8>,
1274    #[serde(
1275        skip_serializing_if = "Option::is_none",
1276        default,
1277        deserialize_with = "crate::deserializer::option_u32_or_string"
1278    )]
1279    /// Specify the number of packets to transmit through a port before moving
1280    /// to the next one. When set to 0 then a port is chosen at random.
1281    ///
1282    /// The valid range is 0 - 65535; the default value is 1. This option has
1283    /// effect only in balance-rr mode.
1284    pub packets_per_slave: Option<u32>,
1285    #[serde(skip_serializing_if = "Option::is_none")]
1286    /// A string (eth0, eth2, etc) specifying which slave is the primary
1287    /// device. The specified device will always be the active slave while
1288    /// it is available. Only when the primary is off-line will alternate
1289    /// devices be used. This is useful when one slave is preferred over
1290    /// another, e.g., when one slave has higher throughput than another.
1291    ///
1292    /// The primary option is only valid for active-backup(1), balance-tlb (5)
1293    /// and balance-alb (6) mode.
1294    pub primary: Option<String>,
1295    #[serde(skip_serializing_if = "Option::is_none")]
1296    /// Specifies the reselection policy for the primary port. This affects
1297    /// how the primary port is chosen to become the active port when failure
1298    /// of the active port or recovery of the primary port occurs. This
1299    /// option is designed to prevent flip-flopping between the primary port
1300    /// and other ports.
1301    pub primary_reselect: Option<BondPrimaryReselect>,
1302    #[serde(
1303        skip_serializing_if = "Option::is_none",
1304        default,
1305        deserialize_with = "crate::deserializer::option_u32_or_string"
1306    )]
1307    /// Specifies the number of IGMP membership reports to be issued after
1308    /// a failover event. One membership report is issued immediately after
1309    /// the failover, subsequent packets are sent in each 200ms interval.
1310    ///
1311    /// The valid range is 0 - 255; the default value is 1. A value of 0
1312    /// prevents the IGMP membership report from being issued in response
1313    /// to the failover event.
1314    ///
1315    /// This option is useful for bonding modes balance-rr (0), active-backup
1316    /// (1), balance-tlb (5) and balance-alb (6), in which a failover can
1317    /// switch the IGMP traffic from one port to another. Therefore a
1318    /// fresh IGMP report must be issued to cause the switch to forward the
1319    /// incoming IGMP traffic over the newly selected port.
1320    pub resend_igmp: Option<u32>,
1321    #[serde(
1322        skip_serializing_if = "Option::is_none",
1323        default,
1324        deserialize_with = "crate::deserializer::option_bool_or_string"
1325    )]
1326    /// Specifies if dynamic shuffling of flows is enabled in tlb mode. The
1327    /// value has no effect on any other modes.
1328    ///
1329    /// The default behavior of tlb mode is to shuffle active flows across
1330    /// ports based on the load in that interval. This gives nice lb
1331    /// characteristics but can cause packet reordering. If re-ordering is a
1332    /// concern use this variable to disable flow shuffling and rely on load
1333    /// balancing provided solely by the hash distribution. xmit-hash-policy
1334    /// can be used to select the appropriate hashing for the setup.
1335    ///
1336    /// The sysfs entry can be used to change the setting per bond device and
1337    /// the initial value is derived from the module parameter. The sysfs entry
1338    /// is allowed to be changed only if the bond device is down.
1339    ///
1340    /// The default value is "1" that enables flow shuffling while value "0"
1341    /// disables it. This option was added in bonding driver 3.7.1
1342    pub tlb_dynamic_lb: Option<bool>,
1343    #[serde(
1344        skip_serializing_if = "Option::is_none",
1345        default,
1346        deserialize_with = "crate::deserializer::option_u32_or_string"
1347    )]
1348    /// Specifies the time, in milliseconds, to wait before enabling a port
1349    /// after a link recovery has been detected. This option is only valid for
1350    /// the miimon link monitor. The updelay value should be a multiple of the
1351    /// miimon value; if not, it will be rounded down to the nearest multiple.
1352    /// The default value is 0.
1353    pub updelay: Option<u32>,
1354    #[serde(
1355        skip_serializing_if = "Option::is_none",
1356        default,
1357        deserialize_with = "crate::deserializer::option_bool_or_string"
1358    )]
1359    /// Specifies whether or not miimon should use MII or ETHTOOL
1360    /// ioctls vs. netif_carrier_ok() to determine the link
1361    /// status. The MII or ETHTOOL ioctls are less efficient and
1362    /// utilize a deprecated calling sequence within the kernel.  The
1363    /// netif_carrier_ok() relies on the device driver to maintain its
1364    /// state with netif_carrier_on/off; at this writing, most, but
1365    /// not all, device drivers support this facility.
1366    ///
1367    /// If bonding insists that the link is up when it should not be,
1368    /// it may be that your network device driver does not support
1369    /// netif_carrier_on/off.  The default state for netif_carrier is
1370    /// "carrier on," so if a driver does not support netif_carrier,
1371    /// it will appear as if the link is always up.  In this case,
1372    /// setting use_carrier to 0 will cause bonding to revert to the
1373    /// MII / ETHTOOL ioctl method to determine the link state.
1374    ///
1375    /// A value of 1 enables the use of netif_carrier_ok(), a value of
1376    /// 0 will use the deprecated MII / ETHTOOL ioctls.  The default
1377    /// value is 1.
1378    pub use_carrier: Option<bool>,
1379    #[serde(skip_serializing_if = "Option::is_none")]
1380    /// Selects the transmit hash policy to use for slave selection in
1381    /// balance-xor, 802.3ad, and tlb modes.
1382    pub xmit_hash_policy: Option<BondXmitHashPolicy>,
1383    #[serde(
1384        skip_serializing_if = "Option::is_none",
1385        default,
1386        deserialize_with = "crate::deserializer::option_bool_or_string",
1387        alias = "balance-slb"
1388    )]
1389    pub balance_slb: Option<bool>,
1390    #[serde(
1391        skip_serializing_if = "Option::is_none",
1392        default,
1393        deserialize_with = "crate::deserializer::option_u8_or_string"
1394    )]
1395    pub arp_missed_max: Option<u8>,
1396    /// Whether send LACPDU frames periodically. Only valid for bond in
1397    /// `802.3ad` mode.
1398    #[serde(
1399        skip_serializing_if = "Option::is_none",
1400        default,
1401        deserialize_with = "crate::deserializer::option_bool_or_string"
1402    )]
1403    pub lacp_active: Option<bool>,
1404    /// Specifies the IPv6 addresses to use as IPv6 monitoring peers when
1405    /// `arp_interval` is > 0.
1406    #[serde(skip_serializing_if = "Option::is_none")]
1407    pub ns_ip6_target: Option<Vec<Ipv6Addr>>,
1408}
1409
1410impl BondOptions {
1411    pub fn new() -> Self {
1412        Self::default()
1413    }
1414
1415    fn validate_ad_actor_system_mac_address(&self) -> Result<(), NmstateError> {
1416        if let Some(ad_actor_system) = &self.ad_actor_system {
1417            if ad_actor_system.to_uppercase().starts_with("01:00:5E") {
1418                let e = NmstateError::new(
1419                    ErrorKind::InvalidArgument,
1420                    "The ad_actor_system bond option cannot be an IANA \
1421                     multicast address(prefix with 01:00:5E)"
1422                        .to_string(),
1423                );
1424                log::error!("{e}");
1425                return Err(e);
1426            }
1427        }
1428        Ok(())
1429    }
1430
1431    fn validate_miimon_and_arp_interval(&self) -> Result<(), NmstateError> {
1432        if let (Some(miimon), Some(arp_interval)) =
1433            (self.miimon, self.arp_interval)
1434        {
1435            if miimon > 0 && arp_interval > 0 {
1436                let e = NmstateError::new(
1437                    ErrorKind::InvalidArgument,
1438                    "Bond miimon and arp interval are not compatible options."
1439                        .to_string(),
1440                );
1441                log::error!("{e}");
1442                return Err(e);
1443            }
1444        }
1445        Ok(())
1446    }
1447
1448    fn validate_balance_slb(
1449        &self,
1450        current: Option<&Self>,
1451        mode: BondMode,
1452    ) -> Result<(), NmstateError> {
1453        if self
1454            .balance_slb
1455            .or_else(|| current.and_then(|c| c.balance_slb))
1456            == Some(true)
1457        {
1458            let xmit_hash_policy = self
1459                .xmit_hash_policy
1460                .or_else(|| current.and_then(|c| c.xmit_hash_policy));
1461            if mode != BondMode::XOR
1462                || xmit_hash_policy != Some(BondXmitHashPolicy::VlanSrcMac)
1463            {
1464                return Err(NmstateError::new(
1465                    ErrorKind::InvalidArgument,
1466                    "To enable balance-slb, bond mode should be balance-xor \
1467                     and xmit_hash_policy: 'vlan+srcmac'"
1468                        .to_string(),
1469                ));
1470            }
1471        }
1472        Ok(())
1473    }
1474
1475    fn validate_lacp_opts(&self, mode: BondMode) -> Result<(), NmstateError> {
1476        if mode != BondMode::LACP {
1477            if self.lacp_rate.is_some() {
1478                return Err(NmstateError::new(
1479                    ErrorKind::InvalidArgument,
1480                    "The `lacp_rate` option is only valid for bond in \
1481                     '802.3ad' mode"
1482                        .to_string(),
1483                ));
1484            }
1485            if self.lacp_active.is_some() {
1486                return Err(NmstateError::new(
1487                    ErrorKind::InvalidArgument,
1488                    "The `lacp_active` option is only valid for bond in \
1489                     '802.3ad' mode"
1490                        .to_string(),
1491                ));
1492            }
1493        }
1494        Ok(())
1495    }
1496
1497    // `arp_interval` should bigger than 0 when `arp_ip_target` or
1498    // `ns_ip6_target` defined.
1499    // The `arp_interval` cannot be used in `802.3ad`, `balance-tlb`
1500    // `balance-alb` mode.
1501    fn validate_arp_interval(
1502        &self,
1503        current: Option<&Self>,
1504        mode: BondMode,
1505    ) -> Result<(), NmstateError> {
1506        if let Some(arp_interval) = self
1507            .arp_interval
1508            .or_else(|| current.and_then(|c| c.arp_interval))
1509        {
1510            if arp_interval > 0 {
1511                if mode == BondMode::LACP
1512                    || mode == BondMode::TLB
1513                    || mode == BondMode::ALB
1514                {
1515                    return Err(NmstateError::new(
1516                        ErrorKind::InvalidArgument,
1517                        "The `arp_interval` option is invalid for bond in \
1518                         '802.3ad', 'balance-tlb' or 'balance-alb' mode"
1519                            .to_string(),
1520                    ));
1521                }
1522            } else {
1523                if let Some(arp_ip_target) = self.arp_ip_target.as_ref() {
1524                    if !arp_ip_target.is_empty() {
1525                        return Err(NmstateError::new(
1526                            ErrorKind::InvalidArgument,
1527                            "The `arp_ip_target` option is only valid when \
1528                             'arp_interval' is enabled(>0)."
1529                                .to_string(),
1530                        ));
1531                    }
1532                }
1533                if let Some(ns_ip6_target) = self.ns_ip6_target.as_ref() {
1534                    if !ns_ip6_target.is_empty() {
1535                        return Err(NmstateError::new(
1536                            ErrorKind::InvalidArgument,
1537                            "The `ns_ip6_target` option is only valid when \
1538                             'arp_interval' is enabled(>0)."
1539                                .to_string(),
1540                        ));
1541                    }
1542                }
1543            }
1544        }
1545        Ok(())
1546    }
1547}
1548
1549impl MergedInterface {
1550    pub(crate) fn post_inter_ifaces_process_bond(
1551        &mut self,
1552    ) -> Result<(), NmstateError> {
1553        if self.merged.is_absent() {
1554            return Ok(());
1555        }
1556        if let Some(Interface::Bond(apply_iface)) = self.for_apply.as_mut() {
1557            apply_iface
1558                .validate_new_iface_with_no_mode(self.current.as_ref())?;
1559            apply_iface.sanitize_mac_restricted_mode(self.current.as_ref());
1560            apply_iface.validate_conflict_in_port_and_port_configs()?;
1561
1562            if let Some(bond_opts) =
1563                apply_iface.bond.as_ref().and_then(|b| b.options.as_ref())
1564            {
1565                bond_opts.validate_ad_actor_system_mac_address()?;
1566                bond_opts.validate_miimon_and_arp_interval()?;
1567
1568                if let Interface::Bond(merged_iface) = &self.merged {
1569                    if let Some(mode) =
1570                        merged_iface.bond.as_ref().and_then(|b| b.mode)
1571                    {
1572                        let cur_bond_opts =
1573                            if let Some(Interface::Bond(cur_iface)) =
1574                                self.current.as_ref()
1575                            {
1576                                cur_iface
1577                                    .bond
1578                                    .as_ref()
1579                                    .and_then(|b| b.options.as_ref())
1580                            } else {
1581                                None
1582                            };
1583                        bond_opts.validate_balance_slb(cur_bond_opts, mode)?;
1584                        bond_opts.validate_lacp_opts(mode)?;
1585                        bond_opts.validate_arp_interval(cur_bond_opts, mode)?;
1586                    }
1587                }
1588            }
1589        }
1590        Ok(())
1591    }
1592}
1593
1594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1595#[serde(rename_all = "kebab-case")]
1596#[non_exhaustive]
1597pub struct BondPortConfig {
1598    /// name is mandatory when specifying the ports configuration.
1599    pub name: String,
1600    #[serde(
1601        skip_serializing_if = "Option::is_none",
1602        default,
1603        deserialize_with = "crate::deserializer::option_i32_or_string"
1604    )]
1605    /// Deserialize and serialize from/to `priority`.
1606    /// When applying, if defined, it will override the current bond port
1607    /// priority. The verification will fail if bonding mode is not
1608    /// active-backup(1) or balance-tlb (5) or balance-alb (6).
1609    pub priority: Option<i32>,
1610    #[serde(
1611        skip_serializing_if = "Option::is_none",
1612        default,
1613        deserialize_with = "crate::deserializer::option_u16_or_string"
1614    )]
1615    /// Deserialize and serialize from/to `queue-id`.
1616    pub queue_id: Option<u16>,
1617}
1618
1619impl std::fmt::Display for BondPortConfig {
1620    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1621        write!(
1622            f,
1623            "BondPortConfig {{ name: {}, priority: {}, queue_id: {} }}",
1624            self.name,
1625            self.priority.unwrap_or_default(),
1626            self.queue_id.unwrap_or_default()
1627        )
1628    }
1629}
1630
1631impl BondPortConfig {
1632    pub fn new() -> Self {
1633        Self::default()
1634    }
1635
1636    fn is_changed(&self, current: &Self) -> bool {
1637        (self.priority.is_some() && self.priority != current.priority)
1638            || (self.queue_id.is_some() && self.queue_id != current.queue_id)
1639    }
1640}