diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -10,6 +10,36 @@ let check = { + global = { + sectionNetwork = checkUnitConfig "Network" [ + (assertOnlyFields [ + "SpeedMeter" + "SpeedMeterIntervalSec" + "ManageForeignRoutingPolicyRules" + "ManageForeignRoutes" + "RouteTable" + ]) + (assertValueOneOf "SpeedMeter" boolValues) + (assertInt "SpeedMeterIntervalSec") + (assertValueOneOf "ManageForeignRoutingPolicyRules" boolValues) + (assertValueOneOf "ManageForeignRoutes" boolValues) + ]; + + sectionDHCPv4 = checkUnitConfig "DHCPv4" [ + (assertOnlyFields [ + "DUIDType" + "DUIDRawData" + ]) + ]; + + sectionDHCPv6 = checkUnitConfig "DHCPv6" [ + (assertOnlyFields [ + "DUIDType" + "DUIDRawData" + ]) + ]; + }; + link = { sectionLink = checkUnitConfig "Link" [ @@ -871,6 +901,44 @@ let }; }; + networkdOptions = { + networkConfig = mkOption { + default = {}; + example = { SpeedMeter = true; ManageForeignRoutingPolicyRules = false; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionNetwork; + description = '' + Each attribute in this set specifies an option in the + [Network] section of the networkd config. + See networkd.conf + 5 for details. + ''; + }; + + dhcpV4Config = mkOption { + default = {}; + example = { DUIDType = "vendor"; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv4; + description = '' + Each attribute in this set specifies an option in the + [DHCPv4] section of the networkd config. + See networkd.conf + 5 for details. + ''; + }; + + dhcpV6Config = mkOption { + default = {}; + example = { DUIDType = "vendor"; }; + type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv6; + description = '' + Each attribute in this set specifies an option in the + [DHCPv6] section of the networkd config. + See networkd.conf + 5 for details. + ''; + }; + }; + linkOptions = commonNetworkOptions // { # overwrite enable option from above enable = mkOption { @@ -1519,6 +1587,39 @@ let }; }; + networkdConfig = { config, ... }: { + options = { + routeTables = mkOption { + default = {}; + example = { foo = 27; }; + type = with types; attrsOf int; + description = '' + Defines route table names as an attrset of name to number. + See networkd.conf + 5 for details. + ''; + }; + + addRouteTablesToIPRoute2 = mkOption { + default = true; + example = false; + type = types.bool; + description = '' + If true and routeTables are set, then the specified route tables + will also be installed into /etc/iproute2/rt_tables. + ''; + }; + }; + + config = { + networkConfig = optionalAttrs (config.routeTables != { }) { + RouteTable = mapAttrsToList + (name: number: "${name}:${toString number}") + config.routeTables; + }; + }; + }; + commonMatchText = def: optionalString (def.matchConfig != { }) '' [Match] ${attrsToSection def.matchConfig} @@ -1600,6 +1701,20 @@ let + def.extraConfig; }; + renderConfig = def: + { text = '' + [Network] + '' + + attrsToSection def.networkConfig + + optionalString (def.dhcpV4Config != { }) '' + [DHCPv4] + ${attrsToSection def.dhcpV4Config} + '' + + optionalString (def.dhcpV6Config != { }) '' + [DHCPv6] + ${attrsToSection def.dhcpV6Config} + ''; }; + networkToUnit = name: def: { inherit (def) enable; text = commonMatchText def @@ -1700,7 +1815,8 @@ let unitFiles = listToAttrs (map (name: { name = "systemd/network/${name}"; value.source = "${cfg.units.${name}.unit}/${name}"; - }) (attrNames cfg.units)); + }) (attrNames cfg.units)) // { + }; in { @@ -1732,6 +1848,12 @@ in description = "Definition of systemd networks."; }; + systemd.network.config = mkOption { + default = {}; + type = with types; submodule [ { options = networkdOptions; } networkdConfig ]; + description = "Definition of global systemd network config."; + }; + systemd.network.units = mkOption { description = "Definition of networkd units."; default = {}; @@ -1776,7 +1898,9 @@ in systemd.services.systemd-networkd = { wantedBy = [ "multi-user.target" ]; aliases = [ "dbus-org.freedesktop.network1.service" ]; - restartTriggers = map (x: x.source) (attrValues unitFiles); + restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [ + config.environment.etc."systemd/networkd.conf".source + ]; }; systemd.services.systemd-networkd-wait-online = { @@ -1795,6 +1919,17 @@ in }; }; + environment.etc."systemd/networkd.conf" = renderConfig cfg.config; + + networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) { + enable = mkDefault true; + rttablesExtraConfig = '' + + # Extra tables defined in NixOS systemd.networkd.config.routeTables. + ${concatStringsSep "\n" (mapAttrsToList (name: number: "${toString number} ${name}") cfg.config.routeTables)} + ''; + }; + services.resolved.enable = mkDefault true; }) ]; diff --git a/nixos/tests/systemd-networkd.nix b/nixos/tests/systemd-networkd.nix --- a/nixos/tests/systemd-networkd.nix +++ b/nixos/tests/systemd-networkd.nix @@ -8,6 +8,9 @@ let generateNodeConf = { lib, pkgs, conf environment.systemPackages = with pkgs; [ wireguard-tools ]; systemd.network = { enable = true; + config = { + routeTables.custom = 23; + }; netdevs = { "90-wg0" = { netdevConfig = { Kind = "wireguard"; Name = "wg0"; }; @@ -39,6 +42,7 @@ let generateNodeConf = { lib, pkgs, conf address = [ "10.0.0.${nodeId}/32" ]; routes = [ { routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; }; } + { routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; Table = "custom"; }; } ]; }; "30-eth1" = { @@ -88,6 +92,12 @@ testScript = '' node2.wait_for_unit("systemd-networkd-wait-online.service") # ================================ + # Networkd Config + # ================================ + node1.succeed("grep RouteTable=custom:23 /etc/systemd/networkd.conf") + node1.succeed("sudo ip route show table custom | grep '10.0.0.0/24 via 10.0.0.1 dev wg0 proto static'") + + # ================================ # Wireguard # ================================ node1.succeed("ping -c 5 10.0.0.2")