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 runBssidTest =
17 name: expectedBssid: extraConfig:
18 runSimulatorTest name extraConfig ''
19 with subtest("Daemon can connect to the right access point"):
20 machine.wait_for_unit("wpa_supplicant-wlan1.service")
21 machine.wait_until_succeeds(
22 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
23 )
24 machine.wait_until_succeeds(
25 "wpa_cli -i wlan1 status | grep -q bssid=${expectedBssid}"
26 )
27 '';
28
29 runConnectionTest =
30 name: extraConfig:
31 runSimulatorTest name extraConfig ''
32 with subtest("Daemon can connect to the access point"):
33 machine.wait_for_unit("wpa_supplicant-wlan1.service")
34 machine.wait_until_succeeds(
35 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
36 )
37 '';
38
39 runSimulatorTest =
40 name: extraConfig: extraTestScript:
41 runTest {
42 name = "wpa_supplicant-${name}";
43 inherit meta;
44
45 nodes.machine = {
46 # add a virtual wlan interface
47 boot.kernelModules = [ "mac80211_hwsim" ];
48
49 # wireless access point
50 services.hostapd = {
51 enable = true;
52 radios.wlan0 = {
53 band = "2g";
54 channel = 6;
55 countryCode = "US";
56 networks = {
57 wlan0 = {
58 ssid = "nixos-test-sae";
59 authentication = {
60 mode = "wpa3-sae";
61 saePasswords = [ { passwordFile = pkgs.writeText "password" naughtyPassphrase; } ];
62 };
63 bssid = "02:00:00:00:00:00";
64 };
65 wlan0-1 = {
66 ssid = "nixos-test-mixed";
67 authentication = {
68 mode = "wpa3-sae-transition";
69 saeAddToMacAllow = true;
70 saePasswordsFile = pkgs.writeText "password" naughtyPassphrase;
71 wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase;
72 };
73 bssid = "02:00:00:00:00:01";
74 };
75 wlan0-2 = {
76 ssid = "nixos-test-mixed";
77 authentication = {
78 mode = "wpa3-sae-transition";
79 saeAddToMacAllow = true;
80 saePasswordsFile = pkgs.writeText "password" naughtyPassphrase;
81 wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase;
82 };
83 bssid = "02:00:00:00:00:02";
84 };
85 wlan0-3 = {
86 ssid = "nixos-test-wpa2";
87 authentication = {
88 mode = "wpa2-sha256";
89 wpaPassword = naughtyPassphrase;
90 };
91 bssid = "02:00:00:00:00:03";
92 };
93 };
94 };
95 };
96
97 # wireless client
98 networking.wireless = lib.mkMerge [
99 {
100 # the override is needed because the wifi is
101 # disabled with mkVMOverride in qemu-vm.nix.
102 enable = lib.mkOverride 0 true;
103 userControlled.enable = true;
104 interfaces = [ "wlan1" ];
105 fallbackToWPA2 = lib.mkDefault true;
106
107 # secrets
108 secretsFile = pkgs.writeText "wpa-secrets" ''
109 psk_nixos_test=${naughtyPassphrase}
110 '';
111 }
112 extraConfig
113 ];
114 };
115
116 testScript = ''
117 # save hostapd config file for manual inspection
118 machine.wait_for_unit("hostapd.service")
119 machine.copy_from_vm("/run/hostapd/wlan0.hostapd.conf")
120
121 ${extraTestScript}
122 '';
123 };
124
125in
126
127{
128 # Test the basic setup:
129 # - automatic interface discovery
130 # - WPA2 fallbacks
131 # - connecting to the daemon
132 basic = runTest {
133 name = "wpa_supplicant-basic";
134 inherit meta;
135
136 nodes.machine = {
137 # add a virtual wlan interface
138 boot.kernelModules = [ "mac80211_hwsim" ];
139
140 # wireless client
141 networking.wireless = {
142 # the override is needed because the wifi is
143 # disabled with mkVMOverride in qemu-vm.nix.
144 enable = lib.mkOverride 0 true;
145 userControlled.enable = true;
146 fallbackToWPA2 = true;
147
148 networks = {
149 # test WPA2 fallback
150 mixed-wpa = {
151 psk = "password";
152 authProtocols = [
153 "WPA-PSK"
154 "SAE"
155 ];
156 };
157 sae-only = {
158 psk = "password";
159 authProtocols = [ "SAE" ];
160 };
161
162 # Test duplicate SSID generation
163 duplicate1 = {
164 ssid = "duplicate";
165 bssid = "00:00:00:00:00:01";
166 psk = "password";
167 };
168 duplicate2 = {
169 ssid = "duplicate";
170 bssid = "00:00:00:00:00:02";
171 psk = "password";
172 };
173 };
174
175 extraConfigFiles = [
176 (pkgs.writeText "test1.conf" ''
177 network={
178 ssid="test1"
179 key_mgmt=WPA-PSK
180 psk="password1"
181 }
182 '')
183 (pkgs.writeText "test2.conf" ''
184 network={
185 ssid="test2"
186 key_mgmt=WPA-PSK
187 psk="password2"
188 }
189 '')
190 ];
191 };
192 };
193
194 testScript = ''
195 with subtest("Daemon is running and accepting connections"):
196 machine.wait_for_unit("wpa_supplicant.service")
197 status = machine.wait_until_succeeds("wpa_cli status")
198 assert "Failed to connect" not in status, \
199 "Failed to connect to the daemon"
200
201 # get the configuration file
202 cmdline = machine.succeed("cat /proc/$(pgrep wpa)/cmdline").split('\x00')
203 config_file = cmdline[cmdline.index("-c") + 1]
204
205 with subtest("WPA2 fallbacks have been generated"):
206 assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1
207 assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2
208
209 with subtest("Duplicate SSID network blocks have been generated"):
210 # more duplication due to fallbacks
211 assert int(machine.succeed(f"grep -c duplicate {config_file}")) == 4
212 assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:01 {config_file}")) == 2
213 assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:02 {config_file}")) == 2
214
215 with subtest("Extra config files have been loaded"):
216 machine.wait_until_succeeds("wpa_cli -i wlan0 list_networks | grep -q test1")
217 machine.succeed("wpa_cli -i wlan0 list_networks | grep -q test2")
218
219 # save file for manual inspection
220 machine.copy_from_vm(config_file)
221 '';
222 };
223
224 # Test configuring the daemon imperatively
225 imperative = runTest {
226 name = "wpa_supplicant-imperative";
227 inherit meta;
228
229 nodes.machine = {
230 # add a virtual wlan interface
231 boot.kernelModules = [ "mac80211_hwsim" ];
232
233 # wireless client
234 networking.wireless = {
235 enable = lib.mkOverride 0 true;
236 userControlled.enable = true;
237 allowAuxiliaryImperativeNetworks = true;
238 interfaces = [ "wlan1" ];
239 };
240 };
241
242 testScript = ''
243 with subtest("Daemon is running and accepting connections"):
244 machine.wait_for_unit("wpa_supplicant-wlan1.service")
245 status = machine.wait_until_succeeds("wpa_cli -i wlan1 status")
246 assert "Failed to connect" not in status, \
247 "Failed to connect to the daemon"
248
249 with subtest("Daemon can be configured imperatively"):
250 machine.succeed("wpa_cli -i wlan1 add_network")
251 machine.succeed("wpa_cli -i wlan1 set_network 0 ssid '\"nixos-test\"'")
252 machine.succeed("wpa_cli -i wlan1 set_network 0 psk '\"reproducibility\"'")
253 machine.succeed("wpa_cli -i wlan1 save_config")
254 machine.succeed("grep -q nixos-test /etc/wpa_supplicant.conf")
255 '';
256 };
257
258 # Test connecting to a SAE-only hotspot using SAE
259 saeOnly = runConnectionTest "sae-only" {
260 fallbackToWPA2 = false;
261 networks.nixos-test-sae = {
262 pskRaw = "ext:psk_nixos_test";
263 authProtocols = [ "SAE" ];
264 };
265 };
266
267 # Test connecting to a mixed SAE/WPA2 hotspot using SAE
268 mixedUsingSae = runConnectionTest "mixed-using-sae" {
269 fallbackToWPA2 = false;
270 networks.nixos-test-mixed = {
271 pskRaw = "ext:psk_nixos_test";
272 authProtocols = [ "SAE" ];
273 };
274 };
275
276 # Test connecting to a mixed SAE/WPA2 hotspot using WPA2
277 mixedUsingWpa2 = runConnectionTest "mixed-using-wpa2" {
278 fallbackToWPA2 = true;
279 networks.nixos-test-mixed = {
280 pskRaw = "ext:psk_nixos_test";
281 authProtocols = [ "WPA-PSK-SHA256" ];
282 };
283 };
284
285 # Test connecting to a legacy WPA2-only hotspot using WPA2
286 legacy = runConnectionTest "legacy" {
287 fallbackToWPA2 = true;
288 networks.nixos-test-wpa2 = {
289 pskRaw = "ext:psk_nixos_test";
290 authProtocols = [ "WPA-PSK-SHA256" ];
291 };
292 };
293
294 # Test connection with the highest prio "matching" network block found.
295 # "Matching" meaning with the right SSID and BSSID
296 bssidGuard = runBssidTest "bssid-guard" "02:00:00:00:00:02" {
297 networks = {
298 "1_first" = {
299 ssid = "nixos-test-mixed";
300 bssid = "02:00:00:00:00:01";
301 pskRaw = "ext:psk_nixos_test";
302 };
303 "2_second" = {
304 ssid = "nixos-test-mixed";
305 bssid = "02:00:00:00:00:02";
306 pskRaw = "ext:psk_nixos_test";
307 priority = 1;
308 };
309 };
310 };
311
312}