nmstate 2.2.60

Library for networking management in a declarative manner
Documentation
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

use crate::{
    BaseInterface, ErrorKind, Interface, InterfaceType, Interfaces,
    MergedInterfaces, NetworkStateMode, NmstateError, SrIovConfig,
};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
/// Ethernet(IEEE 802.3) interface.
/// Besides [BaseInterface], optionally could hold [EthernetConfig] and/or
/// [VethConfig].
/// The yaml output of [crate::NetworkState] containing ethernet interface would
/// be:
/// ```yml
/// interfaces:
/// - name: ens3
///   type: ethernet
///   state: up
///   mac-address: 00:11:22:33:44:FF
///   mtu: 1500
///   min-mtu: 68
///   max-mtu: 65535
///   wait-ip: ipv4
///   ipv4:
///     enabled: true
///     dhcp: false
///     address:
///     - ip: 192.0.2.9
///       prefix-length: 24
///   ipv6:
///     enabled: false
///   mptcp:
///     address-flags: []
///   accept-all-mac-addresses: false
///   lldp:
///     enabled: false
///   ethtool:
///     feature:
///       tx-tcp-ecn-segmentation: true
///       tx-tcp-mangleid-segmentation: false
///       tx-tcp6-segmentation: true
///       tx-tcp-segmentation: true
///       rx-gro-list: false
///       rx-udp-gro-forwarding: false
///       rx-gro-hw: true
///       tx-checksum-ip-generic: true
///       tx-generic-segmentation: true
///       rx-gro: true
///       tx-nocache-copy: false
///     coalesce:
///       rx-frames: 1
///       tx-frames: 1
///     ring:
///       rx: 256
///       rx-max: 256
///       tx: 256
///       tx-max: 256
///   ethernet:
///     auto-negotiation: false
/// ```
pub struct EthernetInterface {
    #[serde(flatten)]
    pub base: BaseInterface,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ethernet: Option<EthernetConfig>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// When applying, the [VethConfig] is only valid when
    /// [BaseInterface.iface_type] is set to [InterfaceType::Veth] explicitly.
    pub veth: Option<VethConfig>,
}

impl Default for EthernetInterface {
    fn default() -> Self {
        let mut base = BaseInterface::new();
        base.iface_type = InterfaceType::Ethernet;
        Self {
            base,
            ethernet: None,
            veth: None,
        }
    }
}

impl EthernetInterface {
    pub(crate) fn sanitize(&mut self) -> Result<(), NmstateError> {
        // Always set interface type to ethernet for verifying and applying
        self.base.iface_type = InterfaceType::Ethernet;

        if let Some(sriov_conf) =
            self.ethernet.as_mut().and_then(|e| e.sr_iov.as_mut())
        {
            sriov_conf.sanitize()?
        }

        Ok(())
    }

    pub fn new() -> Self {
        Self::default()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum EthernetDuplex {
    /// Deserialize and serialize from/to `full`.
    Full,
    /// Deserialize and serialize from/to `half`.
    Half,
}

impl std::fmt::Display for EthernetDuplex {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Full => "full",
                Self::Half => "half",
            }
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[non_exhaustive]
pub struct EthernetConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Single Root I/O Virtualization(SRIOV) configuration.
    /// Deserialize and serialize from/to `sr-iov`.
    pub sr_iov: Option<SrIovConfig>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        rename = "auto-negotiation",
        default,
        deserialize_with = "crate::deserializer::option_bool_or_string"
    )]
    /// Deserialize and serialize from/to `auto-negotiation`.
    pub auto_neg: Option<bool>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        default,
        deserialize_with = "crate::deserializer::option_u32_or_string"
    )]
    pub speed: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duplex: Option<EthernetDuplex>,
}

