Skip to content

Commit

Permalink
ipvlan: add IPVLAN interface support
Browse files Browse the repository at this point in the history
Example:

```
---
interfaces:
  - name: ipvlan0
    type: ipvlan
    state: up
    ipvlan:
      base-iface: eth1
      mode: l2
```

Integration tests added.

Signed-off-by: Fernando Fernandez Mancera <[email protected]>
  • Loading branch information
ffmancera authored and cathay4t committed Sep 29, 2024
1 parent b6e7ade commit 50c267c
Show file tree
Hide file tree
Showing 33 changed files with 533 additions and 32 deletions.
5 changes: 5 additions & 0 deletions examples/ipvlan_absent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
interfaces:
- name: ipvlan0
type: ipvlan
state: absent
8 changes: 8 additions & 0 deletions examples/ipvlan_create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
interfaces:
- name: ipvlan0
type: ipvlan
state: up
ipvlan:
base-iface: eth1
mode: l2
27 changes: 23 additions & 4 deletions rust/src/lib/iface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use serde::{Deserialize, Deserializer, Serialize};

use crate::{
BaseInterface, BondInterface, DummyInterface, ErrorKind, EthernetInterface,
HsrInterface, InfiniBandInterface, IpsecInterface, LinuxBridgeInterface,
LoopbackInterface, MacSecInterface, MacVlanInterface, MacVtapInterface,
NmstateError, OvsBridgeInterface, OvsInterface, VlanInterface,
VrfInterface, VxlanInterface, XfrmInterface,
HsrInterface, InfiniBandInterface, IpVlanInterface, IpsecInterface,
LinuxBridgeInterface, LoopbackInterface, MacSecInterface, MacVlanInterface,
MacVtapInterface, NmstateError, OvsBridgeInterface, OvsInterface,
VlanInterface, VrfInterface, VxlanInterface, XfrmInterface,
};

use crate::state::merge_json_value;
Expand Down Expand Up @@ -78,6 +78,9 @@ pub enum InterfaceType {
Ipsec,
/// Linux Xfrm kernel interface
Xfrm,
/// IPVLAN kernel interface
#[serde(rename = "ipvlan")]
IpVlan,
/// Unknown interface.
Unknown,
/// Reserved for future use.
Expand Down Expand Up @@ -118,6 +121,7 @@ impl std::fmt::Display for InterfaceType {
InterfaceType::MacSec => "macsec",
InterfaceType::Ipsec => "ipsec",
InterfaceType::Xfrm => "xfrm",
InterfaceType::IpVlan => "ipvlan",
InterfaceType::Other(ref s) => s,
}
)
Expand Down Expand Up @@ -278,6 +282,8 @@ pub enum Interface {
Ipsec(Box<IpsecInterface>),
/// Linux xfrm interface
Xfrm(Box<XfrmInterface>),
/// Linux IPVLAN interface
IpVlan(Box<IpVlanInterface>),
}

