Merge pull request #118583 from rnhmjoj/roaming

nixos/wireless: add options for better roaming

authored by

Michele Guerini Rocco and committed by
GitHub
95c7f7ed a9725665

+203 -90
+1
nixos/modules/services/networking/connman.nix
··· 150 150 useDHCP = false; 151 151 wireless = { 152 152 enable = mkIf (!enableIwd) true; 153 + dbusControlled = true; 153 154 iwd = mkIf enableIwd { 154 155 enable = true; 155 156 };
+202 -90
nixos/modules/services/networking/wpa_supplicant.nix
··· 8 8 else pkgs.wpa_supplicant; 9 9 10 10 cfg = config.networking.wireless; 11 - configFile = if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable then pkgs.writeText "wpa_supplicant.conf" '' 12 - ${optionalString cfg.userControlled.enable '' 13 - ctrl_interface=DIR=/run/wpa_supplicant GROUP=${cfg.userControlled.group} 14 - update_config=1''} 15 - ${cfg.extraConfig} 16 - ${concatStringsSep "\n" (mapAttrsToList (ssid: config: with config; let 17 - key = if psk != null 18 - then ''"${psk}"'' 19 - else pskRaw; 20 - baseAuth = if key != null 21 - then "psk=${key}" 22 - else "key_mgmt=NONE"; 23 - in '' 24 - network={ 25 - ssid="${ssid}" 26 - ${optionalString (priority != null) ''priority=${toString priority}''} 27 - ${optionalString hidden "scan_ssid=1"} 28 - ${if (auth != null) then auth else baseAuth} 29 - ${extraConfig} 30 - } 31 - '') cfg.networks)} 32 - '' else "/etc/wpa_supplicant.conf"; 11 + 12 + # Content of wpa_supplicant.conf 13 + generatedConfig = concatStringsSep "\n" ( 14 + (mapAttrsToList mkNetwork cfg.networks) 15 + ++ optional cfg.userControlled.enable (concatStringsSep "\n" 16 + [ "ctrl_interface=/run/wpa_supplicant" 17 + "ctrl_interface_group=${cfg.userControlled.group}" 18 + "update_config=1" 19 + ]) 20 + ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"'' 21 + ++ optional (cfg.extraConfig != "") cfg.extraConfig); 22 + 23 + configFile = 24 + if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable 25 + then pkgs.writeText "wpa_supplicant.conf" generatedConfig 26 + else "/etc/wpa_supplicant.conf"; 27 + 28 + # Creates a network block for wpa_supplicant.conf 29 + mkNetwork = ssid: opts: 30 + let 31 + quote = x: ''"${x}"''; 32 + indent = x: " " + x; 33 + 34 + pskString = if opts.psk != null 35 + then quote opts.psk 36 + else opts.pskRaw; 37 + 38 + options = [ 39 + "ssid=${quote ssid}" 40 + (if pskString != null || opts.auth != null 41 + then "key_mgmt=${concatStringsSep " " opts.authProtocols}" 42 + else "key_mgmt=NONE") 43 + ] ++ optional opts.hidden "scan_ssid=1" 44 + ++ optional (pskString != null) "psk=${pskString}" 45 + ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth)) 46 + ++ optional (opts.priority != null) "priority=${toString opts.priority}" 47 + ++ optional (opts.extraConfig != "") opts.extraConfig; 48 + in '' 49 + network={ 50 + ${concatMapStringsSep "\n" indent options} 51 + } 52 + ''; 53 + 54 + # Creates a systemd unit for wpa_supplicant bound to a given (or any) interface 55 + mkUnit = iface: 56 + let 57 + deviceUnit = optional (iface != null) "sys-subsystem-net-devices-${utils.escapeSystemdPath iface}.device"; 58 + configStr = if cfg.allowAuxiliaryImperativeNetworks 59 + then "-c /etc/wpa_supplicant.conf -I ${configFile}" 60 + else "-c ${configFile}"; 61 + in { 62 + description = "WPA Supplicant instance" + optionalString (iface != null) " for interface ${iface}"; 63 + 64 + after = deviceUnit; 65 + before = [ "network.target" ]; 66 + wants = [ "network.target" ]; 67 + requires = deviceUnit; 68 + wantedBy = [ "multi-user.target" ]; 69 + stopIfChanged = false; 70 + 71 + path = [ package ]; 72 + 73 + script = 74 + '' 75 + if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]; then 76 + echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead." 77 + fi 78 + 79 + iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}" 80 + 81 + ${if iface == null then '' 82 + # detect interfaces automatically 83 + 84 + # check if there are no wireless interfaces 85 + if ! find -H /sys/class/net/* -name wireless | grep -q .; then 86 + # if so, wait until one appears 87 + echo "Waiting for wireless interfaces" 88 + grep -q '^ACTION=add' < <(stdbuf -oL -- udevadm monitor -s net/wlan -pu) 89 + # Note: the above line has been carefully written: 90 + # 1. The process substitution avoids udevadm hanging (after grep has quit) 91 + # until it tries to write to the pipe again. Not even pipefail works here. 92 + # 2. stdbuf is needed because udevadm output is buffered by default and grep 93 + # may hang until more udev events enter the pipe. 94 + fi 95 + 96 + # add any interface found to the daemon arguments 97 + for name in $(find -H /sys/class/net/* -name wireless | cut -d/ -f 5); do 98 + echo "Adding interface $name" 99 + args+="''${args:+ -N} -i$name $iface_args" 100 + done 101 + '' else '' 102 + # add known interface to the daemon arguments 103 + args="-i${iface} $iface_args" 104 + ''} 105 + 106 + # finally start daemon 107 + exec wpa_supplicant $args 108 + ''; 109 + }; 110 + 111 + systemctl = "/run/current-system/systemd/bin/systemctl"; 112 + 33 113 in { 34 114 options = { 35 115 networking.wireless = { ··· 42 122 description = '' 43 123 The interfaces <command>wpa_supplicant</command> will use. If empty, it will 44 124 automatically use all wireless interfaces. 125 + 126 + <note><para> 127 + A separate wpa_supplicant instance will be started for each interface. 128 + </para></note> 45 129 ''; 46 130 }; 47 131 ··· 61 145 ''; 62 146 }; 63 147 148 + scanOnLowSignal = mkOption { 149 + type = types.bool; 150 + default = true; 151 + description = '' 152 + Whether to periodically scan for (better) networks when the signal of 153 + the current one is low. This will make roaming between access points 154 + faster, but will consume more power. 155 + ''; 156 + }; 157 + 64 158 networks = mkOption { 65 159 type = types.attrsOf (types.submodule { 66 160 options = { ··· 89 183 ''; 90 184 }; 91 185 186 + authProtocols = mkOption { 187 + default = [ 188 + # WPA2 and WPA3 189 + "WPA-PSK" "WPA-EAP" "SAE" 190 + # 802.11r variants of the above 191 + "FT-PSK" "FT-EAP" "FT-SAE" 192 + ]; 193 + # The list can be obtained by running this command 194 + # awk ' 195 + # /^# key_mgmt: /{ run=1 } 196 + # /^#$/{ run=0 } 197 + # /^# [A-Z0-9-]{2,}/{ if(run){printf("\"%s\"\n", $2)} } 198 + # ' /run/current-system/sw/share/doc/wpa_supplicant/wpa_supplicant.conf.example 199 + type = types.listOf (types.enum [ 200 + "WPA-PSK" 201 + "WPA-EAP" 202 + "IEEE8021X" 203 + "NONE" 204 + "WPA-NONE" 205 + "FT-PSK" 206 + "FT-EAP" 207 + "FT-EAP-SHA384" 208 + "WPA-PSK-SHA256" 209 + "WPA-EAP-SHA256" 210 + "SAE" 211 + "FT-SAE" 212 + "WPA-EAP-SUITE-B" 213 + "WPA-EAP-SUITE-B-192" 214 + "OSEN" 215 + "FILS-SHA256" 216 + "FILS-SHA384" 217 + "FT-FILS-SHA256" 218 + "FT-FILS-SHA384" 219 + "OWE" 220 + "DPP" 221 + ]); 222 + description = '' 223 + The list of authentication protocols accepted by this network. 224 + This corresponds to the <literal>key_mgmt</literal> option in wpa_supplicant. 225 + ''; 226 + }; 227 + 92 228 auth = mkOption { 93 229 type = types.nullOr types.str; 94 230 default = null; 95 231 example = '' 96 - key_mgmt=WPA-EAP 97 232 eap=PEAP 98 233 identity="user@example.com" 99 234 password="secret" ··· 200 335 description = "Members of this group can control wpa_supplicant."; 201 336 }; 202 337 }; 338 + 339 + dbusControlled = mkOption { 340 + type = types.bool; 341 + default = lib.length cfg.interfaces < 2; 342 + description = '' 343 + Whether to enable the DBus control interface. 344 + This is only needed when using NetworkManager or connman. 345 + ''; 346 + }; 347 + 203 348 extraConfig = mkOption { 204 349 type = types.str; 205 350 default = ""; ··· 223 368 assertions = flip mapAttrsToList cfg.networks (name: cfg: { 224 369 assertion = with cfg; count (x: x != null) [ psk pskRaw auth ] <= 1; 225 370 message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive''; 226 - }); 227 - 228 - environment.systemPackages = [ package ]; 229 - 230 - services.dbus.packages = [ package ]; 371 + }) ++ [ 372 + { 373 + assertion = length cfg.interfaces > 1 -> !cfg.dbusControlled; 374 + message = 375 + let daemon = if config.networking.networkmanager.enable then "NetworkManager" else 376 + if config.services.connman.enable then "connman" else null; 377 + n = toString (length cfg.interfaces); 378 + in '' 379 + It's not possible to run multiple wpa_supplicant instances with DBus support. 380 + Note: you're seeing this error because `networking.wireless.interfaces` has 381 + ${n} entries, implying an equal number of wpa_supplicant instances. 382 + '' + optionalString (daemon != null) '' 383 + You don't need to change `networking.wireless.interfaces` when using ${daemon}: 384 + in this case the interfaces will be configured automatically for you. 385 + ''; 386 + } 387 + ]; 231 388 232 389 hardware.wirelessRegulatoryDatabase = true; 233 390 234 - # FIXME: start a separate wpa_supplicant instance per interface. 235 - systemd.services.wpa_supplicant = let 236 - ifaces = cfg.interfaces; 237 - deviceUnit = interface: [ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device" ]; 238 - in { 239 - description = "WPA Supplicant"; 391 + environment.systemPackages = [ package ]; 392 + services.dbus.packages = optional cfg.dbusControlled package; 240 393 241 - after = lib.concatMap deviceUnit ifaces; 242 - before = [ "network.target" ]; 243 - wants = [ "network.target" ]; 244 - requires = lib.concatMap deviceUnit ifaces; 245 - wantedBy = [ "multi-user.target" ]; 246 - stopIfChanged = false; 394 + systemd.services = 395 + if cfg.interfaces == [] 396 + then { wpa_supplicant = mkUnit null; } 397 + else listToAttrs (map (i: nameValuePair "wpa_supplicant-${i}" (mkUnit i)) cfg.interfaces); 247 398 248 - path = [ package pkgs.udev ]; 399 + # Restart wpa_supplicant after resuming from sleep 400 + powerManagement.resumeCommands = concatStringsSep "\n" ( 401 + optional (cfg.interfaces == []) "${systemctl} try-restart wpa_supplicant" 402 + ++ map (i: "${systemctl} try-restart wpa_supplicant-${i}") cfg.interfaces 403 + ); 249 404 250 - script = let 251 - configStr = if cfg.allowAuxiliaryImperativeNetworks 252 - then "-c /etc/wpa_supplicant.conf -I ${configFile}" 253 - else "-c ${configFile}"; 254 - in '' 255 - if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]; then 256 - echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead." 257 - fi 258 - 259 - iface_args="-s -u -D${cfg.driver} ${configStr}" 260 - 261 - ${if ifaces == [] then '' 262 - # detect interfaces automatically 263 - 264 - # check if there are no wireless interface 265 - if ! find -H /sys/class/net/* -name wireless | grep -q .; then 266 - # if so, wait until one appears 267 - echo "Waiting for wireless interfaces" 268 - grep -q '^ACTION=add' < <(stdbuf -oL -- udevadm monitor -s net/wlan -pu) 269 - # Note: the above line has been carefully written: 270 - # 1. The process substitution avoids udevadm hanging (after grep has quit) 271 - # until it tries to write to the pipe again. Not even pipefail works here. 272 - # 2. stdbuf is needed because udevadm output is buffered by default and grep 273 - # may hang until more udev events enter the pipe. 274 - fi 275 - 276 - # add any interface found to the daemon arguments 277 - for name in $(find -H /sys/class/net/* -name wireless | cut -d/ -f 5); do 278 - echo "Adding interface $name" 279 - args+="''${args:+ -N} -i$name $iface_args" 280 - done 281 - '' else '' 282 - # add known interfaces to the daemon arguments 283 - args="${concatMapStringsSep " -N " (i: "-i${i} $iface_args") ifaces}" 284 - ''} 285 - 286 - # finally start daemon 287 - exec wpa_supplicant $args 288 - ''; 289 - }; 290 - 291 - powerManagement.resumeCommands = '' 292 - /run/current-system/systemd/bin/systemctl try-restart wpa_supplicant 293 - ''; 294 - 295 - # Restart wpa_supplicant when a wlan device appears or disappears. 296 - services.udev.extraRules = '' 297 - ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="/run/current-system/systemd/bin/systemctl try-restart wpa_supplicant.service" 405 + # Restart wpa_supplicant when a wlan device appears or disappears. This is 406 + # only needed when an interface hasn't been specified by the user. 407 + services.udev.extraRules = optionalString (cfg.interfaces == []) '' 408 + ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", \ 409 + RUN+="${systemctl} try-restart wpa_supplicant.service" 298 410 ''; 299 411 }; 300 412 301 - meta.maintainers = with lib.maintainers; [ globin ]; 413 + meta.maintainers = with lib.maintainers; [ globin rnhmjoj ]; 302 414 }