impl EthernetConfig {
    pub fn new() -> Self {
        Self::default()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub struct VethConfig {
    /// The name of veth peer.
    pub peer: String,
}

impl MergedInterfaces {
    // Raise error if new veth interface has no peer defined.
    // Mark old veth peer as absent when veth changed its peer.
    // Mark veth peer as absent also when veth is marked as absent.
    pub(crate) fn process_veth_peer_changes(
        &mut self,
    ) -> Result<(), NmstateError> {
        let mut veth_peers: Vec<&str> = Vec::new();
        for iface in self.iter().filter(|i| {
            i.merged.iface_type() == InterfaceType::Ethernet && i.merged.is_up()
        }) {
            if let Interface::Ethernet(eth_iface) = &iface.merged
                && let Some(v) =
                    eth_iface.veth.as_ref().map(|v| v.peer.as_str())
            {
                veth_peers.push(v);
            }
        }
        for iface in self.iter().filter(|i| {
            i.merged.iface_type() == InterfaceType::Ethernet
                && i.is_desired()
                && i.current.is_none()
                && i.merged.is_up()
        }) {
            if let Some(Interface::Ethernet(eth_iface)) = &iface.desired
                && eth_iface.veth.is_none()
                    && self.mode != NetworkStateMode::GenerateConfig
                    && !veth_peers.contains(&eth_iface.base.name.as_str())
                    // The `resolve_sriov_reference()` will raise error if
                    // SRIOV VF not found, so we skip check on them here.
                    && !eth_iface.base.name.starts_with("sriov:")
            {
                return Err(NmstateError::new(
                    ErrorKind::InvalidArgument,
                    format!(
                        "Ethernet interface {} does not exists",
                        eth_iface.base.name.as_str()
                    ),
                ));
            }
        }

        let mut pending_deletions: Vec<String> = Vec::new();

        for iface in self.iter().filter(|i| {
            i.merged.iface_type() == InterfaceType::Ethernet
                && i.is_desired()
                && i.merged.is_up()
                && i.current.is_some()
        }) {
            if let (
                Some(Interface::Ethernet(des_eth_iface)),
                Some(Interface::Ethernet(cur_eth_iface)),
            ) = (iface.desired.as_ref(), iface.current.as_ref())
                && let (Some(veth_conf), Some(cur_veth_conf)) =
                    (des_eth_iface.veth.as_ref(), cur_eth_iface.veth.as_ref())
                && veth_conf.peer != cur_veth_conf.peer
            {
                pending_deletions.push(cur_veth_conf.peer.to_string());
            }
        }

        for iface in self.iter().filter(|i| {
            i.merged.iface_type() == InterfaceType::Ethernet
                && i.is_desired()
                && i.merged.is_absent()
                && i.current.is_some()
        }) {
            if let Some(Interface::Ethernet(cur_eth_iface)) =
                iface.current.as_ref()
                && let Some(veth_conf) = cur_eth_iface.veth.as_ref()
            {
                pending_deletions.push(veth_conf.peer.to_string());
            }
        }

        for del_peer in pending_deletions {
            if let Some(iface) = self.kernel_ifaces.get_mut(&del_peer) {
                iface.mark_as_absent();
            }
        }
        Ok(())
    }
}

impl Interfaces {
    // Not allowing changing veth peer away from ignored peer unless previous
    // peer changed from ignore to managed
    pub(crate) fn validate_change_veth_ignored_peer(
        &self,
        current: &Self,
        ignored_ifaces: &[(String, InterfaceType)],
    ) -> Result<(), NmstateError> {
        let ignored_veth_ifaces: Vec<&String> = ignored_ifaces
            .iter()
            .filter_map(|(n, t)| {
                if t == &InterfaceType::Ethernet {
                    Some(n)
                } else {
                    None
                }
            })
            .collect();

        for iface in self.kernel_ifaces.values().filter(|i| {
            if let Interface::Ethernet(i) = i {
                i.veth.is_some()
            } else {
                false
            }
        }) {
            if let (
                Interface::Ethernet(des_iface),
                Some(Interface::Ethernet(cur_iface)),
            ) = (iface, current.get_iface(iface.name(), InterfaceType::Veth))
                && let (Some(des_peer), cur_peer) = (
                    des_iface.veth.as_ref().map(|v| v.peer.as_str()),
                    cur_iface.veth.as_ref().map(|v| v.peer.as_str()),
                )
            {
                let cur_peer = if let Some(c) = cur_peer {
                    c
                } else {
                    // The veth peer is in another namespace.
                    let e = NmstateError::new(
                        ErrorKind::InvalidArgument,
                        format!(
                            "Veth interface {} is currently holding peer \
                             assigned to other namespace Please remove this \
                             veth pair before changing veth peer to {des_peer}",
                            iface.name(),
                        ),
                    );
                    log::error!("{e}");
                    return Err(e);
                };

                if des_peer != cur_peer
                    && ignored_veth_ifaces.contains(&&cur_peer.to_string())
                {
                    let e = NmstateError::new(
                        ErrorKind::InvalidArgument,
                        format!(
                            "Veth interface {} is currently holding peer {} \
                             which is marked as ignored. Hence not allowing \
                             changing its peer to {}. Please remove this veth \
                             pair before changing veth peer",
                            iface.name(),
                            cur_peer,
                            des_peer
                        ),
                    );
                    log::error!("{e}");
                    return Err(e);
                }
            }
        }
        Ok(())
    }

    pub(crate) fn validate_new_veth_without_peer(
        &self,
        current: &Self,
    ) -> Result<(), NmstateError> {
        for iface in self.kernel_ifaces.values().filter(|i| {
            i.is_up()
                && i.iface_type() == InterfaceType::Veth
                && !current.kernel_ifaces.contains_key(i.name())
        }) {
            if let Interface::Ethernet(eth_iface) = iface
                && eth_iface.veth.is_none()
            {
                return Err(NmstateError::new(
                    ErrorKind::InvalidArgument,
                    format!(
                        "Veth interface {} does not exist, peer name is \
                         required for creating it",
                        iface.name()
                    ),
                ));
            }
        }
        Ok(())
    }
}