From e388c8608934ad6ab50bb7da63893b8f9c9a6aa1 Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Thu, 15 Jun 2023 21:35:27 +0000 Subject: [PATCH 1/7] netdog: Add IPv6AcceptRA field to networkd config struct --- sources/api/netdog/src/networkd/config/network.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/api/netdog/src/networkd/config/network.rs b/sources/api/netdog/src/networkd/config/network.rs index a331fe58017..0d619dae32b 100644 --- a/sources/api/netdog/src/networkd/config/network.rs +++ b/sources/api/netdog/src/networkd/config/network.rs @@ -43,6 +43,8 @@ struct NetworkSection { configure_wo_carrier: Option, #[systemd(entry = "DHCP")] dhcp: Option, + #[systemd(entry = "IPv6AcceptRA")] + ipv6_accept_ra: Option, #[systemd(entry = "LinkLocalAddressing")] link_local_addressing: Option, #[systemd(entry = "PrimarySlave")] From 27cdbb72edf6982bf7bba4f2ca9e9d2ff9015f0b Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Fri, 16 Jun 2023 22:20:46 +0000 Subject: [PATCH 2/7] netdog: Add "any" to ArpAllTargets enum in netdev config --- sources/api/netdog/src/networkd/config/netdev.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/api/netdog/src/networkd/config/netdev.rs b/sources/api/netdog/src/networkd/config/netdev.rs index 40f8b85feeb..5eb057d45fb 100644 --- a/sources/api/netdog/src/networkd/config/netdev.rs +++ b/sources/api/netdog/src/networkd/config/netdev.rs @@ -100,12 +100,14 @@ impl Display for ArpValidate { #[derive(Debug)] enum ArpAllTargets { All, + Any, } impl Display for ArpAllTargets { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ArpAllTargets::All => write!(f, "all"), + ArpAllTargets::Any => write!(f, "any"), } } } From 92a4b6a6750171616fb06d519e5d4e471e16a973 Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Fri, 16 Jun 2023 22:39:39 +0000 Subject: [PATCH 3/7] netdog: Add methods to network/netdev config to write to file This commit adds adds constants for the networkd config directory, file prefix. Network and netdev config each get an associated constant for the file extension, and each gain a method `write_config_file()` that writes an appropriately named file to the proper path. --- sources/api/netdog/src/networkd/config/mod.rs | 13 +++++ .../api/netdog/src/networkd/config/netdev.rs | 34 ++++++++++++++ .../api/netdog/src/networkd/config/network.rs | 47 +++++++++++++++++++ sources/api/netdog/src/networkd/mod.rs | 21 +++++++++ 4 files changed, 115 insertions(+) diff --git a/sources/api/netdog/src/networkd/config/mod.rs b/sources/api/netdog/src/networkd/config/mod.rs index 51add2b845f..6c05d9f3820 100644 --- a/sources/api/netdog/src/networkd/config/mod.rs +++ b/sources/api/netdog/src/networkd/config/mod.rs @@ -3,10 +3,23 @@ mod netdev; mod network; +use super::Result; use netdev::NetDevConfig; use network::NetworkConfig; +const NETWORKD_CONFIG_DIR: &str = "/etc/systemd/network"; +const CONFIG_FILE_PREFIX: &str = "10-"; + pub(crate) enum NetworkDConfigFile { Network(NetworkConfig), NetDev(NetDevConfig), } + +impl NetworkDConfigFile { + pub(crate) fn write_config_file(&self) -> Result<()> { + match self { + NetworkDConfigFile::Network(network) => network.write_config_file(), + NetworkDConfigFile::NetDev(netdev) => netdev.write_config_file(), + } + } +} diff --git a/sources/api/netdog/src/networkd/config/netdev.rs b/sources/api/netdog/src/networkd/config/netdev.rs index 5eb057d45fb..be3bb6bd4cb 100644 --- a/sources/api/netdog/src/networkd/config/netdev.rs +++ b/sources/api/netdog/src/networkd/config/netdev.rs @@ -1,7 +1,12 @@ +use super::{CONFIG_FILE_PREFIX, NETWORKD_CONFIG_DIR}; use crate::interface_id::InterfaceName; +use crate::networkd::{error, Result}; use crate::vlan_id::VlanId; +use snafu::{OptionExt, ResultExt}; use std::fmt::Display; +use std::fs; use std::net::IpAddr; +use std::path::{Path, PathBuf}; use systemd_derive::{SystemdUnit, SystemdUnitSection}; #[derive(Debug, Default, SystemdUnit)] @@ -111,3 +116,32 @@ impl Display for ArpAllTargets { } } } + +impl NetDevConfig { + const FILE_EXT: &str = "netdev"; + + /// Write the config to the proper directory with the proper prefix and file extention + pub(crate) fn write_config_file(&self) -> Result<()> { + let cfg_path = self.config_path()?; + + fs::write(&cfg_path, self.to_string()).context(error::NetworkDConfigWriteSnafu { + what: "netdev_config", + path: cfg_path, + }) + } + + /// Build the proper prefixed path for the config file + fn config_path(&self) -> Result { + let device_name = &self.netdev.as_ref().and_then(|n| n.name.clone()).context( + error::ConfigMissingNameSnafu { + what: "netdev config".to_string(), + }, + )?; + + let filename = format!("{}{}", CONFIG_FILE_PREFIX, device_name); + let mut path = Path::new(NETWORKD_CONFIG_DIR).join(filename); + path.set_extension(Self::FILE_EXT); + + Ok(path) + } +} diff --git a/sources/api/netdog/src/networkd/config/network.rs b/sources/api/netdog/src/networkd/config/network.rs index 0d619dae32b..5b21b88e98f 100644 --- a/sources/api/netdog/src/networkd/config/network.rs +++ b/sources/api/netdog/src/networkd/config/network.rs @@ -1,7 +1,12 @@ +use super::{CONFIG_FILE_PREFIX, NETWORKD_CONFIG_DIR}; use crate::interface_id::{InterfaceName, MacAddress}; +use crate::networkd::{error, Result}; use ipnet::IpNet; +use snafu::{OptionExt, ResultExt}; use std::fmt::Display; +use std::fs; use std::net::IpAddr; +use std::path::{Path, PathBuf}; use systemd_derive::{SystemdUnit, SystemdUnitSection}; #[derive(Debug, Default, SystemdUnit)] @@ -123,3 +128,45 @@ impl Display for DhcpBool { } } } +impl NetworkConfig { + const FILE_EXT: &str = "network"; + + /// Write the config to the proper directory with the proper prefix and file extention + pub(crate) fn write_config_file(&self) -> Result<()> { + let cfg_path = self.config_path()?; + + fs::write(&cfg_path, self.to_string()).context(error::NetworkDConfigWriteSnafu { + what: "network config", + path: cfg_path, + }) + } + /// Build the proper prefixed path for the config file + fn config_path(&self) -> Result { + let match_section = self + .r#match + .as_ref() + .context(error::ConfigMissingNameSnafu { + what: "network config".to_string(), + })?; + + // Choose the device name for the filename if it exists, otherwise use the MAC + let device_name = match ( + &match_section.name, + match_section.permanent_mac_address.first(), + ) { + (Some(name), _) => name.to_string(), + (None, Some(mac)) => mac.to_string().replace(':', ""), + (None, None) => { + return error::ConfigMissingNameSnafu { + what: "network_config".to_string(), + } + .fail(); + } + }; + + let filename = format!("{}{}", CONFIG_FILE_PREFIX, device_name); + let mut cfg_path = Path::new(NETWORKD_CONFIG_DIR).join(filename); + cfg_path.set_extension(Self::FILE_EXT); + Ok(cfg_path) + } +} diff --git a/sources/api/netdog/src/networkd/mod.rs b/sources/api/netdog/src/networkd/mod.rs index 1bf79dfa1a0..34acefa2bb4 100644 --- a/sources/api/netdog/src/networkd/mod.rs +++ b/sources/api/netdog/src/networkd/mod.rs @@ -1 +1,22 @@ mod config; + +mod error { + use snafu::Snafu; + use std::io; + use std::path::PathBuf; + + #[derive(Debug, Snafu)] + #[snafu(visibility(pub(crate)))] + pub(crate) enum Error { + #[snafu(display("Unable to create '{}', missing name or MAC", what))] + ConfigMissingName { what: String }, + + #[snafu(display("Unable to write {} to {}: {}", what, path.display(), source))] + NetworkDConfigWrite { + what: String, + path: PathBuf, + source: io::Error, + }, + } +} +pub(crate) type Result = std::result::Result; From 1294f6e4dbdf5e79a8c9dec6655437f7aa194b6e Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Wed, 21 Jun 2023 18:19:12 +0000 Subject: [PATCH 4/7] netdog: Add systemd-networkd devices This commit adds a module that contains the structs representing the network devices we currently configure: interfaces, bonds, and vlans. These structures are meant to contain the latest versions of all the underlying config. --- sources/api/netdog/src/networkd/devices/bond.rs | 17 +++++++++++++++++ .../netdog/src/networkd/devices/interface.rs | 12 ++++++++++++ sources/api/netdog/src/networkd/devices/mod.rs | 15 +++++++++++++++ sources/api/netdog/src/networkd/devices/vlan.rs | 15 +++++++++++++++ sources/api/netdog/src/networkd/mod.rs | 1 + 5 files changed, 60 insertions(+) create mode 100644 sources/api/netdog/src/networkd/devices/bond.rs create mode 100644 sources/api/netdog/src/networkd/devices/interface.rs create mode 100644 sources/api/netdog/src/networkd/devices/mod.rs create mode 100644 sources/api/netdog/src/networkd/devices/vlan.rs diff --git a/sources/api/netdog/src/networkd/devices/bond.rs b/sources/api/netdog/src/networkd/devices/bond.rs new file mode 100644 index 00000000000..e619929789c --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/bond.rs @@ -0,0 +1,17 @@ +use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; +use crate::bonding::{BondModeV1, BondMonitoringConfigV1}; +use crate::interface_id::{InterfaceId, InterfaceName}; + +#[derive(Debug)] +pub(crate) struct NetworkDBond { + pub(crate) name: InterfaceName, + pub(crate) dhcp4: Option, + pub(crate) dhcp6: Option, + pub(crate) static4: Option, + pub(crate) static6: Option, + pub(crate) routes: Option>, + pub(crate) mode: BondModeV1, + pub(crate) min_links: Option, + pub(crate) monitoring_config: BondMonitoringConfigV1, + pub(crate) interfaces: Vec, +} diff --git a/sources/api/netdog/src/networkd/devices/interface.rs b/sources/api/netdog/src/networkd/devices/interface.rs new file mode 100644 index 00000000000..8f495b4f484 --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/interface.rs @@ -0,0 +1,12 @@ +use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; +use crate::interface_id::InterfaceId; + +#[derive(Debug)] +pub(crate) struct NetworkDInterface { + pub(crate) name: InterfaceId, + pub(crate) dhcp4: Option, + pub(crate) dhcp6: Option, + pub(crate) static4: Option, + pub(crate) static6: Option, + pub(crate) routes: Option>, +} diff --git a/sources/api/netdog/src/networkd/devices/mod.rs b/sources/api/netdog/src/networkd/devices/mod.rs new file mode 100644 index 00000000000..ae87f2642fb --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/mod.rs @@ -0,0 +1,15 @@ +//! The device module contains the structures representing the latest version of configuration for +//! interfaces, bonds, and VLANs. +mod bond; +mod interface; +mod vlan; + +pub(crate) use bond::NetworkDBond; +pub(crate) use interface::NetworkDInterface; +pub(crate) use vlan::NetworkDVlan; + +pub(crate) enum NetworkDDevice { + Interface(NetworkDInterface), + Bond(NetworkDBond), + Vlan(NetworkDVlan), +} diff --git a/sources/api/netdog/src/networkd/devices/vlan.rs b/sources/api/netdog/src/networkd/devices/vlan.rs new file mode 100644 index 00000000000..8d11b6e696e --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/vlan.rs @@ -0,0 +1,15 @@ +use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; +use crate::interface_id::{InterfaceId, InterfaceName}; +use crate::vlan_id::VlanId; + +#[derive(Debug)] +pub(crate) struct NetworkDVlan { + pub(crate) name: InterfaceName, + pub(crate) dhcp4: Option, + pub(crate) dhcp6: Option, + pub(crate) static4: Option, + pub(crate) static6: Option, + pub(crate) routes: Option>, + pub(crate) device: InterfaceName, + pub(crate) id: VlanId, +} diff --git a/sources/api/netdog/src/networkd/mod.rs b/sources/api/netdog/src/networkd/mod.rs index 34acefa2bb4..6223b2f35b1 100644 --- a/sources/api/netdog/src/networkd/mod.rs +++ b/sources/api/netdog/src/networkd/mod.rs @@ -1,4 +1,5 @@ mod config; +mod devices; mod error { use snafu::Snafu; From af496ac7126d0485381733754d55d522bffbbc7c Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Wed, 21 Jun 2023 18:35:51 +0000 Subject: [PATCH 5/7] netdog: Derive Deserialize for networkd devices for unit test only This commit adds attributes to the networkd device structs to derive Deserialize to enable unit testing. Since we never directly deserialize into these device structs during normal use, gating the Deserialize implementation behind the test attribute keeps the binary size a bit smaller for "normal" builds. --- sources/api/netdog/src/networkd/devices/bond.rs | 7 +++++++ sources/api/netdog/src/networkd/devices/interface.rs | 6 ++++++ sources/api/netdog/src/networkd/devices/vlan.rs | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/sources/api/netdog/src/networkd/devices/bond.rs b/sources/api/netdog/src/networkd/devices/bond.rs index e619929789c..1144717444a 100644 --- a/sources/api/netdog/src/networkd/devices/bond.rs +++ b/sources/api/netdog/src/networkd/devices/bond.rs @@ -2,6 +2,12 @@ use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; use crate::bonding::{BondModeV1, BondMonitoringConfigV1}; use crate::interface_id::{InterfaceId, InterfaceName}; +#[cfg(test)] +use serde::Deserialize; + +// Builder unit tests deserialize config to this struct, but we never expect to do that otherwise so put +// the Deserialize derive behind the test attribute +#[cfg_attr(test, derive(Deserialize))] #[derive(Debug)] pub(crate) struct NetworkDBond { pub(crate) name: InterfaceName, @@ -11,6 +17,7 @@ pub(crate) struct NetworkDBond { pub(crate) static6: Option, pub(crate) routes: Option>, pub(crate) mode: BondModeV1, + #[cfg_attr(test, serde(rename = "min-links"))] pub(crate) min_links: Option, pub(crate) monitoring_config: BondMonitoringConfigV1, pub(crate) interfaces: Vec, diff --git a/sources/api/netdog/src/networkd/devices/interface.rs b/sources/api/netdog/src/networkd/devices/interface.rs index 8f495b4f484..b56315ecfbc 100644 --- a/sources/api/netdog/src/networkd/devices/interface.rs +++ b/sources/api/netdog/src/networkd/devices/interface.rs @@ -1,6 +1,12 @@ use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; use crate::interface_id::InterfaceId; +#[cfg(test)] +use serde::Deserialize; + +// Builder unit tests deserialize config to this struct, but we never expect to do that otherwise so put +// the Deserialize derive behind the test attribute +#[cfg_attr(test, derive(Deserialize))] #[derive(Debug)] pub(crate) struct NetworkDInterface { pub(crate) name: InterfaceId, diff --git a/sources/api/netdog/src/networkd/devices/vlan.rs b/sources/api/netdog/src/networkd/devices/vlan.rs index 8d11b6e696e..74805191a90 100644 --- a/sources/api/netdog/src/networkd/devices/vlan.rs +++ b/sources/api/netdog/src/networkd/devices/vlan.rs @@ -2,6 +2,12 @@ use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteV1, StaticConfigV1}; use crate::interface_id::{InterfaceId, InterfaceName}; use crate::vlan_id::VlanId; +#[cfg(test)] +use serde::Deserialize; + +// Builder unit tests deserialize config to this struct, but we never expect to do that otherwise so put +// the Deserialize derive behind the test attribute +#[cfg_attr(test, derive(Deserialize))] #[derive(Debug)] pub(crate) struct NetworkDVlan { pub(crate) name: InterfaceName, From 86c8d44b204e4fd12582b9db2ad9e24bb17ff165 Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Mon, 19 Jun 2023 20:17:20 +0000 Subject: [PATCH 6/7] netdog: Add builder for networkd NetDevConfig This commit adds a buidler for the systemd-networkd NetDevConfig struct. The builder contains the logic to translate the structures deserialized from `net.toml` into NetDev structures representing systemd-networkd .netdev files. Since NetDevConfig will be created for bonds and vlans (and potentially more later), using a builder centralizes the logic to avoid duplication. Use a builder also means methods can be limited based on the type of device being built, providing safety at development time. --- sources/api/netdog/src/networkd/config/mod.rs | 59 ++++- .../api/netdog/src/networkd/config/netdev.rs | 202 ++++++++++++++++- .../netdog/test_data/networkd/builder.toml | 214 ++++++++++++++++++ .../test_data/networkd/netdev/bond0.netdev | 8 + .../test_data/networkd/netdev/bond1.netdev | 10 + .../test_data/networkd/netdev/bond2.netdev | 9 + .../networkd/netdev/mystaticvlan.netdev | 5 + .../test_data/networkd/netdev/myvlan.netdev | 5 + 8 files changed, 506 insertions(+), 6 deletions(-) create mode 100644 sources/api/netdog/test_data/networkd/builder.toml create mode 100644 sources/api/netdog/test_data/networkd/netdev/bond0.netdev create mode 100644 sources/api/netdog/test_data/networkd/netdev/bond1.netdev create mode 100644 sources/api/netdog/test_data/networkd/netdev/bond2.netdev create mode 100644 sources/api/netdog/test_data/networkd/netdev/mystaticvlan.netdev create mode 100644 sources/api/netdog/test_data/networkd/netdev/myvlan.netdev diff --git a/sources/api/netdog/src/networkd/config/mod.rs b/sources/api/netdog/src/networkd/config/mod.rs index 6c05d9f3820..b9918327a5f 100644 --- a/sources/api/netdog/src/networkd/config/mod.rs +++ b/sources/api/netdog/src/networkd/config/mod.rs @@ -4,7 +4,7 @@ mod netdev; mod network; use super::Result; -use netdev::NetDevConfig; +pub(crate) use netdev::{NetDevBuilder, NetDevConfig}; use network::NetworkConfig; const NETWORKD_CONFIG_DIR: &str = "/etc/systemd/network"; @@ -19,7 +19,62 @@ impl NetworkDConfigFile { pub(crate) fn write_config_file(&self) -> Result<()> { match self { NetworkDConfigFile::Network(network) => network.write_config_file(), - NetworkDConfigFile::NetDev(netdev) => netdev.write_config_file(), + NetworkDConfigFile::NetDev(netdev) => netdev.write_config_file(NETWORKD_CONFIG_DIR), } } } + +// This private module defines some empty traits meant to be used as type parameters for the +// networkd config builders. The type parameters limit the methods that can be called on the +// builders so a user of this code can't inadvertently add configuration options that aren't +// applicable to a particular device. For example, a user can't add bond monitoring options to a +// VLAN config. +// +// The following traits and enums are only meant to be used within the config module of this crate; +// putting them in a private module guarantees this behavior. See the "sealed trait" pattern here: +// https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed +mod private { + // The following zero-variant enums represent the device types we currently support. They + // cannot be constructed and exist only as phantom types. + pub enum Bond {} + pub enum Interface {} + pub enum Vlan {} + pub enum BondWorker {} // interfaces that are bound to a bond + + // The devices for which we are generating a configuration file. All device types should + // implement this trait. + pub trait Device {} + impl Device for Bond {} + impl Device for Interface {} + impl Device for Vlan {} + impl Device for BondWorker {} + + // Devices not bound to a bond, i.e. everything EXCEPT BondWorker(s) + pub trait NotBonded {} + impl NotBonded for Bond {} + impl NotBonded for Interface {} + impl NotBonded for Vlan {} +} + +#[cfg(test)] +mod tests { + use crate::networkd::devices::{NetworkDBond, NetworkDInterface, NetworkDVlan}; + use serde::Deserialize; + use std::fs; + use std::path::{Path, PathBuf}; + + pub(super) const BUILDER_DATA: &str = include_str!("../../../test_data/networkd/builder.toml"); + + pub(super) fn test_data() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("test_data") + .join("networkd") + } + + #[derive(Debug, Deserialize)] + pub(super) struct TestDevices { + pub(super) interface: Vec, + pub(super) bond: Vec, + pub(super) vlan: Vec, + } +} diff --git a/sources/api/netdog/src/networkd/config/netdev.rs b/sources/api/netdog/src/networkd/config/netdev.rs index be3bb6bd4cb..09dff5645cd 100644 --- a/sources/api/netdog/src/networkd/config/netdev.rs +++ b/sources/api/netdog/src/networkd/config/netdev.rs @@ -1,10 +1,13 @@ +use super::private::{Bond, Device, Vlan}; use super::{CONFIG_FILE_PREFIX, NETWORKD_CONFIG_DIR}; +use crate::bonding::{ArpMonitoringConfigV1, ArpValidateV1, BondModeV1, MiiMonitoringConfigV1}; use crate::interface_id::InterfaceName; use crate::networkd::{error, Result}; use crate::vlan_id::VlanId; use snafu::{OptionExt, ResultExt}; use std::fmt::Display; use std::fs; +use std::marker::PhantomData; use std::net::IpAddr; use std::path::{Path, PathBuf}; use systemd_derive::{SystemdUnit, SystemdUnitSection}; @@ -121,8 +124,8 @@ impl NetDevConfig { const FILE_EXT: &str = "netdev"; /// Write the config to the proper directory with the proper prefix and file extention - pub(crate) fn write_config_file(&self) -> Result<()> { - let cfg_path = self.config_path()?; + pub(crate) fn write_config_file>(&self, config_dir: P) -> Result<()> { + let cfg_path = self.config_path(config_dir)?; fs::write(&cfg_path, self.to_string()).context(error::NetworkDConfigWriteSnafu { what: "netdev_config", @@ -131,7 +134,7 @@ impl NetDevConfig { } /// Build the proper prefixed path for the config file - fn config_path(&self) -> Result { + fn config_path>(&self, config_dir: P) -> Result { let device_name = &self.netdev.as_ref().and_then(|n| n.name.clone()).context( error::ConfigMissingNameSnafu { what: "netdev config".to_string(), @@ -139,9 +142,200 @@ impl NetDevConfig { )?; let filename = format!("{}{}", CONFIG_FILE_PREFIX, device_name); - let mut path = Path::new(NETWORKD_CONFIG_DIR).join(filename); + let mut path = Path::new(config_dir.as_ref()).join(filename); path.set_extension(Self::FILE_EXT); Ok(path) } + + // The following *mut() methods are private and primarily meant for use by the NetDevBuilder. + // They are convenience methods to access the referenced structs (which are `Option`s) since + // they may need to be accessed in multiple places during the builder's construction process. + // (And no one wants to call `get_or_insert_with()` everywhere) + fn vlan_mut(&mut self) -> &mut VlanSection { + self.vlan.get_or_insert_with(VlanSection::default) + } + + fn bond_mut(&mut self) -> &mut BondSection { + self.bond.get_or_insert_with(BondSection::default) + } +} + +// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= +// +/// The builder for `NetDevConfig`. +// +// Why a builder? Great question. As you can see below, some logic is involved to translate +// config struct fields to a valid NetDevConfig. Since `NetDevConfig` will be created by multiple +// devices (bonds and VLANs to start), it makes sense to centralize that logic to avoid +// duplication/mistakes. Using a builder means type parameters can be used to limit available +// methods based on the device being created. Putting the type parameter on the builder and not +// NetDevConfig avoids proliferating the type parameter everywhere NetDevConfig may be used. +#[derive(Debug)] +pub(crate) struct NetDevBuilder { + netdev: NetDevConfig, + spooky: PhantomData, +} + +impl NetDevBuilder { + pub(crate) fn build(self) -> NetDevConfig { + self.netdev + } +} + +impl NetDevBuilder { + /// Create a new .netdev config for a bond. + pub(crate) fn new_bond(name: InterfaceName) -> Self { + let netdev = NetDevConfig { + netdev: Some(NetDevSection { + name: Some(name), + kind: Some(NetDevKind::Bond), + }), + ..Default::default() + }; + + Self { + netdev, + spooky: PhantomData, + } + } + + /// Add bond mode + pub(crate) fn with_mode(&mut self, mode: BondModeV1) { + self.netdev.bond_mut().mode = match mode { + BondModeV1::ActiveBackup => Some(BondMode::ActiveBackup), + } + } + + /// Add bond minimum links + pub(crate) fn with_min_links(&mut self, min_links: usize) { + self.netdev.bond_mut().min_links = Some(min_links) + } + + /// Add MIIMon configuration + pub(crate) fn with_miimon_config(&mut self, miimon: MiiMonitoringConfigV1) { + let bond = self.netdev.bond_mut(); + + bond.mii_mon_secs = Some(miimon.frequency); + bond.up_delay_sec = Some(miimon.updelay); + bond.down_delay_sec = Some(miimon.downdelay); + } + + /// Add ARPMon configuration + pub(crate) fn with_arpmon_config(&mut self, arpmon: ArpMonitoringConfigV1) { + let bond = self.netdev.bond_mut(); + + // Legacy alert: wicked defaults to "any", keep that default here + // TODO: add a setting for this + bond.arp_all_targets = Some(ArpAllTargets::Any); + bond.arp_interval_secs = Some(arpmon.interval); + bond.arp_targets.extend(arpmon.targets); + bond.arp_validate = match arpmon.validate { + ArpValidateV1::Active => Some(ArpValidate::Active), + ArpValidateV1::All => Some(ArpValidate::All), + ArpValidateV1::Backup => Some(ArpValidate::Backup), + ArpValidateV1::None => Some(ArpValidate::r#None), + }; + } +} + +impl NetDevBuilder { + /// Create a new .netdev config for a VLAN + pub(crate) fn new_vlan(name: InterfaceName) -> Self { + let netdev = NetDevConfig { + netdev: Some(NetDevSection { + name: Some(name), + kind: Some(NetDevKind::Vlan), + }), + ..Default::default() + }; + + Self { + netdev, + spooky: PhantomData, + } + } + + /// Add the VLAN's ID + pub(crate) fn with_vlan_id(&mut self, id: VlanId) { + self.netdev.vlan_mut().id = Some(id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bonding::BondMonitoringConfigV1; + use crate::networkd::config::tests::{test_data, TestDevices, BUILDER_DATA}; + use crate::networkd::devices::{NetworkDBond, NetworkDVlan}; + + const FAKE_TEST_DIR: &str = "testdir"; + + fn netdev_path(name: String) -> PathBuf { + test_data().join("netdev").join(format!("{}.netdev", name)) + } + + fn netdev_from_bond(bond: NetworkDBond) -> NetDevConfig { + let mut netdev = NetDevBuilder::new_bond(bond.name.clone()); + netdev.with_mode(bond.mode); + bond.min_links.map(|m| netdev.with_min_links(m)); + match bond.monitoring_config { + BondMonitoringConfigV1::MiiMon(miimon) => netdev.with_miimon_config(miimon), + BondMonitoringConfigV1::ArpMon(arpmon) => netdev.with_arpmon_config(arpmon), + } + netdev.build() + } + + fn netdev_from_vlan(vlan: NetworkDVlan) -> NetDevConfig { + let mut netdev = NetDevBuilder::new_vlan(vlan.name.clone()); + netdev.with_vlan_id(vlan.id); + netdev.build() + } + + #[test] + fn bond_netdev_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + for bond in devices.bond { + let expected_filename = netdev_path(bond.name.to_string()); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = netdev_from_bond(bond).to_string(); + + assert_eq!(expected, got) + } + } + + #[test] + fn vlan_netdev_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + for vlan in devices.vlan { + let expected_filename = netdev_path(vlan.name.to_string()); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = netdev_from_vlan(vlan).to_string(); + + assert_eq!(expected, got) + } + } + + #[test] + fn config_path_empty() { + let netdev = NetDevConfig::default(); + assert!(netdev.config_path(FAKE_TEST_DIR).is_err()) + } + + #[test] + fn config_path_name() { + let filename = format!("{}foo", CONFIG_FILE_PREFIX); + let mut expected = Path::new(FAKE_TEST_DIR).join(filename); + expected.set_extension(NetDevConfig::FILE_EXT); + + let netdev = NetDevConfig { + netdev: Some(NetDevSection { + name: Some(InterfaceName::try_from("foo").unwrap()), + ..Default::default() + }), + ..Default::default() + }; + + assert_eq!(expected, netdev.config_path(FAKE_TEST_DIR).unwrap()) + } } diff --git a/sources/api/netdog/test_data/networkd/builder.toml b/sources/api/netdog/test_data/networkd/builder.toml new file mode 100644 index 00000000000..02d4d4e09f4 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/builder.toml @@ -0,0 +1,214 @@ +[[interface]] +name = "eno1" +dhcp4 = true + +[[interface]] +name = "eno2" +dhcp6 = true + +[[interface]] +name = "eno3" +dhcp4 = true +dhcp6 = false + +[[interface]] +name = "eno4" +dhcp4 = false +dhcp6 = true + +[[interface]] +name = "eno5" +dhcp4 = true +dhcp6 = true + +[[interface]] +name = "eno6" +dhcp6 = false +[interface.dhcp4] +enabled = true +route-metric = 100 + +[[interface]] +name = "eno7" +dhcp4 = true +[interface.dhcp6] +enabled = true +optional = true + +[[interface]] +name = "eno8" +[interface.dhcp4] +enabled = true +optional = true +[interface.dhcp6] +enabled = true +optional = true + +[[interface]] +name = "eno9" +[interface.dhcp4] +enabled = true +optional = true + +[[interface]] +name = "eno10" +[interface.dhcp6] +enabled = true +optional = true + +# IPv4 static addresses/routes +[[interface]] +name = "eno11" +[interface.static4] +addresses = ["192.168.14.2/24"] + +[[interface]] +name = "eno12" +[interface.static4] +addresses = ["10.0.0.9/24"] +[[interface.routes]] +to = "10.10.10.0/24" +via = "10.0.0.1" + +[[interface]] +name = "eno13" +[interface.static4] +addresses = ["192.168.14.2/24"] +[[interface.routes]] +to = "9.9.0.0/16" +via = "192.168.1.1" +[[interface.routes]] +to = "10.10.10.0/24" +via = "192.168.1.3" + +[[interface]] +name = "eno14" +[interface.static4] +addresses = ["10.0.0.10/24", "11.0.0.11/24"] +[[interface.routes]] +to = "default" +via = "10.0.0.1" +route-metric = 100 +[[interface.routes]] +to = "default" +via = "11.0.0.1" +route-metric = 200 + +# IPv6 static addresses/routes +[[interface]] +name = "eno15" +[interface.static6] +addresses = ["2001:cafe:face:beef::dead:dead/64"] + +[[interface]] +name = "eno16" +[interface.static6] +addresses = ["2001:dead:beef::2/64"] +[[interface.routes]] +to = "default" +via = "2001:beef:beef::1" + +[[interface]] +name = "eno17" +[interface.static6] +addresses = ["3001:f00f:f00f::2/64", "3001:f00f:f00f::3/64"] +[[interface.routes]] +to = "3001:dead:beef::2/64" +via = "3001:beef:beef::1" +route-metric = 100 +[[interface.routes]] +to = "3001:dead:feed::2/64" +via = "3001:beef:beef::2" +route-metric = 200 + +# # DHCP4/6 and static addresses +[[interface]] +name = "eno18" +dhcp4 = true +[interface.static4] +addresses = ["10.0.0.10/24", "11.0.0.11/24"] + +[[interface]] +name = "eno19" +dhcp6 = true +[interface.static6] +addresses = ["3001:f00f:f00f::2/64", "3001:f00f:f00f::3/64"] + +# Source IP +[[interface]] +name = "eno20" +[interface.static4] +addresses = ["192.168.14.5/24"] +[[interface.routes]] +to = "10.10.10.0/24" +from = "192.168.14.5" +via = "192.168.14.25" + +[[interface]] +name = "eno21" +[interface.static6] +addresses = ["2001:dead:beef::2/64"] +[[interface.routes]] +to = "3001:dead:beef::2/64" +from = "2001:dead:beef::2" +via = "2001:beef:beef::1" + +# Bonds and vlans +[[vlan]] +name = "myvlan" +id = 42 +dhcp4 = true +device = "eno1" + +[[vlan]] +name = "mystaticvlan" +device = "eno1000" +id = 42 +[vlan.static4] +addresses = ["192.168.1.100/24"] + +[[bond]] +name = "bond0" +mode = "active-backup" +interfaces = ["eno51" , "eno52"] +dhcp4 = true +[bond.monitoring_config] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 + +[[bond]] +name = "bond1" +mode = "active-backup" +interfaces = ["eno53" , "eno54"] +dhcp4 = true +dhcp6 = true +[bond.monitoring_config] +arpmon-interval-ms = 200 +arpmon-validate = "all" +arpmon-targets = ["192.168.1.1", "10.0.0.2"] + +[[bond]] +name = "bond2" +kind = "bond" +mode = "active-backup" +interfaces = ["eno55", "eno56", "eno57"] +min-links = 2 +dhcp6 = true +[bond.monitoring_config] +miimon-frequency-ms = 100 +miimon-updelay-ms = 1000 +miimon-downdelay-ms = 1000 + +[[interface]] +name = "f8:74:a4:d5:32:64" +dhcp4 = true + +[[interface]] +name = "c8:74:a4:d5:32:65" +[interface.static4] +addresses = ["192.168.14.5/24"] +[[interface.routes]] +to = "10.10.10.0/24" +from = "192.168.14.5" +via = "192.168.14.25" diff --git a/sources/api/netdog/test_data/networkd/netdev/bond0.netdev b/sources/api/netdog/test_data/networkd/netdev/bond0.netdev new file mode 100644 index 00000000000..efac9cd2e68 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/netdev/bond0.netdev @@ -0,0 +1,8 @@ +[NetDev] +Name=bond0 +Kind=bond +[Bond] +Mode=active-backup +MIIMonitorSec=100 +UpDelaySec=200 +DownDelaySec=200 diff --git a/sources/api/netdog/test_data/networkd/netdev/bond1.netdev b/sources/api/netdog/test_data/networkd/netdev/bond1.netdev new file mode 100644 index 00000000000..c6fe29ed5dc --- /dev/null +++ b/sources/api/netdog/test_data/networkd/netdev/bond1.netdev @@ -0,0 +1,10 @@ +[NetDev] +Name=bond1 +Kind=bond +[Bond] +Mode=active-backup +ARPIntervalSec=200 +ARPValidate=all +ARPIPTargets=192.168.1.1 +ARPIPTargets=10.0.0.2 +ARPAllTargets=any diff --git a/sources/api/netdog/test_data/networkd/netdev/bond2.netdev b/sources/api/netdog/test_data/networkd/netdev/bond2.netdev new file mode 100644 index 00000000000..e94ac58de41 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/netdev/bond2.netdev @@ -0,0 +1,9 @@ +[NetDev] +Name=bond2 +Kind=bond +[Bond] +Mode=active-backup +MinLinks=2 +MIIMonitorSec=100 +UpDelaySec=1000 +DownDelaySec=1000 diff --git a/sources/api/netdog/test_data/networkd/netdev/mystaticvlan.netdev b/sources/api/netdog/test_data/networkd/netdev/mystaticvlan.netdev new file mode 100644 index 00000000000..607da3f7b0d --- /dev/null +++ b/sources/api/netdog/test_data/networkd/netdev/mystaticvlan.netdev @@ -0,0 +1,5 @@ +[NetDev] +Name=mystaticvlan +Kind=vlan +[VLAN] +Id=42 diff --git a/sources/api/netdog/test_data/networkd/netdev/myvlan.netdev b/sources/api/netdog/test_data/networkd/netdev/myvlan.netdev new file mode 100644 index 00000000000..5615e48d920 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/netdev/myvlan.netdev @@ -0,0 +1,5 @@ +[NetDev] +Name=myvlan +Kind=vlan +[VLAN] +Id=42 From e1b4e28441e00b1df019038e8f5363b997c1ce10 Mon Sep 17 00:00:00 2001 From: Zac Mrowicki Date: Mon, 19 Jun 2023 20:08:40 +0000 Subject: [PATCH 7/7] netdog: Add builder for networkd NetworkConfig This commit adds a builder for the systemd-networkd NetworkConfig struct. The builder contains the logic to translate the structures deserialized from `net.toml` into NetworkConfig structures representing systemd-networkd .network files. Since NetworkConfig will be created for interfaces, bonds, and vlans (and potentially more later), using a builder centralizes the logic to avoid duplication. Using a builder also means methods can limited based on the type of device being built, providing safety at development time. --- sources/api/netdog/src/networkd/config/mod.rs | 4 +- .../api/netdog/src/networkd/config/network.rs | 503 +++++++++++++++++- .../test_data/networkd/network/bond0.network | 10 + .../test_data/networkd/network/bond1.network | 14 + .../test_data/networkd/network/bond2.network | 10 + .../networkd/network/c874a4d53265.network | 8 + .../test_data/networkd/network/eno1.network | 9 + .../test_data/networkd/network/eno10.network | 9 + .../test_data/networkd/network/eno11.network | 4 + .../test_data/networkd/network/eno12.network | 7 + .../test_data/networkd/network/eno13.network | 10 + .../test_data/networkd/network/eno14.network | 13 + .../test_data/networkd/network/eno15.network | 4 + .../test_data/networkd/network/eno16.network | 7 + .../test_data/networkd/network/eno17.network | 13 + .../test_data/networkd/network/eno18.network | 11 + .../test_data/networkd/network/eno19.network | 11 + .../test_data/networkd/network/eno2.network | 9 + .../test_data/networkd/network/eno20.network | 8 + .../test_data/networkd/network/eno21.network | 8 + .../test_data/networkd/network/eno3.network | 10 + .../test_data/networkd/network/eno4.network | 10 + .../test_data/networkd/network/eno5.network | 13 + .../test_data/networkd/network/eno51.network | 6 + .../test_data/networkd/network/eno52.network | 5 + .../test_data/networkd/network/eno6.network | 11 + .../test_data/networkd/network/eno7.network | 13 + .../test_data/networkd/network/eno8.network | 12 + .../test_data/networkd/network/eno9.network | 9 + .../networkd/network/f874a4d53264.network | 9 + .../networkd/network/mystaticvlan.network | 5 + .../test_data/networkd/network/myvlan.network | 10 + 32 files changed, 779 insertions(+), 6 deletions(-) create mode 100644 sources/api/netdog/test_data/networkd/network/bond0.network create mode 100644 sources/api/netdog/test_data/networkd/network/bond1.network create mode 100644 sources/api/netdog/test_data/networkd/network/bond2.network create mode 100644 sources/api/netdog/test_data/networkd/network/c874a4d53265.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno1.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno10.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno11.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno12.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno13.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno14.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno15.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno16.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno17.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno18.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno19.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno2.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno20.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno21.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno3.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno4.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno5.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno51.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno52.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno6.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno7.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno8.network create mode 100644 sources/api/netdog/test_data/networkd/network/eno9.network create mode 100644 sources/api/netdog/test_data/networkd/network/f874a4d53264.network create mode 100644 sources/api/netdog/test_data/networkd/network/mystaticvlan.network create mode 100644 sources/api/netdog/test_data/networkd/network/myvlan.network diff --git a/sources/api/netdog/src/networkd/config/mod.rs b/sources/api/netdog/src/networkd/config/mod.rs index b9918327a5f..805a9c53767 100644 --- a/sources/api/netdog/src/networkd/config/mod.rs +++ b/sources/api/netdog/src/networkd/config/mod.rs @@ -5,7 +5,7 @@ mod network; use super::Result; pub(crate) use netdev::{NetDevBuilder, NetDevConfig}; -use network::NetworkConfig; +pub(crate) use network::{NetworkBuilder, NetworkConfig}; const NETWORKD_CONFIG_DIR: &str = "/etc/systemd/network"; const CONFIG_FILE_PREFIX: &str = "10-"; @@ -18,7 +18,7 @@ pub(crate) enum NetworkDConfigFile { impl NetworkDConfigFile { pub(crate) fn write_config_file(&self) -> Result<()> { match self { - NetworkDConfigFile::Network(network) => network.write_config_file(), + NetworkDConfigFile::Network(network) => network.write_config_file(NETWORKD_CONFIG_DIR), NetworkDConfigFile::NetDev(netdev) => netdev.write_config_file(NETWORKD_CONFIG_DIR), } } diff --git a/sources/api/netdog/src/networkd/config/network.rs b/sources/api/netdog/src/networkd/config/network.rs index 5b21b88e98f..fcc4bb713ca 100644 --- a/sources/api/netdog/src/networkd/config/network.rs +++ b/sources/api/netdog/src/networkd/config/network.rs @@ -1,14 +1,24 @@ +use super::private::{Bond, BondWorker, Device, Interface, NotBonded, Vlan}; use super::{CONFIG_FILE_PREFIX, NETWORKD_CONFIG_DIR}; +use crate::addressing::{Dhcp4ConfigV1, Dhcp6ConfigV1, RouteTo, RouteV1, StaticConfigV1}; +use crate::interface_id::InterfaceId; use crate::interface_id::{InterfaceName, MacAddress}; use crate::networkd::{error, Result}; use ipnet::IpNet; +use lazy_static::lazy_static; use snafu::{OptionExt, ResultExt}; use std::fmt::Display; use std::fs; +use std::marker::PhantomData; use std::net::IpAddr; use std::path::{Path, PathBuf}; use systemd_derive::{SystemdUnit, SystemdUnitSection}; +lazy_static! { + static ref DEFAULT_ROUTE_IPV4: IpNet = "0.0.0.0/0".parse().unwrap(); + static ref DEFAULT_ROUTE_IPV6: IpNet = "::/0".parse().unwrap(); +} + #[derive(Debug, Default, SystemdUnit)] pub(crate) struct NetworkConfig { r#match: Option, @@ -128,20 +138,42 @@ impl Display for DhcpBool { } } } + impl NetworkConfig { const FILE_EXT: &str = "network"; + fn new_with_name(name: InterfaceName) -> Self { + Self { + r#match: Some(MatchSection { + name: Some(name), + permanent_mac_address: Vec::default(), + }), + ..Default::default() + } + } + + fn new_with_mac_address(mac: MacAddress) -> Self { + Self { + r#match: Some(MatchSection { + name: None, + permanent_mac_address: vec![mac], + }), + ..Default::default() + } + } + /// Write the config to the proper directory with the proper prefix and file extention - pub(crate) fn write_config_file(&self) -> Result<()> { - let cfg_path = self.config_path()?; + pub(crate) fn write_config_file>(&self, config_dir: P) -> Result<()> { + let cfg_path = self.config_path(config_dir)?; fs::write(&cfg_path, self.to_string()).context(error::NetworkDConfigWriteSnafu { what: "network config", path: cfg_path, }) } + /// Build the proper prefixed path for the config file - fn config_path(&self) -> Result { + fn config_path>(&self, config_dir: P) -> Result { let match_section = self .r#match .as_ref() @@ -165,8 +197,471 @@ impl NetworkConfig { }; let filename = format!("{}{}", CONFIG_FILE_PREFIX, device_name); - let mut cfg_path = Path::new(NETWORKD_CONFIG_DIR).join(filename); + let mut cfg_path = Path::new(config_dir.as_ref()).join(filename); cfg_path.set_extension(Self::FILE_EXT); Ok(cfg_path) } + + // The following methods are private and primarily meant for use by the NetworkBuilder. They + // are convenience methods to access the referenced structs (which are `Option`s) since they + // may need to be accessed in multiple places during the builder's construction process. (And + // no one wants to call `get_or_insert_with()` everywhere) + fn match_mut(&mut self) -> &mut MatchSection { + self.r#match.get_or_insert_with(MatchSection::default) + } + + fn link_mut(&mut self) -> &mut LinkSection { + self.link.get_or_insert_with(LinkSection::default) + } + + fn network_mut(&mut self) -> &mut NetworkSection { + self.network.get_or_insert_with(NetworkSection::default) + } + + fn dhcp4_mut(&mut self) -> &mut Dhcp4Section { + self.dhcp4.get_or_insert_with(Dhcp4Section::default) + } + + fn dhcp6_mut(&mut self) -> &mut Dhcp6Section { + self.dhcp6.get_or_insert_with(Dhcp6Section::default) + } +} + +// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= +// +/// The builder for `NetworkConfig`. +// +// Why a builder? Great question. As you can see below, some logic is involved to translate +// config struct fields to a valid NetworkConfig. Since NetworkConfig will be created by multiple +// devices (interfaces, bonds and VLANs to start), it makes sense to centralize that logic to avoid +// duplication/mistakes. Using a builder means type parameters can be used to limit available +// methods based on the device being created. Putting the type parameter on the builder and not +// NetworkConfig avoids proliferating the type parameter everywhere NetworkConfig may be used. +#[derive(Debug)] +pub(crate) struct NetworkBuilder { + network: NetworkConfig, + spooky: PhantomData, +} + +impl NetworkBuilder { + pub(crate) fn build(self) -> NetworkConfig { + self.network + } +} + +impl NetworkBuilder { + /// Create a new .network config for an interface; not meant for an interface that will be a + /// bond worker. See `.new_bond_worker()` for that case. + pub(crate) fn new_interface(id: I) -> Self + where + I: Into, + { + let network = match id.into() { + InterfaceId::Name(n) => NetworkConfig::new_with_name(n), + InterfaceId::MacAddress(m) => NetworkConfig::new_with_mac_address(m), + }; + + Self { + network, + spooky: PhantomData, + } + } +} + +impl NetworkBuilder { + // Create a new .network config for a network bond + pub(crate) fn new_bond(name: InterfaceName) -> Self { + let mut network = NetworkConfig::new_with_name(name); + // Bonds should be brought up without waiting for a carrier + network.network_mut().configure_wo_carrier = Some(true); + + Self { + network, + spooky: PhantomData, + } + } +} + +impl NetworkBuilder { + /// Create a new .network config for an interface meant to be bound to a bond + pub(crate) fn new_bond_worker(name: InterfaceName) -> Self { + let mut network = NetworkConfig::new_with_name(name); + // Disable all address autoconfig for bond workers + network.network_mut().link_local_addressing = Some(DhcpBool::No); + + Self { + network, + spooky: PhantomData, + } + } + + // Add the bond this worker is bound to + pub(crate) fn bound_to_bond(&mut self, bond: InterfaceName) { + self.network.network_mut().bond = Some(bond); + } + + // Make this bond worker the primary + pub(crate) fn primary_bond_worker(&mut self) { + self.network.network_mut().primary_bond_worker = Some(true) + } +} + +impl NetworkBuilder { + // Create a new .network config for a VLAN + pub(crate) fn new_vlan(name: InterfaceName) -> Self { + let mut network = NetworkConfig::new_with_name(name); + // VLANs should be brought up without waiting for a carrier + network.network_mut().configure_wo_carrier = Some(true); + + Self { + network, + spooky: PhantomData, + } + } +} + +// The following methods are meant only for devices not bound to a bond +impl NetworkBuilder +where + T: NotBonded + Device, +{ + /// Add DHCP4 and/or DHCP6 configuration. If neither exist, this is a no-op + /// These options are somewhat intertwined depending on a protocol being optional, etc. + // + // The builder ingests dhcp4/6 options and processes them immediately, rather than storing them + // and processing them during the build() method. This is intentional as DHCP options are only + // valid for devices not bound to a bond (and potentially more in the future). + pub(crate) fn with_dhcp(&mut self, dhcp4: Option, dhcp6: Option) { + match (dhcp4, dhcp6) { + (Some(dhcp4), Some(dhcp6)) => self.with_dhcp_impl(dhcp4, dhcp6), + (Some(dhcp4), None) => self.with_dhcp4(dhcp4), + (None, Some(dhcp6)) => self.with_dhcp6(dhcp6), + (None, None) => (), + } + } + + /// Private helper for adding both DHCP4 and DHCP6 configuration since the options are + /// intertwined + fn with_dhcp_impl(&mut self, dhcp4: Dhcp4ConfigV1, dhcp6: Dhcp6ConfigV1) { + self.network.network_mut().dhcp = + match (Self::dhcp4_enabled(&dhcp4), Self::dhcp6_enabled(&dhcp6)) { + (true, true) => Some(DhcpBool::Yes), + (true, false) => Some(DhcpBool::Ipv4), + (false, true) => Some(DhcpBool::Ipv6), + (false, false) => Some(DhcpBool::No), + }; + + let link = self.network.link_mut(); + match (Self::dhcp4_required(&dhcp4), Self::dhcp6_required(&dhcp6)) { + (true, true) => { + link.required = Some(true); + link.required_family = Some(RequiredFamily::Both); + } + (true, false) => { + link.required = Some(true); + link.required_family = Some(RequiredFamily::Ipv4); + } + (false, true) => { + link.required = Some(true); + link.required_family = Some(RequiredFamily::Ipv6); + } + (false, false) => link.required = Some(false), + } + + if Self::dhcp4_enabled(&dhcp4) { + let dhcp4_s = self.network.dhcp4_mut(); + dhcp4_s.metric = Self::dhcp4_metric(&dhcp4); + // The following ensure DNS comes back with the lease + dhcp4_s.use_dns = Some(true); + dhcp4_s.use_domains = Some(true); + } + + if Self::dhcp6_enabled(&dhcp6) { + let dhcp6_s = self.network.dhcp6_mut(); + // The following ensure DNS comes back with the lease + dhcp6_s.use_dns = Some(true); + dhcp6_s.use_domains = Some(true); + } + } + + /// Private helper for adding DHCP4 config + fn with_dhcp4(&mut self, dhcp4: Dhcp4ConfigV1) { + self.network.network_mut().dhcp = match Self::dhcp4_enabled(&dhcp4) { + true => Some(DhcpBool::Ipv4), + false => Some(DhcpBool::No), + }; + + self.network.link_mut().required = Some(Self::dhcp4_required(&dhcp4)); + + if Self::dhcp4_enabled(&dhcp4) { + let dhcp = self.network.dhcp4_mut(); + dhcp.metric = Self::dhcp4_metric(&dhcp4); + // The following ensure DNS comes back with the lease + dhcp.use_dns = Some(true); + dhcp.use_domains = Some(true); + } + } + + /// Private helper for adding DHCP6 config + fn with_dhcp6(&mut self, dhcp6: Dhcp6ConfigV1) { + self.network.network_mut().dhcp = match Self::dhcp6_enabled(&dhcp6) { + true => Some(DhcpBool::Ipv6), + false => Some(DhcpBool::No), + }; + + self.network.link_mut().required = Some(Self::dhcp6_required(&dhcp6)); + + if Self::dhcp6_enabled(&dhcp6) { + let dhcp = self.network.dhcp6_mut(); + // The following ensure DNS comes back with the lease + dhcp.use_dns = Some(true); + dhcp.use_domains = Some(true); + } + } + + /// Add static address configuration + pub(crate) fn with_static_config(&mut self, static_config: StaticConfigV1) { + self.network + .network_mut() + .addresses + .append(&mut static_config.addresses.into_iter().collect()) + } + + /// Add multiple static routes + pub(crate) fn with_routes(&mut self, routes: Vec) { + for route in routes { + self.with_route(route) + } + } + + /// Add a single static route + pub(crate) fn with_route(&mut self, route: RouteV1) { + let destination = match route.to { + RouteTo::DefaultRoute => match route.via.or(route.from) { + Some(IpAddr::V4(_)) => Some(*DEFAULT_ROUTE_IPV4), + Some(IpAddr::V6(_)) => Some(*DEFAULT_ROUTE_IPV6), + // If no gateway or from is given, assume the ipv4 default + None => Some(*DEFAULT_ROUTE_IPV4), + }, + RouteTo::Ip(ip) => Some(ip), + }; + + // Each route gets its own RouteSection + let route_section = RouteSection { + destination, + gateway: route.via, + metric: route.route_metric, + preferred_source: route.from, + }; + + self.network.route.push(route_section) + } + + /// Add multiple VLANs + pub(crate) fn with_vlans(&mut self, vlans: Vec) { + for vlan in vlans { + self.with_vlan(vlan) + } + } + + /// Add a single VLAN + pub(crate) fn with_vlan(&mut self, vlan: InterfaceName) { + self.network.network_mut().vlan.push(vlan) + } + + // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= + // The following helper methods on the `DhcpXConfig` structs exist to conveniently parse out + // the required information. Since this is the only place we parse these values, and will only + // ever be parsing them from the latest version of DHCP structs, it doesn't really make sense + // to implement them on the DhcpXConfig structs themselves. (Not to mention all the repeated + // dead code that would exist as new versions were built) + fn dhcp4_enabled(dhcp4: &Dhcp4ConfigV1) -> bool { + match dhcp4 { + Dhcp4ConfigV1::DhcpEnabled(b) => *b, + Dhcp4ConfigV1::WithOptions(o) => o.enabled, + } + } + + fn dhcp4_required(dhcp4: &Dhcp4ConfigV1) -> bool { + match dhcp4 { + // Assume enabled == required + Dhcp4ConfigV1::DhcpEnabled(enabled) => *enabled, + // If "optional" isn't set, assume DHCP is required. + // If optional==true, DHCP is NOT required + Dhcp4ConfigV1::WithOptions(o) => o.optional.map_or(true, |b| !b), + } + } + + fn dhcp4_metric(dhcp4: &Dhcp4ConfigV1) -> Option { + match dhcp4 { + Dhcp4ConfigV1::DhcpEnabled(_) => None, + Dhcp4ConfigV1::WithOptions(o) => o.route_metric, + } + } + + fn dhcp6_enabled(dhcp6: &Dhcp6ConfigV1) -> bool { + match dhcp6 { + Dhcp6ConfigV1::DhcpEnabled(b) => *b, + Dhcp6ConfigV1::WithOptions(o) => o.enabled, + } + } + + fn dhcp6_required(dhcp6: &Dhcp6ConfigV1) -> bool { + match dhcp6 { + // Assume enabled == required + Dhcp6ConfigV1::DhcpEnabled(enabled) => *enabled, + // If "optional" isn't set, assume DHCP is required + // If optional==true, DHCP is NOT required + Dhcp6ConfigV1::WithOptions(o) => o.optional.map_or(true, |b| !b), + } + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::networkd::config::tests::{test_data, TestDevices, BUILDER_DATA}; + use crate::networkd::devices::{NetworkDBond, NetworkDInterface, NetworkDVlan}; + + const FAKE_TEST_DIR: &str = "testdir"; + + fn network_path(name: String) -> PathBuf { + test_data() + .join("network") + .join(format!("{}.network", name)) + } + + fn network_from_interface(iface: NetworkDInterface) -> NetworkConfig { + let mut network = NetworkBuilder::new_interface(iface.name); + network.with_dhcp(iface.dhcp4, iface.dhcp6); + iface.static4.map(|s| network.with_static_config(s)); + iface.static6.map(|s| network.with_static_config(s)); + iface.routes.map(|r| network.with_routes(r)); + network.build() + } + + fn network_from_vlan(vlan: NetworkDVlan) -> NetworkConfig { + let mut network = NetworkBuilder::new_vlan(vlan.name); + network.with_dhcp(vlan.dhcp4, vlan.dhcp6); + vlan.static4.map(|s| network.with_static_config(s)); + vlan.static6.map(|s| network.with_static_config(s)); + vlan.routes.map(|r| network.with_routes(r)); + network.build() + } + + fn network_from_bond(bond: NetworkDBond) -> NetworkConfig { + let mut network = NetworkBuilder::new_bond(bond.name.clone()); + network.with_dhcp(bond.dhcp4, bond.dhcp6); + bond.static4.map(|s| network.with_static_config(s)); + bond.static6.map(|s| network.with_static_config(s)); + bond.routes.map(|r| network.with_routes(r)); + network.build() + } + + #[test] + fn interface_network_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + + for interface in devices.interface { + let expected_filename = network_path(interface.name.to_string().replace(':', "")); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = network_from_interface(interface).to_string(); + + assert_eq!(expected, got) + } + } + + #[test] + fn vlan_network_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + + for vlan in devices.vlan { + let expected_filename = network_path(vlan.name.to_string()); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = network_from_vlan(vlan).to_string(); + + assert_eq!(expected, got) + } + } + + #[test] + fn bond_network_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + + for bond in devices.bond { + let expected_filename = network_path(bond.name.to_string()); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = network_from_bond(bond).to_string(); + + assert_eq!(expected, got) + } + } + + #[test] + fn bond_worker_network_builder() { + let devices = toml::from_str::(BUILDER_DATA).unwrap(); + + // Validate the first interface gets the Primary bit added and the second doesn't. Worker + // config is identical so validating the first set keeps us from creating a bunch of + // redundant identical files + let bond = devices.bond.first().unwrap(); + for (index, worker) in bond.interfaces.iter().enumerate() { + let mut network = NetworkBuilder::new_bond_worker(worker.clone()); + network.bound_to_bond(bond.name.clone()); + if index == 0 { + network.primary_bond_worker(); + } + + let expected_filename = network_path(worker.to_string()); + let expected = fs::read_to_string(expected_filename).unwrap(); + let got = network.build().to_string(); + assert_eq!(expected, got) + } + } + + #[test] + fn config_path_empty() { + let n = NetworkConfig::default(); + assert!(n.config_path(FAKE_TEST_DIR).is_err()) + } + + #[test] + fn config_path_name() { + let filename = format!("{}foo", CONFIG_FILE_PREFIX); + let mut expected = Path::new(FAKE_TEST_DIR).join(filename); + expected.set_extension(NetworkConfig::FILE_EXT); + + let network = NetworkConfig::new_with_name(InterfaceName::try_from("foo").unwrap()); + + assert_eq!(expected, network.config_path(FAKE_TEST_DIR).unwrap()) + } + + #[test] + fn config_path_mac() { + let filename = format!("{}f874a4d53264", CONFIG_FILE_PREFIX); + let mut expected = Path::new(FAKE_TEST_DIR).join(filename); + expected.set_extension(NetworkConfig::FILE_EXT); + + let network = NetworkConfig::new_with_mac_address( + MacAddress::try_from("f8:74:a4:d5:32:64".to_string()).unwrap(), + ); + + assert_eq!(expected, network.config_path(FAKE_TEST_DIR).unwrap()) + } + + #[test] + fn config_path_name_before_mac() { + let filename = format!("{}foo", CONFIG_FILE_PREFIX); + let mut expected = Path::new(FAKE_TEST_DIR).join(filename); + expected.set_extension(NetworkConfig::FILE_EXT); + + let network = NetworkConfig { + r#match: Some(MatchSection { + name: Some(InterfaceName::try_from("foo").unwrap()), + permanent_mac_address: vec![MacAddress::try_from("f8:74:a4:d5:32:64").unwrap()], + }), + ..Default::default() + }; + + assert_eq!(expected, network.config_path(FAKE_TEST_DIR).unwrap()) + } } diff --git a/sources/api/netdog/test_data/networkd/network/bond0.network b/sources/api/netdog/test_data/networkd/network/bond0.network new file mode 100644 index 00000000000..ceb8d87c3e3 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/bond0.network @@ -0,0 +1,10 @@ +[Match] +Name=bond0 +[Link] +RequiredForOnline=true +[Network] +ConfigureWithoutCarrier=true +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/bond1.network b/sources/api/netdog/test_data/networkd/network/bond1.network new file mode 100644 index 00000000000..e38b9e8bdbd --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/bond1.network @@ -0,0 +1,14 @@ +[Match] +Name=bond1 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=both +[Network] +ConfigureWithoutCarrier=true +DHCP=yes +[DHCPv4] +UseDNS=true +UseDomains=true +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/bond2.network b/sources/api/netdog/test_data/networkd/network/bond2.network new file mode 100644 index 00000000000..c5230ab4f64 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/bond2.network @@ -0,0 +1,10 @@ +[Match] +Name=bond2 +[Link] +RequiredForOnline=true +[Network] +ConfigureWithoutCarrier=true +DHCP=ipv6 +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/c874a4d53265.network b/sources/api/netdog/test_data/networkd/network/c874a4d53265.network new file mode 100644 index 00000000000..a2d374a8032 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/c874a4d53265.network @@ -0,0 +1,8 @@ +[Match] +PermanentMACAddress=c8:74:a4:d5:32:65 +[Network] +Address=192.168.14.5/24 +[Route] +Destination=10.10.10.0/24 +Gateway=192.168.14.25 +PreferredSource=192.168.14.5 diff --git a/sources/api/netdog/test_data/networkd/network/eno1.network b/sources/api/netdog/test_data/networkd/network/eno1.network new file mode 100644 index 00000000000..6cdeb1d4298 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno1.network @@ -0,0 +1,9 @@ +[Match] +Name=eno1 +[Link] +RequiredForOnline=true +[Network] +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno10.network b/sources/api/netdog/test_data/networkd/network/eno10.network new file mode 100644 index 00000000000..9a27abd85b4 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno10.network @@ -0,0 +1,9 @@ +[Match] +Name=eno10 +[Link] +RequiredForOnline=false +[Network] +DHCP=ipv6 +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno11.network b/sources/api/netdog/test_data/networkd/network/eno11.network new file mode 100644 index 00000000000..0adbc4c51cd --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno11.network @@ -0,0 +1,4 @@ +[Match] +Name=eno11 +[Network] +Address=192.168.14.2/24 diff --git a/sources/api/netdog/test_data/networkd/network/eno12.network b/sources/api/netdog/test_data/networkd/network/eno12.network new file mode 100644 index 00000000000..7551ed86aab --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno12.network @@ -0,0 +1,7 @@ +[Match] +Name=eno12 +[Network] +Address=10.0.0.9/24 +[Route] +Destination=10.10.10.0/24 +Gateway=10.0.0.1 diff --git a/sources/api/netdog/test_data/networkd/network/eno13.network b/sources/api/netdog/test_data/networkd/network/eno13.network new file mode 100644 index 00000000000..52a972d6a3d --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno13.network @@ -0,0 +1,10 @@ +[Match] +Name=eno13 +[Network] +Address=192.168.14.2/24 +[Route] +Destination=9.9.0.0/16 +Gateway=192.168.1.1 +[Route] +Destination=10.10.10.0/24 +Gateway=192.168.1.3 diff --git a/sources/api/netdog/test_data/networkd/network/eno14.network b/sources/api/netdog/test_data/networkd/network/eno14.network new file mode 100644 index 00000000000..c4c2c574f51 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno14.network @@ -0,0 +1,13 @@ +[Match] +Name=eno14 +[Network] +Address=10.0.0.10/24 +Address=11.0.0.11/24 +[Route] +Destination=0.0.0.0/0 +Gateway=10.0.0.1 +Metric=100 +[Route] +Destination=0.0.0.0/0 +Gateway=11.0.0.1 +Metric=200 diff --git a/sources/api/netdog/test_data/networkd/network/eno15.network b/sources/api/netdog/test_data/networkd/network/eno15.network new file mode 100644 index 00000000000..2b80bd8079b --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno15.network @@ -0,0 +1,4 @@ +[Match] +Name=eno15 +[Network] +Address=2001:cafe:face:beef::dead:dead/64 diff --git a/sources/api/netdog/test_data/networkd/network/eno16.network b/sources/api/netdog/test_data/networkd/network/eno16.network new file mode 100644 index 00000000000..0dd36de6431 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno16.network @@ -0,0 +1,7 @@ +[Match] +Name=eno16 +[Network] +Address=2001:dead:beef::2/64 +[Route] +Destination=::/0 +Gateway=2001:beef:beef::1 diff --git a/sources/api/netdog/test_data/networkd/network/eno17.network b/sources/api/netdog/test_data/networkd/network/eno17.network new file mode 100644 index 00000000000..1a472f975b1 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno17.network @@ -0,0 +1,13 @@ +[Match] +Name=eno17 +[Network] +Address=3001:f00f:f00f::2/64 +Address=3001:f00f:f00f::3/64 +[Route] +Destination=3001:dead:beef::2/64 +Gateway=3001:beef:beef::1 +Metric=100 +[Route] +Destination=3001:dead:feed::2/64 +Gateway=3001:beef:beef::2 +Metric=200 diff --git a/sources/api/netdog/test_data/networkd/network/eno18.network b/sources/api/netdog/test_data/networkd/network/eno18.network new file mode 100644 index 00000000000..9cd29abd564 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno18.network @@ -0,0 +1,11 @@ +[Match] +Name=eno18 +[Link] +RequiredForOnline=true +[Network] +Address=10.0.0.10/24 +Address=11.0.0.11/24 +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno19.network b/sources/api/netdog/test_data/networkd/network/eno19.network new file mode 100644 index 00000000000..d244f9ccb1e --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno19.network @@ -0,0 +1,11 @@ +[Match] +Name=eno19 +[Link] +RequiredForOnline=true +[Network] +Address=3001:f00f:f00f::2/64 +Address=3001:f00f:f00f::3/64 +DHCP=ipv6 +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno2.network b/sources/api/netdog/test_data/networkd/network/eno2.network new file mode 100644 index 00000000000..7c4ab6ed8d6 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno2.network @@ -0,0 +1,9 @@ +[Match] +Name=eno2 +[Link] +RequiredForOnline=true +[Network] +DHCP=ipv6 +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno20.network b/sources/api/netdog/test_data/networkd/network/eno20.network new file mode 100644 index 00000000000..c65f46a7757 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno20.network @@ -0,0 +1,8 @@ +[Match] +Name=eno20 +[Network] +Address=192.168.14.5/24 +[Route] +Destination=10.10.10.0/24 +Gateway=192.168.14.25 +PreferredSource=192.168.14.5 diff --git a/sources/api/netdog/test_data/networkd/network/eno21.network b/sources/api/netdog/test_data/networkd/network/eno21.network new file mode 100644 index 00000000000..a7a553966b8 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno21.network @@ -0,0 +1,8 @@ +[Match] +Name=eno21 +[Network] +Address=2001:dead:beef::2/64 +[Route] +Destination=3001:dead:beef::2/64 +Gateway=2001:beef:beef::1 +PreferredSource=2001:dead:beef::2 diff --git a/sources/api/netdog/test_data/networkd/network/eno3.network b/sources/api/netdog/test_data/networkd/network/eno3.network new file mode 100644 index 00000000000..47650b70521 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno3.network @@ -0,0 +1,10 @@ +[Match] +Name=eno3 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=ipv4 +[Network] +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno4.network b/sources/api/netdog/test_data/networkd/network/eno4.network new file mode 100644 index 00000000000..0e9724e286b --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno4.network @@ -0,0 +1,10 @@ +[Match] +Name=eno4 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=ipv6 +[Network] +DHCP=ipv6 +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno5.network b/sources/api/netdog/test_data/networkd/network/eno5.network new file mode 100644 index 00000000000..58b9c3cd10f --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno5.network @@ -0,0 +1,13 @@ +[Match] +Name=eno5 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=both +[Network] +DHCP=yes +[DHCPv4] +UseDNS=true +UseDomains=true +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno51.network b/sources/api/netdog/test_data/networkd/network/eno51.network new file mode 100644 index 00000000000..617043020f7 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno51.network @@ -0,0 +1,6 @@ +[Match] +Name=eno51 +[Network] +Bond=bond0 +LinkLocalAddressing=no +PrimarySlave=true diff --git a/sources/api/netdog/test_data/networkd/network/eno52.network b/sources/api/netdog/test_data/networkd/network/eno52.network new file mode 100644 index 00000000000..7f8368ef4cd --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno52.network @@ -0,0 +1,5 @@ +[Match] +Name=eno52 +[Network] +Bond=bond0 +LinkLocalAddressing=no diff --git a/sources/api/netdog/test_data/networkd/network/eno6.network b/sources/api/netdog/test_data/networkd/network/eno6.network new file mode 100644 index 00000000000..e8231547a9f --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno6.network @@ -0,0 +1,11 @@ +[Match] +Name=eno6 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=ipv4 +[Network] +DHCP=ipv4 +[DHCPv4] +RouteMetric=100 +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno7.network b/sources/api/netdog/test_data/networkd/network/eno7.network new file mode 100644 index 00000000000..0416906d9e6 --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno7.network @@ -0,0 +1,13 @@ +[Match] +Name=eno7 +[Link] +RequiredForOnline=true +RequiredFamilyForOnline=ipv4 +[Network] +DHCP=yes +[DHCPv4] +UseDNS=true +UseDomains=true +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno8.network b/sources/api/netdog/test_data/networkd/network/eno8.network new file mode 100644 index 00000000000..c1102ff021e --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno8.network @@ -0,0 +1,12 @@ +[Match] +Name=eno8 +[Link] +RequiredForOnline=false +[Network] +DHCP=yes +[DHCPv4] +UseDNS=true +UseDomains=true +[DHCPv6] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/eno9.network b/sources/api/netdog/test_data/networkd/network/eno9.network new file mode 100644 index 00000000000..2abcb7187fb --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/eno9.network @@ -0,0 +1,9 @@ +[Match] +Name=eno9 +[Link] +RequiredForOnline=false +[Network] +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/f874a4d53264.network b/sources/api/netdog/test_data/networkd/network/f874a4d53264.network new file mode 100644 index 00000000000..9b885cc8eab --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/f874a4d53264.network @@ -0,0 +1,9 @@ +[Match] +PermanentMACAddress=f8:74:a4:d5:32:64 +[Link] +RequiredForOnline=true +[Network] +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true diff --git a/sources/api/netdog/test_data/networkd/network/mystaticvlan.network b/sources/api/netdog/test_data/networkd/network/mystaticvlan.network new file mode 100644 index 00000000000..13aad7d84fb --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/mystaticvlan.network @@ -0,0 +1,5 @@ +[Match] +Name=mystaticvlan +[Network] +Address=192.168.1.100/24 +ConfigureWithoutCarrier=true diff --git a/sources/api/netdog/test_data/networkd/network/myvlan.network b/sources/api/netdog/test_data/networkd/network/myvlan.network new file mode 100644 index 00000000000..cd1fef1220e --- /dev/null +++ b/sources/api/netdog/test_data/networkd/network/myvlan.network @@ -0,0 +1,10 @@ +[Match] +Name=myvlan +[Link] +RequiredForOnline=true +[Network] +ConfigureWithoutCarrier=true +DHCP=ipv4 +[DHCPv4] +UseDNS=true +UseDomains=true