nixos/networkd: add [Bridge] section to netdev conf

This setting was missing from netdev.

This commit additionally adds a test using the new
section, ensuring that STP can be enabled.
This commit is contained in:
Astrid Yu 2024-04-11 00:56:18 -07:00
parent ed817f4699
commit ed854ed0e3
5 changed files with 153 additions and 0 deletions

View file

@ -148,6 +148,10 @@ in rec {
optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name})) optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
"Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]"; "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
assertRangeOrOneOf = name: min: max: values: group: attr:
optional (attr ? ${name} && !((min <= attr.${name} && max >= attr.${name}) || elem attr.${name} values))
"Systemd ${group} field `${name}' is not a value in range [${toString min},${toString max}], or one of ${toString values}";
assertMinimum = name: min: group: attr: assertMinimum = name: min: group: attr:
optional (attr ? ${name} && attr.${name} < min) optional (attr ? ${name} && attr.${name} < min)
"Systemd ${group} field `${name}' must be greater than or equal to ${toString min}"; "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";

View file

@ -25,6 +25,9 @@ in {
commonMatchText def + '' commonMatchText def + ''
[NetDev] [NetDev]
${attrsToSection def.netdevConfig} ${attrsToSection def.netdevConfig}
'' + optionalString (def.bridgeConfig != { }) ''
[Bridge]
${attrsToSection def.bridgeConfig}
'' + optionalString (def.vlanConfig != { }) '' '' + optionalString (def.vlanConfig != { }) ''
[VLAN] [VLAN]
${attrsToSection def.vlanConfig} ${attrsToSection def.vlanConfig}

View file

@ -186,6 +186,37 @@ let
(assertNetdevMacAddress "MACAddress") (assertNetdevMacAddress "MACAddress")
]; ];
sectionBridge = checkUnitConfig "Bridge" [
(assertOnlyFields [
"HelloTimeSec"
"MaxAgeSec"
"ForwardDelaySec"
"AgeingTimeSec"
"Priority"
"GroupForwardMask"
"DefaultPVID"
"MulticastQuerier"
"MulticastSnooping"
"VLANFiltering"
"VLANProtocol"
"STP"
"MulticastIGMPVersion"
])
(assertInt "HelloTimeSec")
(assertInt "MaxAgeSec")
(assertInt "ForwardDelaySec")
(assertInt "AgeingTimeSec")
(assertRange "Priority" 0 65535)
(assertRange "GroupForwardMask" 0 65535)
(assertRangeOrOneOf "DefaultPVID" 0 4094 ["none"])
(assertValueOneOf "MulticastQuerier" boolValues)
(assertValueOneOf "MulticastSnooping" boolValues)
(assertValueOneOf "VLANFiltering" boolValues)
(assertValueOneOf "VLANProtocol" ["802.1q" "802.ad"])
(assertValueOneOf "STP" boolValues)
(assertValueOneOf "MulticastIGMPVersion" [2 3])
];
sectionVLAN = checkUnitConfig "VLAN" [ sectionVLAN = checkUnitConfig "VLAN" [
(assertOnlyFields [ (assertOnlyFields [
"Id" "Id"
@ -1635,6 +1666,17 @@ let
''; '';
}; };
bridgeConfig = mkOption {
default = {};
example = { STP = true; };
type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBridge;
description = ''
Each attribute in this set specifies an option in the
`[Bridge]` section of the unit. See
{manpage}`systemd.netdev(5)` for details.
'';
};
vlanConfig = mkOption { vlanConfig = mkOption {
default = {}; default = {};
example = { Id = 4; }; example = { Id = 4; };

View file

@ -899,6 +899,7 @@ in {
systemd-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix; systemd-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix;
systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {};
systemd-networkd = handleTest ./systemd-networkd.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {};
systemd-networkd-bridge = handleTest ./systemd-networkd-bridge.nix {};
systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {}; systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {}; systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {}; systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};

View file

@ -0,0 +1,103 @@
/* This test ensures that we can configure spanning-tree protocol
across bridges using systemd-networkd.
Test topology:
1 2 3
node1 --- sw1 --- sw2 --- node2
\ /
4 \ / 5
sw3
|
6 |
|
node3
where switches 1, 2, and 3 bridge their links and use STP,
and each link is labeled with the VLAN we are assigning it in
virtualisation.vlans.
*/
with builtins;
let
commonConf = {
systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
networking.useNetworkd = true;
networking.useDHCP = false;
networking.firewall.enable = false;
};
generateNodeConf = { octet, vlan }:
{ lib, pkgs, config, ... }: {
imports = [ common/user-account.nix commonConf ];
virtualisation.vlans = [ vlan ];
systemd.network = {
enable = true;
networks = {
"30-eth" = {
matchConfig.Name = "eth1";
address = [ "10.0.0.${toString octet}/24" ];
};
};
};
};
generateSwitchConf = vlans:
{ lib, pkgs, config, ... }: {
imports = [ common/user-account.nix commonConf ];
virtualisation.vlans = vlans;
systemd.network = {
enable = true;
netdevs = {
"40-br0" = {
netdevConfig = {
Kind = "bridge";
Name = "br0";
};
bridgeConfig.STP = "yes";
};
};
networks = {
"30-eth" = {
matchConfig.Name = "eth*";
networkConfig.Bridge = "br0";
};
"40-br0" = { matchConfig.Name = "br0"; };
};
};
};
in import ./make-test-python.nix ({ pkgs, ... }: {
name = "networkd";
meta = with pkgs.lib.maintainers; { maintainers = [ picnoir ]; };
nodes = {
node1 = generateNodeConf {
octet = 1;
vlan = 1;
};
node2 = generateNodeConf {
octet = 2;
vlan = 3;
};
node3 = generateNodeConf {
octet = 3;
vlan = 6;
};
sw1 = generateSwitchConf [ 1 2 4 ];
sw2 = generateSwitchConf [ 2 3 5 ];
sw3 = generateSwitchConf [ 4 5 6 ];
};
testScript = ''
network_nodes = [node1, node2, node3]
network_switches = [sw1, sw2, sw3]
start_all()
for n in network_nodes + network_switches:
n.wait_for_unit("systemd-networkd-wait-online.service")
node1.succeed("ping 10.0.0.2 -w 10 -c 1")
node1.succeed("ping 10.0.0.3 -w 10 -c 1")
node2.succeed("ping 10.0.0.1 -w 10 -c 1")
node2.succeed("ping 10.0.0.3 -w 10 -c 1")
node3.succeed("ping 10.0.0.1 -w 10 -c 1")
node3.succeed("ping 10.0.0.2 -w 10 -c 1")
'';
})