about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorWill Fancher <elvishjerricco@gmail.com>2023-03-06 13:46:17 -0500
committerWill Fancher <elvishjerricco@gmail.com>2023-04-17 16:41:36 -0400
commit3cb9534df64937f3425dbc0f63749cc83276791a (patch)
tree63ac9b4d14e3801f975046adced18042edfae64d /nixos
parent85982346510230f7bfe1aeabfa2f9382c8192ad8 (diff)
systemd-initrd: Flush networkd
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/system/boot/initrd-network.nix6
-rw-r--r--nixos/modules/system/boot/networkd.nix33
-rw-r--r--nixos/tests/systemd-initrd-networkd.nix34
3 files changed, 71 insertions, 2 deletions
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index a1017c3e24204..e8bbf1d040329 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -67,11 +67,15 @@ in
 
     boot.initrd.network.flushBeforeStage2 = mkOption {
       type = types.bool;
-      default = true;
+      default = !config.boot.initrd.systemd.enable;
+      defaultText = "!config.boot.initrd.systemd.enable";
       description = lib.mdDoc ''
         Whether to clear the configuration of the interfaces that were set up in
         the initrd right before stage 2 takes over. Stage 2 will do the regular network
         configuration based on the NixOS networking options.
+
+        The default is false when systemd is enabled in initrd,
+        because the systemd-networkd documentation suggests it.
       '';
     };
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index d2a8a28c4e695..bd2f1cc4374a7 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -3186,6 +3186,39 @@ let
       ];
       kernelModules = [ "af_packet" ];
 
+      systemd.services.nixos-flush-networkd = mkIf config.boot.initrd.network.flushBeforeStage2 {
+        description = "Flush Network Configuration";
+        wantedBy = ["initrd.target"];
+        after = ["systemd-networkd.service" "dbus.socket" "dbus.service"];
+        before = ["shutdown.target" "initrd-switch-root.target"];
+        conflicts = ["shutdown.target" "initrd-switch-root.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          # This service does nothing when starting, but brings down
+          # interfaces when switching root. This is the easiest way to
+          # ensure proper ordering while stopping. See systemd.unit(5)
+          # section on Before= and After=. The important part is that
+          # we are stopped before units we need, like dbus.service,
+          # and that we are stopped before starting units like
+          # initrd-switch-root.target
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "/bin/true";
+        };
+        # systemd-networkd doesn't bring down interfaces on its own
+        # when it exits (see: systemd-networkd(8)), so we have to do
+        # it ourselves. The networkctl command doesn't have a way to
+        # bring all interfaces down, so we have to iterate over the
+        # list and filter out unmanaged interfaces to bring them down
+        # individually.
+        preStop = ''
+          networkctl list --full --no-legend | while read _idx link _type _operational setup _; do
+            [ "$setup" = unmanaged ] && continue
+            networkctl down "$link"
+          done
+        '';
+      };
+
     })
   ];
 
diff --git a/nixos/tests/systemd-initrd-networkd.nix b/nixos/tests/systemd-initrd-networkd.nix
index 1656f5d5e1898..00ecbec5613c4 100644
--- a/nixos/tests/systemd-initrd-networkd.nix
+++ b/nixos/tests/systemd-initrd-networkd.nix
@@ -2,7 +2,23 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "systemd-initrd-network";
   meta.maintainers = [ lib.maintainers.elvishjerricco ];
 
-  nodes = {
+  nodes = let
+    mkFlushTest = flush: script: { ... }: {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network = {
+        enable = true;
+        flushBeforeStage2 = flush;
+      };
+      systemd.services.check-flush = {
+        requiredBy = ["multi-user.target"];
+        before = ["network-pre.target" "multi-user.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+        inherit script;
+      };
+    };
+  in {
     basic = { ... }: {
       boot.initrd.network.enable = true;
 
@@ -29,11 +45,27 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           };
       };
     };
+
+    doFlush = mkFlushTest true ''
+      if ip addr | grep 10.0.2.15; then
+        echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+        exit 1
+      fi
+    '';
+
+    dontFlush = mkFlushTest false ''
+      if ! (ip addr | grep 10.0.2.15); then
+        echo "Network configuration didn't survive switch-root"
+        exit 1
+      fi
+    '';
   };
 
   testScript = ''
     start_all()
     basic.wait_for_unit("multi-user.target")
+    doFlush.wait_for_unit("multi-user.target")
+    dontFlush.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")