From dd392d7c7694c762812f84b4d0a3ba8157ac8a73 Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Wed, 29 Jun 2022 01:01:59 -0400 Subject: systemd-initrd: networkd --- nixos/modules/services/hardware/udev.nix | 11 --- nixos/modules/system/boot/networkd.nix | 132 ++++++++++++++++++++++----- nixos/modules/system/boot/systemd/initrd.nix | 9 -- nixos/tests/all-tests.nix | 1 + nixos/tests/predictable-interface-names.nix | 39 ++++++-- nixos/tests/systemd-initrd-networkd.nix | 45 +++++++++ 6 files changed, 184 insertions(+), 53 deletions(-) create mode 100644 nixos/tests/systemd-initrd-networkd.nix diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix index d9526133241..95c2a4fc5c3 100644 --- a/nixos/modules/services/hardware/udev.nix +++ b/nixos/modules/services/hardware/udev.nix @@ -16,16 +16,6 @@ let ''; - # networkd link files are used early by udev to set up interfaces early. - # This must be done in stage 1 to avoid race conditions between udev and - # network daemons. - # TODO move this into the initrd-network module when it exists - initrdLinkUnits = pkgs.runCommand "initrd-link-units" {} '' - mkdir -p $out - ln -s ${udev}/lib/systemd/network/*.link $out/ - ${lib.concatMapStringsSep "\n" (file: "ln -s ${file} $out/") (lib.mapAttrsToList (n: v: "${v.unit}/${n}") (lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units))} - ''; - extraUdevRules = pkgs.writeTextFile { name = "extra-udev-rules"; text = cfg.extraRules; @@ -398,7 +388,6 @@ in systemd = config.boot.initrd.systemd.package; binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ]; }; - "/etc/systemd/network".source = initrdLinkUnits; }; # Insert initrd rules boot.initrd.services.udev.packages = [ diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 05a667a09ef..e325a33a9e3 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -6,8 +6,6 @@ with lib; let - cfg = config.systemd.network; - check = { global = { @@ -2941,14 +2939,12 @@ let + def.extraConfig; }; - unitFiles = listToAttrs (map (name: { - name = "systemd/network/${name}"; + mkUnitFiles = prefix: cfg: listToAttrs (map (name: { + name = "${prefix}systemd/network/${name}"; value.source = "${cfg.units.${name}.unit}/${name}"; }) (attrNames cfg.units)); -in -{ - options = { + commonOptions = { systemd.network.enable = mkOption { default = false; @@ -3051,12 +3047,11 @@ in }; - config = mkMerge [ + commonConfig = config: let cfg = config.systemd.network; in mkMerge [ # .link units are honored by udev, no matter if systemd-networkd is enabled or not. { systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links; - environment.etc = unitFiles; systemd.network.wait-online.extraArgs = [ "--timeout=${toString cfg.wait-online.timeout}" ] @@ -3066,14 +3061,6 @@ in (mkIf config.systemd.network.enable { - users.users.systemd-network.group = "systemd-network"; - - systemd.additionalUpstreamSystemUnits = [ - "systemd-networkd-wait-online.service" - "systemd-networkd.service" - "systemd-networkd.socket" - ]; - systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; @@ -3082,14 +3069,6 @@ in # networkd. systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ]; - systemd.services.systemd-networkd = { - wantedBy = [ "multi-user.target" ]; - aliases = [ "dbus-org.freedesktop.network1.service" ]; - restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [ - config.environment.etc."systemd/networkd.conf".source - ]; - }; - systemd.services.systemd-networkd-wait-online = { inherit (cfg.wait-online) enable; wantedBy = [ "network-online.target" ]; @@ -3111,8 +3090,37 @@ in }; }; + }) + ]; + + stage2Config = let + cfg = config.systemd.network; + unitFiles = mkUnitFiles "" cfg; + in mkMerge [ + (commonConfig config) + + { environment.etc = unitFiles; } + + (mkIf config.systemd.network.enable { + + users.users.systemd-network.group = "systemd-network"; + + systemd.additionalUpstreamSystemUnits = [ + "systemd-networkd-wait-online.service" + "systemd-networkd.service" + "systemd-networkd.socket" + ]; + environment.etc."systemd/networkd.conf" = renderConfig cfg.config; + systemd.services.systemd-networkd = { + wantedBy = [ "multi-user.target" ]; + restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [ + config.environment.etc."systemd/networkd.conf".source + ]; + aliases = [ "dbus-org.freedesktop.network1.service" ]; + }; + networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) { enable = mkDefault true; rttablesExtraConfig = '' @@ -3123,6 +3131,80 @@ in }; services.resolved.enable = mkDefault true; + + }) + ]; + + stage1Config = let + cfg = config.boot.initrd.systemd.network; + in mkMerge [ + (commonConfig config.boot.initrd) + + { + systemd.network.enable = mkDefault config.boot.initrd.network.enable; + systemd.contents = mkUnitFiles "/etc/" cfg; + + # Networkd link files are used early by udev to set up interfaces early. + # This must be done in stage 1 to avoid race conditions between udev and + # network daemons. + systemd.network.units = lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units; + systemd.storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/network/99-default.link"]; + } + + (mkIf cfg.enable { + + systemd.package = pkgs.systemdStage1Network; + + systemd.additionalUpstreamUnits = [ + "systemd-networkd-wait-online.service" + "systemd-networkd.service" + "systemd-networkd.socket" + "systemd-network-generator.service" + "network-online.target" + "network-pre.target" + "network.target" + "nss-lookup.target" + "nss-user-lookup.target" + "remote-fs-pre.target" + "remote-fs.target" + ]; + systemd.users.systemd-network = {}; + systemd.groups.systemd-network = {}; + + systemd.contents."/etc/systemd/networkd.conf" = renderConfig cfg.config; + + systemd.services.systemd-networkd.wantedBy = [ "initrd.target" ]; + systemd.services.systemd-network-generator.wantedBy = [ "sysinit.target" ]; + + systemd.storePaths = [ + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd" + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd-wait-online" + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-network-generator" + ]; + kernelModules = [ "af_packet" ]; + + }) + ]; + +in + +{ + options = commonOptions // { + boot.initrd = commonOptions; + }; + + config = mkMerge [ + stage2Config + (mkIf config.boot.initrd.systemd.enable { + assertions = [{ + assertion = config.boot.initrd.network.udhcpc.extraArgs == []; + message = '' + boot.initrd.network.udhcpc.extraArgs is not supported when + boot.initrd.systemd.enable is enabled + ''; + }]; + + boot.initrd = stage1Config; }) ]; } diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index e11ab5c824d..d623eddf699 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -72,15 +72,6 @@ let "systemd-tmpfiles-setup.service" "timers.target" "umount.target" - - # TODO: Networking - # "network-online.target" - # "network-pre.target" - # "network.target" - # "nss-lookup.target" - # "nss-user-lookup.target" - # "remote-fs-pre.target" - # "remote-fs.target" ] ++ cfg.additionalUpstreamUnits; upstreamWants = [ diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 0783f3bf68e..00a637b460f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -677,6 +677,7 @@ in { systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {}; systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {}; systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {}; + systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {}; systemd-journal = handleTest ./systemd-journal.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {}; diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix index 684df9c3924..42183625c7c 100644 --- a/nixos/tests/predictable-interface-names.nix +++ b/nixos/tests/predictable-interface-names.nix @@ -8,25 +8,48 @@ let testCombinations = pkgs.lib.cartesianProductOfSets { predictable = [true false]; withNetworkd = [true false]; + systemdStage1 = [true false]; }; -in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: { +in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd, systemdStage1 }: { name = pkgs.lib.optionalString (!predictable) "un" + "predictable" - + pkgs.lib.optionalString withNetworkd "Networkd"; + + pkgs.lib.optionalString withNetworkd "Networkd" + + pkgs.lib.optionalString systemdStage1 "SystemdStage1"; value = makeTest { - name = "${pkgs.lib.optionalString (!predictable) "un"}predictableInterfaceNames${pkgs.lib.optionalString withNetworkd "-with-networkd"}"; + name = pkgs.lib.optionalString (!predictable) "un" + "predictableInterfaceNames" + + pkgs.lib.optionalString withNetworkd "-with-networkd" + + pkgs.lib.optionalString systemdStage1 "-systemd-stage-1"; meta = {}; - nodes.machine = { lib, ... }: { + nodes.machine = { lib, ... }: let + script = '' + ip link + if ${lib.optionalString predictable "!"} ip link show eth0; then + echo Success + else + exit 1 + fi + ''; + in { networking.usePredictableInterfaceNames = lib.mkForce predictable; networking.useNetworkd = withNetworkd; networking.dhcpcd.enable = !withNetworkd; networking.useDHCP = !withNetworkd; # Check if predictable interface names are working in stage-1 - boot.initrd.postDeviceCommands = '' - ip link - ip link show eth0 ${if predictable then "&&" else "||"} exit 1 - ''; + boot.initrd.postDeviceCommands = script; + + boot.initrd.systemd = lib.mkIf systemdStage1 { + enable = true; + initrdBin = [ pkgs.iproute2 ]; + services.systemd-udev-settle.wantedBy = ["initrd.target"]; + services.check-interfaces = { + requiredBy = ["initrd.target"]; + after = ["systemd-udev-settle.service"]; + serviceConfig.Type = "oneshot"; + path = [ pkgs.iproute2 ]; + inherit script; + }; + }; }; testScript = '' diff --git a/nixos/tests/systemd-initrd-networkd.nix b/nixos/tests/systemd-initrd-networkd.nix new file mode 100644 index 00000000000..872a8cd64b2 --- /dev/null +++ b/nixos/tests/systemd-initrd-networkd.nix @@ -0,0 +1,45 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "systemd-initrd-network"; + meta.maintainers = [ lib.maintainers.elvishjerricco ]; + + nodes = { + basic = { ... }: { + boot.initrd.network.enable = true; + + boot.initrd.systemd = { + enable = true; + network.networks."99-eth0" = { + matchConfig.Name = "eth0"; + DHCP = "yes"; + }; + network.wait-online.timeout = 10; + # Drop the boot into emergency mode if we timeout + targets.network-online.requiredBy = [ "initrd.target" ]; + services.systemd-networkd-wait-online.requiredBy = + [ "network-online.target" ]; + + initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ]; + services.check = { + requiredBy = [ "initrd.target" ]; + before = [ "initrd.target" ]; + after = [ "network-online.target" ]; + serviceConfig.Type = "oneshot"; + path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ]; + script = '' + ip addr | grep 10.0.2.15 || exit 1 + ping -c1 10.0.2.2 || exit 1 + ''; + }; + }; + }; + }; + + testScript = '' + start_all() + basic.wait_for_unit("multi-user.target") + # Make sure the systemd-network user was set correctly in initrd + basic.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]") + basic.succeed("ip addr show >&2") + basic.succeed("ip route show >&2") + ''; +}) -- cgit 1.4.1