From 33372e8b752d6c19213e8e6e7badc83175dbfe7d Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 5 Apr 2021 11:25:32 +0200 Subject: [PATCH 1/4] nixos/wireless: generate pretty configuration Turns this mess ctrl_interface=DIR=/run/wpa_supplicant GROUP=wheel update_config=1 network={ ssid="cool-network" psk="ciao" } network={ ssid="fancy-network" eap=PEAP identity="user@example.com" password="secret" } network={ ssid="free-network" key_mgmt=NONE } network={ ssid="raw-network" psk=fafafa } into something more human readable: network={ ssid="cool-network" psk="ciao" } network={ ssid="fancy-network" eap=PEAP identity="user@example.com" password="secret" } network={ ssid="free-network" key_mgmt=NONE } network={ ssid="raw-network" psk=fafafa } ctrl_interface=/run/wpa_supplicant ctrl_interface_group=wheel update_config=1 --- .../services/networking/wpa_supplicant.nix | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 6238a351b998..56896e9c3412 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -8,28 +8,44 @@ let else pkgs.wpa_supplicant; cfg = config.networking.wireless; - configFile = if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable then pkgs.writeText "wpa_supplicant.conf" '' - ${optionalString cfg.userControlled.enable '' - ctrl_interface=DIR=/run/wpa_supplicant GROUP=${cfg.userControlled.group} - update_config=1''} - ${cfg.extraConfig} - ${concatStringsSep "\n" (mapAttrsToList (ssid: config: with config; let - key = if psk != null - then ''"${psk}"'' - else pskRaw; - baseAuth = if key != null - then "psk=${key}" - else "key_mgmt=NONE"; - in '' - network={ - ssid="${ssid}" - ${optionalString (priority != null) ''priority=${toString priority}''} - ${optionalString hidden "scan_ssid=1"} - ${if (auth != null) then auth else baseAuth} - ${extraConfig} - } - '') cfg.networks)} - '' else "/etc/wpa_supplicant.conf"; + + mkNetwork = ssid: opts: + let + quote = x: ''"${x}"''; + indent = x: " " + x; + + pskString = if opts.psk != null + then quote opts.psk + else opts.pskRaw; + + options = [ + "ssid=${quote ssid}" + ] ++ optional opts.hidden "scan_ssid=1" + ++ optional (pskString == null && opts.auth == null) "key_mgmt=NONE" + ++ optional (pskString != null) "psk=${pskString}" + ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth)) + ++ optional (opts.priority != null) "priority=${toString opts.priority}" + ++ optional (opts.extraConfig != "") opts.extraConfig; + in '' + network={ + ${concatMapStringsSep "\n" indent options} + } + ''; + + 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.extraConfig != "") cfg.extraConfig); + + configFile = + if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable + then pkgs.writeText "wpa_supplicant.conf" generatedConfig + else "/etc/wpa_supplicant.conf"; + in { options = { networking.wireless = { @@ -200,6 +216,7 @@ in { description = "Members of this group can control wpa_supplicant."; }; }; + extraConfig = mkOption { type = types.str; default = ""; From bef963963a7c4146daac7f965b2ebb02cccb8364 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 5 Apr 2021 11:37:51 +0200 Subject: [PATCH 2/4] nixos/wireless: add options for better roaming - Add an option to automatically launch a scan when the signal of the current network is low - Enable 802.11r (fast access point transition) by default for all protected networks --- .../services/networking/wpa_supplicant.nix | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 56896e9c3412..0bd44fd13178 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -20,8 +20,10 @@ let options = [ "ssid=${quote ssid}" + (if pskString != null || opts.auth != null + then "key_mgmt=${concatStringsSep " " opts.authProtocols}" + else "key_mgmt=NONE") ] ++ optional opts.hidden "scan_ssid=1" - ++ optional (pskString == null && opts.auth == null) "key_mgmt=NONE" ++ optional (pskString != null) "psk=${pskString}" ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth)) ++ optional (opts.priority != null) "priority=${toString opts.priority}" @@ -39,6 +41,7 @@ let "ctrl_interface_group=${cfg.userControlled.group}" "update_config=1" ]) + ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"'' ++ optional (cfg.extraConfig != "") cfg.extraConfig); configFile = @@ -77,6 +80,16 @@ in { ''; }; + scanOnLowSignal = mkOption { + type = types.bool; + default = true; + description = '' + Whether to periodically scan for (better) networks when the signal of + the current one is low. This will make roaming between access points + faster, but will consume more power. + ''; + }; + networks = mkOption { type = types.attrsOf (types.submodule { options = { @@ -105,11 +118,52 @@ in { ''; }; + authProtocols = mkOption { + default = [ + # WPA2 and WPA3 + "WPA-PSK" "WPA-EAP" "SAE" + # 802.11r variants of the above + "FT-PSK" "FT-EAP" "FT-SAE" + ]; + # The list can be obtained by running this command + # awk ' + # /^# key_mgmt: /{ run=1 } + # /^#$/{ run=0 } + # /^# [A-Z0-9-]{2,}/{ if(run){printf("\"%s\"\n", $2)} } + # ' /run/current-system/sw/share/doc/wpa_supplicant/wpa_supplicant.conf.example + type = types.listOf (types.enum [ + "WPA-PSK" + "WPA-EAP" + "IEEE8021X" + "NONE" + "WPA-NONE" + "FT-PSK" + "FT-EAP" + "FT-EAP-SHA384" + "WPA-PSK-SHA256" + "WPA-EAP-SHA256" + "SAE" + "FT-SAE" + "WPA-EAP-SUITE-B" + "WPA-EAP-SUITE-B-192" + "OSEN" + "FILS-SHA256" + "FILS-SHA384" + "FT-FILS-SHA256" + "FT-FILS-SHA384" + "OWE" + "DPP" + ]); + description = '' + The list of authentication protocols accepted by this network. + This corresponds to the key_mgmt option in wpa_supplicant. + ''; + }; + auth = mkOption { type = types.nullOr types.str; default = null; example = '' - key_mgmt=WPA-EAP eap=PEAP identity="user@example.com" password="secret" From 704f33c888726e7845ca4afe6da64f87713e8104 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Sat, 8 May 2021 12:18:55 +0200 Subject: [PATCH 3/4] nixos/wireless: add one service per interface This sets up a different systemd service for each interface. This way each wpa_supplicant instance waits for his inteface to become ready using the respective device unit, and that only. The configuration file is still shared between all instances, though. This closes a longstanding "fixme" from cbfba81. --- nixos/modules/services/networking/connman.nix | 1 + .../services/networking/wpa_supplicant.nix | 199 +++++++++++------- 2 files changed, 121 insertions(+), 79 deletions(-) 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" ''; }; From 0d082b464dcc303a95e01a37e0b117eeeddfc04e Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Sat, 8 May 2021 12:19:17 +0200 Subject: [PATCH 4/4] nixos/wireless: add rnhmjoj as maintainer --- nixos/modules/services/networking/wpa_supplicant.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index e50912da0d49..155c6fdd0ab0 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -410,5 +410,5 @@ in { ''; }; - meta.maintainers = with lib.maintainers; [ globin ]; + meta.maintainers = with lib.maintainers; [ globin rnhmjoj ]; }