1{ pkgs, runTest }:
2
3let
4
5 inherit (pkgs) lib;
6
7 meta = with lib.maintainers; {
8 maintainers = [
9 oddlama
10 rnhmjoj
11 ];
12 };
13
14 naughtyPassphrase = ''!,./;'[]\-=<>?:"{}|_+@$%^&*()`~ # ceci n'est pas un commentaire'';
15
16 runConnectionTest =
17 name: extraConfig:
18 runTest {
19 name = "wpa_supplicant-${name}";
20 inherit meta;
21
22 nodes.machine = {
23 # add a virtual wlan interface
24 boot.kernelModules = [ "mac80211_hwsim" ];
25
26 # wireless access point
27 services.hostapd = {
28 enable = true;
29 radios.wlan0 = {
30 band = "2g";
31 channel = 6;
32 countryCode = "US";
33 networks = {
34 wlan0 = {
35 ssid = "nixos-test-sae";
36 authentication = {
37 mode = "wpa3-sae";
38 saePasswords = [ { passwordFile = pkgs.writeText "password" naughtyPassphrase; } ];
39 };
40 bssid = "02:00:00:00:00:00";
41 };
42 wlan0-1 = {
43 ssid = "nixos-test-mixed";
44 authentication = {
45 mode = "wpa3-sae-transition";
46 saeAddToMacAllow = true;
47 saePasswordsFile = pkgs.writeText "password" naughtyPassphrase;
48 wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase;
49 };
50 bssid = "02:00:00:00:00:01";
51 };
52 wlan0-2 = {
53 ssid = "nixos-test-wpa2";
54 authentication = {
55 mode = "wpa2-sha256";
56 wpaPassword = naughtyPassphrase;
57 };
58 bssid = "02:00:00:00:00:02";
59 };
60 };
61 };
62 };
63
64 # wireless client
65 networking.wireless = lib.mkMerge [
66 {
67 # the override is needed because the wifi is
68 # disabled with mkVMOverride in qemu-vm.nix.
69 enable = lib.mkOverride 0 true;
70 userControlled.enable = true;
71 interfaces = [ "wlan1" ];
72 fallbackToWPA2 = lib.mkDefault true;
73
74 # secrets
75 secretsFile = pkgs.writeText "wpa-secrets" ''
76 psk_nixos_test=${naughtyPassphrase}
77 '';
78 }
79 extraConfig
80 ];
81 };
82
83 testScript = ''
84 # save hostapd config file for manual inspection
85 machine.wait_for_unit("hostapd.service")
86 machine.copy_from_vm("/run/hostapd/wlan0.hostapd.conf")
87
88 with subtest("Daemon can connect to the access point"):
89 machine.wait_for_unit("wpa_supplicant-wlan1.service")
90 machine.wait_until_succeeds(
91 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
92 )
93 '';
94 };
95
96in
97
98{
99 # Test the basic setup:
100 # - automatic interface discovery
101 # - WPA2 fallbacks
102 # - connecting to the daemon
103 basic = runTest {
104 name = "wpa_supplicant-basic";
105 inherit meta;
106
107 nodes.machine = {
108 # add a virtual wlan interface
109 boot.kernelModules = [ "mac80211_hwsim" ];
110
111 # wireless client
112 networking.wireless = {
113 # the override is needed because the wifi is
114 # disabled with mkVMOverride in qemu-vm.nix.
115 enable = lib.mkOverride 0 true;
116 userControlled.enable = true;
117 fallbackToWPA2 = true;
118
119 networks = {
120 # test WPA2 fallback
121 mixed-wpa = {
122 psk = "password";
123 authProtocols = [
124 "WPA-PSK"
125 "SAE"
126 ];
127 };
128 sae-only = {
129 psk = "password";
130 authProtocols = [ "SAE" ];
131 };
132 };
133 };
134 };
135
136 testScript = ''
137 with subtest("Daemon is running and accepting connections"):
138 machine.wait_for_unit("wpa_supplicant.service")
139 status = machine.wait_until_succeeds("wpa_cli status")
140 assert "Failed to connect" not in status, \
141 "Failed to connect to the daemon"
142
143 # get the configuration file
144 cmdline = machine.succeed("cat /proc/$(pgrep wpa)/cmdline").split('\x00')
145 config_file = cmdline[cmdline.index("-c") + 1]
146
147 with subtest("WPA2 fallbacks have been generated"):
148 assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1
149 assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2
150
151 # save file for manual inspection
152 machine.copy_from_vm(config_file)
153 '';
154 };
155
156 # Test configuring the daemon imperatively
157 imperative = runTest {
158 name = "wpa_supplicant-imperative";
159 inherit meta;
160
161 nodes.machine = {
162 # add a virtual wlan interface
163 boot.kernelModules = [ "mac80211_hwsim" ];
164
165 # wireless client
166 networking.wireless = {
167 enable = lib.mkOverride 0 true;
168 userControlled.enable = true;
169 allowAuxiliaryImperativeNetworks = true;
170 interfaces = [ "wlan1" ];
171 };
172 };
173
174 testScript = ''
175 with subtest("Daemon is running and accepting connections"):
176 machine.wait_for_unit("wpa_supplicant-wlan1.service")
177 status = machine.wait_until_succeeds("wpa_cli -i wlan1 status")
178 assert "Failed to connect" not in status, \
179 "Failed to connect to the daemon"
180
181 with subtest("Daemon can be configured imperatively"):
182 machine.succeed("wpa_cli -i wlan1 add_network")
183 machine.succeed("wpa_cli -i wlan1 set_network 0 ssid '\"nixos-test\"'")
184 machine.succeed("wpa_cli -i wlan1 set_network 0 psk '\"reproducibility\"'")
185 machine.succeed("wpa_cli -i wlan1 save_config")
186 machine.succeed("grep -q nixos-test /etc/wpa_supplicant.conf")
187 '';
188 };
189
190 # Test connecting to a SAE-only hotspot using SAE
191 saeOnly = runConnectionTest "sae-only" {
192 fallbackToWPA2 = false;
193 networks.nixos-test-sae = {
194 pskRaw = "ext:psk_nixos_test";
195 authProtocols = [ "SAE" ];
196 };
197 };
198
199 # Test connecting to a mixed SAE/WPA2 hotspot using SAE
200 mixedUsingSae = runConnectionTest "mixed-using-sae" {
201 fallbackToWPA2 = false;
202 networks.nixos-test-mixed = {
203 pskRaw = "ext:psk_nixos_test";
204 authProtocols = [ "SAE" ];
205 };
206 };
207
208 # Test connecting to a mixed SAE/WPA2 hotspot using WPA2
209 mixedUsingWpa2 = runConnectionTest "mixed-using-wpa2" {
210 fallbackToWPA2 = true;
211 networks.nixos-test-mixed = {
212 pskRaw = "ext:psk_nixos_test";
213 authProtocols = [ "WPA-PSK-SHA256" ];
214 };
215 };
216
217 # Test connecting to a legacy WPA2-only hotspot using WPA2
218 legacy = runConnectionTest "legacy" {
219 fallbackToWPA2 = true;
220 networks.nixos-test-wpa2 = {
221 pskRaw = "ext:psk_nixos_test";
222 authProtocols = [ "WPA-PSK-SHA256" ];
223 };
224 };
225}