From fe4da89657751a459671968db067084f54171f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9ana=20=E6=B1=9F?= Date: Tue, 9 Sep 2025 14:54:21 +0800 Subject: [PATCH 1/3] nixos/wpa_supplicant: allow duplicate network blocks Allow users to specify more than one network block of the same SSID. In addition, allow the specification of its BSSID to better distinguish these network blocks. --- .../services/networking/wpa_supplicant.nix | 332 ++++++++++-------- 1 file changed, 180 insertions(+), 152 deletions(-) diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 88eaffdc1ee1..8564295551a5 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -37,7 +37,9 @@ let mkWPA2Fallback = opts: opts // { authProtocols = subtractLists wpa3Protocols opts.authProtocols; }; # Networks attrset as a list - networkList = mapAttrsToList (ssid: opts: opts // { inherit ssid; }) cfg.networks; + # We use the ssid from the options, which defaults to the ssid but can be overridden. + # The ssid in attrNames is hence unused here. + networkList = attrValues cfg.networks; # List of all networks (normal + generated fallbacks) allNetworks = @@ -89,6 +91,7 @@ let "key_mgmt=NONE" ) ] + ++ optional (opts.bssid != null) "bssid=${opts.bssid}" ++ optional opts.hidden "scan_ssid=1" ++ optional (pskString != null) "psk=${pskString}" ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth)) @@ -280,170 +283,195 @@ in networks = mkOption { type = types.attrsOf ( - types.submodule { - options = { - psk = mkOption { - type = types.nullOr (types.strMatching "[[:print:]]{8,63}"); - default = null; - description = '' - The network's pre-shared key in plaintext defaulting - to being a network without any authentication. + types.submodule ( + { name, ... }: + { + options = { + ssid = mkOption { + type = types.str; + default = name; + description = '' + You could use this field to override the network's ssid. + This can be useful to, for example, specify two networks + that share the same SSID but not the same password. + Specifying the BSSID of the network can make two entries of + the same SSID show up as different ones in wpa_cli. + ''; + }; - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Use {var}`pskRaw` with an external - reference to keep it safe. - ::: + bssid = mkOption { + type = types.nullOr types.str; + default = null; + example = "02:00:00:00:00:01"; + description = '' + If set, this network block is used only when associating with + the AP using the configured BSSID. + ''; + }; - ::: {.note} - Mutually exclusive with {var}`pskRaw`. - ::: - ''; - }; + psk = mkOption { + type = types.nullOr (types.strMatching "[[:print:]]{8,63}"); + default = null; + description = '' + The network's pre-shared key in plaintext defaulting + to being a network without any authentication. - pskRaw = mkOption { - type = types.nullOr (types.strMatching "([[:xdigit:]]{64})|(ext:[^=]+)"); - default = null; - example = "ext:name_of_the_secret_here"; - description = '' - Either the raw pre-shared key in hexadecimal format - or the name of the secret (as defined inside - [](#opt-networking.wireless.secretsFile) and prefixed - with `ext:`) containing the network pre-shared key. + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Use {var}`pskRaw` with an external + reference to keep it safe. + ::: - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Always use an external reference. - ::: + ::: {.note} + Mutually exclusive with {var}`pskRaw`. + ::: + ''; + }; - ::: {.note} - The external secret can be either the plaintext - passphrase or the raw pre-shared key. - ::: + pskRaw = mkOption { + type = types.nullOr (types.strMatching "([[:xdigit:]]{64})|(ext:[^=]+)"); + default = null; + example = "ext:name_of_the_secret_here"; + description = '' + Either the raw pre-shared key in hexadecimal format + or the name of the secret (as defined inside + [](#opt-networking.wireless.secretsFile) and prefixed + with `ext:`) containing the network pre-shared key. - ::: {.note} - Mutually exclusive with {var}`psk` and {var}`auth`. - ::: - ''; - }; + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Always use an external reference. + ::: - 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 [ + ::: {.note} + The external secret can be either the plaintext + passphrase or the raw pre-shared key. + ::: + + ::: {.note} + Mutually exclusive with {var}`psk` and {var}`auth`. + ::: + ''; + }; + + authProtocols = mkOption { + default = [ + # WPA2 and WPA3 "WPA-PSK" "WPA-EAP" - "IEEE8021X" - "NONE" - "WPA-NONE" + "SAE" + # 802.11r variants of the above "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. - ''; + ]; + # 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 = '' + eap=PEAP + identity="user@example.com" + password=ext:example_password + ''; + description = '' + Use this option to configure advanced authentication methods + like EAP. See {manpage}`wpa_supplicant.conf(5)` for example + configurations. + + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Use an external reference like + `ext:secretname` for secrets. + ::: + + ::: {.note} + Mutually exclusive with {var}`psk` and {var}`pskRaw`. + ::: + ''; + }; + + hidden = mkOption { + type = types.bool; + default = false; + description = '' + Set this to `true` if the SSID of the network is hidden. + ''; + example = literalExpression '' + { echelon = { + hidden = true; + psk = "abcdefgh"; + }; + } + ''; + }; + + priority = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + By default, all networks will get same priority group (0). If + some of the networks are more desirable, this field can be used + to change the order in which wpa_supplicant goes through the + networks when selecting a BSS. The priority groups will be + iterated in decreasing priority (i.e., the larger the priority + value, the sooner the network is matched against the scan + results). Within each priority group, networks will be selected + based on security policy, signal strength, etc. + ''; + }; + + extraConfig = mkOption { + type = types.str; + default = ""; + example = '' + bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66 + ''; + description = '' + Extra configuration lines appended to the network block. + See {manpage}`wpa_supplicant.conf(5)` for available options. + ''; + }; + }; - - auth = mkOption { - type = types.nullOr types.str; - default = null; - example = '' - eap=PEAP - identity="user@example.com" - password=ext:example_password - ''; - description = '' - Use this option to configure advanced authentication methods - like EAP. See {manpage}`wpa_supplicant.conf(5)` for example - configurations. - - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Use an external reference like - `ext:secretname` for secrets. - ::: - - ::: {.note} - Mutually exclusive with {var}`psk` and {var}`pskRaw`. - ::: - ''; - }; - - hidden = mkOption { - type = types.bool; - default = false; - description = '' - Set this to `true` if the SSID of the network is hidden. - ''; - example = literalExpression '' - { echelon = { - hidden = true; - psk = "abcdefgh"; - }; - } - ''; - }; - - priority = mkOption { - type = types.nullOr types.int; - default = null; - description = '' - By default, all networks will get same priority group (0). If - some of the networks are more desirable, this field can be used - to change the order in which wpa_supplicant goes through the - networks when selecting a BSS. The priority groups will be - iterated in decreasing priority (i.e., the larger the priority - value, the sooner the network is matched against the scan - results). Within each priority group, networks will be selected - based on security policy, signal strength, etc. - ''; - }; - - extraConfig = mkOption { - type = types.str; - default = ""; - example = '' - bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66 - ''; - description = '' - Extra configuration lines appended to the network block. - See {manpage}`wpa_supplicant.conf(5)` for available options. - ''; - }; - - }; - } + } + ) ); description = '' The network definitions to automatically connect to when From 18a21482c4503a509e3798c5fff01921b73cc2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9ana=20=E6=B1=9F?= Date: Tue, 9 Sep 2025 15:55:06 +0800 Subject: [PATCH 2/3] nixos/tests/wpa_supplicant: test duplicate SSID with different BSSID --- nixos/tests/wpa_supplicant.nix | 78 +++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix index d893885ae65b..f2a0c13de47c 100644 --- a/nixos/tests/wpa_supplicant.nix +++ b/nixos/tests/wpa_supplicant.nix @@ -13,8 +13,31 @@ let naughtyPassphrase = ''!,./;'[]\-=<>?:"{}|_+@$%^&*()`~ # ceci n'est pas un commentaire''; + runBssidTest = + name: expectedBssid: extraConfig: + runSimulatorTest name extraConfig '' + with subtest("Daemon can connect to the right access point"): + machine.wait_for_unit("wpa_supplicant-wlan1.service") + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q bssid=${expectedBssid}" + ) + ''; + runConnectionTest = name: extraConfig: + runSimulatorTest name extraConfig '' + with subtest("Daemon can connect to the access point"): + machine.wait_for_unit("wpa_supplicant-wlan1.service") + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + ''; + + runSimulatorTest = + name: extraConfig: extraTestScript: runTest { name = "wpa_supplicant-${name}"; inherit meta; @@ -50,12 +73,22 @@ let bssid = "02:00:00:00:00:01"; }; wlan0-2 = { + ssid = "nixos-test-mixed"; + authentication = { + mode = "wpa3-sae-transition"; + saeAddToMacAllow = true; + saePasswordsFile = pkgs.writeText "password" naughtyPassphrase; + wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase; + }; + bssid = "02:00:00:00:00:02"; + }; + wlan0-3 = { ssid = "nixos-test-wpa2"; authentication = { mode = "wpa2-sha256"; wpaPassword = naughtyPassphrase; }; - bssid = "02:00:00:00:00:02"; + bssid = "02:00:00:00:00:03"; }; }; }; @@ -85,11 +118,7 @@ let machine.wait_for_unit("hostapd.service") machine.copy_from_vm("/run/hostapd/wlan0.hostapd.conf") - with subtest("Daemon can connect to the access point"): - machine.wait_for_unit("wpa_supplicant-wlan1.service") - machine.wait_until_succeeds( - "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" - ) + ${extraTestScript} ''; }; @@ -129,6 +158,18 @@ in psk = "password"; authProtocols = [ "SAE" ]; }; + + # Test duplicate SSID generation + duplicate1 = { + ssid = "duplicate"; + bssid = "00:00:00:00:00:01"; + psk = "password"; + }; + duplicate2 = { + ssid = "duplicate"; + bssid = "00:00:00:00:00:02"; + psk = "password"; + }; }; }; }; @@ -148,6 +189,12 @@ in assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1 assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2 + with subtest("Duplicate SSID network blocks have been generated"): + # more duplication due to fallbacks + assert int(machine.succeed(f"grep -c duplicate {config_file}")) == 4 + assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:01 {config_file}")) == 2 + assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:02 {config_file}")) == 2 + # save file for manual inspection machine.copy_from_vm(config_file) ''; @@ -222,4 +269,23 @@ in authProtocols = [ "WPA-PSK-SHA256" ]; }; }; + + # Test connection with the highest prio "matching" network block found. + # "Matching" meaning with the right SSID and BSSID + bssidGuard = runBssidTest "bssid-guard" "02:00:00:00:00:02" { + networks = { + "1_first" = { + ssid = "nixos-test-mixed"; + bssid = "02:00:00:00:00:01"; + pskRaw = "ext:psk_nixos_test"; + }; + "2_second" = { + ssid = "nixos-test-mixed"; + bssid = "02:00:00:00:00:02"; + pskRaw = "ext:psk_nixos_test"; + priority = 1; + }; + }; + }; + } From aa61310aa78a1a0c22d0551aa9de6c4488cd3455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9ana=20=E6=B1=9F?= Date: Tue, 9 Sep 2025 19:29:03 +0800 Subject: [PATCH 3/3] doc: mention networking.wireless.networks. new options --- nixos/doc/manual/release-notes/rl-2511.section.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 0053b51c607c..412ddc6f3a58 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -250,6 +250,8 @@ - `services.gitea` supports sending notifications with sendmail again. To do this, activate the parameter `services.gitea.mailerUseSendmail` and configure SMTP server. +- `networking.wireless.networks.` now has an option to specify SSID, hence allowing duplicated SSID setup. The BSSID option is added along side with this. + - Revamp of the ACME certificate acquisication and renewal process to help scale systems with lots (100+) of certificates. Units and targets have been reshaped to better support more specific dependency propagation and avoid