impl<'de> Deserialize<'de> for Interface {
Expand Down Expand Up @@ -399,6 +405,11 @@ impl<'de> Deserialize<'de> for Interface {
.map_err(serde::de::Error::custom)?;
Ok(Interface::Xfrm(Box::new(inner)))
}
Some(InterfaceType::IpVlan) => {
let inner = IpVlanInterface::deserialize(v)
.map_err(serde::de::Error::custom)?;
Ok(Interface::IpVlan(Box::new(inner)))
}
Some(iface_type) => {
log::warn!("Unsupported interface type {}", iface_type);
let inner = UnknownInterface::deserialize(v)
Expand Down Expand Up @@ -524,6 +535,11 @@ impl Interface {
new_iface.base = iface.base.clone_name_type_only();
Self::Xfrm(Box::new(new_iface))
}
Self::IpVlan(iface) => {
let mut new_iface = IpVlanInterface::new();
new_iface.base = iface.base.clone_name_type_only();
Self::IpVlan(Box::new(new_iface))
}
Self::Unknown(iface) => {
let mut new_iface = UnknownInterface::new();
new_iface.base = iface.base.clone_name_type_only();
Expand Down Expand Up @@ -622,6 +638,7 @@ impl Interface {
Self::MacSec(iface) => &iface.base,
Self::Ipsec(iface) => &iface.base,
Self::Xfrm(iface) => &iface.base,
Self::IpVlan(iface) => &iface.base,
Self::Unknown(iface) => &iface.base,
}
}
Expand All @@ -645,6 +662,7 @@ impl Interface {
Self::MacSec(iface) => &mut iface.base,
Self::Ipsec(iface) => &mut iface.base,
Self::Xfrm(iface) => &mut iface.base,
Self::IpVlan(iface) => &mut iface.base,
Self::Unknown(iface) => &mut iface.base,
}
}
Expand Down Expand Up @@ -697,6 +715,7 @@ impl Interface {
Interface::MacSec(iface) => iface.sanitize(is_desired)?,
Interface::Ipsec(iface) => iface.sanitize(is_desired),
Interface::Vlan(iface) => iface.sanitize(is_desired)?,
Interface::IpVlan(iface) => iface.sanitize(is_desired)?,
_ => (),
}
Ok(())
Expand Down
117 changes: 117 additions & 0 deletions rust/src/lib/ifaces/ipvlan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

use crate::{BaseInterface, ErrorKind, InterfaceType, NmstateError};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
/// Linux kernel IPVLAN interface. The example YAML output of
/// [crate::NetworkState] with an IPVLAN interface would be:
/// ```yaml
/// ---
/// interfaces:
/// - name: ipvlan0
/// type: ipvlan
/// state: up
/// ipvlan:
/// base-iface: eth1
/// mode: l3
/// private: true
/// ```
pub struct IpVlanInterface {
#[serde(flatten)]
pub base: BaseInterface,
#[serde(skip_serializing_if = "Option::is_none")]
pub ipvlan: Option<IpVlanConfig>,
}

impl Default for IpVlanInterface {
fn default() -> Self {
Self {
base: BaseInterface {
iface_type: InterfaceType::IpVlan,
..Default::default()
},
ipvlan: None,
}
}
}

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

pub(crate) fn sanitize(
&self,
is_desired: bool,
) -> Result<(), NmstateError> {
if is_desired {
if let Some(conf) = &self.ipvlan {
if conf.private == Some(true) && conf.vepa == Some(true) {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
"Both private and VEPA flags cannot \
be enabled at the same time"
.to_string(),
);
log::error!("{}", e);
return Err(e);
}
}
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[non_exhaustive]
pub struct IpVlanConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub base_iface: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub mode: Option<IpVlanMode>,
#[serde(
skip_serializing_if = "Option::is_none",
default,
deserialize_with = "crate::deserializer::option_bool_or_string"
)]
pub private: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
default,
deserialize_with = "crate::deserializer::option_bool_or_string"
)]
pub vepa: Option<bool>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum IpVlanMode {
/// Deserialize and serialize from/to `l2`.
L2,
/// Deserialize and serialize from/to `l3`.
L3,
#[serde(rename = "l3s")]
/// Deserialize and serialize from/to `l3s`.
L3S,
}

impl From<IpVlanMode> for u32 {
fn from(v: IpVlanMode) -> u32 {
match v {
IpVlanMode::L2 => 1,
IpVlanMode::L3 => 2,
IpVlanMode::L3S => 3,
}
}
}

