]> git.ipfire.org Git - thirdparty/hostap.git/blame - tests/hwsim/wpasupplicant.py
tests: Avoid confusing "DETACH failed" exception prints in D-Bus tests
[thirdparty/hostap.git] / tests / hwsim / wpasupplicant.py
CommitLineData
1ae73b03 1# Python class for controlling wpa_supplicant
04e17e50 2# Copyright (c) 2013-2019, Jouni Malinen <j@w1.fi>
1ae73b03
JM
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import os
8import time
9import logging
daf3806d 10import binascii
c68f9a61 11import re
daf3806d 12import struct
1ae73b03 13import wpaspy
8ce4855b 14import remotehost
7fd9fbc2 15import subprocess
1ae73b03 16
c9aa4308 17logger = logging.getLogger()
1ae73b03
JM
18wpas_ctrl = '/var/run/wpa_supplicant'
19
20class WpaSupplicant:
0fd4792b 21 def __init__(self, ifname=None, global_iface=None, hostname=None,
bb5d761c
JM
22 port=9877, global_port=9878, monitor=True):
23 self.monitor = monitor
0fd4792b 24 self.hostname = hostname
f3f8ee88 25 self.group_ifname = None
b21540e6
JM
26 self.global_mon = None
27 self.global_ctrl = None
4823566c 28 self.gctrl_mon = None
b21540e6
JM
29 self.ctrl = None
30 self.mon = None
31 self.ifname = None
8ce4855b 32 self.host = remotehost.Host(hostname, ifname)
d28cfc2a 33 self._group_dbg = None
9489637b 34 if ifname:
0fd4792b 35 self.set_ifname(ifname, hostname, port)
afb2e8b8 36 res = self.get_driver_status()
50d06776 37 if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000:
afb2e8b8
AS
38 self.p2p_dev_ifname = 'p2p-dev-' + self.ifname
39 else:
40 self.p2p_dev_ifname = ifname
1ae73b03 41
0fa28afe
JM
42 self.global_iface = global_iface
43 if global_iface:
0fd4792b
JD
44 if hostname != None:
45 self.global_ctrl = wpaspy.Ctrl(hostname, global_port)
bb5d761c
JM
46 if self.monitor:
47 self.global_mon = wpaspy.Ctrl(hostname, global_port)
d4944fad 48 self.global_dbg = hostname + "/" + str(global_port) + "/"
0fd4792b
JD
49 else:
50 self.global_ctrl = wpaspy.Ctrl(global_iface)
bb5d761c
JM
51 if self.monitor:
52 self.global_mon = wpaspy.Ctrl(global_iface)
d4944fad 53 self.global_dbg = ""
bb5d761c
JM
54 if self.monitor:
55 self.global_mon.attach()
b21540e6
JM
56
57 def __del__(self):
58 self.close_monitor()
59 self.close_control()
60
61 def close_control_ctrl(self):
62 if self.ctrl:
63 del self.ctrl
64 self.ctrl = None
65
66 def close_control_global(self):
67 if self.global_ctrl:
68 del self.global_ctrl
69 self.global_ctrl = None
70
71 def close_control(self):
72 self.close_control_ctrl()
73 self.close_control_global()
74
75 def close_monitor_mon(self):
76 if not self.mon:
77 return
78 try:
79 while self.mon.pending():
80 ev = self.mon.recv()
81 logger.debug(self.dbg + ": " + ev)
82 except:
83 pass
84 try:
85 self.mon.detach()
86 except ConnectionRefusedError:
87 pass
6541b9db
JM
88 except Exception as e:
89 if str(e) == "DETACH failed":
90 pass
91 else:
92 raise
b21540e6
JM
93 del self.mon
94 self.mon = None
95
96 def close_monitor_global(self):
97 if not self.global_mon:
98 return
99 try:
100 while self.global_mon.pending():
101 ev = self.global_mon.recv()
102 logger.debug(self.global_dbg + ": " + ev)
103 except:
104 pass
105 try:
106 self.global_mon.detach()
107 except ConnectionRefusedError:
108 pass
6541b9db
JM
109 except Exception as e:
110 if str(e) == "DETACH failed":
111 pass
112 else:
113 raise
b21540e6
JM
114 del self.global_mon
115 self.global_mon = None
116
117 def close_monitor_group(self):
118 if not self.gctrl_mon:
119 return
120 try:
121 while self.gctrl_mon.pending():
122 ev = self.gctrl_mon.recv()
123 logger.debug(self.dbg + ": " + ev)
124 except:
125 pass
126 try:
127 self.gctrl_mon.detach()
128 except:
129 pass
130 del self.gctrl_mon
131 self.gctrl_mon = None
132
133 def close_monitor(self):
134 self.close_monitor_mon()
135 self.close_monitor_global()
136 self.close_monitor_group()
0fa28afe 137
e9f2d54f 138 def cmd_execute(self, cmd_array, shell=False):
7fd9fbc2 139 if self.hostname is None:
e9f2d54f
JM
140 if shell:
141 cmd = ' '.join(cmd_array)
142 else:
143 cmd = cmd_array
7fd9fbc2 144 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
e9f2d54f 145 stdout=subprocess.PIPE, shell=shell)
7fd9fbc2
JA
146 out = proc.communicate()[0]
147 ret = proc.returncode
45b0b88f 148 return ret, out.decode()
7fd9fbc2
JA
149 else:
150 return self.host.execute(cmd_array)
151
e3b36d42
JD
152 def terminate(self):
153 if self.global_mon:
b21540e6 154 self.close_monitor_global()
e3b36d42
JD
155 self.global_ctrl.terminate()
156 self.global_ctrl = None
157
a66d2248 158 def close_ctrl(self):
b21540e6
JM
159 self.close_monitor_global()
160 self.close_control_global()
a66d2248
JM
161 self.remove_ifname()
162
0fd4792b 163 def set_ifname(self, ifname, hostname=None, port=9877):
b21540e6 164 self.remove_ifname()
9489637b 165 self.ifname = ifname
0fd4792b
JD
166 if hostname != None:
167 self.ctrl = wpaspy.Ctrl(hostname, port)
bb5d761c
JM
168 if self.monitor:
169 self.mon = wpaspy.Ctrl(hostname, port)
8ce4855b 170 self.host = remotehost.Host(hostname, ifname)
d4944fad 171 self.dbg = hostname + "/" + ifname
0fd4792b
JD
172 else:
173 self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
bb5d761c
JM
174 if self.monitor:
175 self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
d4944fad 176 self.dbg = ifname
bb5d761c
JM
177 if self.monitor:
178 self.mon.attach()
9489637b
JM
179
180 def remove_ifname(self):
b21540e6
JM
181 self.close_monitor_mon()
182 self.close_control_ctrl()
183 self.ifname = None
9489637b 184
0fd4792b
JD
185 def get_ctrl_iface_port(self, ifname):
186 if self.hostname is None:
187 return None
188
189 res = self.global_request("INTERFACES ctrl")
190 lines = res.splitlines()
191 found = False
192 for line in lines:
193 words = line.split()
194 if words[0] == ifname:
195 found = True
196 break
197 if not found:
198 raise Exception("Could not find UDP port for " + ifname)
199 res = line.find("ctrl_iface=udp:")
200 if res == -1:
201 raise Exception("Wrong ctrl_interface format")
202 words = line.split(":")
203 return int(words[1])
204
a1512a0c 205 def interface_add(self, ifname, config="", driver="nl80211",
138bf118 206 drv_params=None, br_ifname=None, create=False,
06119030 207 set_ifname=True, all_params=False, if_type=None):
5a766acc 208 status, groups = self.host.execute(["id"])
8ce4855b 209 if status != 0:
9489637b 210 group = "admin"
8ce4855b 211 group = "admin" if "(admin)" in groups else "adm"
117caa4a 212 cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group
6e917c3e
JM
213 if drv_params:
214 cmd = cmd + '\t' + drv_params
a1512a0c
JM
215 if br_ifname:
216 if not drv_params:
217 cmd += '\t'
218 cmd += '\t' + br_ifname
25f2cb61
JM
219 if create:
220 if not br_ifname:
221 cmd += '\t'
222 if not drv_params:
223 cmd += '\t'
224 cmd += '\tcreate'
06119030
IP
225 if if_type:
226 cmd += '\t' + if_type
138bf118
JM
227 if all_params and not create:
228 if not br_ifname:
229 cmd += '\t'
230 if not drv_params:
231 cmd += '\t'
232 cmd += '\t'
9489637b
JM
233 if "FAIL" in self.global_request(cmd):
234 raise Exception("Failed to add a dynamic wpa_supplicant interface")
138bf118 235 if not create and set_ifname:
0fd4792b
JD
236 port = self.get_ctrl_iface_port(ifname)
237 self.set_ifname(ifname, self.hostname, port)
afb2e8b8 238 res = self.get_driver_status()
50d06776 239 if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000:
afb2e8b8
AS
240 self.p2p_dev_ifname = 'p2p-dev-' + self.ifname
241 else:
242 self.p2p_dev_ifname = ifname
9489637b
JM
243
244 def interface_remove(self, ifname):
245 self.remove_ifname()
246 self.global_request("INTERFACE_REMOVE " + ifname)
247
874057da 248 def request(self, cmd, timeout=10):
d4944fad 249 logger.debug(self.dbg + ": CTRL: " + cmd)
874057da 250 return self.ctrl.request(cmd, timeout=timeout)
1ae73b03 251
0fa28afe
JM
252 def global_request(self, cmd):
253 if self.global_iface is None:
2b5488a4 254 return self.request(cmd)
0fa28afe 255 else:
9489637b 256 ifname = self.ifname or self.global_iface
d4944fad 257 logger.debug(self.global_dbg + ifname + ": CTRL(global): " + cmd)
0fa28afe
JM
258 return self.global_ctrl.request(cmd)
259
d28cfc2a
JA
260 @property
261 def group_dbg(self):
262 if self._group_dbg is not None:
263 return self._group_dbg
264 if self.group_ifname is None:
265 raise Exception("Cannot have group_dbg without group_ifname")
266 if self.hostname is None:
267 self._group_dbg = self.group_ifname
268 else:
269 self._group_dbg = self.hostname + "/" + self.group_ifname
270 return self._group_dbg
271
f3f8ee88
JM
272 def group_request(self, cmd):
273 if self.group_ifname and self.group_ifname != self.ifname:
d28cfc2a
JA
274 if self.hostname is None:
275 gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname))
276 else:
277 port = self.get_ctrl_iface_port(self.group_ifname)
278 gctrl = wpaspy.Ctrl(self.hostname, port)
279 logger.debug(self.group_dbg + ": CTRL(group): " + cmd)
f3f8ee88
JM
280 return gctrl.request(cmd)
281 return self.request(cmd)
282
1ae73b03
JM
283 def ping(self):
284 return "PONG" in self.request("PING")
285
521b7e79
JM
286 def global_ping(self):
287 return "PONG" in self.global_request("PING")
288
1ae73b03 289 def reset(self):
521b7e79 290 self.dump_monitor()
ea0e92ee 291 res = self.request("FLUSH")
a8b8da11 292 if "OK" not in res:
ea0e92ee 293 logger.info("FLUSH to " + self.ifname + " failed: " + res)
09f60224 294 self.global_request("REMOVE_NETWORK all")
a2168cf4 295 self.global_request("SET p2p_no_group_iface 1")
a2168cf4 296 self.global_request("P2P_FLUSH")
b21540e6 297 self.close_monitor_group()
f3f8ee88 298 self.group_ifname = None
6edaee9c 299 self.dump_monitor()
6ca3a98b
JM
300
301 iter = 0
302 while iter < 60:
53606b10
AO
303 state1 = self.get_driver_status_field("scan_state")
304 p2pdev = "p2p-dev-" + self.ifname
305 state2 = self.get_driver_status_field("scan_state", ifname=p2pdev)
306 states = str(state1) + " " + str(state2)
307 if "SCAN_STARTED" in states or "SCAN_REQUESTED" in states:
6ca3a98b
JM
308 logger.info(self.ifname + ": Waiting for scan operation to complete before continuing")
309 time.sleep(1)
310 else:
311 break
312 iter = iter + 1
313 if iter == 60:
314 logger.error(self.ifname + ": Driver scan state did not clear")
89896c00 315 print("Trying to clear cfg80211/mac80211 scan state")
5a766acc 316 status, buf = self.host.execute(["ifconfig", self.ifname, "down"])
8ce4855b
JD
317 if status != 0:
318 logger.info("ifconfig failed: " + buf)
319 logger.info(status)
5a766acc 320 status, buf = self.host.execute(["ifconfig", self.ifname, "up"])
8ce4855b
JD
321 if status != 0:
322 logger.info("ifconfig failed: " + buf)
323 logger.info(status)
c57c1ed6
JM
324 if iter > 0:
325 # The ongoing scan could have discovered BSSes or P2P peers
326 logger.info("Run FLUSH again since scan was in progress")
327 self.request("FLUSH")
b21df6e7 328 self.dump_monitor()
6ca3a98b 329
ea0e92ee
JM
330 if not self.ping():
331 logger.info("No PING response from " + self.ifname + " after reset")
1ae73b03 332
79124c7b 333 def set(self, field, value):
a8b8da11 334 if "OK" not in self.request("SET " + field + " " + value):
79124c7b
JM
335 raise Exception("Failed to set wpa_supplicant parameter " + field)
336
07a2e61b
JM
337 def add_network(self):
338 id = self.request("ADD_NETWORK")
339 if "FAIL" in id:
340 raise Exception("ADD_NETWORK failed")
341 return int(id)
342
343 def remove_network(self, id):
344 id = self.request("REMOVE_NETWORK " + str(id))
345 if "FAIL" in id:
346 raise Exception("REMOVE_NETWORK failed")
347 return None
348
ca5b81a5
JM
349 def get_network(self, id, field):
350 res = self.request("GET_NETWORK " + str(id) + " " + field)
351 if res == "FAIL\n":
352 return None
353 return res
354
07a2e61b
JM
355 def set_network(self, id, field, value):
356 res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value)
357 if "FAIL" in res:
358 raise Exception("SET_NETWORK failed")
359 return None
360
361 def set_network_quoted(self, id, field, value):
362 res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"')
363 if "FAIL" in res:
364 raise Exception("SET_NETWORK failed")
365 return None
366
5e65346a
AS
367 def p2pdev_request(self, cmd):
368 return self.global_request("IFNAME=" + self.p2p_dev_ifname + " " + cmd)
369
370 def p2pdev_add_network(self):
371 id = self.p2pdev_request("ADD_NETWORK")
372 if "FAIL" in id:
373 raise Exception("p2pdev ADD_NETWORK failed")
374 return int(id)
375
376 def p2pdev_set_network(self, id, field, value):
377 res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + " " + value)
378 if "FAIL" in res:
379 raise Exception("p2pdev SET_NETWORK failed")
380 return None
381
382 def p2pdev_set_network_quoted(self, id, field, value):
383 res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"')
384 if "FAIL" in res:
385 raise Exception("p2pdev SET_NETWORK failed")
386 return None
387
8e751cfa
BR
388 def list_networks(self, p2p=False):
389 if p2p:
390 res = self.global_request("LIST_NETWORKS")
391 else:
392 res = self.request("LIST_NETWORKS")
6a0b4002
JM
393 lines = res.splitlines()
394 networks = []
395 for l in lines:
396 if "network id" in l:
397 continue
fab49f61 398 [id, ssid, bssid, flags] = l.split('\t')
6a0b4002
JM
399 network = {}
400 network['id'] = id
401 network['ssid'] = ssid
402 network['bssid'] = bssid
403 network['flags'] = flags
404 networks.append(network)
405 return networks
406
543f9f7e 407 def hs20_enable(self, auto_interworking=False):
bbe86767
JM
408 self.request("SET interworking 1")
409 self.request("SET hs20 1")
543f9f7e
JM
410 if auto_interworking:
411 self.request("SET auto_interworking 1")
412 else:
413 self.request("SET auto_interworking 0")
bbe86767 414
22653762
JM
415 def interworking_add_network(self, bssid):
416 id = self.request("INTERWORKING_ADD_NETWORK " + bssid)
417 if "FAIL" in id or "OK" in id:
418 raise Exception("INTERWORKING_ADD_NETWORK failed")
419 return int(id)
420
93a06242
JM
421 def add_cred(self):
422 id = self.request("ADD_CRED")
423 if "FAIL" in id:
424 raise Exception("ADD_CRED failed")
425 return int(id)
426
427 def remove_cred(self, id):
428 id = self.request("REMOVE_CRED " + str(id))
429 if "FAIL" in id:
430 raise Exception("REMOVE_CRED failed")
431 return None
432
433 def set_cred(self, id, field, value):
434 res = self.request("SET_CRED " + str(id) + " " + field + " " + value)
435 if "FAIL" in res:
436 raise Exception("SET_CRED failed")
437 return None
438
439 def set_cred_quoted(self, id, field, value):
440 res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"')
441 if "FAIL" in res:
442 raise Exception("SET_CRED failed")
443 return None
444
aa45859e
JM
445 def get_cred(self, id, field):
446 return self.request("GET_CRED " + str(id) + " " + field)
447
2232edf8 448 def add_cred_values(self, params):
bbe86767 449 id = self.add_cred()
2232edf8 450
fab49f61
JM
451 quoted = ["realm", "username", "password", "domain", "imsi",
452 "excluded_ssid", "milenage", "ca_cert", "client_cert",
453 "private_key", "domain_suffix_match", "provisioning_sp",
454 "roaming_partner", "phase1", "phase2", "private_key_passwd",
455 "roaming_consortiums"]
2232edf8
JM
456 for field in quoted:
457 if field in params:
458 self.set_cred_quoted(id, field, params[field])
459
fab49f61
JM
460 not_quoted = ["eap", "roaming_consortium", "priority",
461 "required_roaming_consortium", "sp_priority",
462 "max_bss_load", "update_identifier", "req_conn_capab",
463 "min_dl_bandwidth_home", "min_ul_bandwidth_home",
464 "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"]
2232edf8
JM
465 for field in not_quoted:
466 if field in params:
467 self.set_cred(id, field, params[field])
468
bc6e3288 469 return id
bbe86767 470
ca4fd182
JM
471 def select_network(self, id, freq=None):
472 if freq:
22653762 473 extra = " freq=" + str(freq)
ca4fd182
JM
474 else:
475 extra = ""
476 id = self.request("SELECT_NETWORK " + str(id) + extra)
81266da7
JM
477 if "FAIL" in id:
478 raise Exception("SELECT_NETWORK failed")
479 return None
480
68157c06
JL
481 def mesh_group_add(self, id):
482 id = self.request("MESH_GROUP_ADD " + str(id))
483 if "FAIL" in id:
484 raise Exception("MESH_GROUP_ADD failed")
485 return None
486
487 def mesh_group_remove(self):
488 id = self.request("MESH_GROUP_REMOVE " + str(self.ifname))
489 if "FAIL" in id:
490 raise Exception("MESH_GROUP_REMOVE failed")
491 return None
492
67a0d4f9
JA
493 def connect_network(self, id, timeout=None):
494 if timeout is None:
495 timeout = 10 if self.hostname is None else 60
81266da7
JM
496 self.dump_monitor()
497 self.select_network(id)
5f35a5e2 498 self.wait_connected(timeout=timeout)
81266da7
JM
499 self.dump_monitor()
500
f44c45ac
JM
501 def get_status(self, extra=None):
502 if extra:
503 extra = "-" + extra
504 else:
505 extra = ""
506 res = self.request("STATUS" + extra)
1ae73b03 507 lines = res.splitlines()
302b7a1b 508 vals = dict()
1ae73b03 509 for l in lines:
e01929c6 510 try:
fab49f61 511 [name, value] = l.split('=', 1)
e01929c6 512 vals[name] = value
bab493b9 513 except ValueError as e:
e01929c6 514 logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l)
302b7a1b
JM
515 return vals
516
f44c45ac
JM
517 def get_status_field(self, field, extra=None):
518 vals = self.get_status(extra)
302b7a1b
JM
519 if field in vals:
520 return vals[field]
1ae73b03
JM
521 return None
522
f44c45ac
JM
523 def get_group_status(self, extra=None):
524 if extra:
525 extra = "-" + extra
526 else:
527 extra = ""
528 res = self.group_request("STATUS" + extra)
7cb08cdb 529 lines = res.splitlines()
302b7a1b 530 vals = dict()
7cb08cdb 531 for l in lines:
6bb9d861 532 try:
fab49f61 533 [name, value] = l.split('=', 1)
6bb9d861
JB
534 except ValueError:
535 logger.info(self.ifname + ": Ignore unexpected status line: " + l)
536 continue
302b7a1b
JM
537 vals[name] = value
538 return vals
539
f44c45ac
JM
540 def get_group_status_field(self, field, extra=None):
541 vals = self.get_group_status(extra)
302b7a1b
JM
542 if field in vals:
543 return vals[field]
7cb08cdb
JM
544 return None
545
53606b10
AO
546 def get_driver_status(self, ifname=None):
547 if ifname is None:
548 res = self.request("STATUS-DRIVER")
549 else:
550 res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname)
c0183847
JM
551 if res.startswith("FAIL"):
552 return dict()
6ca3a98b
JM
553 lines = res.splitlines()
554 vals = dict()
555 for l in lines:
6bb9d861 556 try:
fab49f61 557 [name, value] = l.split('=', 1)
6bb9d861
JB
558 except ValueError:
559 logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l)
560 continue
6ca3a98b
JM
561 vals[name] = value
562 return vals
563
53606b10
AO
564 def get_driver_status_field(self, field, ifname=None):
565 vals = self.get_driver_status(ifname)
6ca3a98b
JM
566 if field in vals:
567 return vals[field]
568 return None
569
5fe7a426 570 def get_mcc(self):
0663ae22
JM
571 mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent'))
572 return 1 if mcc < 2 else mcc
5fe7a426 573
44bb9106
JM
574 def get_mib(self):
575 res = self.request("MIB")
576 lines = res.splitlines()
577 vals = dict()
578 for l in lines:
579 try:
fab49f61 580 [name, value] = l.split('=', 1)
44bb9106 581 vals[name] = value
bab493b9 582 except ValueError as e:
44bb9106
JM
583 logger.info(self.ifname + ": Ignore unexpected MIB line: " + l)
584 return vals
585
1ae73b03 586 def p2p_dev_addr(self):
302b7a1b 587 return self.get_status_field("p2p_device_address")
1ae73b03 588
7cb08cdb 589 def p2p_interface_addr(self):
302b7a1b 590 return self.get_group_status_field("address")
7cb08cdb 591
f6420942
JM
592 def own_addr(self):
593 try:
594 res = self.p2p_interface_addr()
595 except:
596 res = self.p2p_dev_addr()
597 return res
598
1ae73b03 599 def p2p_listen(self):
0fa28afe 600 return self.global_request("P2P_LISTEN")
1ae73b03 601
a3dc7502
AO
602 def p2p_ext_listen(self, period, interval):
603 return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval))
604
605 def p2p_cancel_ext_listen(self):
606 return self.global_request("P2P_EXT_LISTEN")
607
7457c635 608 def p2p_find(self, social=False, progressive=False, dev_id=None,
6d0b4474 609 dev_type=None, delay=None, freq=None):
c70ebce0 610 cmd = "P2P_FIND"
1ae73b03 611 if social:
c70ebce0 612 cmd = cmd + " type=social"
5070b14a
JM
613 elif progressive:
614 cmd = cmd + " type=progressive"
c70ebce0
JM
615 if dev_id:
616 cmd = cmd + " dev_id=" + dev_id
617 if dev_type:
618 cmd = cmd + " dev_type=" + dev_type
7457c635
JM
619 if delay:
620 cmd = cmd + " delay=" + str(delay)
6d0b4474
JM
621 if freq:
622 cmd = cmd + " freq=" + str(freq)
c70ebce0 623 return self.global_request(cmd)
1ae73b03 624
5743006d 625 def p2p_stop_find(self):
0fa28afe 626 return self.global_request("P2P_STOP_FIND")
5743006d 627
1ae73b03 628 def wps_read_pin(self):
861671b6
JM
629 self.pin = self.request("WPS_PIN get").rstrip("\n")
630 if "FAIL" in self.pin:
631 raise Exception("Could not generate PIN")
1ae73b03
JM
632 return self.pin
633
634 def peer_known(self, peer, full=True):
0fa28afe 635 res = self.global_request("P2P_PEER " + peer)
1ae73b03
JM
636 if peer.lower() not in res.lower():
637 return False
638 if not full:
639 return True
640 return "[PROBE_REQ_ONLY]" not in res
641
7f2fea59
JM
642 def discover_peer(self, peer, full=True, timeout=15, social=True,
643 force_find=False, freq=None):
1ae73b03 644 logger.info(self.ifname + ": Trying to discover peer " + peer)
d4b21766 645 if not force_find and self.peer_known(peer, full):
1ae73b03 646 return True
7f2fea59 647 self.p2p_find(social, freq=freq)
1ae73b03 648 count = 0
ee3f9f38
JM
649 while count < timeout * 4:
650 time.sleep(0.25)
1ae73b03
JM
651 count = count + 1
652 if self.peer_known(peer, full):
653 return True
654 return False
655
451afb4f
JM
656 def get_peer(self, peer):
657 res = self.global_request("P2P_PEER " + peer)
658 if peer.lower() not in res.lower():
659 raise Exception("Peer information not available")
660 lines = res.splitlines()
661 vals = dict()
662 for l in lines:
663 if '=' in l:
fab49f61 664 [name, value] = l.split('=', 1)
451afb4f
JM
665 vals[name] = value
666 return vals
667
46f2cfce 668 def group_form_result(self, ev, expect_failure=False, go_neg_res=None):
f7b1a750
JM
669 if expect_failure:
670 if "P2P-GROUP-STARTED" in ev:
671 raise Exception("Group formation succeeded when expecting failure")
672 exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)'
673 s = re.split(exp, ev)
674 if len(s) < 3:
675 return None
676 res = {}
677 res['result'] = 'go-neg-failed'
678 res['status'] = int(s[2])
679 return res
680
681 if "P2P-GROUP-STARTED" not in ev:
682 raise Exception("No P2P-GROUP-STARTED event seen")
683
c9dc5623 684 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*) ip_addr=([0-9.]*) ip_mask=([0-9.]*) go_ip_addr=([0-9.]*)'
c68f9a61 685 s = re.split(exp, ev)
c9dc5623
JM
686 if len(s) < 11:
687 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)'
688 s = re.split(exp, ev)
689 if len(s) < 8:
690 raise Exception("Could not parse P2P-GROUP-STARTED")
c68f9a61
JM
691 res = {}
692 res['result'] = 'success'
693 res['ifname'] = s[2]
f3f8ee88 694 self.group_ifname = s[2]
4823566c 695 try:
d28cfc2a
JA
696 if self.hostname is None:
697 self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl,
698 self.group_ifname))
699 else:
700 port = self.get_ctrl_iface_port(self.group_ifname)
701 self.gctrl_mon = wpaspy.Ctrl(self.hostname, port)
4d916a7f
JM
702 if self.monitor:
703 self.gctrl_mon.attach()
4823566c
JM
704 except:
705 logger.debug("Could not open monitor socket for group interface")
706 self.gctrl_mon = None
c68f9a61
JM
707 res['role'] = s[3]
708 res['ssid'] = s[4]
709 res['freq'] = s[5]
451afb4f
JM
710 if "[PERSISTENT]" in ev:
711 res['persistent'] = True
712 else:
713 res['persistent'] = False
c68f9a61
JM
714 p = re.match(r'psk=([0-9a-f]*)', s[6])
715 if p:
716 res['psk'] = p.group(1)
717 p = re.match(r'passphrase="(.*)"', s[6])
718 if p:
719 res['passphrase'] = p.group(1)
720 res['go_dev_addr'] = s[7]
46f2cfce 721
ebf25480 722 if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]:
c9dc5623
JM
723 res['ip_addr'] = s[8]
724 if len(s) > 9:
725 res['ip_mask'] = s[9]
726 if len(s) > 10:
727 res['go_ip_addr'] = s[10]
728
46f2cfce
JM
729 if go_neg_res:
730 exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)'
731 s = re.split(exp, go_neg_res)
732 if len(s) < 4:
733 raise Exception("Could not parse P2P-GO-NEG-SUCCESS")
734 res['go_neg_role'] = s[2]
735 res['go_neg_freq'] = s[3]
736
c68f9a61
JM
737 return res
738
35b61a49
JM
739 def p2p_go_neg_auth(self, peer, pin, method, go_intent=None,
740 persistent=False, freq=None, freq2=None,
741 max_oper_chwidth=None, ht40=False, vht=False):
1ae73b03
JM
742 if not self.discover_peer(peer):
743 raise Exception("Peer " + peer + " not found")
744 self.dump_monitor()
1f53fe03
JM
745 if pin:
746 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth"
747 else:
748 cmd = "P2P_CONNECT " + peer + " " + method + " auth"
809079d3
JM
749 if go_intent:
750 cmd = cmd + ' go_intent=' + str(go_intent)
46f2cfce
JM
751 if freq:
752 cmd = cmd + ' freq=' + str(freq)
35b61a49
JM
753 if freq2:
754 cmd = cmd + ' freq2=' + str(freq2)
755 if max_oper_chwidth:
756 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
757 if ht40:
758 cmd = cmd + ' ht40'
759 if vht:
760 cmd = cmd + ' vht'
451afb4f
JM
761 if persistent:
762 cmd = cmd + " persistent"
0fa28afe 763 if "OK" in self.global_request(cmd):
1ae73b03
JM
764 return None
765 raise Exception("P2P_CONNECT (auth) failed")
766
10f312d4
JM
767 def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False):
768 if timeout is None:
769 timeout = 1 if expect_failure else 5
46f2cfce
JM
770 go_neg_res = None
771 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
bc6e3288 772 "P2P-GO-NEG-FAILURE"], timeout)
c68f9a61 773 if ev is None:
809079d3
JM
774 if expect_failure:
775 return None
c68f9a61 776 raise Exception("Group formation timed out")
46f2cfce
JM
777 if "P2P-GO-NEG-SUCCESS" in ev:
778 go_neg_res = ev
bc6e3288 779 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
46f2cfce
JM
780 if ev is None:
781 if expect_failure:
782 return None
783 raise Exception("Group formation timed out")
c68f9a61 784 self.dump_monitor()
46f2cfce 785 return self.group_form_result(ev, expect_failure, go_neg_res)
c68f9a61 786
35b61a49
JM
787 def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None,
788 expect_failure=False, persistent=False,
789 persistent_id=None, freq=None, provdisc=False,
790 wait_group=True, freq2=None, max_oper_chwidth=None,
791 ht40=False, vht=False):
1ae73b03
JM
792 if not self.discover_peer(peer):
793 raise Exception("Peer " + peer + " not found")
794 self.dump_monitor()
1a4d80b8
JM
795 if pin:
796 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method
797 else:
798 cmd = "P2P_CONNECT " + peer + " " + method
a68a7493 799 if go_intent is not None:
809079d3 800 cmd = cmd + ' go_intent=' + str(go_intent)
ef2bd5a3
JM
801 if freq:
802 cmd = cmd + ' freq=' + str(freq)
35b61a49
JM
803 if freq2:
804 cmd = cmd + ' freq2=' + str(freq2)
805 if max_oper_chwidth:
806 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
807 if ht40:
808 cmd = cmd + ' ht40'
809 if vht:
810 cmd = cmd + ' vht'
451afb4f
JM
811 if persistent:
812 cmd = cmd + " persistent"
bdc44764
JM
813 elif persistent_id:
814 cmd = cmd + " persistent=" + persistent_id
08e4bd87
JM
815 if provdisc:
816 cmd = cmd + " provdisc"
0fa28afe 817 if "OK" in self.global_request(cmd):
1ae73b03 818 if timeout == 0:
1ae73b03 819 return None
46f2cfce
JM
820 go_neg_res = None
821 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
822 "P2P-GO-NEG-FAILURE"], timeout)
c68f9a61 823 if ev is None:
809079d3
JM
824 if expect_failure:
825 return None
c68f9a61 826 raise Exception("Group formation timed out")
46f2cfce 827 if "P2P-GO-NEG-SUCCESS" in ev:
b43ac5d5
JM
828 if not wait_group:
829 return ev
46f2cfce
JM
830 go_neg_res = ev
831 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
832 if ev is None:
833 if expect_failure:
834 return None
835 raise Exception("Group formation timed out")
c68f9a61 836 self.dump_monitor()
46f2cfce 837 return self.group_form_result(ev, expect_failure, go_neg_res)
1ae73b03
JM
838 raise Exception("P2P_CONNECT failed")
839
a2674fd9 840 def _wait_event(self, mon, pfx, events, timeout):
36408936
JM
841 start = os.times()[4]
842 while True:
a2674fd9
JB
843 while mon.pending():
844 ev = mon.recv()
845 logger.debug(self.dbg + pfx + ev)
f7b1a750
JM
846 for event in events:
847 if event in ev:
848 return ev
36408936
JM
849 now = os.times()[4]
850 remaining = start + timeout - now
851 if remaining <= 0:
852 break
a2674fd9 853 if not mon.pending(timeout=remaining):
36408936 854 break
c68f9a61 855 return None
1ae73b03 856
a2674fd9
JB
857 def wait_event(self, events, timeout=10):
858 return self._wait_event(self.mon, ": ", events, timeout)
859
0fa28afe
JM
860 def wait_global_event(self, events, timeout):
861 if self.global_iface is None:
a2674fd9
JB
862 return self.wait_event(events, timeout)
863 return self._wait_event(self.global_mon, "(global): ",
864 events, timeout)
0fa28afe 865
4823566c
JM
866 def wait_group_event(self, events, timeout=10):
867 if self.group_ifname and self.group_ifname != self.ifname:
868 if self.gctrl_mon is None:
869 return None
870 start = os.times()[4]
871 while True:
872 while self.gctrl_mon.pending():
873 ev = self.gctrl_mon.recv()
d28cfc2a 874 logger.debug(self.group_dbg + "(group): " + ev)
4823566c
JM
875 for event in events:
876 if event in ev:
877 return ev
878 now = os.times()[4]
879 remaining = start + timeout - now
880 if remaining <= 0:
881 break
882 if not self.gctrl_mon.pending(timeout=remaining):
883 break
884 return None
885
886 return self.wait_event(events, timeout)
887
2c914e24 888 def wait_go_ending_session(self):
b21540e6 889 self.close_monitor_group()
c7ca843d
JA
890 timeout = 3 if self.hostname is None else 10
891 ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout)
2c914e24
JM
892 if ev is None:
893 raise Exception("Group removal event timed out")
894 if "reason=GO_ENDING_SESSION" not in ev:
895 raise Exception("Unexpected group removal reason")
896
3a88b7e6 897 def dump_monitor(self, mon=True, global_mon=True):
d9052150
JM
898 count_iface = 0
899 count_global = 0
3a88b7e6 900 while mon and self.monitor and self.mon.pending():
1ae73b03 901 ev = self.mon.recv()
d4944fad 902 logger.debug(self.dbg + ": " + ev)
d9052150 903 count_iface += 1
3a88b7e6 904 while global_mon and self.monitor and self.global_mon and self.global_mon.pending():
9d507452 905 ev = self.global_mon.recv()
d4944fad 906 logger.debug(self.global_dbg + self.ifname + "(global): " + ev)
d9052150
JM
907 count_global += 1
908 return (count_iface, count_global)
3eb29b7b 909
a311c61d 910 def remove_group(self, ifname=None):
b21540e6 911 self.close_monitor_group()
a311c61d 912 if ifname is None:
451afb4f 913 ifname = self.group_ifname if self.group_ifname else self.ifname
0fa28afe 914 if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname):
3eb29b7b 915 raise Exception("Group could not be removed")
f3f8ee88 916 self.group_ifname = None
4ea8d3b5 917
25057d92 918 def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False):
4ea8d3b5
JM
919 self.dump_monitor()
920 cmd = "P2P_GROUP_ADD"
07a2e61b
JM
921 if persistent is None:
922 pass
923 elif persistent is True:
924 cmd = cmd + " persistent"
925 else:
926 cmd = cmd + " persistent=" + str(persistent)
5924d4c1 927 if freq:
ef2bd5a3 928 cmd = cmd + " freq=" + str(freq)
0fa28afe
JM
929 if "OK" in self.global_request(cmd):
930 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5)
4ea8d3b5
JM
931 if ev is None:
932 raise Exception("GO start up timed out")
25057d92
JM
933 if not no_event_clear:
934 self.dump_monitor()
4ea8d3b5
JM
935 return self.group_form_result(ev)
936 raise Exception("P2P_GROUP_ADD failed")
937
938 def p2p_go_authorize_client(self, pin):
939 cmd = "WPS_PIN any " + pin
f3f8ee88 940 if "FAIL" in self.group_request(cmd):
4ea8d3b5
JM
941 raise Exception("Failed to authorize client connection on GO")
942 return None
943
b162675f
JM
944 def p2p_go_authorize_client_pbc(self):
945 cmd = "WPS_PBC"
946 if "FAIL" in self.group_request(cmd):
947 raise Exception("Failed to authorize client connection on GO")
948 return None
949
ee3f9f38
JM
950 def p2p_connect_group(self, go_addr, pin, timeout=0, social=False,
951 freq=None):
4ea8d3b5 952 self.dump_monitor()
7f2fea59 953 if not self.discover_peer(go_addr, social=social, freq=freq):
54c20c9b
JM
954 if social or not self.discover_peer(go_addr, social=social):
955 raise Exception("GO " + go_addr + " not found")
8251be17 956 self.p2p_stop_find()
4ea8d3b5
JM
957 self.dump_monitor()
958 cmd = "P2P_CONNECT " + go_addr + " " + pin + " join"
ee3f9f38
JM
959 if freq:
960 cmd += " freq=" + str(freq)
0fa28afe 961 if "OK" in self.global_request(cmd):
4ea8d3b5
JM
962 if timeout == 0:
963 self.dump_monitor()
964 return None
8251be17
JM
965 ev = self.wait_global_event(["P2P-GROUP-STARTED",
966 "P2P-GROUP-FORMATION-FAILURE"],
967 timeout)
4ea8d3b5
JM
968 if ev is None:
969 raise Exception("Joining the group timed out")
8251be17
JM
970 if "P2P-GROUP-STARTED" not in ev:
971 raise Exception("Failed to join the group")
4ea8d3b5
JM
972 self.dump_monitor()
973 return self.group_form_result(ev)
974 raise Exception("P2P_CONNECT(join) failed")
7cb08cdb
JM
975
976 def tdls_setup(self, peer):
977 cmd = "TDLS_SETUP " + peer
978 if "FAIL" in self.group_request(cmd):
979 raise Exception("Failed to request TDLS setup")
980 return None
981
982 def tdls_teardown(self, peer):
983 cmd = "TDLS_TEARDOWN " + peer
984 if "FAIL" in self.group_request(cmd):
985 raise Exception("Failed to request TDLS teardown")
986 return None
b61e418c 987
2380d804
OG
988 def tdls_link_status(self, peer):
989 cmd = "TDLS_LINK_STATUS " + peer
990 ret = self.group_request(cmd)
991 if "FAIL" in ret:
992 raise Exception("Failed to request TDLS link status")
993 return ret
994
bc32f830
EP
995 def tspecs(self):
996 """Return (tsid, up) tuples representing current tspecs"""
997 res = self.request("WMM_AC_STATUS")
998 tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res)
999 tspecs = [tuple(map(int, tspec)) for tspec in tspecs]
1000
1001 logger.debug("tspecs: " + str(tspecs))
1002 return tspecs
1003
ceb767d5
JM
1004 def add_ts(self, tsid, up, direction="downlink", expect_failure=False,
1005 extra=None):
76133458
EP
1006 params = {
1007 "sba": 9000,
1008 "nominal_msdu_size": 1500,
1009 "min_phy_rate": 6000000,
1010 "mean_data_rate": 1500,
1011 }
ceb767d5 1012 cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up)
35d8c254 1013 for (key, value) in params.items():
76133458 1014 cmd += " %s=%d" % (key, value)
ceb767d5
JM
1015 if extra:
1016 cmd += " " + extra
76133458
EP
1017
1018 if self.request(cmd).strip() != "OK":
1019 raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up))
1020
ceb767d5
JM
1021 if expect_failure:
1022 ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2)
1023 if ev is None:
1024 raise Exception("ADDTS failed (time out while waiting failure)")
1025 if "tsid=%d" % (tsid) not in ev:
1026 raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED")
1027 return
1028
76133458
EP
1029 ev = self.wait_event(["TSPEC-ADDED"], timeout=1)
1030 if ev is None:
1031 raise Exception("ADDTS failed (time out)")
1032 if "tsid=%d" % (tsid) not in ev:
1033 raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)")
1034
a8b8da11 1035 if (tsid, up) not in self.tspecs():
bc32f830
EP
1036 raise Exception("ADDTS failed (tsid not in tspec list)")
1037
76133458
EP
1038 def del_ts(self, tsid):
1039 if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK":
1040 raise Exception("DELTS failed")
1041
1042 ev = self.wait_event(["TSPEC-REMOVED"], timeout=1)
1043 if ev is None:
1044 raise Exception("DELTS failed (time out)")
1045 if "tsid=%d" % (tsid) not in ev:
1046 raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)")
1047
bc32f830
EP
1048 tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid]
1049 if tspecs:
1050 raise Exception("DELTS failed (still in tspec list)")
1051
6f939e59 1052 def connect(self, ssid=None, ssid2=None, **kwargs):
b61e418c
JM
1053 logger.info("Connect STA " + self.ifname + " to AP")
1054 id = self.add_network()
d78f3303
JM
1055 if ssid:
1056 self.set_network_quoted(id, "ssid", ssid)
1057 elif ssid2:
1058 self.set_network(id, "ssid", ssid2)
6f939e59 1059
fab49f61
JM
1060 quoted = ["psk", "identity", "anonymous_identity", "password",
1061 "ca_cert", "client_cert", "private_key",
1062 "private_key_passwd", "ca_cert2", "client_cert2",
1063 "private_key2", "phase1", "phase2", "domain_suffix_match",
1064 "altsubject_match", "subject_match", "pac_file", "dh_file",
1065 "bgscan", "ht_mcs", "id_str", "openssl_ciphers",
1066 "domain_match", "dpp_connector", "sae_password",
1067 "sae_password_id", "check_cert_subject"]
6f939e59
JM
1068 for field in quoted:
1069 if field in kwargs and kwargs[field]:
1070 self.set_network_quoted(id, field, kwargs[field])
1071
fab49f61
JM
1072 not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise",
1073 "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
1074 "wep_tx_keyidx", "scan_freq", "freq_list", "eap",
1075 "eapol_flags", "fragment_size", "scan_ssid", "auth_alg",
1076 "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid",
1077 "disable_max_amsdu", "ampdu_factor", "ampdu_density",
1078 "disable_ht40", "disable_sgi", "disable_ldpc",
1079 "ht40_intolerant", "update_identifier", "mac_addr",
1080 "erp", "bg_scan_period", "bssid_blacklist",
1081 "bssid_whitelist", "mem_only_psk", "eap_workaround",
1082 "engine", "fils_dh_group", "bssid_hint",
1083 "dpp_csign", "dpp_csign_expiry",
1084 "dpp_netaccesskey", "dpp_netaccesskey_expiry",
1085 "group_mgmt", "owe_group",
1086 "roaming_consortium_selection", "ocv",
1087 "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc"]
6f939e59
JM
1088 for field in not_quoted:
1089 if field in kwargs and kwargs[field]:
1090 self.set_network(id, field, kwargs[field])
1091
1092 if "raw_psk" in kwargs and kwargs['raw_psk']:
1093 self.set_network(id, "psk", kwargs['raw_psk'])
1094 if "password_hex" in kwargs and kwargs['password_hex']:
1095 self.set_network(id, "password", kwargs['password_hex'])
1096 if "peerkey" in kwargs and kwargs['peerkey']:
4a5a5792 1097 self.set_network(id, "peerkey", "1")
6f939e59 1098 if "okc" in kwargs and kwargs['okc']:
0fab9ce6 1099 self.set_network(id, "proactive_key_caching", "1")
6f939e59
JM
1100 if "ocsp" in kwargs and kwargs['ocsp']:
1101 self.set_network(id, "ocsp", str(kwargs['ocsp']))
1102 if "only_add_network" in kwargs and kwargs['only_add_network']:
a6cf5cd6 1103 return id
6f939e59
JM
1104 if "wait_connect" not in kwargs or kwargs['wait_connect']:
1105 if "eap" in kwargs:
7559ad7a
JM
1106 self.connect_network(id, timeout=20)
1107 else:
1108 self.connect_network(id)
9626962d
JM
1109 else:
1110 self.dump_monitor()
1111 self.select_network(id)
709f18d5 1112 return id
5126138c 1113
c0dd37f5
JM
1114 def scan(self, type=None, freq=None, no_wait=False, only_new=False,
1115 passive=False):
043a29ec
JM
1116 if not no_wait:
1117 self.dump_monitor()
5126138c
JM
1118 if type:
1119 cmd = "SCAN TYPE=" + type
1120 else:
1121 cmd = "SCAN"
0589f401 1122 if freq:
243dcc4a 1123 cmd = cmd + " freq=" + str(freq)
b3ec107c
JM
1124 if only_new:
1125 cmd += " only_new=1"
c0dd37f5
JM
1126 if passive:
1127 cmd += " passive=1"
d7a99700
JM
1128 if not no_wait:
1129 self.dump_monitor()
64766a77 1130 res = self.request(cmd)
a8b8da11 1131 if "OK" not in res:
64766a77 1132 raise Exception("Failed to trigger scan: " + str(res))
d7a99700
JM
1133 if no_wait:
1134 return
dff690b8
JM
1135 ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS",
1136 "CTRL-EVENT-SCAN-FAILED"], 15)
5126138c
JM
1137 if ev is None:
1138 raise Exception("Scan timed out")
dff690b8
JM
1139 if "CTRL-EVENT-SCAN-FAILED" in ev:
1140 raise Exception("Scan failed: " + ev)
5126138c 1141
c0dd37f5
JM
1142 def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False,
1143 passive=False):
841bed04
JM
1144 if not force_scan and self.get_bss(bssid) is not None:
1145 return
1146 for i in range(0, 10):
c0dd37f5
JM
1147 self.scan(freq=freq, type="ONLY", only_new=only_new,
1148 passive=passive)
841bed04
JM
1149 if self.get_bss(bssid) is not None:
1150 return
1151 raise Exception("Could not find BSS " + bssid + " in scan")
1152
970a23f6 1153 def flush_scan_cache(self, freq=2417):
243dcc4a 1154 self.request("BSS_FLUSH 0")
970a23f6 1155 self.scan(freq=freq, only_new=True)
c1e38fec
JM
1156 res = self.request("SCAN_RESULTS")
1157 if len(res.splitlines()) > 1:
64766a77 1158 logger.debug("Scan results remaining after first attempt to flush the results:\n" + res)
c1e38fec
JM
1159 self.request("BSS_FLUSH 0")
1160 self.scan(freq=2422, only_new=True)
1161 res = self.request("SCAN_RESULTS")
1162 if len(res.splitlines()) > 1:
1163 logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res)
243dcc4a 1164
e01a492c
JM
1165 def disconnect_and_stop_scan(self):
1166 self.request("DISCONNECT")
1167 res = self.request("ABORT_SCAN")
1168 for i in range(2 if "OK" in res else 1):
1169 self.wait_event(["CTRL-EVENT-DISCONNECTED",
1170 "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5)
1171 self.dump_monitor()
1172
63a7683b 1173 def roam(self, bssid, fail_test=False, assoc_reject_ok=False):
5126138c 1174 self.dump_monitor()
655bc8bf
JM
1175 if "OK" not in self.request("ROAM " + bssid):
1176 raise Exception("ROAM failed")
3b808945 1177 if fail_test:
63a7683b
JM
1178 if assoc_reject_ok:
1179 ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1180 "CTRL-EVENT-ASSOC-REJECT"], timeout=1)
1181 else:
1182 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
1183 if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev:
3b808945
JM
1184 raise Exception("Unexpected connection")
1185 self.dump_monitor()
1186 return
04e17e50
JM
1187 ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1188 "CTRL-EVENT-ASSOC-REJECT"], timeout=10)
1189 if ev is None:
1190 raise Exception("Roaming with the AP timed out")
1191 if "CTRL-EVENT-ASSOC-REJECT" in ev:
1192 raise Exception("Roaming association rejected")
5126138c 1193 self.dump_monitor()
6edaee9c 1194
3b808945 1195 def roam_over_ds(self, bssid, fail_test=False):
b553eab1 1196 self.dump_monitor()
655bc8bf
JM
1197 if "OK" not in self.request("FT_DS " + bssid):
1198 raise Exception("FT_DS failed")
3b808945
JM
1199 if fail_test:
1200 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
1201 if ev is not None:
1202 raise Exception("Unexpected connection")
1203 self.dump_monitor()
1204 return
04e17e50
JM
1205 ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1206 "CTRL-EVENT-ASSOC-REJECT"], timeout=10)
1207 if ev is None:
1208 raise Exception("Roaming with the AP timed out")
1209 if "CTRL-EVENT-ASSOC-REJECT" in ev:
1210 raise Exception("Roaming association rejected")
b553eab1
JM
1211 self.dump_monitor()
1212
6edaee9c 1213 def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None,
6645ff50 1214 new_passphrase=None, no_wait=False):
6edaee9c
JM
1215 self.dump_monitor()
1216 if new_ssid:
1217 self.request("WPS_REG " + bssid + " " + pin + " " +
54c58f29
MH
1218 binascii.hexlify(new_ssid.encode()).decode() + " " +
1219 key_mgmt + " " + cipher + " " +
1220 binascii.hexlify(new_passphrase.encode()).decode())
6645ff50
JM
1221 if no_wait:
1222 return
6edaee9c
JM
1223 ev = self.wait_event(["WPS-SUCCESS"], timeout=15)
1224 else:
1225 self.request("WPS_REG " + bssid + " " + pin)
6645ff50
JM
1226 if no_wait:
1227 return
6edaee9c
JM
1228 ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15)
1229 if ev is None:
1230 raise Exception("WPS cred timed out")
1231 ev = self.wait_event(["WPS-FAIL"], timeout=15)
1232 if ev is None:
1233 raise Exception("WPS timed out")
5f35a5e2 1234 self.wait_connected(timeout=15)
57661377
JM
1235
1236 def relog(self):
5f797376 1237 self.global_request("RELOG")
41af1305
JM
1238
1239 def wait_completed(self, timeout=10):
1240 for i in range(0, timeout * 2):
1241 if self.get_status_field("wpa_state") == "COMPLETED":
1242 return
1243 time.sleep(0.5)
1244 raise Exception("Timeout while waiting for COMPLETED state")
0eff1ab3
JM
1245
1246 def get_capability(self, field):
1247 res = self.request("GET_CAPABILITY " + field)
1248 if "FAIL" in res:
1249 return None
1250 return res.split(' ')
2cdd91d8 1251
fb9cf82e 1252 def get_bss(self, bssid, ifname=None):
0663ae22 1253 if not ifname or ifname == self.ifname:
fb9cf82e
IP
1254 res = self.request("BSS " + bssid)
1255 elif ifname == self.group_ifname:
1256 res = self.group_request("BSS " + bssid)
1257 else:
1258 return None
1259
c126cb4d
JM
1260 if "FAIL" in res:
1261 return None
2cdd91d8
JM
1262 lines = res.splitlines()
1263 vals = dict()
1264 for l in lines:
fab49f61 1265 [name, value] = l.split('=', 1)
2cdd91d8 1266 vals[name] = value
c126cb4d
JM
1267 if len(vals) == 0:
1268 return None
2cdd91d8 1269 return vals
0fab9ce6
JM
1270
1271 def get_pmksa(self, bssid):
1272 res = self.request("PMKSA")
1273 lines = res.splitlines()
1274 for l in lines:
1275 if bssid not in l:
1276 continue
1277 vals = dict()
6bb32582 1278 try:
fab49f61 1279 [index, aa, pmkid, expiration, opportunistic] = l.split(' ')
6bb32582
JM
1280 cache_id = None
1281 except ValueError:
fab49f61 1282 [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ')
0fab9ce6
JM
1283 vals['index'] = index
1284 vals['pmkid'] = pmkid
1285 vals['expiration'] = expiration
1286 vals['opportunistic'] = opportunistic
6bb32582
JM
1287 if cache_id != None:
1288 vals['cache_id'] = cache_id
0fab9ce6
JM
1289 return vals
1290 return None
e1a5e09a
JM
1291
1292 def get_sta(self, addr, info=None, next=False):
1293 cmd = "STA-NEXT " if next else "STA "
1294 if addr is None:
1295 res = self.request("STA-FIRST")
1296 elif info:
1297 res = self.request(cmd + addr + " " + info)
1298 else:
1299 res = self.request(cmd + addr)
1300 lines = res.splitlines()
1301 vals = dict()
1302 first = True
1303 for l in lines:
1304 if first:
1305 vals['addr'] = l
1306 first = False
1307 else:
fab49f61 1308 [name, value] = l.split('=', 1)
e1a5e09a
JM
1309 vals[name] = value
1310 return vals
daf3806d
JM
1311
1312 def mgmt_rx(self, timeout=5):
1313 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
1314 if ev is None:
1315 return None
1316 msg = {}
1317 items = ev.split(' ')
fab49f61 1318 field, val = items[1].split('=')
daf3806d
JM
1319 if field != "freq":
1320 raise Exception("Unexpected MGMT-RX event format: " + ev)
1321 msg['freq'] = val
df49b90e 1322
fab49f61 1323 field, val = items[2].split('=')
df49b90e
JM
1324 if field != "datarate":
1325 raise Exception("Unexpected MGMT-RX event format: " + ev)
1326 msg['datarate'] = val
1327
fab49f61 1328 field, val = items[3].split('=')
df49b90e
JM
1329 if field != "ssi_signal":
1330 raise Exception("Unexpected MGMT-RX event format: " + ev)
1331 msg['ssi_signal'] = val
1332
daf3806d
JM
1333 frame = binascii.unhexlify(items[4])
1334 msg['frame'] = frame
1335
1336 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
1337 msg['fc'] = hdr[0]
1338 msg['subtype'] = (hdr[0] >> 4) & 0xf
1339 hdr = hdr[1:]
1340 msg['duration'] = hdr[0]
1341 hdr = hdr[1:]
1342 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1343 hdr = hdr[6:]
1344 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1345 hdr = hdr[6:]
1346 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1347 hdr = hdr[6:]
1348 msg['seq_ctrl'] = hdr[0]
1349 msg['payload'] = frame[24:]
1350
1351 return msg
5f35a5e2
JM
1352
1353 def wait_connected(self, timeout=10, error="Connection timed out"):
1354 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout)
1355 if ev is None:
1356 raise Exception(error)
1357 return ev
1358
67a0d4f9
JA
1359 def wait_disconnected(self, timeout=None, error="Disconnection timed out"):
1360 if timeout is None:
1361 timeout = 10 if self.hostname is None else 30
5f35a5e2
JM
1362 ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout)
1363 if ev is None:
1364 raise Exception(error)
1365 return ev
bbcbbd37
DS
1366
1367 def get_group_ifname(self):
1368 return self.group_ifname if self.group_ifname else self.ifname
7ebc7e8f
JM
1369
1370 def get_config(self):
1371 res = self.request("DUMP")
1372 if res.startswith("FAIL"):
1373 raise Exception("DUMP failed")
1374 lines = res.splitlines()
1375 vals = dict()
1376 for l in lines:
fab49f61 1377 [name, value] = l.split('=', 1)
7ebc7e8f
JM
1378 vals[name] = value
1379 return vals
1a1b0156
AO
1380
1381 def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac,
a5378870 1382 method="1000", info="", status=None, cpt=None, role=None):
1a1b0156
AO
1383 if status is None:
1384 cmd = "P2P_ASP_PROVISION"
1385 params = "info='%s' method=%s" % (info, method)
1386 else:
1387 cmd = "P2P_ASP_PROVISION_RESP"
1388 params = "status=%d" % status
1389
0663ae22
JM
1390 if role is not None:
1391 params += " role=" + role
c1e31bdb
MS
1392 if cpt is not None:
1393 params += " cpt=" + cpt
1394
1a1b0156
AO
1395 if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" %
1396 (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)):
1397 raise Exception("%s request failed" % cmd)
0a374295
JM
1398
1399 def note(self, txt):
1400 self.request("NOTE " + txt)
99bc57d0
JM
1401
1402 def wait_regdom(self, country_ie=False):
1403 for i in range(5):
1404 ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1)
1405 if ev is None:
1406 break
1407 if country_ie:
1408 if "init=COUNTRY_IE" in ev:
1409 break
1410 else:
1411 break
0422d06b
JM
1412
1413 def dpp_qr_code(self, uri):
1414 res = self.request("DPP_QR_CODE " + uri)
1415 if "FAIL" in res:
1416 raise Exception("Failed to parse QR Code URI")
1417 return int(res)
a5387062
JM
1418
1419 def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None,
1420 curve=None, key=None):
1421 cmd = "DPP_BOOTSTRAP_GEN type=" + type
1422 if chan:
1423 cmd += " chan=" + chan
1424 if mac:
1425 if mac is True:
1426 mac = self.own_addr()
1427 cmd += " mac=" + mac.replace(':', '')
1428 if info:
1429 cmd += " info=" + info
1430 if curve:
1431 cmd += " curve=" + curve
1432 if key:
1433 cmd += " key=" + key
1434 res = self.request(cmd)
1435 if "FAIL" in res:
1436 raise Exception("Failed to generate bootstrapping info")
1437 return int(res)
7e009100
JM
1438
1439 def dpp_listen(self, freq, netrole=None, qr=None, role=None):
1440 cmd = "DPP_LISTEN " + str(freq)
1441 if netrole:
1442 cmd += " netrole=" + netrole
1443 if qr:
1444 cmd += " qr=" + qr
1445 if role:
1446 cmd += " role=" + role
1447 if "OK" not in self.request(cmd):
1448 raise Exception("Failed to start listen operation")
6d196e59 1449
5725b3e3
JM
1450 def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None,
1451 extra=None, own=None, role=None, neg_freq=None,
1452 ssid=None, passphrase=None, expect_fail=False):
1453 cmd = "DPP_AUTH_INIT"
1454 if peer is None:
1455 peer = self.dpp_qr_code(uri)
1456 cmd += " peer=%d" % peer
1457 if own is not None:
1458 cmd += " own=%d" % own
1459 if role:
1460 cmd += " role=" + role
1461 if extra:
1462 cmd += " " + extra
1463 if conf:
1464 cmd += " conf=" + conf
1465 if configurator is not None:
1466 cmd += " configurator=%d" % configurator
1467 if neg_freq:
1468 cmd += " neg_freq=%d" % neg_freq
1469 if ssid:
1470 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
1471 if passphrase:
1472 cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode()
1473 res = self.request(cmd)
1474 if expect_fail:
1475 if "FAIL" not in res:
1476 raise Exception("DPP authentication started unexpectedly")
1477 return
1478 if "OK" not in res:
1479 raise Exception("Failed to initiate DPP Authentication")
1480
6d196e59
JM
1481 def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None,
1482 extra=None, use_id=None, allow_fail=False):
1483 if use_id is None:
1484 id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1485 else:
1486 id1 = use_id
1487 cmd = "own=%d " % id1
1488 if identifier:
1489 cmd += "identifier=%s " % identifier
1490 cmd += "init=1 "
1491 if role:
1492 cmd += "role=%s " % role
1493 if extra:
1494 cmd += extra + " "
1495 cmd += "code=%s" % code
1496 res = self.request("DPP_PKEX_ADD " + cmd)
1497 if allow_fail:
1498 return id1
1499 if "FAIL" in res:
1500 raise Exception("Failed to set PKEX data (initiator)")
1501 return id1
1502
1503 def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None,
1504 listen_role=None, use_id=None):
1505 if use_id is None:
1506 id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1507 else:
1508 id0 = use_id
1509 cmd = "own=%d " % id0
1510 if identifier:
1511 cmd += "identifier=%s " % identifier
1512 cmd += "code=%s" % code
1513 res = self.request("DPP_PKEX_ADD " + cmd)
1514 if "FAIL" in res:
1515 raise Exception("Failed to set PKEX data (responder)")
1516 self.dpp_listen(freq, role=listen_role)
1517 return id0
e105110f
JM
1518
1519 def dpp_configurator_add(self, curve=None, key=None):
1520 cmd = "DPP_CONFIGURATOR_ADD"
1521 if curve:
1522 cmd += " curve=" + curve
1523 if key:
1524 cmd += " key=" + key
1525 res = self.request(cmd)
1526 if "FAIL" in res:
1527 raise Exception("Failed to add configurator")
1528 return int(res)
1529
1530 def dpp_configurator_remove(self, conf_id):
1531 res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id)
1532 if "OK" not in res:
1533 raise Exception("DPP_CONFIGURATOR_REMOVE failed")