1# Python class for controlling wpa_supplicant 2# Copyright (c) 2013-2019, Jouni Malinen <j@w1.fi> 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 10import binascii 11import re 12import struct 13import wpaspy 14import remotehost 15import subprocess 16 17logger = logging.getLogger() 18wpas_ctrl = '/var/run/wpa_supplicant' 19 20class WpaSupplicant: 21 def __init__(self, ifname=None, global_iface=None, hostname=None, 22 port=9877, global_port=9878, monitor=True): 23 self.monitor = monitor 24 self.hostname = hostname 25 self.group_ifname = None 26 self.global_mon = None 27 self.global_ctrl = None 28 self.gctrl_mon = None 29 self.ctrl = None 30 self.mon = None 31 self.ifname = None 32 self.host = remotehost.Host(hostname, ifname) 33 self._group_dbg = None 34 if ifname: 35 self.set_ifname(ifname, hostname, port) 36 res = self.get_driver_status() 37 if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000: 38 self.p2p_dev_ifname = 'p2p-dev-' + self.ifname 39 else: 40 self.p2p_dev_ifname = ifname 41 42 self.global_iface = global_iface 43 if global_iface: 44 if hostname != None: 45 self.global_ctrl = wpaspy.Ctrl(hostname, global_port) 46 if self.monitor: 47 self.global_mon = wpaspy.Ctrl(hostname, global_port) 48 self.global_dbg = hostname + "/" + str(global_port) + "/" 49 else: 50 self.global_ctrl = wpaspy.Ctrl(global_iface) 51 if self.monitor: 52 self.global_mon = wpaspy.Ctrl(global_iface) 53 self.global_dbg = "" 54 if self.monitor: 55 self.global_mon.attach() 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 88 except Exception as e: 89 if str(e) == "DETACH failed": 90 pass 91 else: 92 raise 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 109 except Exception as e: 110 if str(e) == "DETACH failed": 111 pass 112 else: 113 raise 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() 137 138 def cmd_execute(self, cmd_array, shell=False): 139 if self.hostname is None: 140 if shell: 141 cmd = ' '.join(cmd_array) 142 else: 143 cmd = cmd_array 144 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, 145 stdout=subprocess.PIPE, shell=shell) 146 out = proc.communicate()[0] 147 ret = proc.returncode 148 return ret, out.decode() 149 else: 150 return self.host.execute(cmd_array) 151 152 def terminate(self): 153 if self.global_mon: 154 self.close_monitor_global() 155 self.global_ctrl.terminate() 156 self.global_ctrl = None 157 158 def close_ctrl(self): 159 self.close_monitor_global() 160 self.close_control_global() 161 self.remove_ifname() 162 163 def set_ifname(self, ifname, hostname=None, port=9877): 164 self.remove_ifname() 165 self.ifname = ifname 166 if hostname != None: 167 self.ctrl = wpaspy.Ctrl(hostname, port) 168 if self.monitor: 169 self.mon = wpaspy.Ctrl(hostname, port) 170 self.host = remotehost.Host(hostname, ifname) 171 self.dbg = hostname + "/" + ifname 172 else: 173 self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) 174 if self.monitor: 175 self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) 176 self.dbg = ifname 177 if self.monitor: 178 self.mon.attach() 179 180 def remove_ifname(self): 181 self.close_monitor_mon() 182 self.close_control_ctrl() 183 self.ifname = None 184 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 205 def interface_add(self, ifname, config="", driver="nl80211", 206 drv_params=None, br_ifname=None, create=False, 207 set_ifname=True, all_params=False, if_type=None): 208 status, groups = self.host.execute(["id"]) 209 if status != 0: 210 group = "admin" 211 group = "admin" if "(admin)" in groups else "adm" 212 cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group 213 if drv_params: 214 cmd = cmd + '\t' + drv_params 215 if br_ifname: 216 if not drv_params: 217 cmd += '\t' 218 cmd += '\t' + br_ifname 219 if create: 220 if not br_ifname: 221 cmd += '\t' 222 if not drv_params: 223 cmd += '\t' 224 cmd += '\tcreate' 225 if if_type: 226 cmd += '\t' + if_type 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' 233 if "FAIL" in self.global_request(cmd): 234 raise Exception("Failed to add a dynamic wpa_supplicant interface") 235 if not create and set_ifname: 236 port = self.get_ctrl_iface_port(ifname) 237 self.set_ifname(ifname, self.hostname, port) 238 res = self.get_driver_status() 239 if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000: 240 self.p2p_dev_ifname = 'p2p-dev-' + self.ifname 241 else: 242 self.p2p_dev_ifname = ifname 243 244 def interface_remove(self, ifname): 245 self.remove_ifname() 246 self.global_request("INTERFACE_REMOVE " + ifname) 247 248 def request(self, cmd, timeout=10): 249 logger.debug(self.dbg + ": CTRL: " + cmd) 250 return self.ctrl.request(cmd, timeout=timeout) 251 252 def global_request(self, cmd): 253 if self.global_iface is None: 254 return self.request(cmd) 255 else: 256 ifname = self.ifname or self.global_iface 257 logger.debug(self.global_dbg + ifname + ": CTRL(global): " + cmd) 258 return self.global_ctrl.request(cmd) 259 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 272 def group_request(self, cmd): 273 if self.group_ifname and self.group_ifname != self.ifname: 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) 280 return gctrl.request(cmd) 281 return self.request(cmd) 282 283 def ping(self): 284 return "PONG" in self.request("PING") 285 286 def global_ping(self): 287 return "PONG" in self.global_request("PING") 288 289 def reset(self): 290 self.dump_monitor() 291 res = self.request("FLUSH") 292 if "OK" not in res: 293 logger.info("FLUSH to " + self.ifname + " failed: " + res) 294 self.global_request("REMOVE_NETWORK all") 295 self.global_request("SET p2p_no_group_iface 1") 296 self.global_request("P2P_FLUSH") 297 self.close_monitor_group() 298 self.group_ifname = None 299 self.dump_monitor() 300 301 iter = 0 302 while iter < 60: 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: 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") 315 print("Trying to clear cfg80211/mac80211 scan state") 316 status, buf = self.host.execute(["ifconfig", self.ifname, "down"]) 317 if status != 0: 318 logger.info("ifconfig failed: " + buf) 319 logger.info(status) 320 status, buf = self.host.execute(["ifconfig", self.ifname, "up"]) 321 if status != 0: 322 logger.info("ifconfig failed: " + buf) 323 logger.info(status) 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") 328 self.dump_monitor() 329 330 if not self.ping(): 331 logger.info("No PING response from " + self.ifname + " after reset") 332 333 def set(self, field, value, allow_fail=False): 334 if "OK" not in self.request("SET " + field + " " + value): 335 if allow_fail: 336 return 337 raise Exception("Failed to set wpa_supplicant parameter " + field) 338 339 def add_network(self): 340 id = self.request("ADD_NETWORK") 341 if "FAIL" in id: 342 raise Exception("ADD_NETWORK failed") 343 return int(id) 344 345 def remove_network(self, id): 346 id = self.request("REMOVE_NETWORK " + str(id)) 347 if "FAIL" in id: 348 raise Exception("REMOVE_NETWORK failed") 349 return None 350 351 def get_network(self, id, field): 352 res = self.request("GET_NETWORK " + str(id) + " " + field) 353 if res == "FAIL\n": 354 return None 355 return res 356 357 def set_network(self, id, field, value): 358 res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value) 359 if "FAIL" in res: 360 raise Exception("SET_NETWORK failed") 361 return None 362 363 def set_network_quoted(self, id, field, value): 364 res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') 365 if "FAIL" in res: 366 raise Exception("SET_NETWORK failed") 367 return None 368 369 def p2pdev_request(self, cmd): 370 return self.global_request("IFNAME=" + self.p2p_dev_ifname + " " + cmd) 371 372 def p2pdev_add_network(self): 373 id = self.p2pdev_request("ADD_NETWORK") 374 if "FAIL" in id: 375 raise Exception("p2pdev ADD_NETWORK failed") 376 return int(id) 377 378 def p2pdev_set_network(self, id, field, value): 379 res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + " " + value) 380 if "FAIL" in res: 381 raise Exception("p2pdev SET_NETWORK failed") 382 return None 383 384 def p2pdev_set_network_quoted(self, id, field, value): 385 res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') 386 if "FAIL" in res: 387 raise Exception("p2pdev SET_NETWORK failed") 388 return None 389 390 def list_networks(self, p2p=False): 391 if p2p: 392 res = self.global_request("LIST_NETWORKS") 393 else: 394 res = self.request("LIST_NETWORKS") 395 lines = res.splitlines() 396 networks = [] 397 for l in lines: 398 if "network id" in l: 399 continue 400 [id, ssid, bssid, flags] = l.split('\t') 401 network = {} 402 network['id'] = id 403 network['ssid'] = ssid 404 network['bssid'] = bssid 405 network['flags'] = flags 406 networks.append(network) 407 return networks 408 409 def hs20_enable(self, auto_interworking=False): 410 self.request("SET interworking 1") 411 self.request("SET hs20 1") 412 if auto_interworking: 413 self.request("SET auto_interworking 1") 414 else: 415 self.request("SET auto_interworking 0") 416 417 def interworking_add_network(self, bssid): 418 id = self.request("INTERWORKING_ADD_NETWORK " + bssid) 419 if "FAIL" in id or "OK" in id: 420 raise Exception("INTERWORKING_ADD_NETWORK failed") 421 return int(id) 422 423 def add_cred(self): 424 id = self.request("ADD_CRED") 425 if "FAIL" in id: 426 raise Exception("ADD_CRED failed") 427 return int(id) 428 429 def remove_cred(self, id): 430 id = self.request("REMOVE_CRED " + str(id)) 431 if "FAIL" in id: 432 raise Exception("REMOVE_CRED failed") 433 return None 434 435 def set_cred(self, id, field, value): 436 res = self.request("SET_CRED " + str(id) + " " + field + " " + value) 437 if "FAIL" in res: 438 raise Exception("SET_CRED failed") 439 return None 440 441 def set_cred_quoted(self, id, field, value): 442 res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"') 443 if "FAIL" in res: 444 raise Exception("SET_CRED failed") 445 return None 446 447 def get_cred(self, id, field): 448 return self.request("GET_CRED " + str(id) + " " + field) 449 450 def add_cred_values(self, params): 451 id = self.add_cred() 452 453 quoted = ["realm", "username", "password", "domain", "imsi", 454 "excluded_ssid", "milenage", "ca_cert", "client_cert", 455 "private_key", "domain_suffix_match", "provisioning_sp", 456 "roaming_partner", "phase1", "phase2", "private_key_passwd", 457 "roaming_consortiums", "imsi_privacy_cert", 458 "imsi_privacy_attr"] 459 for field in quoted: 460 if field in params: 461 self.set_cred_quoted(id, field, params[field]) 462 463 not_quoted = ["eap", "roaming_consortium", "priority", 464 "required_roaming_consortium", "sp_priority", 465 "max_bss_load", "update_identifier", "req_conn_capab", 466 "min_dl_bandwidth_home", "min_ul_bandwidth_home", 467 "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"] 468 for field in not_quoted: 469 if field in params: 470 self.set_cred(id, field, params[field]) 471 472 as_list = ["home_ois", "required_home_ois"] 473 for field in as_list: 474 if field in params: 475 self.set_cred_quoted(id, field, ','.join(params[field])) 476 477 return id 478 479 def select_network(self, id, freq=None): 480 if freq: 481 extra = " freq=" + str(freq) 482 else: 483 extra = "" 484 id = self.request("SELECT_NETWORK " + str(id) + extra) 485 if "FAIL" in id: 486 raise Exception("SELECT_NETWORK failed") 487 return None 488 489 def mesh_group_add(self, id): 490 id = self.request("MESH_GROUP_ADD " + str(id)) 491 if "FAIL" in id: 492 raise Exception("MESH_GROUP_ADD failed") 493 return None 494 495 def mesh_group_remove(self): 496 id = self.request("MESH_GROUP_REMOVE " + str(self.ifname)) 497 if "FAIL" in id: 498 raise Exception("MESH_GROUP_REMOVE failed") 499 return None 500 501 def connect_network(self, id, timeout=None): 502 if timeout is None: 503 timeout = 10 if self.hostname is None else 60 504 self.dump_monitor() 505 self.select_network(id) 506 self.wait_connected(timeout=timeout) 507 508 def get_status(self, extra=None): 509 if extra: 510 extra = "-" + extra 511 else: 512 extra = "" 513 res = self.request("STATUS" + extra) 514 lines = res.splitlines() 515 vals = dict() 516 for l in lines: 517 try: 518 [name, value] = l.split('=', 1) 519 vals[name] = value 520 except ValueError as e: 521 logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) 522 return vals 523 524 def get_status_field(self, field, extra=None): 525 vals = self.get_status(extra) 526 if field in vals: 527 return vals[field] 528 return None 529 530 def get_group_status(self, extra=None): 531 if extra: 532 extra = "-" + extra 533 else: 534 extra = "" 535 res = self.group_request("STATUS" + extra) 536 lines = res.splitlines() 537 vals = dict() 538 for l in lines: 539 try: 540 [name, value] = l.split('=', 1) 541 except ValueError: 542 logger.info(self.ifname + ": Ignore unexpected status line: " + l) 543 continue 544 vals[name] = value 545 return vals 546 547 def get_group_status_field(self, field, extra=None): 548 vals = self.get_group_status(extra) 549 if field in vals: 550 return vals[field] 551 return None 552 553 def get_driver_status(self, ifname=None): 554 if ifname is None: 555 res = self.request("STATUS-DRIVER") 556 else: 557 res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname) 558 if res.startswith("FAIL"): 559 return dict() 560 lines = res.splitlines() 561 vals = dict() 562 for l in lines: 563 try: 564 [name, value] = l.split('=', 1) 565 except ValueError: 566 logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l) 567 continue 568 vals[name] = value 569 return vals 570 571 def get_driver_status_field(self, field, ifname=None): 572 vals = self.get_driver_status(ifname) 573 if field in vals: 574 return vals[field] 575 return None 576 577 def get_mcc(self): 578 mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent')) 579 return 1 if mcc < 2 else mcc 580 581 def get_mib(self): 582 res = self.request("MIB") 583 lines = res.splitlines() 584 vals = dict() 585 for l in lines: 586 try: 587 [name, value] = l.split('=', 1) 588 vals[name] = value 589 except ValueError as e: 590 logger.info(self.ifname + ": Ignore unexpected MIB line: " + l) 591 return vals 592 593 def p2p_dev_addr(self): 594 return self.get_status_field("p2p_device_address") 595 596 def p2p_interface_addr(self): 597 return self.get_group_status_field("address") 598 599 def own_addr(self): 600 try: 601 res = self.p2p_interface_addr() 602 except: 603 res = self.p2p_dev_addr() 604 return res 605 606 def get_addr(self, group=False): 607 dev_addr = self.own_addr() 608 if not group: 609 addr = self.get_status_field('address') 610 if addr: 611 dev_addr = addr 612 613 return dev_addr 614 615 def p2p_listen(self): 616 return self.global_request("P2P_LISTEN") 617 618 def p2p_ext_listen(self, period, interval): 619 return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval)) 620 621 def p2p_cancel_ext_listen(self): 622 return self.global_request("P2P_EXT_LISTEN") 623 624 def p2p_find(self, social=False, progressive=False, dev_id=None, 625 dev_type=None, delay=None, freq=None): 626 cmd = "P2P_FIND" 627 if social: 628 cmd = cmd + " type=social" 629 elif progressive: 630 cmd = cmd + " type=progressive" 631 if dev_id: 632 cmd = cmd + " dev_id=" + dev_id 633 if dev_type: 634 cmd = cmd + " dev_type=" + dev_type 635 if delay: 636 cmd = cmd + " delay=" + str(delay) 637 if freq: 638 cmd = cmd + " freq=" + str(freq) 639 return self.global_request(cmd) 640 641 def p2p_stop_find(self): 642 return self.global_request("P2P_STOP_FIND") 643 644 def wps_read_pin(self): 645 self.pin = self.request("WPS_PIN get").rstrip("\n") 646 if "FAIL" in self.pin: 647 raise Exception("Could not generate PIN") 648 return self.pin 649 650 def peer_known(self, peer, full=True): 651 res = self.global_request("P2P_PEER " + peer) 652 if peer.lower() not in res.lower(): 653 return False 654 if not full: 655 return True 656 return "[PROBE_REQ_ONLY]" not in res 657 658 def discover_peer(self, peer, full=True, timeout=15, social=True, 659 force_find=False, freq=None): 660 logger.info(self.ifname + ": Trying to discover peer " + peer) 661 if not force_find and self.peer_known(peer, full): 662 return True 663 self.p2p_find(social, freq=freq) 664 count = 0 665 while count < timeout * 4: 666 time.sleep(0.25) 667 count = count + 1 668 if self.peer_known(peer, full): 669 return True 670 return False 671 672 def get_peer(self, peer): 673 res = self.global_request("P2P_PEER " + peer) 674 if peer.lower() not in res.lower(): 675 raise Exception("Peer information not available") 676 lines = res.splitlines() 677 vals = dict() 678 for l in lines: 679 if '=' in l: 680 [name, value] = l.split('=', 1) 681 vals[name] = value 682 return vals 683 684 def group_form_result(self, ev, expect_failure=False, go_neg_res=None): 685 if expect_failure: 686 if "P2P-GROUP-STARTED" in ev: 687 raise Exception("Group formation succeeded when expecting failure") 688 exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' 689 s = re.split(exp, ev) 690 if len(s) < 3: 691 return None 692 res = {} 693 res['result'] = 'go-neg-failed' 694 res['status'] = int(s[2]) 695 return res 696 697 if "P2P-GROUP-STARTED" not in ev: 698 raise Exception("No P2P-GROUP-STARTED event seen") 699 700 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.]*)' 701 s = re.split(exp, ev) 702 if len(s) < 11: 703 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' 704 s = re.split(exp, ev) 705 if len(s) < 8: 706 raise Exception("Could not parse P2P-GROUP-STARTED") 707 res = {} 708 res['result'] = 'success' 709 res['ifname'] = s[2] 710 self.group_ifname = s[2] 711 try: 712 if self.hostname is None: 713 self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, 714 self.group_ifname)) 715 else: 716 port = self.get_ctrl_iface_port(self.group_ifname) 717 self.gctrl_mon = wpaspy.Ctrl(self.hostname, port) 718 if self.monitor: 719 self.gctrl_mon.attach() 720 except: 721 logger.debug("Could not open monitor socket for group interface") 722 self.gctrl_mon = None 723 res['role'] = s[3] 724 res['ssid'] = s[4] 725 res['freq'] = s[5] 726 if "[PERSISTENT]" in ev: 727 res['persistent'] = True 728 else: 729 res['persistent'] = False 730 p = re.match(r'psk=([0-9a-f]*)', s[6]) 731 if p: 732 res['psk'] = p.group(1) 733 p = re.match(r'passphrase="(.*)"', s[6]) 734 if p: 735 res['passphrase'] = p.group(1) 736 res['go_dev_addr'] = s[7] 737 738 if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]: 739 res['ip_addr'] = s[8] 740 if len(s) > 9: 741 res['ip_mask'] = s[9] 742 if len(s) > 10: 743 res['go_ip_addr'] = s[10] 744 745 if go_neg_res: 746 exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' 747 s = re.split(exp, go_neg_res) 748 if len(s) < 4: 749 raise Exception("Could not parse P2P-GO-NEG-SUCCESS") 750 res['go_neg_role'] = s[2] 751 res['go_neg_freq'] = s[3] 752 753 return res 754 755 def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, 756 persistent=False, freq=None, freq2=None, 757 max_oper_chwidth=None, ht40=False, vht=False): 758 if not self.discover_peer(peer): 759 raise Exception("Peer " + peer + " not found") 760 self.dump_monitor() 761 if pin: 762 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" 763 else: 764 cmd = "P2P_CONNECT " + peer + " " + method + " auth" 765 if go_intent: 766 cmd = cmd + ' go_intent=' + str(go_intent) 767 if freq: 768 cmd = cmd + ' freq=' + str(freq) 769 if freq2: 770 cmd = cmd + ' freq2=' + str(freq2) 771 if max_oper_chwidth: 772 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) 773 if ht40: 774 cmd = cmd + ' ht40' 775 if vht: 776 cmd = cmd + ' vht' 777 if persistent: 778 cmd = cmd + " persistent" 779 if "OK" in self.global_request(cmd): 780 return None 781 raise Exception("P2P_CONNECT (auth) failed") 782 783 def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False): 784 if timeout is None: 785 timeout = 1 if expect_failure else 5 786 go_neg_res = None 787 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", 788 "P2P-GO-NEG-FAILURE"], timeout) 789 if ev is None: 790 if expect_failure: 791 return None 792 raise Exception("Group formation timed out") 793 if "P2P-GO-NEG-SUCCESS" in ev: 794 go_neg_res = ev 795 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) 796 if ev is None: 797 if expect_failure: 798 return None 799 raise Exception("Group formation timed out") 800 return self.group_form_result(ev, expect_failure, go_neg_res) 801 802 def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None, 803 expect_failure=False, persistent=False, 804 persistent_id=None, freq=None, provdisc=False, 805 wait_group=True, freq2=None, max_oper_chwidth=None, 806 ht40=False, vht=False): 807 if not self.discover_peer(peer,timeout=timeout if timeout else 15): 808 raise Exception("Peer " + peer + " not found") 809 self.dump_monitor() 810 if pin: 811 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method 812 else: 813 cmd = "P2P_CONNECT " + peer + " " + method 814 if go_intent is not None: 815 cmd = cmd + ' go_intent=' + str(go_intent) 816 if freq: 817 cmd = cmd + ' freq=' + str(freq) 818 if freq2: 819 cmd = cmd + ' freq2=' + str(freq2) 820 if max_oper_chwidth: 821 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) 822 if ht40: 823 cmd = cmd + ' ht40' 824 if vht: 825 cmd = cmd + ' vht' 826 if persistent: 827 cmd = cmd + " persistent" 828 elif persistent_id: 829 cmd = cmd + " persistent=" + persistent_id 830 if provdisc: 831 cmd = cmd + " provdisc" 832 if "OK" in self.global_request(cmd): 833 if timeout == 0: 834 return None 835 go_neg_res = None 836 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", 837 "P2P-GO-NEG-FAILURE"], timeout) 838 if ev is None: 839 if expect_failure: 840 return None 841 raise Exception("Group formation timed out") 842 if "P2P-GO-NEG-SUCCESS" in ev: 843 if not wait_group: 844 return ev 845 go_neg_res = ev 846 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) 847 if ev is None: 848 if expect_failure: 849 return None 850 raise Exception("Group formation timed out") 851 self.dump_monitor() 852 return self.group_form_result(ev, expect_failure, go_neg_res) 853 raise Exception("P2P_CONNECT failed") 854 855 def _wait_event(self, mon, pfx, events, timeout): 856 if not isinstance(events, list): 857 raise Exception("WpaSupplicant._wait_event() called with incorrect events argument type") 858 start = os.times()[4] 859 while True: 860 while mon.pending(): 861 ev = mon.recv() 862 logger.debug(self.dbg + pfx + ev) 863 for event in events: 864 if event in ev: 865 return ev 866 now = os.times()[4] 867 remaining = start + timeout - now 868 if remaining <= 0: 869 break 870 if not mon.pending(timeout=remaining): 871 break 872 return None 873 874 def wait_event(self, events, timeout=10): 875 return self._wait_event(self.mon, ": ", events, timeout) 876 877 def wait_global_event(self, events, timeout): 878 if self.global_iface is None: 879 return self.wait_event(events, timeout) 880 return self._wait_event(self.global_mon, "(global): ", 881 events, timeout) 882 883 def wait_group_event(self, events, timeout=10): 884 if not isinstance(events, list): 885 raise Exception("WpaSupplicant.wait_group_event() called with incorrect events argument type") 886 if self.group_ifname and self.group_ifname != self.ifname: 887 if self.gctrl_mon is None: 888 return None 889 start = os.times()[4] 890 while True: 891 while self.gctrl_mon.pending(): 892 ev = self.gctrl_mon.recv() 893 logger.debug(self.group_dbg + "(group): " + ev) 894 for event in events: 895 if event in ev: 896 return ev 897 now = os.times()[4] 898 remaining = start + timeout - now 899 if remaining <= 0: 900 break 901 if not self.gctrl_mon.pending(timeout=remaining): 902 break 903 return None 904 905 return self.wait_event(events, timeout) 906 907 def wait_go_ending_session(self): 908 self.close_monitor_group() 909 timeout = 3 if self.hostname is None else 10 910 ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout) 911 if ev is None: 912 raise Exception("Group removal event timed out") 913 if "reason=GO_ENDING_SESSION" not in ev: 914 raise Exception("Unexpected group removal reason") 915 916 def dump_monitor(self, mon=True, global_mon=True): 917 count_iface = 0 918 count_global = 0 919 while mon and self.monitor and self.mon.pending(): 920 ev = self.mon.recv() 921 logger.debug(self.dbg + ": " + ev) 922 count_iface += 1 923 while global_mon and self.monitor and self.global_mon and self.global_mon.pending(): 924 ev = self.global_mon.recv() 925 logger.debug(self.global_dbg + self.ifname + "(global): " + ev) 926 count_global += 1 927 return (count_iface, count_global) 928 929 def remove_group(self, ifname=None): 930 self.close_monitor_group() 931 if ifname is None: 932 ifname = self.group_ifname if self.group_ifname else self.ifname 933 if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): 934 raise Exception("Group could not be removed") 935 self.group_ifname = None 936 937 def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False): 938 self.dump_monitor() 939 cmd = "P2P_GROUP_ADD" 940 if persistent is None: 941 pass 942 elif persistent is True: 943 cmd = cmd + " persistent" 944 else: 945 cmd = cmd + " persistent=" + str(persistent) 946 if freq: 947 cmd = cmd + " freq=" + str(freq) 948 if "OK" in self.global_request(cmd): 949 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) 950 if ev is None: 951 raise Exception("GO start up timed out") 952 if not no_event_clear: 953 self.dump_monitor() 954 return self.group_form_result(ev) 955 raise Exception("P2P_GROUP_ADD failed") 956 957 def p2p_go_authorize_client(self, pin): 958 cmd = "WPS_PIN any " + pin 959 if "FAIL" in self.group_request(cmd): 960 raise Exception("Failed to authorize client connection on GO") 961 return None 962 963 def p2p_go_authorize_client_pbc(self): 964 cmd = "WPS_PBC" 965 if "FAIL" in self.group_request(cmd): 966 raise Exception("Failed to authorize client connection on GO") 967 return None 968 969 def p2p_connect_group(self, go_addr, pin, timeout=0, social=False, 970 freq=None): 971 self.dump_monitor() 972 if not self.discover_peer(go_addr, social=social, freq=freq): 973 if social or not self.discover_peer(go_addr, social=social): 974 raise Exception("GO " + go_addr + " not found") 975 self.p2p_stop_find() 976 self.dump_monitor() 977 cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" 978 if freq: 979 cmd += " freq=" + str(freq) 980 if "OK" in self.global_request(cmd): 981 if timeout == 0: 982 self.dump_monitor() 983 return None 984 ev = self.wait_global_event(["P2P-GROUP-STARTED", 985 "P2P-GROUP-FORMATION-FAILURE"], 986 timeout) 987 if ev is None: 988 raise Exception("Joining the group timed out") 989 if "P2P-GROUP-STARTED" not in ev: 990 raise Exception("Failed to join the group") 991 self.dump_monitor() 992 return self.group_form_result(ev) 993 raise Exception("P2P_CONNECT(join) failed") 994 995 def tdls_setup(self, peer): 996 cmd = "TDLS_SETUP " + peer 997 if "FAIL" in self.group_request(cmd): 998 raise Exception("Failed to request TDLS setup") 999 return None 1000 1001 def tdls_teardown(self, peer): 1002 cmd = "TDLS_TEARDOWN " + peer 1003 if "FAIL" in self.group_request(cmd): 1004 raise Exception("Failed to request TDLS teardown") 1005 return None 1006 1007 def tdls_link_status(self, peer): 1008 cmd = "TDLS_LINK_STATUS " + peer 1009 ret = self.group_request(cmd) 1010 if "FAIL" in ret: 1011 raise Exception("Failed to request TDLS link status") 1012 return ret 1013 1014 def tspecs(self): 1015 """Return (tsid, up) tuples representing current tspecs""" 1016 res = self.request("WMM_AC_STATUS") 1017 tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res) 1018 tspecs = [tuple(map(int, tspec)) for tspec in tspecs] 1019 1020 logger.debug("tspecs: " + str(tspecs)) 1021 return tspecs 1022 1023 def add_ts(self, tsid, up, direction="downlink", expect_failure=False, 1024 extra=None): 1025 params = { 1026 "sba": 9000, 1027 "nominal_msdu_size": 1500, 1028 "min_phy_rate": 6000000, 1029 "mean_data_rate": 1500, 1030 } 1031 cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up) 1032 for (key, value) in params.items(): 1033 cmd += " %s=%d" % (key, value) 1034 if extra: 1035 cmd += " " + extra 1036 1037 if self.request(cmd).strip() != "OK": 1038 raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up)) 1039 1040 if expect_failure: 1041 ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2) 1042 if ev is None: 1043 raise Exception("ADDTS failed (time out while waiting failure)") 1044 if "tsid=%d" % (tsid) not in ev: 1045 raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED") 1046 return 1047 1048 ev = self.wait_event(["TSPEC-ADDED"], timeout=1) 1049 if ev is None: 1050 raise Exception("ADDTS failed (time out)") 1051 if "tsid=%d" % (tsid) not in ev: 1052 raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)") 1053 1054 if (tsid, up) not in self.tspecs(): 1055 raise Exception("ADDTS failed (tsid not in tspec list)") 1056 1057 def del_ts(self, tsid): 1058 if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK": 1059 raise Exception("DELTS failed") 1060 1061 ev = self.wait_event(["TSPEC-REMOVED"], timeout=1) 1062 if ev is None: 1063 raise Exception("DELTS failed (time out)") 1064 if "tsid=%d" % (tsid) not in ev: 1065 raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)") 1066 1067 tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid] 1068 if tspecs: 1069 raise Exception("DELTS failed (still in tspec list)") 1070 1071 def connect(self, ssid=None, ssid2=None, timeout=None, **kwargs): 1072 logger.info("Connect STA " + self.ifname + " to AP") 1073 id = self.add_network() 1074 if ssid: 1075 self.set_network_quoted(id, "ssid", ssid) 1076 elif ssid2: 1077 self.set_network(id, "ssid", ssid2) 1078 1079 quoted = ["psk", "identity", "anonymous_identity", "password", 1080 "machine_identity", "machine_password", 1081 "ca_cert", "client_cert", "private_key", 1082 "private_key_passwd", "ca_cert2", "client_cert2", 1083 "private_key2", "phase1", "phase2", "domain_suffix_match", 1084 "altsubject_match", "subject_match", "pac_file", 1085 "bgscan", "ht_mcs", "id_str", "openssl_ciphers", 1086 "domain_match", "dpp_connector", "sae_password", 1087 "sae_password_id", "check_cert_subject", 1088 "machine_ca_cert", "machine_client_cert", 1089 "machine_private_key", "machine_phase2", 1090 "imsi_identity", "imsi_privacy_cert", "imsi_privacy_attr"] 1091 for field in quoted: 1092 if field in kwargs and kwargs[field]: 1093 self.set_network_quoted(id, field, kwargs[field]) 1094 1095 not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise", 1096 "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3", 1097 "wep_tx_keyidx", "scan_freq", "freq_list", "eap", 1098 "eapol_flags", "fragment_size", "scan_ssid", "auth_alg", 1099 "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid", 1100 "disable_he", "disable_eht", 1101 "disable_max_amsdu", "ampdu_factor", "ampdu_density", 1102 "disable_ht40", "disable_sgi", "disable_ldpc", 1103 "ht40_intolerant", "update_identifier", "mac_addr", 1104 "erp", "bg_scan_period", "bssid_ignore", 1105 "bssid_accept", "mem_only_psk", "eap_workaround", 1106 "engine", "fils_dh_group", "bssid_hint", 1107 "dpp_csign", "dpp_csign_expiry", 1108 "dpp_netaccesskey", "dpp_netaccesskey_expiry", "dpp_pfs", 1109 "dpp_connector_privacy", 1110 "group_mgmt", "owe_group", "owe_only", 1111 "owe_ptk_workaround", 1112 "transition_disable", "sae_pk", 1113 "roaming_consortium_selection", "ocv", 1114 "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc", 1115 "ft_eap_pmksa_caching", "beacon_prot", 1116 "mac_value", 1117 "wpa_deny_ptk0_rekey", 1118 "max_idle", 1119 "ssid_protection", 1120 "enable_4addr_mode"] 1121 for field in not_quoted: 1122 if field in kwargs and kwargs[field]: 1123 self.set_network(id, field, kwargs[field]) 1124 1125 if timeout is None: 1126 if "eap" in kwargs: 1127 timeout=20 1128 else: 1129 timeout=15 1130 1131 known_args = {"raw_psk", "password_hex", "peerkey", "okc", "ocsp", 1132 "only_add_network", "wait_connect", "raw_identity"} 1133 unknown = set(kwargs.keys()) 1134 unknown -= set(quoted) 1135 unknown -= set(not_quoted) 1136 unknown -= known_args 1137 if unknown: 1138 raise Exception("Unknown WpaSupplicant::connect() arguments: " + str(unknown)) 1139 1140 if "raw_identity" in kwargs and kwargs['raw_identity']: 1141 self.set_network(id, "identity", kwargs['raw_identity']) 1142 if "raw_psk" in kwargs and kwargs['raw_psk']: 1143 self.set_network(id, "psk", kwargs['raw_psk']) 1144 if "password_hex" in kwargs and kwargs['password_hex']: 1145 self.set_network(id, "password", kwargs['password_hex']) 1146 if "peerkey" in kwargs and kwargs['peerkey']: 1147 self.set_network(id, "peerkey", "1") 1148 if "okc" in kwargs and kwargs['okc']: 1149 self.set_network(id, "proactive_key_caching", "1") 1150 if "ocsp" in kwargs and kwargs['ocsp']: 1151 self.set_network(id, "ocsp", str(kwargs['ocsp'])) 1152 if "only_add_network" in kwargs and kwargs['only_add_network']: 1153 return id 1154 if "wait_connect" not in kwargs or kwargs['wait_connect']: 1155 self.connect_network(id, timeout=timeout) 1156 else: 1157 self.dump_monitor() 1158 self.select_network(id) 1159 return id 1160 1161 def scan(self, type=None, freq=None, no_wait=False, only_new=False, 1162 passive=False, timeout=15): 1163 if not no_wait: 1164 self.dump_monitor() 1165 if type: 1166 cmd = "SCAN TYPE=" + type 1167 else: 1168 cmd = "SCAN" 1169 if freq: 1170 cmd = cmd + " freq=" + str(freq) 1171 if only_new: 1172 cmd += " only_new=1" 1173 if passive: 1174 cmd += " passive=1" 1175 if not no_wait: 1176 self.dump_monitor() 1177 res = self.request(cmd) 1178 if "OK" not in res: 1179 raise Exception("Failed to trigger scan: " + str(res)) 1180 if no_wait: 1181 return 1182 ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS", 1183 "CTRL-EVENT-SCAN-FAILED"], timeout) 1184 if ev is None: 1185 raise Exception("Scan timed out") 1186 if "CTRL-EVENT-SCAN-FAILED" in ev: 1187 raise Exception("Scan failed: " + ev) 1188 1189 def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False, 1190 passive=False): 1191 if not force_scan and self.get_bss(bssid) is not None: 1192 return 1193 for i in range(0, 10): 1194 self.scan(freq=freq, type="ONLY", only_new=only_new, 1195 passive=passive) 1196 if self.get_bss(bssid) is not None: 1197 return 1198 raise Exception("Could not find BSS " + bssid + " in scan") 1199 1200 def flush_scan_cache(self, freq=2417): 1201 for i in range(3): 1202 self.request("BSS_FLUSH 0") 1203 try: 1204 self.scan(freq=freq, only_new=True) 1205 except Exception as e: 1206 if i < 2: 1207 logger.info("flush_scan_cache: Failed to start scan: " + str(e)) 1208 self.request("ABORT_SCAN") 1209 time.sleep(0.1) 1210 res = self.request("SCAN_RESULTS") 1211 if len(res.splitlines()) > 1: 1212 logger.debug("Scan results remaining after first attempt to flush the results:\n" + res) 1213 self.request("BSS_FLUSH 0") 1214 self.scan(freq=2422, only_new=True) 1215 res = self.request("SCAN_RESULTS") 1216 if len(res.splitlines()) > 1: 1217 logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res) 1218 1219 def disconnect_and_stop_scan(self): 1220 self.request("DISCONNECT") 1221 res = self.request("ABORT_SCAN") 1222 for i in range(2 if "OK" in res else 1): 1223 self.wait_event(["CTRL-EVENT-DISCONNECTED", 1224 "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5) 1225 self.dump_monitor() 1226 1227 def roam(self, bssid, fail_test=False, assoc_reject_ok=False, 1228 check_bssid=True): 1229 self.dump_monitor() 1230 if "OK" not in self.request("ROAM " + bssid): 1231 raise Exception("ROAM failed") 1232 if fail_test: 1233 if assoc_reject_ok: 1234 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1235 "CTRL-EVENT-DISCONNECTED", 1236 "CTRL-EVENT-ASSOC-REJECT"], timeout=1) 1237 else: 1238 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1239 "CTRL-EVENT-DISCONNECTED"], timeout=1) 1240 if ev and "CTRL-EVENT-DISCONNECTED" in ev: 1241 self.dump_monitor() 1242 return 1243 if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev: 1244 raise Exception("Unexpected connection") 1245 self.dump_monitor() 1246 return 1247 if assoc_reject_ok: 1248 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1249 "CTRL-EVENT-DISCONNECTED"], timeout=10) 1250 else: 1251 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1252 "CTRL-EVENT-DISCONNECTED", 1253 "CTRL-EVENT-ASSOC-REJECT"], timeout=10) 1254 if ev is None: 1255 raise Exception("Roaming with the AP timed out") 1256 if "CTRL-EVENT-ASSOC-REJECT" in ev: 1257 raise Exception("Roaming association rejected") 1258 if "CTRL-EVENT-DISCONNECTED" in ev: 1259 raise Exception("Unexpected disconnection when waiting for roam to complete") 1260 self.dump_monitor() 1261 if check_bssid and self.get_status_field('bssid') != bssid: 1262 raise Exception("Did not roam to correct BSSID") 1263 1264 def roam_over_ds(self, bssid, fail_test=False, force=False): 1265 self.dump_monitor() 1266 cmd = "FT_DS " + bssid 1267 if force: 1268 cmd += " force" 1269 if "OK" not in self.request(cmd): 1270 raise Exception("FT_DS failed") 1271 if fail_test: 1272 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) 1273 if ev is not None: 1274 raise Exception("Unexpected connection") 1275 self.dump_monitor() 1276 return 1277 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1278 "CTRL-EVENT-ASSOC-REJECT"], timeout=10) 1279 if ev is None: 1280 raise Exception("Roaming with the AP timed out") 1281 if "CTRL-EVENT-ASSOC-REJECT" in ev: 1282 raise Exception("Roaming association rejected") 1283 self.dump_monitor() 1284 1285 def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, 1286 new_passphrase=None, no_wait=False): 1287 self.dump_monitor() 1288 if new_ssid: 1289 self.request("WPS_REG " + bssid + " " + pin + " " + 1290 binascii.hexlify(new_ssid.encode()).decode() + " " + 1291 key_mgmt + " " + cipher + " " + 1292 binascii.hexlify(new_passphrase.encode()).decode()) 1293 if no_wait: 1294 return 1295 ev = self.wait_event(["WPS-SUCCESS"], timeout=15) 1296 else: 1297 self.request("WPS_REG " + bssid + " " + pin) 1298 if no_wait: 1299 return 1300 ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) 1301 if ev is None: 1302 raise Exception("WPS cred timed out") 1303 ev = self.wait_event(["WPS-FAIL"], timeout=15) 1304 if ev is None: 1305 raise Exception("WPS timed out") 1306 self.wait_connected(timeout=15) 1307 1308 def relog(self): 1309 self.global_request("RELOG") 1310 1311 def wait_completed(self, timeout=10): 1312 for i in range(0, timeout * 2): 1313 if self.get_status_field("wpa_state") == "COMPLETED": 1314 return 1315 time.sleep(0.5) 1316 raise Exception("Timeout while waiting for COMPLETED state") 1317 1318 def get_capability(self, field): 1319 res = self.request("GET_CAPABILITY " + field) 1320 if "FAIL" in res: 1321 return None 1322 return res.split(' ') 1323 1324 def get_bss(self, bssid, ifname=None): 1325 if not ifname or ifname == self.ifname: 1326 res = self.request("BSS " + bssid) 1327 elif ifname == self.group_ifname: 1328 res = self.group_request("BSS " + bssid) 1329 else: 1330 return None 1331 1332 if "FAIL" in res: 1333 return None 1334 lines = res.splitlines() 1335 vals = dict() 1336 for l in lines: 1337 [name, value] = l.split('=', 1) 1338 vals[name] = value 1339 if len(vals) == 0: 1340 return None 1341 return vals 1342 1343 def get_pmksa(self, bssid): 1344 res = self.request("PMKSA") 1345 lines = res.splitlines() 1346 for l in lines: 1347 if bssid not in l: 1348 continue 1349 vals = dict() 1350 try: 1351 [index, aa, pmkid, expiration, opportunistic] = l.split(' ') 1352 cache_id = None 1353 except ValueError: 1354 [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ') 1355 vals['index'] = index 1356 vals['pmkid'] = pmkid 1357 vals['expiration'] = expiration 1358 vals['opportunistic'] = opportunistic 1359 if cache_id != None: 1360 vals['cache_id'] = cache_id 1361 return vals 1362 return None 1363 1364 def get_pmk(self, network_id): 1365 bssid = self.get_status_field('bssid') 1366 res = self.request("PMKSA_GET %d" % network_id) 1367 for val in res.splitlines(): 1368 if val.startswith(bssid): 1369 return val.split(' ')[2] 1370 return None 1371 1372 def get_sta(self, addr, info=None, next=False): 1373 cmd = "STA-NEXT " if next else "STA " 1374 if addr is None: 1375 res = self.request("STA-FIRST") 1376 elif info: 1377 res = self.request(cmd + addr + " " + info) 1378 else: 1379 res = self.request(cmd + addr) 1380 lines = res.splitlines() 1381 vals = dict() 1382 first = True 1383 for l in lines: 1384 if first: 1385 vals['addr'] = l 1386 first = False 1387 else: 1388 [name, value] = l.split('=', 1) 1389 vals[name] = value 1390 return vals 1391 1392 def mgmt_rx(self, timeout=5): 1393 ev = self.wait_event(["MGMT-RX"], timeout=timeout) 1394 if ev is None: 1395 return None 1396 return self.mgmt_rx_parse(ev) 1397 1398 def mgmt_rx_parse(self, ev): 1399 msg = {} 1400 items = ev.split(' ') 1401 field, val = items[1].split('=') 1402 if field != "freq": 1403 raise Exception("Unexpected MGMT-RX event format: " + ev) 1404 msg['freq'] = val 1405 1406 field, val = items[2].split('=') 1407 if field != "datarate": 1408 raise Exception("Unexpected MGMT-RX event format: " + ev) 1409 msg['datarate'] = val 1410 1411 field, val = items[3].split('=') 1412 if field != "ssi_signal": 1413 raise Exception("Unexpected MGMT-RX event format: " + ev) 1414 msg['ssi_signal'] = val 1415 1416 frame = binascii.unhexlify(items[4]) 1417 msg['frame'] = frame 1418 1419 hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) 1420 msg['fc'] = hdr[0] 1421 msg['subtype'] = (hdr[0] >> 4) & 0xf 1422 hdr = hdr[1:] 1423 msg['duration'] = hdr[0] 1424 hdr = hdr[1:] 1425 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1426 hdr = hdr[6:] 1427 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1428 hdr = hdr[6:] 1429 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1430 hdr = hdr[6:] 1431 msg['seq_ctrl'] = hdr[0] 1432 msg['payload'] = frame[24:] 1433 1434 return msg 1435 1436 def wait_connected(self, timeout=10, error="Connection timed out"): 1437 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) 1438 if ev is None: 1439 raise Exception(error) 1440 return ev 1441 1442 def wait_disconnected(self, timeout=None, error="Disconnection timed out"): 1443 if timeout is None: 1444 timeout = 10 if self.hostname is None else 30 1445 ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout) 1446 if ev is None: 1447 raise Exception(error) 1448 return ev 1449 1450 def get_group_ifname(self): 1451 return self.group_ifname if self.group_ifname else self.ifname 1452 1453 def get_config(self): 1454 res = self.request("DUMP") 1455 if res.startswith("FAIL"): 1456 raise Exception("DUMP failed") 1457 lines = res.splitlines() 1458 vals = dict() 1459 for l in lines: 1460 [name, value] = l.split('=', 1) 1461 vals[name] = value 1462 return vals 1463 1464 def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac, 1465 method="1000", info="", status=None, cpt=None, role=None): 1466 if status is None: 1467 cmd = "P2P_ASP_PROVISION" 1468 params = "info='%s' method=%s" % (info, method) 1469 else: 1470 cmd = "P2P_ASP_PROVISION_RESP" 1471 params = "status=%d" % status 1472 1473 if role is not None: 1474 params += " role=" + role 1475 if cpt is not None: 1476 params += " cpt=" + cpt 1477 1478 if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" % 1479 (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)): 1480 raise Exception("%s request failed" % cmd) 1481 1482 def note(self, txt): 1483 self.request("NOTE " + txt) 1484 1485 def save_config(self): 1486 if "OK" not in self.request("SAVE_CONFIG"): 1487 raise Exception("Failed to save configuration file") 1488 1489 def wait_regdom(self, country_ie=False): 1490 for i in range(5): 1491 ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1) 1492 if ev is None: 1493 break 1494 if country_ie: 1495 if "init=COUNTRY_IE" in ev: 1496 break 1497 else: 1498 break 1499 1500 def dpp_qr_code(self, uri): 1501 res = self.request("DPP_QR_CODE " + uri) 1502 if "FAIL" in res: 1503 raise Exception("Failed to parse QR Code URI") 1504 return int(res) 1505 1506 def dpp_nfc_uri(self, uri): 1507 res = self.request("DPP_NFC_URI " + uri) 1508 if "FAIL" in res: 1509 raise Exception("Failed to parse NFC URI") 1510 return int(res) 1511 1512 def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None, 1513 curve=None, key=None, supported_curves=None, 1514 host=None): 1515 cmd = "DPP_BOOTSTRAP_GEN type=" + type 1516 if chan: 1517 cmd += " chan=" + chan 1518 if mac: 1519 if mac is True: 1520 mac = self.own_addr() 1521 cmd += " mac=" + mac.replace(':', '') 1522 if info: 1523 cmd += " info=" + info 1524 if curve: 1525 cmd += " curve=" + curve 1526 if key: 1527 cmd += " key=" + key 1528 if supported_curves: 1529 cmd += " supported_curves=" + supported_curves 1530 if host: 1531 cmd += " host=" + host 1532 res = self.request(cmd) 1533 if "FAIL" in res: 1534 raise Exception("Failed to generate bootstrapping info") 1535 return int(res) 1536 1537 def dpp_bootstrap_set(self, id, conf=None, configurator=None, ssid=None, 1538 extra=None): 1539 cmd = "DPP_BOOTSTRAP_SET %d" % id 1540 if ssid: 1541 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 1542 if extra: 1543 cmd += " " + extra 1544 if conf: 1545 cmd += " conf=" + conf 1546 if configurator is not None: 1547 cmd += " configurator=%d" % configurator 1548 if "OK" not in self.request(cmd): 1549 raise Exception("Failed to set bootstrapping parameters") 1550 1551 def dpp_listen(self, freq, netrole=None, qr=None, role=None): 1552 cmd = "DPP_LISTEN " + str(freq) 1553 if netrole: 1554 cmd += " netrole=" + netrole 1555 if qr: 1556 cmd += " qr=" + qr 1557 if role: 1558 cmd += " role=" + role 1559 if "OK" not in self.request(cmd): 1560 raise Exception("Failed to start listen operation") 1561 # Since DPP listen is a radio work, make sure it has started 1562 # by the time we return and continue with the test, since it 1563 # usually will send something this side should receive. 1564 work_started = "dpp-listen@" + self.ifname + ":" + str(freq) + ":1" 1565 for i in range(10): 1566 if work_started in self.request("RADIO_WORK show"): 1567 time.sleep(0.0005) 1568 return 1569 time.sleep(0.01) 1570 raise Exception("Failed to start DPP listen work") 1571 1572 def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None, 1573 extra=None, own=None, role=None, neg_freq=None, 1574 ssid=None, passphrase=None, expect_fail=False, 1575 tcp_addr=None, tcp_port=None, conn_status=False, 1576 ssid_charset=None, nfc_uri=None, netrole=None, 1577 csrattrs=None): 1578 cmd = "DPP_AUTH_INIT" 1579 if peer is None: 1580 if nfc_uri: 1581 peer = self.dpp_nfc_uri(nfc_uri) 1582 else: 1583 peer = self.dpp_qr_code(uri) 1584 cmd += " peer=%d" % peer 1585 if own is not None: 1586 cmd += " own=%d" % own 1587 if role: 1588 cmd += " role=" + role 1589 if extra: 1590 cmd += " " + extra 1591 if conf: 1592 cmd += " conf=" + conf 1593 if configurator is not None: 1594 cmd += " configurator=%d" % configurator 1595 if neg_freq: 1596 cmd += " neg_freq=%d" % neg_freq 1597 if ssid: 1598 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 1599 if ssid_charset: 1600 cmd += " ssid_charset=%d" % ssid_charset 1601 if passphrase: 1602 cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode() 1603 if tcp_addr: 1604 cmd += " tcp_addr=" + tcp_addr 1605 if tcp_port: 1606 cmd += " tcp_port=" + tcp_port 1607 if conn_status: 1608 cmd += " conn_status=1" 1609 if netrole: 1610 cmd += " netrole=" + netrole 1611 if csrattrs: 1612 cmd += " csrattrs=" + csrattrs 1613 res = self.request(cmd) 1614 if expect_fail: 1615 if "FAIL" not in res: 1616 raise Exception("DPP authentication started unexpectedly") 1617 return 1618 if "OK" not in res: 1619 raise Exception("Failed to initiate DPP Authentication") 1620 return int(peer) 1621 1622 def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None, 1623 extra=None, use_id=None, allow_fail=False, ver=None, 1624 tcp_addr=None, tcp_port=None): 1625 if use_id is None: 1626 id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 1627 else: 1628 id1 = use_id 1629 cmd = "own=%d " % id1 1630 if identifier: 1631 cmd += "identifier=%s " % identifier 1632 cmd += "init=1 " 1633 if ver is not None: 1634 cmd += "ver=" + str(ver) + " " 1635 if role: 1636 cmd += "role=%s " % role 1637 if tcp_addr: 1638 cmd += "tcp_addr=" + tcp_addr + " " 1639 if tcp_port: 1640 cmd += "tcp_port=" + tcp_port + " " 1641 if extra: 1642 cmd += extra + " " 1643 cmd += "code=%s" % code 1644 res = self.request("DPP_PKEX_ADD " + cmd) 1645 if allow_fail: 1646 return id1 1647 if "FAIL" in res: 1648 raise Exception("Failed to set PKEX data (initiator)") 1649 return id1 1650 1651 def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None, 1652 listen_role=None, use_id=None): 1653 if use_id is None: 1654 id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 1655 else: 1656 id0 = use_id 1657 cmd = "own=%d " % id0 1658 if identifier: 1659 cmd += "identifier=%s " % identifier 1660 cmd += "code=%s" % code 1661 res = self.request("DPP_PKEX_ADD " + cmd) 1662 if "FAIL" in res: 1663 raise Exception("Failed to set PKEX data (responder)") 1664 self.dpp_listen(freq, role=listen_role) 1665 return id0 1666 1667 def dpp_configurator_add(self, curve=None, key=None, 1668 net_access_key_curve=None): 1669 cmd = "DPP_CONFIGURATOR_ADD" 1670 if curve: 1671 cmd += " curve=" + curve 1672 if net_access_key_curve: 1673 cmd += " net_access_key_curve=" + net_access_key_curve 1674 if key: 1675 cmd += " key=" + key 1676 res = self.request(cmd) 1677 if "FAIL" in res: 1678 raise Exception("Failed to add configurator") 1679 return int(res) 1680 1681 def dpp_configurator_set(self, conf_id, net_access_key_curve=None): 1682 cmd = "DPP_CONFIGURATOR_SET %d" % conf_id 1683 if net_access_key_curve: 1684 cmd += " net_access_key_curve=" + net_access_key_curve 1685 res = self.request(cmd) 1686 if "FAIL" in res: 1687 raise Exception("Failed to set configurator") 1688 1689 def dpp_configurator_remove(self, conf_id): 1690 res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id) 1691 if "OK" not in res: 1692 raise Exception("DPP_CONFIGURATOR_REMOVE failed") 1693 1694 def get_ptksa(self, bssid, cipher): 1695 res = self.request("PTKSA_CACHE_LIST") 1696 lines = res.splitlines() 1697 for l in lines: 1698 if bssid not in l or cipher not in l: 1699 continue 1700 1701 vals = dict() 1702 [index, addr, cipher, expiration, tk, kdk] = l.split(' ', 5) 1703 vals['index'] = index 1704 vals['addr'] = addr 1705 vals['cipher'] = cipher 1706 vals['expiration'] = expiration 1707 vals['tk'] = tk 1708 vals['kdk'] = kdk 1709 return vals 1710 return None 1711 1712 def wait_sta(self, addr=None, timeout=2, wait_4way_hs=False): 1713 ev = self.wait_group_event(["AP-STA-CONNECT"], timeout=timeout) 1714 if ev is None: 1715 ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout) 1716 if ev is None: 1717 raise Exception("AP did not report STA connection") 1718 if addr and addr not in ev: 1719 raise Exception("Unexpected STA address in connection event: " + ev) 1720 if wait_4way_hs: 1721 ev2 = self.wait_group_event(["EAPOL-4WAY-HS-COMPLETED"], 1722 timeout=timeout) 1723 if ev2 is None: 1724 raise Exception("AP did not report 4-way handshake completion") 1725 if addr and addr not in ev2: 1726 raise Exception("Unexpected STA address in 4-way handshake completion event: " + ev2) 1727 return ev 1728 1729 def wait_sta_disconnect(self, addr=None, timeout=2): 1730 ev = self.wait_group_event(["AP-STA-DISCONNECT"], timeout=timeout) 1731 if ev is None: 1732 ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout) 1733 if ev is None: 1734 raise Exception("AP did not report STA disconnection") 1735 if addr and addr not in ev: 1736 raise Exception("Unexpected STA address in disconnection event: " + ev) 1737 return ev 1738