diff --git a/sources/api/netdog/src/networkd/config/mod.rs b/sources/api/netdog/src/networkd/config/mod.rs index 51add2b845f..805a9c53767 100644 --- a/sources/api/netdog/src/networkd/config/mod.rs +++ b/sources/api/netdog/src/networkd/config/mod.rs @@ -3,10 +3,78 @@ mod netdev; mod network; -use netdev::NetDevConfig; -use network::NetworkConfig; +use super::Result; +pub(crate) use netdev::{NetDevBuilder, NetDevConfig}; +pub(crate) use network::{NetworkBuilder, 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(NETWORKD_CONFIG_DIR), + 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 40f8b85feeb..09dff5645cd 100644 --- a/sources/api/netdog/src/networkd/config/netdev.rs +++ b/sources/api/netdog/src/networkd/config/netdev.rs @@ -1,7 +1,15 @@ +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}; #[derive(Debug, Default, SystemdUnit)] @@ -100,12 +108,234 @@ 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"), } } } + +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, 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", + path: cfg_path, + }) + } + + /// Build the proper prefixed path for the config file + 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(), + }, + )?; + + let filename = format!("{}{}", CONFIG_FILE_PREFIX, device_name); + 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/src/networkd/config/network.rs b/sources/api/netdog/src/networkd/config/network.rs index a331fe58017..fcc4bb713ca 100644 --- a/sources/api/netdog/src/networkd/config/network.rs +++ b/sources/api/netdog/src/networkd/config/network.rs @@ -1,9 +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, @@ -43,6 +58,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")] @@ -121,3 +138,530 @@ 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, 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, config_dir: P) -> 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(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/src/networkd/devices/bond.rs b/sources/api/netdog/src/networkd/devices/bond.rs new file mode 100644 index 00000000000..1144717444a --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/bond.rs @@ -0,0 +1,24 @@ +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, + pub(crate) dhcp4: Option, + pub(crate) dhcp6: Option, + pub(crate) static4: Option, + 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 new file mode 100644 index 00000000000..b56315ecfbc --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/interface.rs @@ -0,0 +1,18 @@ +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, + 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..74805191a90 --- /dev/null +++ b/sources/api/netdog/src/networkd/devices/vlan.rs @@ -0,0 +1,21 @@ +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, + 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 1bf79dfa1a0..6223b2f35b1 100644 --- a/sources/api/netdog/src/networkd/mod.rs +++ b/sources/api/netdog/src/networkd/mod.rs @@ -1 +1,23 @@ mod config; +mod devices; + +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; 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 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