From f47caf769b0741479b35a37c3473ff947c5ec291 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Sun, 13 Mar 2022 04:30:49 +0000 Subject: [PATCH] nixos/networkd: configure /etc/systemd/networkd.conf The networkd.conf file controls a variety of interesting settings which don't seem to be configurable at the moment, including adding names to route tables (for networkd only, although this commit also exports them into iproute2 for convenience's sake), and the speed metering functionality built into networkd. Importantly, however, this also allows disabling the systemd functionality where it likes to delete all the routes and routing rules that haven't been configured through networkd whenever something causes it to perform a reconfiguration. --- nixos/modules/system/boot/networkd.nix | 136 ++++++++++++++++++++++++- nixos/tests/systemd-networkd.nix | 10 ++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index ac1e4ef34b46..3efdd32c4133 100644 --- 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" [ @@ -867,6 +897,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 { @@ -1515,6 +1583,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} @@ -1596,6 +1697,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 @@ -1728,6 +1843,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 = {}; @@ -1772,7 +1893,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 = { @@ -1791,6 +1914,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 index 7faeae3704ec..6c423f4140b1 100644 --- a/nixos/tests/systemd-networkd.nix +++ b/nixos/tests/systemd-networkd.nix @@ -8,6 +8,9 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: { 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, config, privk, pubk, peerId, nodeId, ...}: { 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" = { @@ -87,6 +91,12 @@ testScript = '' node1.wait_for_unit("systemd-networkd-wait-online.service") 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 # ================================