summary refs log tree commit diff
diff options
context:
space:
mode:
authorrnhmjoj <rnhmjoj@inventati.org>2021-06-20 21:20:37 +0200
committerrnhmjoj <rnhmjoj@inventati.org>2021-08-12 00:31:06 +0200
commit99e8af51b28f9e978e8137a6c4ca16d817c64d40 (patch)
tree963ce486e7ab72f855813c98174af383f3c1001d
parentdc54724bd66bacbc0b050b316e9aed6bcec2e43d (diff)
downloadnixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar.gz
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar.bz2
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar.lz
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar.xz
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.tar.zst
nixpkgs-99e8af51b28f9e978e8137a6c4ca16d817c64d40.zip
nixos/wireless: use udev to wait for interfaces
I may have finally found a clean solution to the issues[1][2][3] with
the automatic discovery of wireless network interfaces.

[1]: https://github.com/NixOS/nixpkgs/issues/101963
[2]: https://github.com/NixOS/nixpkgs/issues/23196
[3]: https://github.com/NixOS/nixpkgs/pull/125917#issuecomment-856000426

Currently the start script fails right away if no interface is available
by the time it's running, possibly leaving the system without network.
This happens when running a little early in the boot. A solution is to
instead wait for at least one interface to appear before scanning the
/sys/class/net/ directory. This is done here by listening for the right
udev events (from the net/wlan subsystem) using the `udevadm monitor`
command and grep to match its output.

This methods guarantees the availability of at least one interface to
wpa_supplicant, but won't add additional interfaces once it has started.
However, if the current interface is lost, say unplugged, the service is
automatically stopped and will be restarted as soon as a one (not
necessarily the same) is detected. It would be possible make this fully
dynamic by running another service that continously listen for udev
events and manages the main wpa_supplicant daemon, but this is probably
overkill.

I tested the following cases:

  - one interface, starting at boot, w/o predictable naming scheme
  - two interfaces, starting at boot (intel wireless and a usb adapter),
    w/o predictable naming scheme
  - one interface after the system booted, w/o predictable naming scheme
  - two interfaces after the system booted, w/o predictable naming scheme
  - unplugging and plugging back the current interface
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix51
1 files changed, 26 insertions, 25 deletions
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index c0a4ce40760..49790da0e3c 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -42,11 +42,6 @@ in {
         description = ''
           The interfaces <command>wpa_supplicant</command> will use. If empty, it will
           automatically use all wireless interfaces.
-          <warning><para>
-            The automatic discovery of interfaces does not work reliably on boot:
-            it may fail and leave the system without network. When possible, specify
-            a known interface name.
-          </para></warning>
         '';
       };
 
@@ -230,14 +225,6 @@ in {
       message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive'';
     });
 
-    warnings =
-      optional (cfg.interfaces == [] && config.systemd.services.wpa_supplicant.wantedBy != [])
-      ''
-        No network interfaces for wpa_supplicant have been configured: the service
-        may randomly fail to start at boot. You should specify at least one using the option
-        networking.wireless.interfaces.
-      '';
-
     environment.systemPackages = [ package ];
 
     services.dbus.packages = [ package ];
@@ -257,31 +244,45 @@ in {
       wantedBy = [ "multi-user.target" ];
       stopIfChanged = false;
 
-      path = [ package ];
+      path = [ package pkgs.udev ];
 
       script = let
         configStr = if cfg.allowAuxiliaryImperativeNetworks
           then "-c /etc/wpa_supplicant.conf -I ${configFile}"
           else "-c ${configFile}";
       in ''
-        if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]
-        then echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
+        if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]; then
+          echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
         fi
+
         iface_args="-s -u -D${cfg.driver} ${configStr}"
+
         ${if ifaces == [] then ''
-          for i in $(cd /sys/class/net && echo *); do
-            DEVTYPE=
-            UEVENT_PATH=/sys/class/net/$i/uevent
-            if [ -e "$UEVENT_PATH" ]; then
-              source "$UEVENT_PATH"
-              if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then
-                args+="''${args:+ -N} -i$i $iface_args"
-              fi
-            fi
+          # detect interfaces automatically
+
+          # check if there are no wireless interface
+          if ! find -H /sys/class/net/* -name wireless | grep -q .; then
+            # if so, wait until one appears
+            echo "Waiting for wireless interfaces"
+            grep -q '^ACTION=add' < <(stdbuf -oL -- udevadm monitor -s net/wlan -pu)
+            # Note: the above line has been carefully written:
+            # 1. The process substitution avoids udevadm hanging (after grep has quit)
+            #    until it tries to write to the pipe again. Not even pipefail works here.
+            # 2. stdbuf is needed because udevadm output is buffered by default and grep
+            #    may hang until more udev events enter the pipe.
+          fi
+
+          # add any interface found to the daemon arguments
+          for name in $(find -H /sys/class/net/* -name wireless | cut -d/ -f 5); do
+            echo "Adding interface $name"
+            args+="''${args:+ -N} -i$name $iface_args"
           done
         '' else ''
+          # add known interfaces to the daemon arguments
           args="${concatMapStringsSep " -N " (i: "-i${i} $iface_args") ifaces}"
         ''}
+
+        # finally start daemon
         exec wpa_supplicant $args
       '';
     };