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.

+4
nixos/lib/systemd-lib.nix
···
optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
"Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
assertMinimum = name: min: group: attr:
optional (attr ? ${name} && attr.${name} < min)
"Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";
···
optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
"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:
optional (attr ? ${name} && attr.${name} < min)
"Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";
+3
nixos/lib/systemd-network-units.nix
···
commonMatchText def + ''
[NetDev]
${attrsToSection def.netdevConfig}
'' + optionalString (def.vlanConfig != { }) ''
[VLAN]
${attrsToSection def.vlanConfig}
···
commonMatchText def + ''
[NetDev]
${attrsToSection def.netdevConfig}
+
'' + optionalString (def.bridgeConfig != { }) ''
+
[Bridge]
+
${attrsToSection def.bridgeConfig}
'' + optionalString (def.vlanConfig != { }) ''
[VLAN]
${attrsToSection def.vlanConfig}
+42
nixos/modules/system/boot/networkd.nix
···
(assertNetdevMacAddress "MACAddress")
];
sectionVLAN = checkUnitConfig "VLAN" [
(assertOnlyFields [
"Id"
···
description = ''
Each attribute in this set specifies an option in the
`[Netdev]` section of the unit. See
{manpage}`systemd.netdev(5)` for details.
'';
};
···
(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" [
(assertOnlyFields [
"Id"
···
description = ''
Each attribute in this set specifies an option in the
`[Netdev]` section of the unit. See
+
{manpage}`systemd.netdev(5)` for details.
+
'';
+
};
+
+
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.
'';
};
+1
nixos/tests/all-tests.nix
···
systemd-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix;
systemd-machinectl = handleTest ./systemd-machinectl.nix {};
systemd-networkd = handleTest ./systemd-networkd.nix {};
systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.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-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix;
systemd-machinectl = handleTest ./systemd-machinectl.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-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
+103
nixos/tests/systemd-networkd-bridge.nix
···
···
+
/* 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")
+
'';
+
})