]>
Commit | Line | Data |
---|---|---|
ebc61dc2 JM |
1 | # GAS tests |
2 | # Copyright (c) 2013, Qualcomm Atheros, Inc. | |
d7a99700 | 3 | # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi> |
ebc61dc2 JM |
4 | # |
5 | # This software may be distributed under the terms of the BSD license. | |
6 | # See README for more details. | |
7 | ||
8 | import time | |
bfe375ec | 9 | import binascii |
ebc61dc2 | 10 | import logging |
c9aa4308 | 11 | logger = logging.getLogger() |
ebc61dc2 | 12 | import re |
d9474958 | 13 | import struct |
ebc61dc2 JM |
14 | |
15 | import hostapd | |
1d1de230 | 16 | from wpasupplicant import WpaSupplicant |
ebc61dc2 JM |
17 | |
18 | def hs20_ap_params(): | |
19 | params = hostapd.wpa2_params(ssid="test-gas") | |
20 | params['wpa_key_mgmt'] = "WPA-EAP" | |
21 | params['ieee80211w'] = "1" | |
22 | params['ieee8021x'] = "1" | |
23 | params['auth_server_addr'] = "127.0.0.1" | |
24 | params['auth_server_port'] = "1812" | |
25 | params['auth_server_shared_secret'] = "radius" | |
26 | params['interworking'] = "1" | |
27 | params['access_network_type'] = "14" | |
28 | params['internet'] = "1" | |
29 | params['asra'] = "0" | |
30 | params['esr'] = "0" | |
31 | params['uesa'] = "0" | |
32 | params['venue_group'] = "7" | |
33 | params['venue_type'] = "1" | |
34 | params['venue_name'] = [ "eng:Example venue", "fin:Esimerkkipaikka" ] | |
35 | params['roaming_consortium'] = [ "112233", "1020304050", "010203040506", | |
36 | "fedcba" ] | |
37 | params['domain_name'] = "example.com,another.example.com" | |
38 | params['nai_realm'] = [ "0,example.com,13[5:6],21[2:4][5:7]", | |
39 | "0,another.example.com" ] | |
40 | params['anqp_3gpp_cell_net'] = "244,91" | |
cef16c47 JM |
41 | params['network_auth_type'] = "02http://www.example.com/redirect/me/here/" |
42 | params['ipaddr_type_availability'] = "14" | |
43 | params['hs20'] = "1" | |
44 | params['hs20_oper_friendly_name'] = [ "eng:Example operator", "fin:Esimerkkioperaattori" ] | |
45 | params['hs20_wan_metrics'] = "01:8000:1000:80:240:3000" | |
46 | params['hs20_conn_capab'] = [ "1:0:2", "6:22:1", "17:5060:0" ] | |
47 | params['hs20_operating_class'] = "5173" | |
ebc61dc2 JM |
48 | return params |
49 | ||
18dd2af0 JM |
50 | def start_ap(ap): |
51 | params = hs20_ap_params() | |
52 | params['hessid'] = ap['bssid'] | |
53 | hostapd.add_ap(ap['ifname'], params) | |
54 | return hostapd.Hostapd(ap['ifname']) | |
55 | ||
5e80b502 JM |
56 | def get_gas_response(dev, bssid, info, allow_fetch_failure=False, |
57 | extra_test=False): | |
ebc61dc2 JM |
58 | exp = r'<.>(GAS-RESPONSE-INFO) addr=([0-9a-f:]*) dialog_token=([0-9]*) status_code=([0-9]*) resp_len=([\-0-9]*)' |
59 | res = re.split(exp, info) | |
60 | if len(res) < 6: | |
61 | raise Exception("Could not parse GAS-RESPONSE-INFO") | |
62 | if res[2] != bssid: | |
63 | raise Exception("Unexpected BSSID in response") | |
64 | token = res[3] | |
65 | status = res[4] | |
66 | if status != "0": | |
67 | raise Exception("GAS query failed") | |
68 | resp_len = res[5] | |
69 | if resp_len == "-1": | |
70 | raise Exception("GAS query reported invalid response length") | |
71 | if int(resp_len) > 2000: | |
72 | raise Exception("Unexpected long GAS response") | |
73 | ||
5e80b502 JM |
74 | if extra_test: |
75 | if "FAIL" not in dev.request("GAS_RESPONSE_GET " + bssid + " 123456"): | |
76 | raise Exception("Invalid dialog token accepted") | |
77 | if "FAIL-Invalid range" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 10000,10001"): | |
78 | raise Exception("Invalid range accepted") | |
79 | if "FAIL-Invalid range" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 0,10000"): | |
80 | raise Exception("Invalid range accepted") | |
81 | if "FAIL" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 0"): | |
82 | raise Exception("Invalid GAS_RESPONSE_GET accepted") | |
83 | ||
84 | res1_2 = dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 1,2") | |
85 | res5_3 = dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 5,3") | |
86 | ||
ebc61dc2 JM |
87 | resp = dev.request("GAS_RESPONSE_GET " + bssid + " " + token) |
88 | if "FAIL" in resp: | |
89 | if allow_fetch_failure: | |
90 | logger.debug("GAS response was not available anymore") | |
91 | return | |
92 | raise Exception("Could not fetch GAS response") | |
93 | if len(resp) != int(resp_len) * 2: | |
94 | raise Exception("Unexpected GAS response length") | |
95 | logger.debug("GAS response: " + resp) | |
5e80b502 JM |
96 | if extra_test: |
97 | if resp[2:6] != res1_2: | |
98 | raise Exception("Unexpected response substring res1_2: " + res1_2) | |
99 | if resp[10:16] != res5_3: | |
100 | raise Exception("Unexpected response substring res5_3: " + res5_3) | |
ebc61dc2 JM |
101 | |
102 | def test_gas_generic(dev, apdev): | |
103 | """Generic GAS query""" | |
104 | bssid = apdev[0]['bssid'] | |
105 | params = hs20_ap_params() | |
106 | params['hessid'] = bssid | |
107 | hostapd.add_ap(apdev[0]['ifname'], params) | |
108 | ||
5e80b502 JM |
109 | cmds = [ "foo", |
110 | "00:11:22:33:44:55", | |
111 | "00:11:22:33:44:55 ", | |
112 | "00:11:22:33:44:55 ", | |
113 | "00:11:22:33:44:55 1", | |
114 | "00:11:22:33:44:55 1 1234", | |
115 | "00:11:22:33:44:55 qq", | |
116 | "00:11:22:33:44:55 qq 1234", | |
117 | "00:11:22:33:44:55 00 1", | |
118 | "00:11:22:33:44:55 00 123", | |
119 | "00:11:22:33:44:55 00 ", | |
120 | "00:11:22:33:44:55 00 qq" ] | |
121 | for cmd in cmds: | |
122 | if "FAIL" not in dev[0].request("GAS_REQUEST " + cmd): | |
123 | raise Exception("Invalid GAS_REQUEST accepted: " + cmd) | |
124 | ||
4ef70531 | 125 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
ebc61dc2 JM |
126 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101") |
127 | if "FAIL" in req: | |
128 | raise Exception("GAS query request rejected") | |
129 | ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10) | |
130 | if ev is None: | |
131 | raise Exception("GAS query timed out") | |
5e80b502 JM |
132 | get_gas_response(dev[0], bssid, ev, extra_test=True) |
133 | ||
134 | if "FAIL" not in dev[0].request("GAS_RESPONSE_GET ff"): | |
135 | raise Exception("Invalid GAS_RESPONSE_GET accepted") | |
ebc61dc2 JM |
136 | |
137 | def test_gas_concurrent_scan(dev, apdev): | |
138 | """Generic GAS queries with concurrent scan operation""" | |
139 | bssid = apdev[0]['bssid'] | |
140 | params = hs20_ap_params() | |
141 | params['hessid'] = bssid | |
142 | hostapd.add_ap(apdev[0]['ifname'], params) | |
143 | ||
d7a99700 | 144 | # get BSS entry available to allow GAS query |
4ef70531 | 145 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
ebc61dc2 JM |
146 | |
147 | logger.info("Request concurrent operations") | |
148 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101") | |
149 | if "FAIL" in req: | |
150 | raise Exception("GAS query request rejected") | |
151 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000801") | |
152 | if "FAIL" in req: | |
153 | raise Exception("GAS query request rejected") | |
d7a99700 | 154 | dev[0].scan(no_wait=True) |
ebc61dc2 JM |
155 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000201") |
156 | if "FAIL" in req: | |
157 | raise Exception("GAS query request rejected") | |
158 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000501") | |
159 | if "FAIL" in req: | |
160 | raise Exception("GAS query request rejected") | |
161 | ||
162 | responses = 0 | |
163 | for i in range(0, 5): | |
164 | ev = dev[0].wait_event(["GAS-RESPONSE-INFO", "CTRL-EVENT-SCAN-RESULTS"], | |
165 | timeout=10) | |
166 | if ev is None: | |
167 | raise Exception("Operation timed out") | |
168 | if "GAS-RESPONSE-INFO" in ev: | |
169 | responses = responses + 1 | |
170 | get_gas_response(dev[0], bssid, ev, allow_fetch_failure=True) | |
171 | ||
172 | if responses != 4: | |
173 | raise Exception("Unexpected number of GAS responses") | |
174 | ||
175 | def test_gas_concurrent_connect(dev, apdev): | |
176 | """Generic GAS queries with concurrent connection operation""" | |
177 | bssid = apdev[0]['bssid'] | |
178 | params = hs20_ap_params() | |
179 | params['hessid'] = bssid | |
180 | hostapd.add_ap(apdev[0]['ifname'], params) | |
181 | ||
4ef70531 | 182 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
ebc61dc2 JM |
183 | |
184 | logger.debug("Start concurrent connect and GAS request") | |
185 | dev[0].connect("test-gas", key_mgmt="WPA-EAP", eap="TTLS", | |
186 | identity="DOMAIN\mschapv2 user", anonymous_identity="ttls", | |
187 | password="password", phase2="auth=MSCHAPV2", | |
d7a99700 JM |
188 | ca_cert="auth_serv/ca.pem", wait_connect=False, |
189 | scan_freq="2412") | |
ebc61dc2 JM |
190 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101") |
191 | if "FAIL" in req: | |
192 | raise Exception("GAS query request rejected") | |
193 | ||
194 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", "GAS-RESPONSE-INFO"], | |
195 | timeout=20) | |
196 | if ev is None: | |
197 | raise Exception("Operation timed out") | |
198 | if "CTRL-EVENT-CONNECTED" not in ev: | |
199 | raise Exception("Unexpected operation order") | |
200 | ||
201 | ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", "GAS-RESPONSE-INFO"], | |
202 | timeout=20) | |
203 | if ev is None: | |
204 | raise Exception("Operation timed out") | |
205 | if "GAS-RESPONSE-INFO" not in ev: | |
206 | raise Exception("Unexpected operation order") | |
207 | get_gas_response(dev[0], bssid, ev) | |
208 | ||
209 | dev[0].request("DISCONNECT") | |
5f35a5e2 | 210 | dev[0].wait_disconnected(timeout=5) |
ebc61dc2 JM |
211 | |
212 | logger.debug("Wait six seconds for expiration of connect-without-scan") | |
213 | time.sleep(6) | |
adf277a0 | 214 | dev[0].dump_monitor() |
ebc61dc2 JM |
215 | |
216 | logger.debug("Start concurrent GAS request and connect") | |
217 | req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101") | |
218 | if "FAIL" in req: | |
219 | raise Exception("GAS query request rejected") | |
220 | dev[0].request("RECONNECT") | |
221 | ||
222 | ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10) | |
223 | if ev is None: | |
224 | raise Exception("Operation timed out") | |
225 | get_gas_response(dev[0], bssid, ev) | |
226 | ||
227 | ev = dev[0].wait_event(["CTRL-EVENT-SCAN-RESULTS"], timeout=20) | |
228 | if ev is None: | |
229 | raise Exception("No new scan results reported") | |
230 | ||
5f35a5e2 | 231 | ev = dev[0].wait_connected(timeout=20, error="Operation tiemd out") |
ebc61dc2 JM |
232 | if "CTRL-EVENT-CONNECTED" not in ev: |
233 | raise Exception("Unexpected operation order") | |
836a3745 JM |
234 | |
235 | def test_gas_fragment(dev, apdev): | |
236 | """GAS fragmentation""" | |
18dd2af0 | 237 | hapd = start_ap(apdev[0]) |
836a3745 JM |
238 | hapd.set("gas_frag_limit", "50") |
239 | ||
4ef70531 | 240 | dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True) |
e4a44b3c | 241 | dev[0].request("FETCH_ANQP") |
cef16c47 JM |
242 | for i in range(0, 13): |
243 | ev = dev[0].wait_event(["RX-ANQP", "RX-HS20-ANQP"], timeout=5) | |
e4a44b3c JM |
244 | if ev is None: |
245 | raise Exception("Operation timed out") | |
246 | ||
247 | def test_gas_comeback_delay(dev, apdev): | |
248 | """GAS fragmentation""" | |
18dd2af0 | 249 | hapd = start_ap(apdev[0]) |
e4a44b3c JM |
250 | hapd.set("gas_comeback_delay", "500") |
251 | ||
4ef70531 | 252 | dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True) |
836a3745 JM |
253 | dev[0].request("FETCH_ANQP") |
254 | for i in range(0, 6): | |
255 | ev = dev[0].wait_event(["RX-ANQP"], timeout=5) | |
256 | if ev is None: | |
257 | raise Exception("Operation timed out") | |
2cace98e | 258 | |
ea27f662 JM |
259 | def test_gas_stop_fetch_anqp(dev, apdev): |
260 | """Stop FETCH_ANQP operation""" | |
261 | hapd = start_ap(apdev[0]) | |
262 | ||
263 | dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True) | |
264 | hapd.set("ext_mgmt_frame_handling", "1") | |
265 | dev[0].request("FETCH_ANQP") | |
266 | dev[0].request("STOP_FETCH_ANQP") | |
267 | hapd.set("ext_mgmt_frame_handling", "0") | |
268 | ev = dev[0].wait_event(["RX-ANQP", "GAS-QUERY-DONE"], timeout=10) | |
269 | if ev is None: | |
270 | raise Exception("GAS-QUERY-DONE timed out") | |
271 | if "RX-ANQP" in ev: | |
272 | raise Exception("Unexpected ANQP response received") | |
273 | ||
e56e286d JM |
274 | def test_gas_anqp_get(dev, apdev): |
275 | """GAS/ANQP query for both IEEE 802.11 and Hotspot 2.0 elements""" | |
276 | hapd = start_ap(apdev[0]) | |
277 | bssid = apdev[0]['bssid'] | |
278 | ||
4ef70531 JM |
279 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
280 | if "OK" not in dev[0].request("ANQP_GET " + bssid + " 258,268,hs20:3,hs20:4"): | |
281 | raise Exception("ANQP_GET command failed") | |
e56e286d JM |
282 | |
283 | ev = dev[0].wait_event(["GAS-QUERY-START"], timeout=5) | |
284 | if ev is None: | |
285 | raise Exception("GAS query start timed out") | |
286 | ||
287 | ev = dev[0].wait_event(["GAS-QUERY-DONE"], timeout=10) | |
288 | if ev is None: | |
289 | raise Exception("GAS query timed out") | |
290 | ||
291 | ev = dev[0].wait_event(["RX-ANQP"], timeout=1) | |
292 | if ev is None or "Venue Name" not in ev: | |
293 | raise Exception("Did not receive Venue Name") | |
294 | ||
295 | ev = dev[0].wait_event(["RX-ANQP"], timeout=1) | |
296 | if ev is None or "Domain Name list" not in ev: | |
297 | raise Exception("Did not receive Domain Name list") | |
298 | ||
299 | ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1) | |
300 | if ev is None or "Operator Friendly Name" not in ev: | |
6d6f3c09 JM |
301 | raise Exception("Did not receive Operator Friendly Name") |
302 | ||
303 | ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1) | |
304 | if ev is None or "WAN Metrics" not in ev: | |
305 | raise Exception("Did not receive WAN Metrics") | |
306 | ||
4ef70531 JM |
307 | if "OK" not in dev[0].request("HS20_ANQP_GET " + bssid + " 3,4"): |
308 | raise Exception("ANQP_GET command failed") | |
6d6f3c09 JM |
309 | |
310 | ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1) | |
311 | if ev is None or "Operator Friendly Name" not in ev: | |
e56e286d JM |
312 | raise Exception("Did not receive Operator Friendly Name") |
313 | ||
314 | ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1) | |
315 | if ev is None or "WAN Metrics" not in ev: | |
316 | raise Exception("Did not receive WAN Metrics") | |
317 | ||
ea68ed56 JM |
318 | cmds = [ "", |
319 | "foo", | |
320 | "00:11:22:33:44:55 258,hs20:-1", | |
321 | "00:11:22:33:44:55 258,hs20:0", | |
322 | "00:11:22:33:44:55 258,hs20:32", | |
323 | "00:11:22:33:44:55 hs20:-1", | |
324 | "00:11:22:33:44:55 hs20:0", | |
325 | "00:11:22:33:44:55 hs20:32", | |
326 | "00:11:22:33:44:55", | |
327 | "00:11:22:33:44:55 ", | |
328 | "00:11:22:33:44:55 0" ] | |
329 | for cmd in cmds: | |
330 | if "FAIL" not in dev[0].request("ANQP_GET " + cmd): | |
331 | raise Exception("Invalid ANQP_GET accepted") | |
332 | ||
333 | cmds = [ "", | |
334 | "foo", | |
335 | "00:11:22:33:44:55 -1", | |
336 | "00:11:22:33:44:55 0", | |
337 | "00:11:22:33:44:55 32", | |
338 | "00:11:22:33:44:55", | |
339 | "00:11:22:33:44:55 ", | |
340 | "00:11:22:33:44:55 0" ] | |
341 | for cmd in cmds: | |
342 | if "FAIL" not in dev[0].request("HS20_ANQP_GET " + cmd): | |
343 | raise Exception("Invalid HS20_ANQP_GET accepted") | |
344 | ||
75f6134d | 345 | def expect_gas_result(dev, result, status=None): |
bfe375ec JM |
346 | ev = dev.wait_event(["GAS-QUERY-DONE"], timeout=10) |
347 | if ev is None: | |
348 | raise Exception("GAS query timed out") | |
349 | if "result=" + result not in ev: | |
350 | raise Exception("Unexpected GAS query result") | |
75f6134d JM |
351 | if status and "status_code=" + str(status) + ' ' not in ev: |
352 | raise Exception("Unexpected GAS status code") | |
bfe375ec | 353 | |
18dd2af0 | 354 | def anqp_get(dev, bssid, id): |
4ef70531 JM |
355 | if "OK" not in dev.request("ANQP_GET " + bssid + " " + str(id)): |
356 | raise Exception("ANQP_GET command failed") | |
18dd2af0 JM |
357 | ev = dev.wait_event(["GAS-QUERY-START"], timeout=5) |
358 | if ev is None: | |
359 | raise Exception("GAS query start timed out") | |
360 | ||
2cace98e JM |
361 | def test_gas_timeout(dev, apdev): |
362 | """GAS timeout""" | |
18dd2af0 | 363 | hapd = start_ap(apdev[0]) |
2cace98e | 364 | bssid = apdev[0]['bssid'] |
2cace98e | 365 | |
4ef70531 | 366 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
2cace98e JM |
367 | hapd.set("ext_mgmt_frame_handling", "1") |
368 | ||
18dd2af0 | 369 | anqp_get(dev[0], bssid, 263) |
2cace98e JM |
370 | |
371 | ev = hapd.wait_event(["MGMT-RX"], timeout=5) | |
372 | if ev is None: | |
373 | raise Exception("MGMT RX wait timed out") | |
374 | ||
bfe375ec JM |
375 | expect_gas_result(dev[0], "TIMEOUT") |
376 | ||
d9474958 JM |
377 | MGMT_SUBTYPE_ACTION = 13 |
378 | ACTION_CATEG_PUBLIC = 4 | |
379 | ||
380 | GAS_INITIAL_REQUEST = 10 | |
381 | GAS_INITIAL_RESPONSE = 11 | |
382 | GAS_COMEBACK_REQUEST = 12 | |
383 | GAS_COMEBACK_RESPONSE = 13 | |
384 | GAS_ACTIONS = [ GAS_INITIAL_REQUEST, GAS_INITIAL_RESPONSE, | |
385 | GAS_COMEBACK_REQUEST, GAS_COMEBACK_RESPONSE ] | |
386 | ||
387 | def anqp_adv_proto(): | |
388 | return struct.pack('BBBB', 108, 2, 127, 0) | |
389 | ||
75f6134d | 390 | def anqp_initial_resp(dialog_token, status_code, comeback_delay=0): |
fe871e48 | 391 | return struct.pack('<BBBHH', ACTION_CATEG_PUBLIC, GAS_INITIAL_RESPONSE, |
75f6134d JM |
392 | dialog_token, status_code, comeback_delay) + anqp_adv_proto() |
393 | ||
394 | def anqp_comeback_resp(dialog_token, status_code=0, id=0, more=False, comeback_delay=0, bogus_adv_proto=False): | |
395 | if more: | |
396 | id |= 0x80 | |
397 | if bogus_adv_proto: | |
398 | adv = struct.pack('BBBB', 108, 2, 127, 1) | |
399 | else: | |
400 | adv = anqp_adv_proto() | |
d9474958 | 401 | return struct.pack('<BBBHBH', ACTION_CATEG_PUBLIC, GAS_COMEBACK_RESPONSE, |
75f6134d | 402 | dialog_token, status_code, id, comeback_delay) + adv |
d9474958 JM |
403 | |
404 | def gas_rx(hapd): | |
405 | count = 0 | |
406 | while count < 30: | |
407 | count = count + 1 | |
408 | query = hapd.mgmt_rx() | |
409 | if query is None: | |
410 | raise Exception("Action frame not received") | |
411 | if query['subtype'] != MGMT_SUBTYPE_ACTION: | |
412 | continue | |
413 | payload = query['payload'] | |
414 | if len(payload) < 2: | |
415 | continue | |
416 | (category, action) = struct.unpack('BB', payload[0:2]) | |
417 | if category != ACTION_CATEG_PUBLIC or action not in GAS_ACTIONS: | |
418 | continue | |
419 | return query | |
420 | raise Exception("No Action frame received") | |
421 | ||
422 | def parse_gas(payload): | |
423 | pos = payload | |
424 | (category, action, dialog_token) = struct.unpack('BBB', pos[0:3]) | |
425 | if category != ACTION_CATEG_PUBLIC: | |
426 | return None | |
427 | if action not in GAS_ACTIONS: | |
428 | return None | |
429 | gas = {} | |
430 | gas['action'] = action | |
431 | pos = pos[3:] | |
432 | ||
75f6134d | 433 | if len(pos) < 1 and action != GAS_COMEBACK_REQUEST: |
d9474958 JM |
434 | return None |
435 | ||
436 | gas['dialog_token'] = dialog_token | |
1d1de230 JM |
437 | |
438 | if action == GAS_INITIAL_RESPONSE: | |
439 | if len(pos) < 4: | |
440 | return None | |
441 | (status_code, comeback_delay) = struct.unpack('<HH', pos[0:4]) | |
442 | gas['status_code'] = status_code | |
443 | gas['comeback_delay'] = comeback_delay | |
de8c4144 JM |
444 | |
445 | if action == GAS_COMEBACK_RESPONSE: | |
446 | if len(pos) < 5: | |
447 | return None | |
448 | (status_code, frag, comeback_delay) = struct.unpack('<HBH', pos[0:5]) | |
449 | gas['status_code'] = status_code | |
450 | gas['frag'] = frag | |
451 | gas['comeback_delay'] = comeback_delay | |
452 | ||
d9474958 JM |
453 | return gas |
454 | ||
455 | def action_response(req): | |
456 | resp = {} | |
457 | resp['fc'] = req['fc'] | |
458 | resp['da'] = req['sa'] | |
459 | resp['sa'] = req['da'] | |
460 | resp['bssid'] = req['bssid'] | |
461 | return resp | |
462 | ||
75f6134d JM |
463 | def send_gas_resp(hapd, resp): |
464 | hapd.mgmt_tx(resp) | |
465 | ev = hapd.wait_event(["MGMT-TX-STATUS"], timeout=5) | |
466 | if ev is None: | |
467 | raise Exception("Missing TX status for GAS response") | |
468 | if "ok=1" not in ev: | |
469 | raise Exception("GAS response not acknowledged") | |
470 | ||
bfe375ec JM |
471 | def test_gas_invalid_response_type(dev, apdev): |
472 | """GAS invalid response type""" | |
18dd2af0 | 473 | hapd = start_ap(apdev[0]) |
bfe375ec | 474 | bssid = apdev[0]['bssid'] |
bfe375ec | 475 | |
4ef70531 | 476 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
bfe375ec JM |
477 | hapd.set("ext_mgmt_frame_handling", "1") |
478 | ||
18dd2af0 | 479 | anqp_get(dev[0], bssid, 263) |
bfe375ec | 480 | |
d9474958 JM |
481 | query = gas_rx(hapd) |
482 | gas = parse_gas(query['payload']) | |
483 | ||
484 | resp = action_response(query) | |
bfe375ec | 485 | # GAS Comeback Response instead of GAS Initial Response |
d9474958 | 486 | resp['payload'] = anqp_comeback_resp(gas['dialog_token']) + struct.pack('<H', 0) |
75f6134d | 487 | send_gas_resp(hapd, resp) |
bfe375ec JM |
488 | |
489 | # station drops the invalid frame, so this needs to result in GAS timeout | |
490 | expect_gas_result(dev[0], "TIMEOUT") | |
fe871e48 JM |
491 | |
492 | def test_gas_failure_status_code(dev, apdev): | |
493 | """GAS failure status code""" | |
494 | hapd = start_ap(apdev[0]) | |
495 | bssid = apdev[0]['bssid'] | |
496 | ||
4ef70531 | 497 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
fe871e48 JM |
498 | hapd.set("ext_mgmt_frame_handling", "1") |
499 | ||
500 | anqp_get(dev[0], bssid, 263) | |
501 | ||
502 | query = gas_rx(hapd) | |
503 | gas = parse_gas(query['payload']) | |
504 | ||
505 | resp = action_response(query) | |
506 | resp['payload'] = anqp_initial_resp(gas['dialog_token'], 61) + struct.pack('<H', 0) | |
75f6134d | 507 | send_gas_resp(hapd, resp) |
fe871e48 JM |
508 | |
509 | expect_gas_result(dev[0], "FAILURE") | |
84262fef JM |
510 | |
511 | def test_gas_malformed(dev, apdev): | |
512 | """GAS malformed response frames""" | |
513 | hapd = start_ap(apdev[0]) | |
514 | bssid = apdev[0]['bssid'] | |
515 | ||
4ef70531 | 516 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
84262fef JM |
517 | hapd.set("ext_mgmt_frame_handling", "1") |
518 | ||
519 | anqp_get(dev[0], bssid, 263) | |
520 | ||
521 | query = gas_rx(hapd) | |
522 | gas = parse_gas(query['payload']) | |
523 | ||
524 | resp = action_response(query) | |
525 | ||
526 | resp['payload'] = struct.pack('<BBBH', ACTION_CATEG_PUBLIC, | |
527 | GAS_COMEBACK_RESPONSE, | |
528 | gas['dialog_token'], 0) | |
529 | hapd.mgmt_tx(resp) | |
530 | ||
531 | resp['payload'] = struct.pack('<BBBHB', ACTION_CATEG_PUBLIC, | |
532 | GAS_COMEBACK_RESPONSE, | |
533 | gas['dialog_token'], 0, 0) | |
534 | hapd.mgmt_tx(resp) | |
535 | ||
536 | hdr = struct.pack('<BBBHH', ACTION_CATEG_PUBLIC, GAS_INITIAL_RESPONSE, | |
537 | gas['dialog_token'], 0, 0) | |
538 | resp['payload'] = hdr + struct.pack('B', 108) | |
539 | hapd.mgmt_tx(resp) | |
540 | resp['payload'] = hdr + struct.pack('BB', 108, 0) | |
541 | hapd.mgmt_tx(resp) | |
542 | resp['payload'] = hdr + struct.pack('BB', 108, 1) | |
543 | hapd.mgmt_tx(resp) | |
544 | resp['payload'] = hdr + struct.pack('BB', 108, 255) | |
545 | hapd.mgmt_tx(resp) | |
546 | resp['payload'] = hdr + struct.pack('BBB', 108, 1, 127) | |
547 | hapd.mgmt_tx(resp) | |
548 | resp['payload'] = hdr + struct.pack('BBB', 108, 2, 127) | |
549 | hapd.mgmt_tx(resp) | |
550 | resp['payload'] = hdr + struct.pack('BBBB', 0, 2, 127, 0) | |
551 | hapd.mgmt_tx(resp) | |
552 | ||
553 | resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<H', 1) | |
554 | hapd.mgmt_tx(resp) | |
555 | ||
556 | resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<HB', 2, 0) | |
557 | hapd.mgmt_tx(resp) | |
558 | ||
559 | resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<H', 65535) | |
560 | hapd.mgmt_tx(resp) | |
561 | ||
562 | resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<HBB', 1, 0, 0) | |
563 | hapd.mgmt_tx(resp) | |
564 | ||
565 | # Station drops invalid frames, but the last of the responses is valid from | |
566 | # GAS view point even though it has an extra octet in the end and the ANQP | |
567 | # part of the response is not valid. This is reported as successfulyl | |
568 | # completed GAS exchange. | |
569 | expect_gas_result(dev[0], "SUCCESS") | |
75f6134d JM |
570 | |
571 | def init_gas(hapd, bssid, dev): | |
572 | anqp_get(dev, bssid, 263) | |
573 | query = gas_rx(hapd) | |
574 | gas = parse_gas(query['payload']) | |
575 | dialog_token = gas['dialog_token'] | |
576 | ||
577 | resp = action_response(query) | |
578 | resp['payload'] = anqp_initial_resp(dialog_token, 0, comeback_delay=1) + struct.pack('<H', 0) | |
579 | send_gas_resp(hapd, resp) | |
580 | ||
581 | query = gas_rx(hapd) | |
582 | gas = parse_gas(query['payload']) | |
583 | if gas['action'] != GAS_COMEBACK_REQUEST: | |
584 | raise Exception("Unexpected request action") | |
585 | if gas['dialog_token'] != dialog_token: | |
586 | raise Exception("Unexpected dialog token change") | |
587 | return query, dialog_token | |
588 | ||
589 | def test_gas_malformed_comeback_resp(dev, apdev): | |
590 | """GAS malformed comeback response frames""" | |
591 | hapd = start_ap(apdev[0]) | |
592 | bssid = apdev[0]['bssid'] | |
593 | ||
4ef70531 | 594 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
75f6134d JM |
595 | hapd.set("ext_mgmt_frame_handling", "1") |
596 | ||
597 | logger.debug("Non-zero status code in comeback response") | |
598 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
599 | resp = action_response(query) | |
600 | resp['payload'] = anqp_comeback_resp(dialog_token, status_code=2) + struct.pack('<H', 0) | |
601 | send_gas_resp(hapd, resp) | |
602 | expect_gas_result(dev[0], "FAILURE", status=2) | |
603 | ||
604 | logger.debug("Different advertisement protocol in comeback response") | |
605 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
606 | resp = action_response(query) | |
607 | resp['payload'] = anqp_comeback_resp(dialog_token, bogus_adv_proto=True) + struct.pack('<H', 0) | |
608 | send_gas_resp(hapd, resp) | |
609 | expect_gas_result(dev[0], "PEER_ERROR") | |
610 | ||
611 | logger.debug("Non-zero frag id and comeback delay in comeback response") | |
612 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
613 | resp = action_response(query) | |
614 | resp['payload'] = anqp_comeback_resp(dialog_token, id=1, comeback_delay=1) + struct.pack('<H', 0) | |
615 | send_gas_resp(hapd, resp) | |
616 | expect_gas_result(dev[0], "PEER_ERROR") | |
617 | ||
618 | logger.debug("Unexpected frag id in comeback response") | |
619 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
620 | resp = action_response(query) | |
621 | resp['payload'] = anqp_comeback_resp(dialog_token, id=1) + struct.pack('<H', 0) | |
622 | send_gas_resp(hapd, resp) | |
623 | expect_gas_result(dev[0], "PEER_ERROR") | |
624 | ||
625 | logger.debug("Empty fragment and replay in comeback response") | |
626 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
627 | resp = action_response(query) | |
628 | resp['payload'] = anqp_comeback_resp(dialog_token, more=True) + struct.pack('<H', 0) | |
629 | send_gas_resp(hapd, resp) | |
630 | query = gas_rx(hapd) | |
631 | gas = parse_gas(query['payload']) | |
632 | if gas['action'] != GAS_COMEBACK_REQUEST: | |
633 | raise Exception("Unexpected request action") | |
634 | if gas['dialog_token'] != dialog_token: | |
635 | raise Exception("Unexpected dialog token change") | |
636 | resp = action_response(query) | |
637 | resp['payload'] = anqp_comeback_resp(dialog_token) + struct.pack('<H', 0) | |
638 | send_gas_resp(hapd, resp) | |
639 | resp['payload'] = anqp_comeback_resp(dialog_token, id=1) + struct.pack('<H', 0) | |
640 | send_gas_resp(hapd, resp) | |
641 | expect_gas_result(dev[0], "SUCCESS") | |
642 | ||
643 | logger.debug("Unexpected initial response when waiting for comeback response") | |
644 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
645 | resp = action_response(query) | |
646 | resp['payload'] = anqp_initial_resp(dialog_token, 0) + struct.pack('<H', 0) | |
647 | send_gas_resp(hapd, resp) | |
648 | ev = hapd.wait_event(["MGMT-RX"], timeout=1) | |
649 | if ev is not None: | |
650 | raise Exception("Unexpected management frame") | |
651 | expect_gas_result(dev[0], "TIMEOUT") | |
652 | ||
653 | logger.debug("Too short comeback response") | |
654 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
655 | resp = action_response(query) | |
656 | resp['payload'] = struct.pack('<BBBH', ACTION_CATEG_PUBLIC, | |
657 | GAS_COMEBACK_RESPONSE, dialog_token, 0) | |
658 | send_gas_resp(hapd, resp) | |
659 | ev = hapd.wait_event(["MGMT-RX"], timeout=1) | |
660 | if ev is not None: | |
661 | raise Exception("Unexpected management frame") | |
662 | expect_gas_result(dev[0], "TIMEOUT") | |
663 | ||
664 | logger.debug("Too short comeback response(2)") | |
665 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
666 | resp = action_response(query) | |
667 | resp['payload'] = struct.pack('<BBBHBB', ACTION_CATEG_PUBLIC, | |
668 | GAS_COMEBACK_RESPONSE, dialog_token, 0, 0x80, | |
669 | 0) | |
670 | send_gas_resp(hapd, resp) | |
671 | ev = hapd.wait_event(["MGMT-RX"], timeout=1) | |
672 | if ev is not None: | |
673 | raise Exception("Unexpected management frame") | |
674 | expect_gas_result(dev[0], "TIMEOUT") | |
675 | ||
676 | logger.debug("Maximum comeback response fragment claiming more fragments") | |
677 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
678 | resp = action_response(query) | |
679 | resp['payload'] = anqp_comeback_resp(dialog_token, more=True) + struct.pack('<H', 0) | |
680 | send_gas_resp(hapd, resp) | |
681 | for i in range(1, 129): | |
682 | query = gas_rx(hapd) | |
683 | gas = parse_gas(query['payload']) | |
684 | if gas['action'] != GAS_COMEBACK_REQUEST: | |
685 | raise Exception("Unexpected request action") | |
686 | if gas['dialog_token'] != dialog_token: | |
687 | raise Exception("Unexpected dialog token change") | |
688 | resp = action_response(query) | |
689 | resp['payload'] = anqp_comeback_resp(dialog_token, id=i, more=True) + struct.pack('<H', 0) | |
690 | send_gas_resp(hapd, resp) | |
691 | expect_gas_result(dev[0], "PEER_ERROR") | |
692 | ||
693 | def test_gas_comeback_resp_additional_delay(dev, apdev): | |
694 | """GAS comeback response requesting additional delay""" | |
695 | hapd = start_ap(apdev[0]) | |
696 | bssid = apdev[0]['bssid'] | |
697 | ||
4ef70531 | 698 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
75f6134d JM |
699 | hapd.set("ext_mgmt_frame_handling", "1") |
700 | ||
701 | query, dialog_token = init_gas(hapd, bssid, dev[0]) | |
702 | for i in range(0, 2): | |
703 | resp = action_response(query) | |
704 | resp['payload'] = anqp_comeback_resp(dialog_token, status_code=95, comeback_delay=50) + struct.pack('<H', 0) | |
705 | send_gas_resp(hapd, resp) | |
706 | query = gas_rx(hapd) | |
707 | gas = parse_gas(query['payload']) | |
708 | if gas['action'] != GAS_COMEBACK_REQUEST: | |
709 | raise Exception("Unexpected request action") | |
710 | if gas['dialog_token'] != dialog_token: | |
711 | raise Exception("Unexpected dialog token change") | |
712 | resp = action_response(query) | |
713 | resp['payload'] = anqp_comeback_resp(dialog_token, status_code=0) + struct.pack('<H', 0) | |
714 | send_gas_resp(hapd, resp) | |
715 | expect_gas_result(dev[0], "SUCCESS") | |
d61ed3ac JM |
716 | |
717 | def test_gas_unknown_adv_proto(dev, apdev): | |
718 | """Unknown advertisement protocol id""" | |
719 | bssid = apdev[0]['bssid'] | |
720 | params = hs20_ap_params() | |
721 | params['hessid'] = bssid | |
722 | hostapd.add_ap(apdev[0]['ifname'], params) | |
723 | ||
4ef70531 | 724 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) |
d61ed3ac JM |
725 | req = dev[0].request("GAS_REQUEST " + bssid + " 42 000102000101") |
726 | if "FAIL" in req: | |
727 | raise Exception("GAS query request rejected") | |
728 | expect_gas_result(dev[0], "FAILURE", "59") | |
729 | ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10) | |
730 | if ev is None: | |
731 | raise Exception("GAS query timed out") | |
732 | exp = r'<.>(GAS-RESPONSE-INFO) addr=([0-9a-f:]*) dialog_token=([0-9]*) status_code=([0-9]*) resp_len=([\-0-9]*)' | |
733 | res = re.split(exp, ev) | |
734 | if len(res) < 6: | |
735 | raise Exception("Could not parse GAS-RESPONSE-INFO") | |
736 | if res[2] != bssid: | |
737 | raise Exception("Unexpected BSSID in response") | |
738 | status = res[4] | |
739 | if status != "59": | |
740 | raise Exception("Unexpected GAS-RESPONSE-INFO status") | |
1d1de230 JM |
741 | |
742 | def test_gas_max_pending(dev, apdev): | |
743 | """GAS and maximum pending query limit""" | |
744 | hapd = start_ap(apdev[0]) | |
745 | hapd.set("gas_frag_limit", "50") | |
746 | bssid = apdev[0]['bssid'] | |
747 | ||
748 | wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') | |
749 | wpas.interface_add("wlan5") | |
750 | if "OK" not in wpas.request("P2P_SET listen_channel 1"): | |
751 | raise Exception("Failed to set listen channel") | |
752 | if "OK" not in wpas.p2p_listen(): | |
753 | raise Exception("Failed to start listen state") | |
754 | if "FAIL" in wpas.request("SET ext_mgmt_frame_handling 1"): | |
755 | raise Exception("Failed to enable external management frame handling") | |
756 | ||
757 | anqp_query = struct.pack('<HHHHHHHHHH', 256, 16, 257, 258, 260, 261, 262, 263, 264, 268) | |
758 | gas = struct.pack('<H', len(anqp_query)) + anqp_query | |
759 | ||
760 | for dialog_token in range(1, 10): | |
761 | msg = struct.pack('<BBB', ACTION_CATEG_PUBLIC, GAS_INITIAL_REQUEST, | |
762 | dialog_token) + anqp_adv_proto() + gas | |
763 | req = "MGMT_TX {} {} freq=2412 wait_time=10 action={}".format(bssid, bssid, binascii.hexlify(msg)) | |
764 | if "OK" not in wpas.request(req): | |
765 | raise Exception("Could not send management frame") | |
766 | resp = wpas.mgmt_rx() | |
767 | if resp is None: | |
768 | raise Exception("MGMT-RX timeout") | |
769 | if 'payload' not in resp: | |
770 | raise Exception("Missing payload") | |
771 | gresp = parse_gas(resp['payload']) | |
772 | if gresp['dialog_token'] != dialog_token: | |
773 | raise Exception("Dialog token mismatch") | |
774 | status_code = gresp['status_code'] | |
775 | if dialog_token < 9 and status_code != 0: | |
776 | raise Exception("Unexpected failure status code {} for dialog token {}".format(status_code, dialog_token)) | |
777 | if dialog_token > 8 and status_code == 0: | |
778 | raise Exception("Unexpected success status code {} for dialog token {}".format(status_code, dialog_token)) | |
de8c4144 JM |
779 | |
780 | def test_gas_no_pending(dev, apdev): | |
781 | """GAS and no pending query for comeback request""" | |
782 | hapd = start_ap(apdev[0]) | |
783 | bssid = apdev[0]['bssid'] | |
784 | ||
785 | wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5') | |
786 | wpas.interface_add("wlan5") | |
787 | if "OK" not in wpas.request("P2P_SET listen_channel 1"): | |
788 | raise Exception("Failed to set listen channel") | |
789 | if "OK" not in wpas.p2p_listen(): | |
790 | raise Exception("Failed to start listen state") | |
791 | if "FAIL" in wpas.request("SET ext_mgmt_frame_handling 1"): | |
792 | raise Exception("Failed to enable external management frame handling") | |
793 | ||
794 | msg = struct.pack('<BBB', ACTION_CATEG_PUBLIC, GAS_COMEBACK_REQUEST, 1) | |
795 | req = "MGMT_TX {} {} freq=2412 wait_time=10 action={}".format(bssid, bssid, binascii.hexlify(msg)) | |
796 | if "OK" not in wpas.request(req): | |
797 | raise Exception("Could not send management frame") | |
798 | resp = wpas.mgmt_rx() | |
799 | if resp is None: | |
800 | raise Exception("MGMT-RX timeout") | |
801 | if 'payload' not in resp: | |
802 | raise Exception("Missing payload") | |
803 | gresp = parse_gas(resp['payload']) | |
804 | status_code = gresp['status_code'] | |
805 | if status_code != 60: | |
806 | raise Exception("Unexpected status code {} (expected 60)".format(status_code)) | |
6ec64f3e JM |
807 | |
808 | def test_gas_missing_payload(dev, apdev): | |
809 | """No action code in the query frame""" | |
810 | bssid = apdev[0]['bssid'] | |
811 | params = hs20_ap_params() | |
812 | params['hessid'] = bssid | |
813 | hostapd.add_ap(apdev[0]['ifname'], params) | |
814 | ||
815 | dev[0].scan_for_bss(bssid, freq="2412", force_scan=True) | |
816 | ||
817 | cmd = "MGMT_TX {} {} freq=2412 action=040A".format(bssid, bssid) | |
818 | if "FAIL" in dev[0].request(cmd): | |
819 | raise Exception("Could not send test Action frame") | |
820 | ev = dev[0].wait_event(["MGMT-TX-STATUS"], timeout=10) | |
821 | if ev is None: | |
822 | raise Exception("Timeout on MGMT-TX-STATUS") | |
823 | if "result=SUCCESS" not in ev: | |
824 | raise Exception("AP did not ack Action frame") | |
825 | ||
826 | cmd = "MGMT_TX {} {} freq=2412 action=04".format(bssid, bssid) | |
827 | if "FAIL" in dev[0].request(cmd): | |
828 | raise Exception("Could not send test Action frame") | |
829 | ev = dev[0].wait_event(["MGMT-TX-STATUS"], timeout=10) | |
830 | if ev is None: | |
831 | raise Exception("Timeout on MGMT-TX-STATUS") | |
832 | if "result=SUCCESS" not in ev: | |
833 | raise Exception("AP did not ack Action frame") |