diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index a77dbc609f46..6dfbe66fc647 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -67,7 +67,12 @@ let
(assertOnlyFields [
"PrivateKeyFile" "ListenPort" "FwMark"
])
- (assertRange "FwMark" 1 4294967295)
+ # The following check won't work on nix <= 2.2
+ # see https://github.com/NixOS/nix/pull/2378
+ #
+ # Add this again when we'll have drop the
+ # nix < 2.2 support.
+ # (assertRange "FwMark" 1 4294967295)
];
# NOTE The PresharedKey directive is missing on purpose here, please
@@ -181,7 +186,12 @@ let
(assertOnlyFields [
"InterfaceId" "Independent"
])
- (assertRange "InterfaceId" 1 4294967295)
+ # The following check won't work on nix <= 2.2
+ # see https://github.com/NixOS/nix/pull/2378
+ #
+ # Add this again when we'll have drop the
+ # nix < 2.2 support.
+ # (assertRange "InterfaceId" 1 4294967295)
(assertValueOneOf "Independent" boolValues)
];
@@ -235,6 +245,26 @@ let
(assertValueOneOf "AutoJoin" boolValues)
];
+ checkRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [
+ (assertOnlyFields [
+ "TypeOfService" "From" "To" "FirewallMark" "Table" "Priority"
+ "IncomingInterface" "OutgoingInterface" "SourcePort" "DestinationPort"
+ "IPProtocol" "InvertRule" "Family"
+ ])
+ (assertRange "TypeOfService" 0 255)
+ # The following check won't work on nix <= 2.2
+ # see https://github.com/NixOS/nix/pull/2378
+ #
+ # Add this again when we'll have drop the
+ # nix < 2.2 support.
+ # (assertRange "FirewallMark" 1 4294967295)
+ (assertInt "Priority")
+ (assertPort "SourcePort")
+ (assertPort "DestinationPort")
+ (assertValueOneOf "InvertRule" boolValues)
+ (assertValueOneOf "Family" ["ipv4" "ipv6" "both"])
+ ];
+
checkRoute = checkUnitConfig "Route" [
(assertOnlyFields [
"Gateway" "GatewayOnLink" "Destination" "Source" "Metric"
@@ -535,6 +565,22 @@ let
};
};
+ routingPolicyRulesOptions = {
+ options = {
+ routingPolicyRuleConfig = mkOption {
+ default = { };
+ example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;};
+ type = types.addCheck (types.attrsOf unitOption) checkRoutingPolicyRule;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [RoutingPolicyRule] section of the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+ };
+ };
+
routeOptions = {
options = {
routeConfig = mkOption {
@@ -772,6 +818,16 @@ let
'';
};
+ routingPolicyRules = mkOption {
+ default = [ ];
+ type = with types; listOf (submodule routingPolicyRulesOptions);
+ description = ''
+ A list of routing policy rules sections to be added to the unit. See
+ systemd.network
+ 5 for details.
+ '';
+ };
+
routes = mkOption {
default = [ ];
type = with types; listOf (submodule routeOptions);
@@ -928,6 +984,11 @@ let
[Route]
${attrsToSection x.routeConfig}
+ '')}
+ ${flip concatMapStrings def.routingPolicyRules (x: ''
+ [RoutingPolicyRule]
+ ${attrsToSection x.routingPolicyRuleConfig}
+
'')}
${def.extraConfig}
'';
diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix
index fd1a5b9f62c5..a33602915867 100644
--- a/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixos/modules/system/boot/systemd-lib.nix
@@ -59,6 +59,11 @@ in rec {
optional (attr ? ${name} && ! isMacAddress attr.${name})
"Systemd ${group} field `${name}' must be a valid mac address.";
+ isPort = i: i >= 0 && i <= 65535;
+
+ assertPort = name: group: attr:
+ optional (attr ? ${name} && ! isPort attr.${name})
+ "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number.";
assertValueOneOf = name: values: group: attr:
optional (attr ? ${name} && !elem attr.${name} values)
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index d67ded83edb8..7dd0f23df658 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -285,7 +285,7 @@ in
systemd-confinement = handleTest ./systemd-confinement.nix {};
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
- systemd-networkd-wireguard = handleTest ./systemd-networkd-wireguard.nix {};
+ systemd-networkd = handleTest ./systemd-networkd.nix {};
systemd-nspawn = handleTest ./systemd-nspawn.nix {};
pdns-recursor = handleTest ./pdns-recursor.nix {};
taskserver = handleTest ./taskserver.nix {};
diff --git a/nixos/tests/systemd-networkd-wireguard.nix b/nixos/tests/systemd-networkd.nix
similarity index 65%
rename from nixos/tests/systemd-networkd-wireguard.nix
rename to nixos/tests/systemd-networkd.nix
index be5c0da981d2..319e5e94eceb 100644
--- a/nixos/tests/systemd-networkd-wireguard.nix
+++ b/nixos/tests/systemd-networkd.nix
@@ -41,15 +41,25 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: {
{ routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; }; }
];
};
- "90-eth1" = {
+ "30-eth1" = {
matchConfig = { Name = "eth1"; };
- address = [ "192.168.1.${nodeId}/24" ];
+ address = [
+ "192.168.1.${nodeId}/24"
+ "fe80::${nodeId}/64"
+ ];
+ routingPolicyRules = [
+ { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };}
+ { routingPolicyRuleConfig = { Table = 20; OutgoingInterface = "eth1"; };}
+ { routingPolicyRuleConfig = { Table = 30; From = "192.168.1.1"; To = "192.168.1.2"; SourcePort = 666 ; DestinationPort = 667; };}
+ { routingPolicyRuleConfig = { Table = 40; IPProtocol = "tcp"; InvertRule = true; };}
+ { routingPolicyRuleConfig = { Table = 50; IncomingInterface = "eth1"; Family = "ipv4"; };}
+ ];
};
};
};
};
in import ./make-test-python.nix ({pkgs, ... }: {
- name = "networkd-wireguard";
+ name = "networkd";
meta = with pkgs.stdenv.lib.maintainers; {
maintainers = [ ninjatrappeur ];
};
@@ -76,9 +86,28 @@ testScript = ''
start_all()
node1.wait_for_unit("systemd-networkd-wait-online.service")
node2.wait_for_unit("systemd-networkd-wait-online.service")
+
+ # ================================
+ # Wireguard
+ # ================================
node1.succeed("ping -c 5 10.0.0.2")
node2.succeed("ping -c 5 10.0.0.1")
# Is the fwmark set?
node2.succeed("wg | grep -q 42")
+
+ # ================================
+ # Routing Policies
+ # ================================
+ # Testing all the routingPolicyRuleConfig members:
+ # Table + IncomingInterface
+ node1.succeed("sudo ip rule | grep 'from all iif eth1 lookup 10'")
+ # OutgoingInterface
+ node1.succeed("sudo ip rule | grep 'from all oif eth1 lookup 20'")
+ # From + To + SourcePort + DestinationPort
+ node1.succeed(
+ "sudo ip rule | grep 'from 192.168.1.1 to 192.168.1.2 sport 666 dport 667 lookup 30'"
+ )
+ # IPProtocol + InvertRule
+ node1.succeed("sudo ip rule | grep 'not from all ipproto tcp lookup 40'")
'';
})