impl Default for IpVlanMode {
fn default() -> Self {
Self::L3
}
}
2 changes: 2 additions & 0 deletions rust/src/lib/ifaces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod ethtool;
mod hsr;
pub(crate) mod inter_ifaces;
mod ipsec;
mod ipvlan;
mod loopback;
mod vrf;
mod vxlan;
Expand Down Expand Up @@ -51,6 +52,7 @@ pub use ipsec::{
IpsecInterface, LibreswanAddressFamily, LibreswanConfig,
LibreswanConnectionType,
};
pub use ipvlan::{IpVlanConfig, IpVlanInterface, IpVlanMode};
pub use linux_bridge::{
LinuxBridgeConfig, LinuxBridgeInterface, LinuxBridgeMulticastRouterType,
LinuxBridgeOptions, LinuxBridgePortConfig, LinuxBridgeStpOptions,
Expand Down
25 changes: 13 additions & 12 deletions rust/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,19 @@ pub use crate::ifaces::{
EthernetDuplex, EthernetInterface, EthtoolCoalesceConfig, EthtoolConfig,
EthtoolFeatureConfig, EthtoolPauseConfig, EthtoolRingConfig, HsrConfig,
HsrInterface, HsrProtocol, InfiniBandConfig, InfiniBandInterface,
InfiniBandMode, Interfaces, IpsecInterface, LibreswanAddressFamily,
LibreswanConfig, LibreswanConnectionType, LinuxBridgeConfig,
LinuxBridgeInterface, LinuxBridgeMulticastRouterType, LinuxBridgeOptions,
LinuxBridgePortConfig, LinuxBridgeStpOptions, LoopbackInterface,
MacSecConfig, MacSecInterface, MacSecOffload, MacSecValidate,
MacVlanConfig, MacVlanInterface, MacVlanMode, MacVtapConfig,
MacVtapInterface, MacVtapMode, OvsBridgeBondConfig, OvsBridgeBondMode,
OvsBridgeBondPortConfig, OvsBridgeConfig, OvsBridgeInterface,
OvsBridgeOptions, OvsBridgePortConfig, OvsBridgeStpOptions, OvsDpdkConfig,
OvsInterface, OvsPatchConfig, SrIovConfig, SrIovVfConfig, VethConfig,
VlanConfig, VlanInterface, VlanProtocol, VlanRegistrationProtocol,
VrfConfig, VrfInterface, VxlanConfig, VxlanInterface, XfrmInterface,
InfiniBandMode, Interfaces, IpVlanConfig, IpVlanInterface, IpVlanMode,
IpsecInterface, LibreswanAddressFamily, LibreswanConfig,
LibreswanConnectionType, LinuxBridgeConfig, LinuxBridgeInterface,
LinuxBridgeMulticastRouterType, LinuxBridgeOptions, LinuxBridgePortConfig,
LinuxBridgeStpOptions, LoopbackInterface, MacSecConfig, MacSecInterface,
MacSecOffload, MacSecValidate, MacVlanConfig, MacVlanInterface,
MacVlanMode, MacVtapConfig, MacVtapInterface, MacVtapMode,
OvsBridgeBondConfig, OvsBridgeBondMode, OvsBridgeBondPortConfig,
OvsBridgeConfig, OvsBridgeInterface, OvsBridgeOptions, OvsBridgePortConfig,
OvsBridgeStpOptions, OvsDpdkConfig, OvsInterface, OvsPatchConfig,
SrIovConfig, SrIovVfConfig, VethConfig, VlanConfig, VlanInterface,
VlanProtocol, VlanRegistrationProtocol, VrfConfig, VrfInterface,
VxlanConfig, VxlanInterface, XfrmInterface,
};
pub use crate::ip::{
AddressFamily, Dhcpv4ClientId, Dhcpv6Duid, InterfaceIpAddr, InterfaceIpv4,
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib/nispor/base_iface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fn np_iface_type_to_nmstate(
nispor::IfaceType::Ipoib => InterfaceType::InfiniBand,
nispor::IfaceType::Tun => InterfaceType::Tun,
nispor::IfaceType::Xfrm => InterfaceType::Xfrm,
nispor::IfaceType::IpVlan => InterfaceType::IpVlan,
nispor::IfaceType::Other(v) => InterfaceType::Other(v.to_lowercase()),
_ => InterfaceType::Other(format!("{np_iface_type:?}").to_lowercase()),
}
Expand Down
39 changes: 39 additions & 0 deletions rust/src/lib/nispor/ipvlan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0

use crate::{BaseInterface, IpVlanConfig, IpVlanInterface, IpVlanMode};

pub(crate) fn np_ipvlan_to_nmstate(
np_iface: &nispor::Iface,
base_iface: BaseInterface,
) -> IpVlanInterface {
let ipvlan_conf =
np_iface
.ip_vlan
.as_ref()
.map(|np_ipvlan_info| IpVlanConfig {
mode: match &np_ipvlan_info.mode {
nispor::IpVlanMode::L2 => Some(IpVlanMode::L2),
nispor::IpVlanMode::L3 => Some(IpVlanMode::L3),
nispor::IpVlanMode::L3S => Some(IpVlanMode::L3S),
_ => {
log::warn!(
"Unknown supported IPVLAN mode {:?}",
np_ipvlan_info.mode
);
Some(IpVlanMode::L3)
}
},
private: Some(
np_ipvlan_info.flags.contains(&nispor::IpVlanFlag::Private),
),
vepa: Some(
np_ipvlan_info.flags.contains(&nispor::IpVlanFlag::Vepa),
),
base_iface: Some(np_ipvlan_info.base_iface.clone()),
});

IpVlanInterface {
base: base_iface,
ipvlan: ipvlan_conf,
}
}
1 change: 1 addition & 0 deletions rust/src/lib/nispor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod hostname;
mod hsr;
mod infiniband;
mod ip;
mod ipvlan;
mod linux_bridge;
mod linux_bridge_port_vlan;
mod mac_vlan;
Expand Down
4 changes: 4 additions & 0 deletions rust/src/lib/nispor/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
hostname::get_hostname_state,
hsr::np_hsr_to_nmstate,
infiniband::np_ib_to_nmstate,
ipvlan::np_ipvlan_to_nmstate,
linux_bridge::{append_bridge_port_config, np_bridge_to_nmstate},
mac_vlan::{np_mac_vlan_to_nmstate, np_mac_vtap_to_nmstate},
macsec::np_macsec_to_nmstate,
Expand Down Expand Up @@ -146,6 +147,9 @@ pub(crate) async fn nispor_retrieve(
iface.base = base_iface;
Interface::Xfrm(Box::new(iface))
}
InterfaceType::IpVlan => Interface::IpVlan(Box::new(
np_ipvlan_to_nmstate(np_iface, base_iface),
)),
_ => {
log::info!(
"Got unsupported interface {} type {:?}",
Expand Down
3 changes: 2 additions & 1 deletion rust/src/lib/nm/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::nm_dbus::{

const DEFAULT_DNS_PRIORITY: i32 = 40;

const SUPPORT_NM_KERNEL_IFACES: [NmIfaceType; 14] = [
const SUPPORT_NM_KERNEL_IFACES: [NmIfaceType; 15] = [
NmIfaceType::Ethernet,
NmIfaceType::Veth,
NmIfaceType::Bond,
Expand All @@ -28,6 +28,7 @@ const SUPPORT_NM_KERNEL_IFACES: [NmIfaceType; 14] = [
NmIfaceType::Infiniband,
NmIfaceType::Macsec,
NmIfaceType::Hsr,
NmIfaceType::Ipvlan,
];

pub(crate) fn store_dns_config_to_iface(
Expand Down
6 changes: 6 additions & 0 deletions rust/src/lib/nm/nm_dbus/connection/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::super::{
connection::ieee8021x::NmSetting8021X,
connection::infiniband::NmSettingInfiniBand,
connection::ip::NmSettingIp,
connection::ipvlan::NmSettingIpVlan,
connection::loopback::NmSettingLoopback,
connection::mac_vlan::NmSettingMacVlan,
connection::macsec::NmSettingMacSec,
Expand Down Expand Up @@ -109,6 +110,7 @@ pub struct NmConnection {
pub macsec: Option<NmSettingMacSec>,
pub hsr: Option<NmSettingHsr>,
pub vpn: Option<NmSettingVpn>,
pub ipvlan: Option<NmSettingIpVlan>,
#[serde(skip)]
pub obj_path: String,
#[serde(skip)]
Expand Down Expand Up @@ -187,6 +189,7 @@ impl TryFrom<NmConnectionDbusOwnedValue> for NmConnection {
loopback: _from_map!(v, "loopback", NmSettingLoopback::try_from)?,
hsr: _from_map!(v, "hsr", NmSettingHsr::try_from)?,
vpn: _from_map!(v, "vpn", NmSettingVpn::try_from)?,
ipvlan: _from_map!(v, "ipvlan", NmSettingIpVlan::try_from)?,
_other: v,
..Default::default()
})
Expand Down Expand Up @@ -307,6 +310,9 @@ impl NmConnection {
if let Some(v) = &self.vpn {
ret.insert("vpn", v.to_value()?);
}
if let Some(ipvlan) = &self.ipvlan {
ret.insert("ipvlan", ipvlan.to_value()?);
}
for (key, setting_value) in &self._other {
let mut other_setting_value: HashMap<&str, zvariant::Value> =
HashMap::new();
Expand Down
Loading

0 comments on commit 50c267c

Please sign in to comment.