From f891584adb9cf5526ab0c6160b963f92381dc3f1 Mon Sep 17 00:00:00 2001 From: "Mikael Knutsson (mikn)" Date: Mon, 6 Oct 2025 09:01:30 +0200 Subject: [PATCH] feat: add support for MTU in Netdog --- sources/netdog/src/net_config/devices/bond.rs | 1 + .../src/net_config/devices/interface.rs | 1 + sources/netdog/src/net_config/devices/vlan.rs | 1 + .../netdog/src/net_config/test_macros/mod.rs | 3 ++ .../netdog/src/net_config/test_macros/mtu.rs | 40 +++++++++++++++++++ sources/netdog/src/net_config/v3.rs | 3 +- sources/netdog/src/networkd/config/network.rs | 7 ++++ sources/netdog/src/networkd/conversions.rs | 5 +++ sources/netdog/src/networkd/devices/bond.rs | 6 +++ .../netdog/src/networkd/devices/interface.rs | 7 ++++ sources/netdog/src/networkd/devices/vlan.rs | 6 +++ sources/netdog/src/networkd/mod.rs | 1 + .../test_data/net_config/mtu/net_config.toml | 38 ++++++++++++++++++ 13 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 sources/netdog/src/net_config/test_macros/mtu.rs create mode 100644 sources/netdog/test_data/net_config/mtu/net_config.toml diff --git a/sources/netdog/src/net_config/devices/bond.rs b/sources/netdog/src/net_config/devices/bond.rs index 5045abcea..11d784f38 100644 --- a/sources/netdog/src/net_config/devices/bond.rs +++ b/sources/netdog/src/net_config/devices/bond.rs @@ -26,6 +26,7 @@ pub(crate) struct NetBondV1 { #[serde(rename = "monitoring")] pub(crate) monitoring_config: BondMonitoringConfigV1, pub(crate) interfaces: Vec, + pub(crate) mtu: Option, } // Single variant enum only used to direct deserialization. If the kind is not "Bond" or "bond", diff --git a/sources/netdog/src/net_config/devices/interface.rs b/sources/netdog/src/net_config/devices/interface.rs index 9f7c8c8af..7bd7a15e2 100644 --- a/sources/netdog/src/net_config/devices/interface.rs +++ b/sources/netdog/src/net_config/devices/interface.rs @@ -15,6 +15,7 @@ pub(crate) struct NetInterfaceV2 { pub(crate) static6: Option, #[serde(rename = "route")] pub(crate) routes: Option>, + pub(crate) mtu: Option, } impl Validate for NetInterfaceV2 { diff --git a/sources/netdog/src/net_config/devices/vlan.rs b/sources/netdog/src/net_config/devices/vlan.rs index 4b2e4a7f0..997e39193 100644 --- a/sources/netdog/src/net_config/devices/vlan.rs +++ b/sources/netdog/src/net_config/devices/vlan.rs @@ -20,6 +20,7 @@ pub(crate) struct NetVlanV1 { _kind: VlanKind, pub(crate) device: InterfaceName, pub(crate) id: VlanId, + pub(crate) mtu: Option, } // Single variant enum only used to direct deserialization. If the kind is not "VLAN", "Vlan", or diff --git a/sources/netdog/src/net_config/test_macros/mod.rs b/sources/netdog/src/net_config/test_macros/mod.rs index 8cf01f138..376ca90ec 100644 --- a/sources/netdog/src/net_config/test_macros/mod.rs +++ b/sources/netdog/src/net_config/test_macros/mod.rs @@ -18,6 +18,8 @@ pub(super) mod bonding; #[cfg(test)] pub(super) mod dhcp; #[cfg(test)] +pub(super) mod mtu; +#[cfg(test)] pub(super) mod static_address; #[cfg(test)] pub(super) mod vlan; @@ -25,6 +27,7 @@ pub(super) mod vlan; pub(super) use basic::basic_tests; pub(super) use bonding::bonding_tests; pub(super) use dhcp::dhcp_tests; +pub(super) use mtu::mtu_tests; pub(super) use static_address::static_address_tests; pub(super) use vlan::vlan_tests; diff --git a/sources/netdog/src/net_config/test_macros/mtu.rs b/sources/netdog/src/net_config/test_macros/mtu.rs new file mode 100644 index 000000000..932b96085 --- /dev/null +++ b/sources/netdog/src/net_config/test_macros/mtu.rs @@ -0,0 +1,40 @@ +/// The mtu_tests macro contains MTU-related net config tests. It accepts a version number, +/// and creates a module for that version containing all applicable MTU tests. +macro_rules! mtu_tests { + ($version:expr) => { + mod mtu { + use $crate::net_config::deserialize_config; + use $crate::net_config::test_macros::gen_boilerplate; + + gen_boilerplate!($version, "mtu"); + + // Only test MTU for version 3 and later + #[test] + fn mtu_configuration() { + if VERSION < 3 { + // MTU is only supported in version 3 and later + return; + } + + let config_str = render_config_template(net_config().join("net_config.toml")); + let net_config = deserialize_config(&config_str); + assert!(net_config.is_ok(), "Failed to deserialize config: {:?}", net_config.err()); + + let net_config = net_config.unwrap(); + + // Check that the config has interfaces + assert!(net_config.has_interfaces()); + + // Convert to networkd config to ensure MTU values are passed through + let networkd_config = net_config.as_networkd_config(); + assert!(networkd_config.is_ok(), "Failed to create networkd config: {:?}", networkd_config.err()); + + // The test passes if we can successfully deserialize and create networkd config + // with MTU values. The actual MTU values will be written to the .network files + // when the networkd config is written to disk. + } + } + }; +} + +pub(crate) use mtu_tests; \ No newline at end of file diff --git a/sources/netdog/src/net_config/v3.rs b/sources/netdog/src/net_config/v3.rs index 703830aaa..78a613b79 100644 --- a/sources/netdog/src/net_config/v3.rs +++ b/sources/netdog/src/net_config/v3.rs @@ -108,7 +108,7 @@ impl Validate for NetConfigV3 { #[cfg(test)] mod tests { use crate::net_config::test_macros::{ - basic_tests, bonding_tests, dhcp_tests, static_address_tests, vlan_tests, + basic_tests, bonding_tests, dhcp_tests, mtu_tests, static_address_tests, vlan_tests, }; basic_tests!(3); @@ -116,4 +116,5 @@ mod tests { static_address_tests!(3); vlan_tests!(3); bonding_tests!(3); + mtu_tests!(3); } diff --git a/sources/netdog/src/networkd/config/network.rs b/sources/netdog/src/networkd/config/network.rs index 99716a7a4..105a4c5bc 100644 --- a/sources/netdog/src/networkd/config/network.rs +++ b/sources/netdog/src/networkd/config/network.rs @@ -48,6 +48,8 @@ struct LinkSection { required: Option, #[systemd(entry = "RequiredFamilyForOnline")] required_family: Option, + #[systemd(entry = "MTUBytes")] + mtu_bytes: Option, } #[derive(Debug, Default, SystemdUnitSection)] @@ -601,6 +603,11 @@ where self.network.route.push(route_section) } + /// Set the MTU for the interface + pub(crate) fn with_mtu(&mut self, mtu: u32) { + self.network.link_mut().mtu_bytes = Some(mtu); + } + // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= // 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 diff --git a/sources/netdog/src/networkd/conversions.rs b/sources/netdog/src/networkd/conversions.rs index 0adc77383..ef9b56da7 100644 --- a/sources/netdog/src/networkd/conversions.rs +++ b/sources/netdog/src/networkd/conversions.rs @@ -33,6 +33,7 @@ impl TryFrom<(InterfaceName, NetInterfaceV1)> for NetworkDDevice { static4: None, static6: None, routes: None, + mtu: None, // v1 doesn't support MTU })) } } @@ -49,6 +50,7 @@ impl TryFrom<(InterfaceName, NetInterfaceV2)> for NetworkDDevice { static4: config.static4, static6: config.static6, routes: config.routes, + mtu: config.mtu, })) } } @@ -65,6 +67,7 @@ impl TryFrom<(InterfaceId, NetInterfaceV2)> for NetworkDDevice { static4: config.static4, static6: config.static6, routes: config.routes, + mtu: config.mtu, })) } } @@ -94,6 +97,7 @@ impl TryFrom<(InterfaceId, NetBondV1)> for NetworkDDevice { min_links: config.min_links, monitoring_config: config.monitoring_config, interfaces: config.interfaces, + mtu: config.mtu, })) } } @@ -121,6 +125,7 @@ impl TryFrom<(InterfaceId, NetVlanV1)> for NetworkDDevice { routes: config.routes, device: config.device, id: config.id, + mtu: config.mtu, })) } } diff --git a/sources/netdog/src/networkd/devices/bond.rs b/sources/netdog/src/networkd/devices/bond.rs index ab51a9e53..46f8494c3 100644 --- a/sources/netdog/src/networkd/devices/bond.rs +++ b/sources/netdog/src/networkd/devices/bond.rs @@ -24,6 +24,7 @@ pub(crate) struct NetworkDBond { pub(crate) min_links: Option, pub(crate) monitoring_config: BondMonitoringConfigV1, pub(crate) interfaces: Vec, + pub(crate) mtu: Option, } impl NetDevFileCreator for NetworkDBond { @@ -42,6 +43,7 @@ impl NetDevFileCreator for NetworkDBond { min_links, monitoring_config, interfaces: _, // Used in .network files, not here + mtu: _, // MTU is configured in .network files, not .netdev } = self; let mut netdev = NetDevBuilder::new_bond(name.clone()); @@ -75,6 +77,7 @@ impl NetworkFileCreator for NetworkDBond { min_links: _, monitoring_config: _, interfaces, + mtu, } = self; let mut network = NetworkBuilder::new_bond(name.clone()); @@ -82,6 +85,9 @@ impl NetworkFileCreator for NetworkDBond { maybe_add_some!(network, with_static_config, static4); maybe_add_some!(network, with_static_config, static6); maybe_add_some!(network, with_routes, routes); + if let Some(m) = mtu { + network.with_mtu(*m); + } network.with_bind_carrier(interfaces.clone()); diff --git a/sources/netdog/src/networkd/devices/interface.rs b/sources/netdog/src/networkd/devices/interface.rs index 7d47d8c7c..cbc3e86d4 100644 --- a/sources/netdog/src/networkd/devices/interface.rs +++ b/sources/netdog/src/networkd/devices/interface.rs @@ -18,6 +18,7 @@ pub(crate) struct NetworkDInterface { pub(crate) static4: Option, pub(crate) static6: Option, pub(crate) routes: Option>, + pub(crate) mtu: Option, } impl NetworkDInterface { @@ -32,12 +33,14 @@ impl NetworkDInterface { static4, static6, routes, + mtu, } = self; dhcp4.is_none() && dhcp6.is_none() && static4.is_none() && static6.is_none() && routes.is_none() + && mtu.is_none() } } @@ -53,6 +56,7 @@ impl NetworkFileCreator for NetworkDInterface { static4, static6, routes, + mtu, } = self; // Attach VLANs to this interface if configured with a name. @@ -77,6 +81,9 @@ impl NetworkFileCreator for NetworkDInterface { maybe_add_some!(network, with_static_config, static4); maybe_add_some!(network, with_static_config, static6); maybe_add_some!(network, with_routes, routes); + if let Some(m) = mtu { + network.with_mtu(*m); + } if let Some(vlans) = attached_vlans { network.with_vlans(vlans.to_vec()) } diff --git a/sources/netdog/src/networkd/devices/vlan.rs b/sources/netdog/src/networkd/devices/vlan.rs index 0b2050d40..413257562 100644 --- a/sources/netdog/src/networkd/devices/vlan.rs +++ b/sources/netdog/src/networkd/devices/vlan.rs @@ -24,6 +24,7 @@ pub(crate) struct NetworkDVlan { // entry for this VLAN pub(crate) device: InterfaceName, pub(crate) id: VlanId, + pub(crate) mtu: Option, } impl NetDevFileCreator for NetworkDVlan { @@ -40,6 +41,7 @@ impl NetDevFileCreator for NetworkDVlan { routes: _, device: _, // Device isn't used in .netdev files id, + mtu: _, // MTU is configured in .network files, not .netdev } = self; let mut netdev = NetDevBuilder::new_vlan(name.clone()); @@ -63,6 +65,7 @@ impl NetworkFileCreator for NetworkDVlan { routes, device: _, // device and id aren't used in .network files id: _, + mtu, } = self; let mut network = NetworkBuilder::new_vlan(name.clone()); @@ -70,6 +73,9 @@ impl NetworkFileCreator for NetworkDVlan { maybe_add_some!(network, with_static_config, static4); maybe_add_some!(network, with_static_config, static6); maybe_add_some!(network, with_routes, routes); + if let Some(m) = mtu { + network.with_mtu(*m); + } vec![network.build()] } diff --git a/sources/netdog/src/networkd/mod.rs b/sources/netdog/src/networkd/mod.rs index af3b16290..1ddb725f4 100644 --- a/sources/netdog/src/networkd/mod.rs +++ b/sources/netdog/src/networkd/mod.rs @@ -56,6 +56,7 @@ impl NetworkDConfig { static4: None, static6: None, routes: None, + mtu: None, })) } } diff --git a/sources/netdog/test_data/net_config/mtu/net_config.toml b/sources/netdog/test_data/net_config/mtu/net_config.toml new file mode 100644 index 000000000..2f91d0db2 --- /dev/null +++ b/sources/netdog/test_data/net_config/mtu/net_config.toml @@ -0,0 +1,38 @@ +version = {{version}} + +# Interface with jumbo frames +[eno1] +dhcp4 = true +mtu = 9000 +primary = true + +# Interface with standard MTU +[eno2] +dhcp6 = true +mtu = 1500 + +# Bond with custom MTU +[bond0] +kind = "bond" +mode = "active-backup" +mtu = 1500 +interfaces = ["eth0", "eth1"] +dhcp4 = true + +[bond0.monitoring] +miimon-frequency-ms = 100 +miimon-updelay-ms = 200 +miimon-downdelay-ms = 200 + +# VLAN with custom MTU +[vlan100] +kind = "vlan" +device = "eno1" +id = 100 +mtu = 1500 +dhcp4 = true + +# Interface without MTU specified (will use default) +[eno3] +dhcp4 = true +dhcp6 = true \ No newline at end of file