diff --git a/PROVISIONING-METAL.md b/PROVISIONING-METAL.md index c3ff040d585..483fa123874 100644 --- a/PROVISIONING-METAL.md +++ b/PROVISIONING-METAL.md @@ -77,7 +77,7 @@ When these services fail, your machine will not connect to any cluster and will #### `net.toml` structure The configuration file must be valid TOML and have the filename `net.toml`. -The first and required top level key in the file is `version`; the latest is version `2`. +The first and required top level key in the file is `version`; the latest is version `3`. The rest of the file is a map of interface name to supported settings. Interface names are expected to be correct as per `udevd` naming, no interface naming or matching is supported. (See the note below regarding `udevd` interface naming.) @@ -95,6 +95,7 @@ Interface names are expected to be correct as per `udevd` naming, no interface n As of version `2` static addressing with simple routes is supported via the below settings. Please keep in mind that when using static addresses, DNS information must be supplied to the system via user data: [`settings.dns`](/~https://github.com/bottlerocket-os/bottlerocket#network-settings). + * `static4` (map): IPv4 static address settings. * `addresses` (list of quoted IPv4 address including prefix): The desired IPv4 IP addresses, including prefix i.e. `["192.168.14.2/24"]`. The first IP in the list will be used as the primary IP which `kubelet` will use when joining the cluster. If IPv4 and IPv6 static addresses exist, the first IPv4 address is used. * `static6` (map): IPv6 static address settings. @@ -106,10 +107,50 @@ Please keep in mind that when using static addresses, DNS information must be su * `via` (IP address): Gateway IP address. If no gateway is provided, a scope of `link` is assumed. * `route-metric` (integer): Relative route priority. -Example `net.toml` with comments: +Version `3` adds in support for bonding and vlan tagging. +The support is limited to mode `1` (`active-backup`) for [bonding](https://www.kernel.org/doc/Documentation/networking/bonding.txt). +Future support may include other bonding options - pull requests are welcome! +Version `3` adds the concept of virtual network devices in addition to interfaces. +The default type of device is an interface and the syntax is the same as previous versions. +The name of an interface must match an existing interface on the system such as `eno1` or `enp0s16`. +For virtual network devices, a `kind` is required. +If no `kind` is specified, it is assumed to be an interface. +Currently, `bond` and `vlan` are the two supported `kind`s. +Virtual network devices are created, and therefore a name has to be chosen. + +Names for virtual network devices must conform to kernel naming restrictions: +* Names must not have line terminators in them +* Names must be between 1-15 characters +* Names must not contain `.`, `/` or whitespace + +Bonding configuration creates a virtual network device across several other devices: + +* Bonding configuration (map): + * `kind = "bond"`: This setting is required to specify a bond device. Required. + * `interfaces` (list of quoted strings of interfaces): Which interfaces should be added to the bond (i.e. `["eno1"]`). The first in the list is considered the default `primary`. These interfaces are "consumed" so no other configuration can refer to them. Required. + * `mode` (string): Currently `active-backup` is the only supported option. Required. + * `min-links` (integer): Number of links required to bring up the device + * `monitoring` (map): Values m ust all be of `miimon` or `arpmon` type. + The user must choose one type of monitoring and configure it fully in order for the bond to properly function. + See [section 7](https://www.kernel.org/doc/Documentation/networking/bonding.txt) for more background on what to choose. + * `miimon-frequency-ms` (integer): MII Monitoring frequency in milliseconds + * `miimon-updelay-ms` (integer): MII Monitoring delay before the link is enabled after link is detected in milliseconds + * `miimon-downdelay-ms` (integer): MII Monitoring delay before the link is disabled after link is no longer detected in milliseconds + * `arpmon-interval-ms` (integer): Number of milliseconds between intervals to determine link status, must be greater than 0 + * `arpmon-validate` (one of `all`, `none`, `active`, or `backup`): What packets should be used to validate link + * `arpmon-targets` (list of quoted IPv4 address including prefix): List of targets to use for validating ARP. Min = 1, Max = 16 + +Vlan tagging is configured as a new virtual network device stacked on another device: + +* Vlan configuration (map): + * `kind = "vlan"`: This setting is required to specify a vlan device. + * `device` (string for device): Defines the device the vlan should be configured on. If VLAN tagging is required, this device should recieve all IP address configuration instead of the underlying device. + * `id` (integer): Number between 0 and 4096 specifying the vlan tag on the device + +Example `net.toml` version `3` with comments: ```toml -version = 2 +version = 3 # "eno1" is the interface name [eno1] @@ -152,13 +193,65 @@ addresses = ["192.168.14.5/24"] to = "10.10.10.0/24" from = "192.168.14.5" via = "192.168.14.25" + +# A bond is a network device that is of `kind` `bond` +[bond0] +kind = "bond" +# Currently `active-backup` is the only supported option +mode = "active-backup" +# In this case, the vlan will have addressing, the bond is simply there for use in the vlan +dhcp4 = false +dhcp6 = false +# The first interface in the array is considered `primary` by default +interfaces = ["eno11", "eno12"] + +[bond0.monitoring] +miimon-frequency-ms = 100 # 100 milliseconds +miimon-updelay-ms = 200 # 200 milliseconds +miimon-downdelay-ms = 200 # 200 milliseconds + +[bond1] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52", "eno53"] +min-links = 2 # Optional min-links +dhcp4 = true + +[bond1.monitoring] +arpmon-interval-ms = 200 # 200 milliseconds +arpmon-validate = "all" +arpmon-targets = ["192.168.1.1", "10.0.0.2"] + +# A vlan is a network device that is of `kind` `vlan` +# VLAN42 is the name of the device, can be anything that is a valid network interface name +[VLAN42] +kind = "vlan" +device = "bond0" +id = 42 +dhcp4 = true + +[internal_vlan] +kind = "vlan" +device = "eno2" +id = 1234 +dhcp6 = true ``` -**An additional note on network device names** +#### **An additional note on network device names** Interface name policies are [specified in this file](/~https://github.com/bottlerocket-os/bottlerocket/blob/develop/packages/release/80-release.link#L6); with name precedence in the following order: onboard, slot, path. Typically on-board devices are named `eno*`, hot-plug devices are named `ens*`, and if neither of those names are able to be generated, the “path” name is given, i.e `enp*s*f*`. +#### Networking configuration versions and Releases + +Older networking configuration versions (such as `1` or `2`) are supported in newer releases. In order to use a newer version, the following table provides guidance on what release first enabled the version. + +| Network Configuration Version | First Release | +|-------------------------------|---------------------------------------------------------------------------------| +| Version 1 | [v1.9.0](/~https://github.com/bottlerocket-os/bottlerocket/releases/tag/v1.9.0) | +| Version 2 | [v1.10.0](/~https://github.com/bottlerocket-os/bottlerocket/releases/tag/v1.10.0) | +| Version 3 | Unreleased | + ### Boot Configuration Bottlerocket for bare metal uses a feature of the Linux kernel called [Boot Configuration](https://www.kernel.org/doc/html/latest/admin-guide/bootconfig.html), which allows a user to pass additional arguments to the kernel command line at runtime. diff --git a/sources/api/netdog/src/net_config/devices/bonding.rs b/sources/api/netdog/src/net_config/devices/bonding.rs new file mode 100644 index 00000000000..c10f7322af9 --- /dev/null +++ b/sources/api/netdog/src/net_config/devices/bonding.rs @@ -0,0 +1,163 @@ +use super::validate_addressing; +use super::{error, Dhcp4ConfigV1, Dhcp6ConfigV1, Result, RouteV1, StaticConfigV1, Validate}; +use crate::interface_name::InterfaceName; +use crate::net_config::devices::generate_addressing_validation; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; +use snafu::ensure; +use std::net::IpAddr; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(remote = "Self")] +pub(crate) struct NetBondV1 { + 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>, + kind: String, + pub(crate) mode: BondMode, + #[serde(rename = "min-links")] + pub(crate) min_links: Option, + #[serde(rename = "monitoring")] + pub(crate) monitoring_config: BondMonitoringConfig, + pub(crate) interfaces: Vec, +} + +impl<'de> Deserialize<'de> for NetBondV1 { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let this = Self::deserialize(deserializer)?; + if this.kind.to_lowercase().as_str() != "bond" { + return Err(D::Error::custom(format!( + "kind of '{}' does not match 'bond'", + this.kind.as_str() + ))); + } + + Ok(this) + } +} + +generate_addressing_validation!(&NetBondV1); + +impl Validate for NetBondV1 { + fn validate(&self) -> Result<()> { + validate_addressing(self)?; + + // TODO: We should move this and other validation logic into Deserialize when messaging + // is better for enum failures /~https://github.com/serde-rs/serde/issues/2157 + let interfaces_count = self.interfaces.len(); + ensure!( + interfaces_count > 0, + error::InvalidNetConfigSnafu { + reason: "bonds must have 1 or more interfaces specified" + } + ); + if let Some(min_links) = self.min_links { + ensure!( + min_links <= interfaces_count, + error::InvalidNetConfigSnafu { + reason: "min-links is greater than number of interfaces configured" + } + ) + } + // Validate monitoring configuration + match &self.monitoring_config { + BondMonitoringConfig::MiiMon(config) => config.validate()?, + BondMonitoringConfig::ArpMon(config) => config.validate()?, + } + + Ok(()) + } +} + +// Currently only mode 1 (active-backup) is supported but eventually 0-6 could be added +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) enum BondMode { + ActiveBackup, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub(crate) enum BondMonitoringConfig { + MiiMon(MiiMonitoringConfig), + ArpMon(ArpMonitoringConfig), +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct MiiMonitoringConfig { + #[serde(rename = "miimon-frequency-ms")] + pub(crate) frequency: u32, + #[serde(rename = "miimon-updelay-ms")] + pub(crate) updelay: u32, + #[serde(rename = "miimon-downdelay-ms")] + pub(crate) downdelay: u32, +} + +impl Validate for MiiMonitoringConfig { + fn validate(&self) -> Result<()> { + ensure!( + self.frequency > 0, + error::InvalidNetConfigSnafu { + reason: "miimon-frequency-ms of 0 disables Mii Monitoring, either set a value or configure Arp Monitoring" + } + ); + // updelay and downdelay should be a multiple of frequency, but will be rounded down + // by the kernel, this ensures they are at least the size of frequency (non-zero) + ensure!( + self.frequency <= self.updelay && self.frequency <= self.downdelay, + error::InvalidNetConfigSnafu { + reason: "miimon-updelay-ms and miimon-downdelay-ms must be equal to or larger than miimon-frequency-ms" + } + ); + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct ArpMonitoringConfig { + #[serde(rename = "arpmon-interval-ms")] + pub(crate) interval: u32, + #[serde(rename = "arpmon-validate")] + pub(crate) validate: ArpValidate, + #[serde(rename = "arpmon-targets")] + pub(crate) targets: Vec, +} + +impl Validate for ArpMonitoringConfig { + fn validate(&self) -> Result<()> { + ensure!( + self.interval > 0, + error::InvalidNetConfigSnafu { + reason: "arpmon-interval-ms of 0 disables Arp Monitoring, either set a value or configure Mii Monitoring" + } + ); + // If using Arp Monitoring, 1-16 targets must be specified + let targets_length: u32 = self.targets.len() as u32; + ensure!( + targets_length > 0 && targets_length <= 16, + error::InvalidNetConfigSnafu { + reason: "arpmon-targets must include between 1 and 16 targets" + } + ); + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) enum ArpValidate { + Active, + All, + Backup, + None, +} 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/devices/vlan.rs b/sources/api/netdog/src/net_config/devices/vlan.rs new file mode 100644 index 00000000000..c9a66236ae9 --- /dev/null +++ b/sources/api/netdog/src/net_config/devices/vlan.rs @@ -0,0 +1,58 @@ +use super::validate_addressing; +use super::{Dhcp4ConfigV1, Dhcp6ConfigV1, Result, Validate}; +use crate::interface_name::InterfaceName; +use crate::net_config::devices::generate_addressing_validation; +use crate::net_config::{RouteV1, StaticConfigV1}; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(remote = "Self")] +pub(crate) struct NetVlanV1 { + 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>, + kind: String, + pub(crate) device: InterfaceName, + pub(crate) id: u16, +} + +impl<'de> Deserialize<'de> for NetVlanV1 { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let this = Self::deserialize(deserializer)?; + + if this.kind.to_lowercase().as_str() != "vlan" { + return Err(D::Error::custom(format!( + "kind of '{}' does not match 'vlan'", + this.kind.as_str() + ))); + } + + // Validate its a valid vlan id - 0-4095 + if this.id > 4095 { + return Err(D::Error::custom( + "invalid vlan ID specified, must be between 0-4095", + )); + } + + Ok(this) + } +} + +impl Validate for NetVlanV1 { + fn validate(&self) -> Result<()> { + validate_addressing(self)?; + Ok(()) + } +} + +// Generate the traits for IP Address validation +generate_addressing_validation!(&NetVlanV1); 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/bonding.rs b/sources/api/netdog/src/wicked/bonding.rs new file mode 100644 index 00000000000..a5efd28d190 --- /dev/null +++ b/sources/api/netdog/src/wicked/bonding.rs @@ -0,0 +1,163 @@ +use crate::interface_name::InterfaceName; +use crate::net_config::devices::bonding::{ + ArpMonitoringConfig, ArpValidate, BondMode, MiiMonitoringConfig, +}; + +use serde::Serialize; +use std::net::IpAddr; + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct WickedBond { + #[serde(rename = "$unflatten=mode")] + mode: WickedBondMode, + #[serde(rename = "slaves")] + devices: SubDevices, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "miimon")] + pub(crate) mii_monitoring: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "arpmon")] + pub(crate) arp_monitoring: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "$unflatten=min-links")] + pub(crate) min_links: Option, +} + +impl WickedBond { + pub(crate) fn new(mode: WickedBondMode, devices: Vec) -> Self { + let mut sub_devices: Vec = Vec::new(); + // The first device is primary, the rest are not + let mut devices_iter = devices.iter(); + if let Some(primary_device) = devices_iter.next() { + sub_devices.push(SubDevice { + device: primary_device.clone(), + primary: Some(true), + }); + for name in devices_iter { + sub_devices.push(SubDevice { + device: name.clone(), + primary: None, + }) + } + } + + let s = SubDevices { + devices: sub_devices, + }; + Self { + mode, + devices: s, + mii_monitoring: None, + arp_monitoring: None, + min_links: None, + } + } +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) enum WickedBondMode { + #[serde(rename = "$primitive=active-backup")] + PrimaryBackup, +} + +impl From for WickedBondMode { + fn from(mode: BondMode) -> Self { + match mode { + BondMode::ActiveBackup => WickedBondMode::PrimaryBackup, + } + } +} + +impl From for WickedMiiMonitoringConfig { + fn from(config: MiiMonitoringConfig) -> Self { + WickedMiiMonitoringConfig { + frequency: config.frequency, + updelay: config.updelay, + downdelay: config.downdelay, + carrier_detect: 1, + } + } +} + +impl From for WickedArpMonitoringConfig { + fn from(config: ArpMonitoringConfig) -> Self { + let mut t_vec = Vec::new(); + for t in config.targets { + t_vec.push(ArpTarget(t)) + } + let targets = ArpTargets { t: t_vec }; + + WickedArpMonitoringConfig { + interval: config.interval, + validate: WickedArpValidate::from(config.validate), + targets, + } + } +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct SubDevices { + #[serde(rename = "slave")] + devices: Vec, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct SubDevice { + #[serde(rename = "$unflatten=device")] + device: InterfaceName, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "$unflatten=primary")] + primary: Option, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct WickedMiiMonitoringConfig { + #[serde(rename = "$unflatten=frequency")] + frequency: u32, + #[serde(rename = "$unflatten=updelay")] + updelay: u32, + #[serde(rename = "$unflatten=downdelay")] + downdelay: u32, + #[serde(rename = "$unflatten=carrier-detect")] + carrier_detect: u32, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct WickedArpMonitoringConfig { + #[serde(rename = "$unflatten=interval")] + interval: u32, + #[serde(rename = "$unflatten=validate")] + validate: WickedArpValidate, + targets: ArpTargets, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) enum WickedArpValidate { + #[serde(rename = "$primitive=active")] + Active, + #[serde(rename = "$primitive=all")] + All, + #[serde(rename = "$primitive=backup")] + Backup, + #[serde(rename = "$primitive=none")] + None, +} + +impl From for WickedArpValidate { + fn from(validate: ArpValidate) -> Self { + match validate { + ArpValidate::Active => WickedArpValidate::Active, + ArpValidate::All => WickedArpValidate::All, + ArpValidate::Backup => WickedArpValidate::Backup, + ArpValidate::None => WickedArpValidate::None, + } + } +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct ArpTargets { + t: Vec, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct ArpTarget(IpAddr); 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/src/wicked/vlan.rs b/sources/api/netdog/src/wicked/vlan.rs new file mode 100644 index 00000000000..ed9f16b1263 --- /dev/null +++ b/sources/api/netdog/src/wicked/vlan.rs @@ -0,0 +1,16 @@ +use crate::interface_name::InterfaceName; +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub(crate) struct WickedVlanTag { + #[serde(rename = "$unflatten=device")] + device: InterfaceName, + #[serde(rename = "$unflatten=tag")] + id: u16, +} + +impl WickedVlanTag { + pub(crate) fn new(device: InterfaceName, id: u16) -> Self { + WickedVlanTag { device, id } + } +} diff --git a/sources/api/netdog/test_data/net_config/bonding/arpmon_no_targets.toml b/sources/api/netdog/test_data/net_config/bonding/arpmon_no_targets.toml new file mode 100644 index 00000000000..cc90889fe7c --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/arpmon_no_targets.toml @@ -0,0 +1,10 @@ +[bond1] +kind = "bond" +mode = "active-backup" +interfaces = ["eno53", "eno54"] +dhcp4 = true + +[bond1.monitoring] +arpmon-interval-ms = 200 +arpmon-validate = "all" +arpmon-targets = [] diff --git a/sources/api/netdog/test_data/net_config/bonding/both_monitoring.toml b/sources/api/netdog/test_data/net_config/bonding/both_monitoring.toml new file mode 100644 index 00000000000..950bd6daf9b --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/both_monitoring.toml @@ -0,0 +1,17 @@ +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 + +[bond0.monitoring] +arpmon-interval-ms = 200 +arpmon-validate = "all" +arpmon-targets = ["192.168.1.1", "10.0.0.2"] diff --git a/sources/api/netdog/test_data/net_config/bonding/disabled_arpmon.toml b/sources/api/netdog/test_data/net_config/bonding/disabled_arpmon.toml new file mode 100644 index 00000000000..8f27396ae1c --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/disabled_arpmon.toml @@ -0,0 +1,12 @@ +version = {{version}} + +[bond1] +kind = "bond" +mode = "active-backup" +interfaces = ["eno53" , "eno54"] +dhcp4 = true + +[bond1.monitoring] +arpmon-interval-ms = 0 +arpmon-validate = "none" +arpmon-targets = [] diff --git a/sources/api/netdog/test_data/net_config/bonding/disabled_miimon.toml b/sources/api/netdog/test_data/net_config/bonding/disabled_miimon.toml new file mode 100644 index 00000000000..31ddb5a9d09 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/disabled_miimon.toml @@ -0,0 +1,12 @@ +version = {{version}} + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 0 +miimon-updelay-ms = 0 +miimon-downdelay-ms = 0 diff --git a/sources/api/netdog/test_data/net_config/bonding/missing_kind.toml b/sources/api/netdog/test_data/net_config/bonding/missing_kind.toml new file mode 100644 index 00000000000..6523d060f26 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/missing_kind.toml @@ -0,0 +1,11 @@ +version = {{version}} + +[bond0] +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 diff --git a/sources/api/netdog/test_data/net_config/bonding/net_config.toml b/sources/api/netdog/test_data/net_config/bonding/net_config.toml new file mode 100644 index 00000000000..d32ee080b11 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/net_config.toml @@ -0,0 +1,35 @@ +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 + +[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 diff --git a/sources/api/netdog/test_data/net_config/bonding/no_interfaces.toml b/sources/api/netdog/test_data/net_config/bonding/no_interfaces.toml new file mode 100644 index 00000000000..6db8e05b962 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/no_interfaces.toml @@ -0,0 +1,12 @@ +version = {{version}} + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = [] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 diff --git a/sources/api/netdog/test_data/net_config/bonding/no_monitoring.toml b/sources/api/netdog/test_data/net_config/bonding/no_monitoring.toml new file mode 100644 index 00000000000..cace765057e --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/no_monitoring.toml @@ -0,0 +1,8 @@ +version = {{version}} + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true + diff --git a/sources/api/netdog/test_data/net_config/bonding/too_many_min_links.toml b/sources/api/netdog/test_data/net_config/bonding/too_many_min_links.toml new file mode 100644 index 00000000000..67b5159223b --- /dev/null +++ b/sources/api/netdog/test_data/net_config/bonding/too_many_min_links.toml @@ -0,0 +1,13 @@ +version = {{version}} + +[bond0] +kind = "bond" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +min-links = 3 +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 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/net_config/vlan/missing_kind.toml b/sources/api/netdog/test_data/net_config/vlan/missing_kind.toml new file mode 100644 index 00000000000..ddb51fde697 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/vlan/missing_kind.toml @@ -0,0 +1,6 @@ +version = {{version}} + +[vlan42] +device = "eno1" +id = 42 +dhcp4 = true diff --git a/sources/api/netdog/test_data/net_config/vlan/net_config.toml b/sources/api/netdog/test_data/net_config/vlan/net_config.toml new file mode 100644 index 00000000000..ab037c0ad7b --- /dev/null +++ b/sources/api/netdog/test_data/net_config/vlan/net_config.toml @@ -0,0 +1,36 @@ +version = {{version}} + +# Basic vlan dhcp +[vlan2] +kind = "vlan" +device = "eno1" +id = 2 +dhcp4 = true + +[vlan3] +kind = "vlan" +device = "eno2" +id = 3 +dhcp6 = true + +# Static4 +[vlan4] +kind = "vlan" +device = "eno3" +id = 4 + +[vlan4.static4] +addresses = ["10.0.0.9/24"] + +# two devices on same vlan +[firstvlan10] +kind = "vlan" +device = "eno4" +id = 10 +dhcp4 = true + +[secondvlan10] +kind = "vlan" +device = "eno5" +id = 10 +dhcp4 = true diff --git a/sources/api/netdog/test_data/net_config/vlan/no_device.toml b/sources/api/netdog/test_data/net_config/vlan/no_device.toml new file mode 100644 index 00000000000..3e96162a974 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/vlan/no_device.toml @@ -0,0 +1,6 @@ +version = {{version}} + +[vlan42] +kind = "vlan" +id = 42 +dhcp4 = true diff --git a/sources/api/netdog/test_data/net_config/vlan/no_id.toml b/sources/api/netdog/test_data/net_config/vlan/no_id.toml new file mode 100644 index 00000000000..602255271b2 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/vlan/no_id.toml @@ -0,0 +1,6 @@ +version = {{version}} + +[vlan6] +kind = "vlan" +device = "eno1" +dhcp4 = true diff --git a/sources/api/netdog/test_data/net_config/vlan/oob_id.toml b/sources/api/netdog/test_data/net_config/vlan/oob_id.toml new file mode 100644 index 00000000000..caaedf418b1 --- /dev/null +++ b/sources/api/netdog/test_data/net_config/vlan/oob_id.toml @@ -0,0 +1,7 @@ +version = {{version}} + +[vlan6] +kind = "vlan" +device = "eno1" +id = 5000 +dhcp4 = true diff --git a/sources/api/netdog/test_data/wicked/bond0.xml b/sources/api/netdog/test_data/wicked/bond0.xml new file mode 100644 index 00000000000..77a30b85add --- /dev/null +++ b/sources/api/netdog/test_data/wicked/bond0.xml @@ -0,0 +1 @@ +bond0boottrueactive-backupeno51trueeno521002002001 diff --git a/sources/api/netdog/test_data/wicked/bond1.xml b/sources/api/netdog/test_data/wicked/bond1.xml new file mode 100644 index 00000000000..bdc552e1dcd --- /dev/null +++ b/sources/api/netdog/test_data/wicked/bond1.xml @@ -0,0 +1 @@ +bond1boottrueactive-backupeno53trueeno54200all192.168.1.110.0.0.2 diff --git a/sources/api/netdog/test_data/wicked/bond2.xml b/sources/api/netdog/test_data/wicked/bond2.xml new file mode 100644 index 00000000000..1e54c472bbb --- /dev/null +++ b/sources/api/netdog/test_data/wicked/bond2.xml @@ -0,0 +1 @@ +bond2boottrueactive-backupeno55trueeno56eno571001000100012 diff --git a/sources/api/netdog/test_data/wicked/eno51.xml b/sources/api/netdog/test_data/wicked/eno51.xml new file mode 100644 index 00000000000..629302038cb --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno51.xml @@ -0,0 +1 @@ +eno51bootbond0 diff --git a/sources/api/netdog/test_data/wicked/eno52.xml b/sources/api/netdog/test_data/wicked/eno52.xml new file mode 100644 index 00000000000..aa856c4b1d4 --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno52.xml @@ -0,0 +1 @@ +eno52bootbond0 diff --git a/sources/api/netdog/test_data/wicked/eno53.xml b/sources/api/netdog/test_data/wicked/eno53.xml new file mode 100644 index 00000000000..fbfff9f755b --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno53.xml @@ -0,0 +1 @@ +eno53bootbond1 diff --git a/sources/api/netdog/test_data/wicked/eno54.xml b/sources/api/netdog/test_data/wicked/eno54.xml new file mode 100644 index 00000000000..0bece9bf981 --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno54.xml @@ -0,0 +1 @@ +eno54bootbond1 diff --git a/sources/api/netdog/test_data/wicked/eno55.xml b/sources/api/netdog/test_data/wicked/eno55.xml new file mode 100644 index 00000000000..3b91d02925a --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno55.xml @@ -0,0 +1 @@ +eno55bootbond2 diff --git a/sources/api/netdog/test_data/wicked/eno56.xml b/sources/api/netdog/test_data/wicked/eno56.xml new file mode 100644 index 00000000000..12b880e4bef --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno56.xml @@ -0,0 +1 @@ +eno56bootbond2 diff --git a/sources/api/netdog/test_data/wicked/eno57.xml b/sources/api/netdog/test_data/wicked/eno57.xml new file mode 100644 index 00000000000..7bd885f5581 --- /dev/null +++ b/sources/api/netdog/test_data/wicked/eno57.xml @@ -0,0 +1 @@ +eno57bootbond2 diff --git a/sources/api/netdog/test_data/wicked/mystaticvlan.xml b/sources/api/netdog/test_data/wicked/mystaticvlan.xml new file mode 100644 index 00000000000..ecd270810a7 --- /dev/null +++ b/sources/api/netdog/test_data/wicked/mystaticvlan.xml @@ -0,0 +1 @@ +mystaticvlanboot
192.168.1.100/24
eno100042
diff --git a/sources/api/netdog/test_data/wicked/myvlan.xml b/sources/api/netdog/test_data/wicked/myvlan.xml new file mode 100644 index 00000000000..cc7af4583a0 --- /dev/null +++ b/sources/api/netdog/test_data/wicked/myvlan.xml @@ -0,0 +1 @@ +myvlanboottrueeno142 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}}