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