From 9e43a1a911b8aba09fbb0f6ff2070e3180b15985 Mon Sep 17 00:00:00 2001 From: Matthew Yeazel Date: Fri, 18 Nov 2022 18:23:18 -0800 Subject: [PATCH] [netdog] Add version 3 of network configuration This adds version 3 of network configuration along with bonding and vlan support end to end from net.toml to wicked xml. The support now works as intended for both vlan virtual devices and bonding devices on metal. This also moves around some of the device files into a new module so that future expansion of devices is seperated from expansion of network configuration versions. This also changes the validation logic to add in traits that allow a generic IP Addressing validation function to run on any network device that accepts addressing. --- .../src/net_config/devices/interface.rs | 27 ++++ .../api/netdog/src/net_config/devices/mod.rs | 128 ++++++++++++++++++ sources/api/netdog/src/net_config/mod.rs | 7 +- .../netdog/src/net_config/static_address.rs | 17 ++- .../src/net_config/test_macros/bonding.rs | 81 +++++++++++ .../netdog/src/net_config/test_macros/mod.rs | 6 + .../netdog/src/net_config/test_macros/vlan.rs | 46 +++++++ sources/api/netdog/src/net_config/v2.rs | 85 +----------- sources/api/netdog/src/net_config/v3.rs | 120 ++++++++++++++++ sources/api/netdog/src/wicked/mod.rs | 120 +++++++++++++++- .../net_config/bonding/vlan_using_bond.toml | 18 +++ .../netdog/test_data/wicked/net_config.toml | 51 +++++++ 12 files changed, 624 insertions(+), 82 deletions(-) create mode 100644 sources/api/netdog/src/net_config/devices/interface.rs create mode 100644 sources/api/netdog/src/net_config/devices/mod.rs create mode 100644 sources/api/netdog/src/net_config/test_macros/bonding.rs create mode 100644 sources/api/netdog/src/net_config/test_macros/vlan.rs create mode 100644 sources/api/netdog/src/net_config/v3.rs create mode 100644 sources/api/netdog/test_data/net_config/bonding/vlan_using_bond.toml diff --git a/sources/api/netdog/src/net_config/devices/interface.rs b/sources/api/netdog/src/net_config/devices/interface.rs new file mode 100644 index 00000000000..a97b2fa8f64 --- /dev/null +++ b/sources/api/netdog/src/net_config/devices/interface.rs @@ -0,0 +1,27 @@ +use super::validate_addressing; +use super::{Dhcp4ConfigV1, Dhcp6ConfigV1, Result, Validate}; +use crate::net_config::devices::generate_addressing_validation; +use crate::net_config::{RouteV1, StaticConfigV1}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct NetInterfaceV2 { + // Use this interface as the primary interface for the system + pub(crate) primary: Option, + pub(crate) dhcp4: Option, + pub(crate) dhcp6: Option, + pub(crate) static4: Option, + pub(crate) static6: Option, + #[serde(rename = "route")] + pub(crate) routes: Option>, +} + +impl Validate for NetInterfaceV2 { + fn validate(&self) -> Result<()> { + validate_addressing(self) + } +} + +// Generate the traits for IP Address validation +generate_addressing_validation!(&NetInterfaceV2); diff --git a/sources/api/netdog/src/net_config/devices/mod.rs b/sources/api/netdog/src/net_config/devices/mod.rs new file mode 100644 index 00000000000..7ad603395f6 --- /dev/null +++ b/sources/api/netdog/src/net_config/devices/mod.rs @@ -0,0 +1,128 @@ +//! The devices module contains all the types of network devices that `netdog` supports. These are +//! intended to be the structs used for net.toml deserialization including the validation logic for +//! each device. + +pub(crate) mod bonding; +pub(crate) mod interface; +pub(crate) mod vlan; + +use super::{error, Result, Validate}; +use crate::net_config::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; +use bonding::NetBondV1; +use interface::NetInterfaceV2; +use serde::Deserialize; +use vlan::NetVlanV1; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub(crate) enum NetworkDeviceV1 { + Interface(NetInterfaceV2), + BondDevice(NetBondV1), + VlanDevice(NetVlanV1), +} + +impl NetworkDeviceV1 { + pub(crate) fn primary(&self) -> Option { + match self { + Self::Interface(i) => i.primary, + Self::BondDevice(i) => i.primary, + Self::VlanDevice(i) => i.primary, + } + } +} + +impl Validate for NetworkDeviceV1 { + fn validate(&self) -> Result<()> { + match self { + Self::Interface(config) => config.validate()?, + Self::BondDevice(config) => config.validate()?, + Self::VlanDevice(config) => config.validate()?, + } + Ok(()) + } +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub(crate) enum DeviceType { + #[serde(rename = "bond")] + Bond, + #[serde(rename = "vlan")] + Vlan, +} + +pub(crate) trait HasIpAddressing { + fn has_static(&self) -> bool; + + fn validate_static4(&self) -> Result<()>; + fn validate_static6(&self) -> Result<()>; + + fn has_dhcp(&self) -> bool; + fn has_routes(&self) -> bool; +} + +pub(crate) fn validate_addressing(device: D) -> Result<()> +where + D: HasIpAddressing, +{ + if !device.has_dhcp() && !device.has_static() { + return error::InvalidNetConfigSnafu { + reason: "each interface must configure dhcp and/or static addresses", + } + .fail(); + } + + // wicked doesn't support static routes with dhcp + if device.has_dhcp() && device.has_routes() { + return error::InvalidNetConfigSnafu { + reason: "static routes are not supported with dhcp", + } + .fail(); + } + + if device.has_routes() && !device.has_static() { + return error::InvalidNetConfigSnafu { + reason: "interfaces must set static addresses in order to use routes", + } + .fail(); + } + + // call into struct for access to fields for validation + device.validate_static4()?; + device.validate_static6()?; + + Ok(()) +} + +// For all devices that have IP Addressing available, generate the trait implementation +macro_rules! generate_addressing_validation { + ($name:ty) => { + use crate::net_config::devices::HasIpAddressing; + impl HasIpAddressing for $name { + fn has_static(&self) -> bool { + self.static4.is_some() || self.static6.is_some() + } + fn validate_static4(&self) -> Result<()> { + if let Some(config) = &self.static4 { + config.validate()? + } + Ok(()) + } + + fn validate_static6(&self) -> Result<()> { + if let Some(config) = &self.static6 { + config.validate()? + } + Ok(()) + } + + fn has_dhcp(&self) -> bool { + self.dhcp4.is_some() || self.dhcp6.is_some() + } + fn has_routes(&self) -> bool { + self.routes.is_some() + } + } + }; +} +pub(crate) use generate_addressing_validation; diff --git a/sources/api/netdog/src/net_config/mod.rs b/sources/api/netdog/src/net_config/mod.rs index 977203eebce..b81365d5209 100644 --- a/sources/api/netdog/src/net_config/mod.rs +++ b/sources/api/netdog/src/net_config/mod.rs @@ -3,11 +3,14 @@ //! the kernel command line. //! //! These structures are the user-facing options for configuring one or more network interfaces. + +pub(crate) mod devices; mod dhcp; mod error; mod static_address; mod v1; mod v2; +mod v3; use crate::wicked::WickedInterface; pub(crate) use dhcp::{Dhcp4ConfigV1, Dhcp4OptionsV1, Dhcp6ConfigV1, Dhcp6OptionsV1}; @@ -98,11 +101,12 @@ fn deserialize_config(config_str: &str) -> Result> { let net_config: Box = match version { 1 => validate_config::(interface_config)?, 2 => validate_config::(interface_config)?, + 3 => validate_config::(interface_config)?, _ => { return error::InvalidNetConfigSnafu { reason: format!("Unknown network config version: {}", version), } - .fail() + .fail(); } }; @@ -152,6 +156,7 @@ where #[cfg(test)] mod test_macros; + #[cfg(test)] mod tests { use std::path::PathBuf; diff --git a/sources/api/netdog/src/net_config/static_address.rs b/sources/api/netdog/src/net_config/static_address.rs index 9d4c7beceba..34925e7e49b 100644 --- a/sources/api/netdog/src/net_config/static_address.rs +++ b/sources/api/netdog/src/net_config/static_address.rs @@ -1,6 +1,8 @@ +use super::error::{InvalidNetConfigSnafu, Result as ValidateResult}; +use crate::net_config::Validate; use ipnet::IpNet; use serde::Deserialize; -use snafu::ResultExt; +use snafu::{ensure, ResultExt}; use std::collections::BTreeSet; use std::convert::TryFrom; use std::net::IpAddr; @@ -48,6 +50,19 @@ impl TryFrom for RouteTo { } } +impl Validate for StaticConfigV1 { + fn validate(&self) -> ValidateResult<()> { + ensure!( + self.addresses.iter().all(|a| matches!(a, IpNet::V4(_))) + || self.addresses.iter().all(|a| matches!(a, IpNet::V6(_))), + InvalidNetConfigSnafu { + reason: "static configuration must only contain all IPv4 or all IPv6 addresses" + } + ); + Ok(()) + } +} + mod error { use snafu::Snafu; diff --git a/sources/api/netdog/src/net_config/test_macros/bonding.rs b/sources/api/netdog/src/net_config/test_macros/bonding.rs new file mode 100644 index 00000000000..8d750782154 --- /dev/null +++ b/sources/api/netdog/src/net_config/test_macros/bonding.rs @@ -0,0 +1,81 @@ +macro_rules! bonding_tests { + ($version:expr) => { + mod bonding { + use $crate::net_config::deserialize_config; + use $crate::net_config::test_macros::gen_boilerplate; + + gen_boilerplate!($version, "bonding"); + + #[test] + fn ok_config() { + let ok = net_config().join("net_config.toml"); + let rendered = render_config_template(ok); + assert!(deserialize_config(&rendered).is_ok()) + } + + #[test] + fn missing_kind() { + let bad = net_config().join("missing_kind.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn no_monitoring() { + let bad = net_config().join("no_monitoring.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn both_monitoring() { + let bad = net_config().join("both_monitoring.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn no_interfaces() { + let bad = net_config().join("no_interfaces.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn disabled_miimon() { + let bad = net_config().join("disabled_miimon.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn disabled_arpmon() { + let bad = net_config().join("disabled_arpmon.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn too_many_min_links() { + let bad = net_config().join("too_many_min_links.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn arpmon_no_targets() { + let bad = net_config().join("arpmon_no_targets.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn vlan_using_bond_interface() { + let bad = net_config().join("vlan_using_bond.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + } + }; +} +pub(crate) use bonding_tests; diff --git a/sources/api/netdog/src/net_config/test_macros/mod.rs b/sources/api/netdog/src/net_config/test_macros/mod.rs index 7677dfa02b6..8cf01f138fa 100644 --- a/sources/api/netdog/src/net_config/test_macros/mod.rs +++ b/sources/api/netdog/src/net_config/test_macros/mod.rs @@ -14,13 +14,19 @@ #[cfg(test)] pub(super) mod basic; #[cfg(test)] +pub(super) mod bonding; +#[cfg(test)] pub(super) mod dhcp; #[cfg(test)] pub(super) mod static_address; +#[cfg(test)] +pub(super) mod vlan; pub(super) use basic::basic_tests; +pub(super) use bonding::bonding_tests; pub(super) use dhcp::dhcp_tests; pub(super) use static_address::static_address_tests; +pub(super) use vlan::vlan_tests; /// gen_boilerplate!() is a convenience macro meant to be used inside of test macros to generate /// some generally useful boilerplate code. It creates a `VERSION` constant in case the test diff --git a/sources/api/netdog/src/net_config/test_macros/vlan.rs b/sources/api/netdog/src/net_config/test_macros/vlan.rs new file mode 100644 index 00000000000..6ffa0187e1c --- /dev/null +++ b/sources/api/netdog/src/net_config/test_macros/vlan.rs @@ -0,0 +1,46 @@ +macro_rules! vlan_tests { + ($version:expr) => { + mod vlan { + use $crate::net_config::deserialize_config; + use $crate::net_config::test_macros::gen_boilerplate; + + gen_boilerplate!($version, "vlan"); + + #[test] + fn ok_config() { + let ok = net_config().join("net_config.toml"); + let rendered = render_config_template(ok); + assert!(deserialize_config(&rendered).is_ok()) + } + + #[test] + fn no_id() { + let bad = net_config().join("no_id.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn out_of_bounds_id() { + let bad = net_config().join("oob_id.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn missing_kind() { + let bad = net_config().join("missing_kind.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + + #[test] + fn no_device() { + let bad = net_config().join("no_device.toml"); + let rendered = render_config_template(bad); + assert!(deserialize_config(&rendered).is_err()) + } + } + }; +} +pub(crate) use vlan_tests; diff --git a/sources/api/netdog/src/net_config/v2.rs b/sources/api/netdog/src/net_config/v2.rs index 1dd4965fd33..3e5f52859b4 100644 --- a/sources/api/netdog/src/net_config/v2.rs +++ b/sources/api/netdog/src/net_config/v2.rs @@ -1,12 +1,13 @@ //! The `v2` module contains the second version of the network configuration and implements the //! appropriate traits. -use super::static_address::{RouteV1, StaticConfigV1}; -use super::{error, Dhcp4ConfigV1, Dhcp6ConfigV1, Interfaces, Result, Validate}; +use super::{error, Interfaces, Result, Validate}; use crate::interface_name::InterfaceName; -use crate::wicked::{WickedDhcp4, WickedDhcp6, WickedInterface, WickedRoutes, WickedStaticAddress}; +use crate::net_config::devices::interface::NetInterfaceV2; +use crate::wicked::{ + wicked_from, WickedDhcp4, WickedDhcp6, WickedInterface, WickedRoutes, WickedStaticAddress, +}; use indexmap::IndexMap; -use ipnet::IpNet; use serde::Deserialize; use snafu::ensure; @@ -16,19 +17,6 @@ pub(crate) struct NetConfigV2 { pub(crate) interfaces: IndexMap, } -#[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub(crate) struct NetInterfaceV2 { - // Use this interface as the primary interface for the system - pub(crate) primary: Option, - pub(crate) dhcp4: Option, - pub(crate) dhcp6: Option, - pub(crate) static4: Option, - pub(crate) static6: Option, - #[serde(rename = "route")] - pub(crate) routes: Option>, -} - impl Interfaces for NetConfigV2 { fn primary_interface(&self) -> Option { self.interfaces @@ -45,24 +33,7 @@ impl Interfaces for NetConfigV2 { fn as_wicked_interfaces(&self) -> Vec { let mut wicked_interfaces = Vec::with_capacity(self.interfaces.len()); for (name, config) in &self.interfaces { - let mut interface = WickedInterface::new(name.clone()); - interface.ipv4_dhcp = config.dhcp4.clone().map(WickedDhcp4::from); - interface.ipv6_dhcp = config.dhcp6.clone().map(WickedDhcp6::from); - - // Based on the existence of static addresses and routes, create the ipv4/6_static - // struct members. They must be `Option`s because we want to avoid serializing empty - // tags into the config file - let maybe_routes = config.routes.clone().map(WickedRoutes::from); - let maybe_ipv4_static = WickedStaticAddress::maybe_new( - config.static4.clone(), - maybe_routes.as_ref().and_then(|s| s.ipv4.clone()), - ); - let maybe_ipv6_static = WickedStaticAddress::maybe_new( - config.static6.clone(), - maybe_routes.as_ref().and_then(|s| s.ipv6.clone()), - ); - interface.ipv4_static = maybe_ipv4_static; - interface.ipv6_static = maybe_ipv6_static; + let interface = wicked_from!(name, config); wicked_interfaces.push(interface); } @@ -74,49 +45,7 @@ impl Interfaces for NetConfigV2 { impl Validate for NetConfigV2 { fn validate(&self) -> Result<()> { for (_name, config) in &self.interfaces { - let has_static = config.static4.is_some() || config.static6.is_some(); - let has_dhcp = config.dhcp4.is_some() || config.dhcp6.is_some(); - let has_routes = config.routes.is_some(); - - if !has_dhcp && !has_static { - return error::InvalidNetConfigSnafu { - reason: "each interface must configure dhcp and/or static addresses", - } - .fail(); - } - - // wicked doesn't support static routes with dhcp - if has_dhcp && has_routes { - return error::InvalidNetConfigSnafu { - reason: "static routes are not supported with dhcp", - } - .fail(); - } - - if has_routes && !has_static { - return error::InvalidNetConfigSnafu { - reason: "interfaces must set static addresses in order to use routes", - } - .fail(); - } - - if let Some(config) = &config.static4 { - ensure!( - config.addresses.iter().all(|a| matches!(a, IpNet::V4(_))), - error::InvalidNetConfigSnafu { - reason: "'static4' may only contain IPv4 addresses" - } - ) - } - - if let Some(config) = &config.static6 { - ensure!( - config.addresses.iter().all(|a| matches!(a, IpNet::V6(_))), - error::InvalidNetConfigSnafu { - reason: "'static6' may only contain IPv6 addresses" - } - ) - } + config.validate()?; } let primary_count = self diff --git a/sources/api/netdog/src/net_config/v3.rs b/sources/api/netdog/src/net_config/v3.rs new file mode 100644 index 00000000000..cc8c69e508d --- /dev/null +++ b/sources/api/netdog/src/net_config/v3.rs @@ -0,0 +1,120 @@ +//! The `v3` module contains the third version of the network configuration and implements the +//! appropriate traits. + +use super::devices::NetworkDeviceV1; +use super::{error, Interfaces, Result, Validate}; +use crate::interface_name::InterfaceName; +use crate::wicked::{WickedInterface, WickedLinkConfig}; +use indexmap::IndexMap; +use serde::Deserialize; +use snafu::ensure; +use std::collections::HashSet; + +#[derive(Debug, Deserialize)] +pub(crate) struct NetConfigV3 { + #[serde(flatten)] + pub(crate) net_devices: IndexMap, +} + +impl Interfaces for NetConfigV3 { + fn primary_interface(&self) -> Option { + self.net_devices + .iter() + .find(|(_, v)| v.primary() == Some(true)) + .or_else(|| self.net_devices.first()) + .map(|(n, _)| n.to_string()) + } + + fn has_interfaces(&self) -> bool { + !self.net_devices.is_empty() + } + + fn as_wicked_interfaces(&self) -> Vec { + let mut wicked_interfaces = Vec::new(); + for (name, config) in &self.net_devices { + let interface = WickedInterface::from((name, config)); + + // If config is a Bond, we will generate the interface configuration for interfaces in + // that bond since we have all of the data and the bond consumes the device for other uses. + // For each interface: call WickedInterface::new(name), configure it and add that to + // wicked_interfaces Vec + if let NetworkDeviceV1::BondDevice(b) = config { + for device in &b.interfaces { + let mut wicked_sub_interface = WickedInterface::new(device.clone()); + wicked_sub_interface.link = Some(WickedLinkConfig { + master: name.clone(), + }); + + wicked_interfaces.push(wicked_sub_interface) + } + } + + wicked_interfaces.push(interface) + } + + wicked_interfaces + } +} + +#[allow(clippy::to_string_in_format_args)] +impl Validate for NetConfigV3 { + fn validate(&self) -> Result<()> { + // Create HashSet of known device names for checking duplicates + let mut interface_names: HashSet<&InterfaceName> = self.net_devices.keys().collect(); + for (_name, device) in &self.net_devices { + if let NetworkDeviceV1::VlanDevice(vlan) = device { + // It is valid to stack more than one vlan on a single device, but we need them all + // for checking bonds which can't share devices. + interface_names.insert(&vlan.device); + } + } + + for (name, device) in &self.net_devices { + // Bonds create the interfaces automatically, specifying those interfaces would cause a + // collision so this emits an error for any that are found + if let NetworkDeviceV1::BondDevice(config) = device { + for interface in &config.interfaces { + // This checks if the insert already found one, which would be a failure + if !interface_names.insert(interface) { + return error::InvalidNetConfigSnafu { + reason: format!( + "{} in bond {} cannot be manually configured", + interface.to_string(), + name.to_string() + ), + } + .fail(); + } + } + } + device.validate()?; + } + + let primary_count = self + .net_devices + .values() + .filter(|v| v.primary() == Some(true)) + .count(); + ensure!( + primary_count <= 1, + error::InvalidNetConfigSnafu { + reason: "multiple primary interfaces defined, expected 1" + } + ); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::net_config::test_macros::{ + basic_tests, bonding_tests, dhcp_tests, static_address_tests, vlan_tests, + }; + + basic_tests!(3); + dhcp_tests!(3); + static_address_tests!(3); + vlan_tests!(3); + bonding_tests!(3); +} diff --git a/sources/api/netdog/src/wicked/mod.rs b/sources/api/netdog/src/wicked/mod.rs index 19ff91bf8bc..0b11ba3302f 100644 --- a/sources/api/netdog/src/wicked/mod.rs +++ b/sources/api/netdog/src/wicked/mod.rs @@ -3,20 +3,61 @@ //! //! The structures in this module are meant to be created from the user-facing structures in the //! `net_config` module. `Default` implementations for WickedInterface exist here as well. +mod bonding; mod dhcp; mod static_address; +mod vlan; use crate::interface_name::InterfaceName; +use crate::net_config::devices::bonding::{BondMonitoringConfig, NetBondV1}; +use crate::net_config::devices::interface::NetInterfaceV2; +use crate::net_config::devices::vlan::NetVlanV1; +use crate::net_config::devices::NetworkDeviceV1; +use crate::wicked::bonding::{ + WickedArpMonitoringConfig, WickedBondMode, WickedMiiMonitoringConfig, +}; +use bonding::WickedBond; pub(crate) use dhcp::{WickedDhcp4, WickedDhcp6}; +pub(crate) use error::Error; use serde::Serialize; use snafu::ResultExt; pub(crate) use static_address::{WickedRoutes, WickedStaticAddress}; use std::fs; use std::path::Path; +use vlan::WickedVlanTag; const WICKED_CONFIG_DIR: &str = "/etc/wicked/ifconfig"; const WICKED_FILE_EXT: &str = "xml"; +macro_rules! wicked_from { + ($name:ident, $config:ident) => { + ({ + let mut wicked_interface = WickedInterface::new($name.clone()); + wicked_interface.ipv4_dhcp = $config.dhcp4.clone().map(WickedDhcp4::from); + wicked_interface.ipv6_dhcp = $config.dhcp6.clone().map(WickedDhcp6::from); + + // Based on the existence of static addresses and routes, create the ipv4/6_static + // struct members. They must be `Option`s because we want to avoid serializing empty + // tags into the config file + let maybe_routes = $config.routes.clone().map(WickedRoutes::from); + let maybe_ipv4_static = WickedStaticAddress::maybe_new( + $config.static4.clone(), + maybe_routes.as_ref().and_then(|s| s.ipv4.clone()), + ); + let maybe_ipv6_static = WickedStaticAddress::maybe_new( + $config.static6.clone(), + maybe_routes.as_ref().and_then(|s| s.ipv6.clone()), + ); + wicked_interface.ipv4_static = maybe_ipv4_static; + wicked_interface.ipv6_static = maybe_ipv6_static; + + wicked_interface + }) as WickedInterface + }; +} + +pub(crate) use wicked_from; + #[derive(Debug, Serialize, PartialEq)] #[serde(rename = "interface")] pub(crate) struct WickedInterface { @@ -35,6 +76,14 @@ pub(crate) struct WickedInterface { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "ipv6:static")] pub(crate) ipv6_static: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "vlan")] + pub(crate) vlan_tag: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "bond")] + pub(crate) bond: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) link: Option, } #[derive(Debug, Serialize, PartialEq)] @@ -75,6 +124,9 @@ impl WickedInterface { ipv6_dhcp: None, ipv4_static: None, ipv6_static: None, + vlan_tag: None, + bond: None, + link: None, } } @@ -90,6 +142,71 @@ impl WickedInterface { } } +impl From<(&InterfaceName, &NetworkDeviceV1)> for WickedInterface { + fn from(device_tup: (&InterfaceName, &NetworkDeviceV1)) -> Self { + match device_tup.1 { + NetworkDeviceV1::Interface(i) => WickedInterface::from((device_tup.0, i)), + NetworkDeviceV1::BondDevice(b) => WickedInterface::from((device_tup.0, b)), + NetworkDeviceV1::VlanDevice(v) => WickedInterface::from((device_tup.0, v)), + } + } +} + +impl From<(&InterfaceName, &NetInterfaceV2)> for WickedInterface { + fn from(device_tup: (&InterfaceName, &NetInterfaceV2)) -> Self { + let name = device_tup.0; + let config = device_tup.1; + wicked_from!(name, config) + } +} + +impl From<(&InterfaceName, &NetBondV1)> for WickedInterface { + fn from(device_tup: (&InterfaceName, &NetBondV1)) -> Self { + let name = device_tup.0; + let config = device_tup.1; + let mut wicked_interface = wicked_from!(name, config); + + // Here is where bonding specific things begin + let mut wicked_bond = WickedBond::new( + WickedBondMode::from(config.mode.clone()), + config.interfaces.clone(), + ); + + wicked_bond.min_links = config.min_links; + + match &config.monitoring_config { + BondMonitoringConfig::MiiMon(config) => { + wicked_bond.mii_monitoring = Some(WickedMiiMonitoringConfig::from(config.clone())) + } + BondMonitoringConfig::ArpMon(config) => { + wicked_bond.arp_monitoring = Some(WickedArpMonitoringConfig::from(config.clone())) + } + } + + wicked_interface.bond = Some(wicked_bond); + + wicked_interface + } +} + +impl From<(&InterfaceName, &NetVlanV1)> for WickedInterface { + fn from(device_tup: (&InterfaceName, &NetVlanV1)) -> Self { + let name = device_tup.0; + let config = device_tup.1; + let mut wicked_interface = wicked_from!(name, config); + + wicked_interface.vlan_tag = Some(WickedVlanTag::new(config.device.clone(), config.id)); + + wicked_interface + } +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct WickedLinkConfig { + #[serde(rename = "$unflatten=master")] + pub(crate) master: InterfaceName, +} + mod error { use snafu::Snafu; use std::io; @@ -109,7 +226,6 @@ mod error { } } -pub(crate) use error::Error; type Result = std::result::Result; #[cfg(test)] @@ -121,7 +237,7 @@ mod tests { use std::path::PathBuf; use std::str::FromStr; - static NET_CONFIG_VERSIONS: &[u8] = &[1, 2]; + static NET_CONFIG_VERSIONS: &[u8] = &[1, 2, 3]; fn test_data() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_data") diff --git a/sources/api/netdog/test_data/net_config/bonding/vlan_using_bond.toml b/sources/api/netdog/test_data/net_config/bonding/vlan_using_bond.toml new file mode 100644 index 00000000000..c91ea52472a --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/vlan_using_bond.toml @@ -0,0 +1,18 @@ +version = {{version}} + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 + + +[vlan_using] +kind = "vlan" +id = 1234 +device = "eno51" diff --git a/sources/api/netdog/test_data/wicked/net_config.toml b/sources/api/netdog/test_data/wicked/net_config.toml index b3ad92b7452..4827dcc6e7b 100644 --- a/sources/api/netdog/test_data/wicked/net_config.toml +++ b/sources/api/netdog/test_data/wicked/net_config.toml @@ -139,3 +139,54 @@ to = "3001:dead:beef::2/64" from = "2001:dead:beef::2" via = "2001:beef:beef::1" {{/if}} + +{{#if (eq version 3)}} +[myvlan] +kind = "vlan" +device = "eno1" +id = 42 +dhcp4 = true + +[mystaticvlan] +kind = "vlan" +device = "eno1000" +id = 42 + +[mystaticvlan.static4] +addresses = ["192.168.1.100/24"] + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 + +[bond1] +kind = "bond" +mode = "active-backup" +interfaces = ["eno53" , "eno54"] +dhcp4 = true + +[bond1.monitoring] +arpmon-interval-ms = 200 +arpmon-validate = "all" +arpmon-targets = ["192.168.1.1", "10.0.0.2"] + +[bond2] +kind = "bond" +mode = "active-backup" +interfaces = ["eno55", "eno56", "eno57"] +min-links = 2 +dhcp6 = true + +[bond2.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 1000 +miimon-downdelay-ms = 1000 + +{{/if}}