diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix index 11f66b05df12..608672c6446c 100644 --- a/nixos/modules/services/networking/connman.nix +++ b/nixos/modules/services/networking/connman.nix @@ -150,6 +150,7 @@ in { useDHCP = false; wireless = { enable = mkIf (!enableIwd) true; + dbusControlled = true; iwd = mkIf enableIwd { enable = true; }; diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 0bd44fd13178..e50912da0d49 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -9,6 +9,23 @@ let cfg = config.networking.wireless; + # Content of wpa_supplicant.conf + generatedConfig = concatStringsSep "\n" ( + (mapAttrsToList mkNetwork cfg.networks) + ++ optional cfg.userControlled.enable (concatStringsSep "\n" + [ "ctrl_interface=/run/wpa_supplicant" + "ctrl_interface_group=${cfg.userControlled.group}" + "update_config=1" + ]) + ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"'' + ++ optional (cfg.extraConfig != "") cfg.extraConfig); + + configFile = + if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable + then pkgs.writeText "wpa_supplicant.conf" generatedConfig + else "/etc/wpa_supplicant.conf"; + + # Creates a network block for wpa_supplicant.conf mkNetwork = ssid: opts: let quote = x: ''"${x}"''; @@ -34,20 +51,64 @@ let } ''; - generatedConfig = concatStringsSep "\n" ( - (mapAttrsToList mkNetwork cfg.networks) - ++ optional cfg.userControlled.enable (concatStringsSep "\n" - [ "ctrl_interface=/run/wpa_supplicant" - "ctrl_interface_group=${cfg.userControlled.group}" - "update_config=1" - ]) - ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"'' - ++ optional (cfg.extraConfig != "") cfg.extraConfig); + # Creates a systemd unit for wpa_supplicant bound to a given (or any) interface + mkUnit = iface: + let + deviceUnit = optional (iface != null) "sys-subsystem-net-devices-${utils.escapeSystemdPath iface}.device"; + configStr = if cfg.allowAuxiliaryImperativeNetworks + then "-c /etc/wpa_supplicant.conf -I ${configFile}" + else "-c ${configFile}"; + in { + description = "WPA Supplicant instance" + optionalString (iface != null) " for interface ${iface}"; - configFile = - if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable - then pkgs.writeText "wpa_supplicant.conf" generatedConfig - else "/etc/wpa_supplicant.conf"; + after = deviceUnit; + before = [ "network.target" ]; + wants = [ "network.target" ]; + requires = deviceUnit; + wantedBy = [ "multi-user.target" ]; + stopIfChanged = false; + + path = [ package ]; + + script = + '' + 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 ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}" + + ${if iface == null then '' + # detect interfaces automatically + + # check if there are no wireless interfaces + 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 interface to the daemon arguments + args="-i${iface} $iface_args" + ''} + + # finally start daemon + exec wpa_supplicant $args + ''; + }; + + systemctl = "/run/current-system/systemd/bin/systemctl"; in { options = { @@ -61,6 +122,10 @@ in { description = '' The interfaces wpa_supplicant will use. If empty, it will automatically use all wireless interfaces. + + + A separate wpa_supplicant instance will be started for each interface. + ''; }; @@ -271,6 +336,15 @@ in { }; }; + dbusControlled = mkOption { + type = types.bool; + default = lib.length cfg.interfaces < 2; + description = '' + Whether to enable the DBus control interface. + This is only needed when using NetworkManager or connman. + ''; + }; + extraConfig = mkOption { type = types.str; default = ""; @@ -294,78 +368,45 @@ in { assertions = flip mapAttrsToList cfg.networks (name: cfg: { assertion = with cfg; count (x: x != null) [ psk pskRaw auth ] <= 1; message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive''; - }); - - environment.systemPackages = [ package ]; - - services.dbus.packages = [ package ]; + }) ++ [ + { + assertion = length cfg.interfaces > 1 -> !cfg.dbusControlled; + message = + let daemon = if config.networking.networkmanager.enable then "NetworkManager" else + if config.services.connman.enable then "connman" else null; + n = toString (length cfg.interfaces); + in '' + It's not possible to run multiple wpa_supplicant instances with DBus support. + Note: you're seeing this error because `networking.wireless.interfaces` has + ${n} entries, implying an equal number of wpa_supplicant instances. + '' + optionalString (daemon != null) '' + You don't need to change `networking.wireless.interfaces` when using ${daemon}: + in this case the interfaces will be configured automatically for you. + ''; + } + ]; hardware.wirelessRegulatoryDatabase = true; - # FIXME: start a separate wpa_supplicant instance per interface. - systemd.services.wpa_supplicant = let - ifaces = cfg.interfaces; - deviceUnit = interface: [ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device" ]; - in { - description = "WPA Supplicant"; + environment.systemPackages = [ package ]; + services.dbus.packages = optional cfg.dbusControlled package; - after = lib.concatMap deviceUnit ifaces; - before = [ "network.target" ]; - wants = [ "network.target" ]; - requires = lib.concatMap deviceUnit ifaces; - wantedBy = [ "multi-user.target" ]; - stopIfChanged = false; + systemd.services = + if cfg.interfaces == [] + then { wpa_supplicant = mkUnit null; } + else listToAttrs (map (i: nameValuePair "wpa_supplicant-${i}" (mkUnit i)) cfg.interfaces); - path = [ package pkgs.udev ]; + # Restart wpa_supplicant after resuming from sleep + powerManagement.resumeCommands = concatStringsSep "\n" ( + optional (cfg.interfaces == []) "${systemctl} try-restart wpa_supplicant" + ++ map (i: "${systemctl} try-restart wpa_supplicant-${i}") cfg.interfaces + ); - 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." - fi - - iface_args="-s -u -D${cfg.driver} ${configStr}" - - ${if ifaces == [] then '' - # 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 - ''; - }; - - powerManagement.resumeCommands = '' - /run/current-system/systemd/bin/systemctl try-restart wpa_supplicant - ''; - - # Restart wpa_supplicant when a wlan device appears or disappears. - services.udev.extraRules = '' - ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="/run/current-system/systemd/bin/systemctl try-restart wpa_supplicant.service" + # Restart wpa_supplicant when a wlan device appears or disappears. This is + # only needed when an interface hasn't been specified by the user. + services.udev.extraRules = optionalString (cfg.interfaces == []) '' + ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", \ + RUN+="${systemctl} try-restart wpa_supplicant.service" ''; };