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"] 458 for field in quoted: 459 if field in params: 460 self.set_cred_quoted(id, field, params[field]) 461 462 not_quoted = ["eap", "roaming_consortium", "priority", 463 "required_roaming_consortium", "sp_priority", 464 "max_bss_load", "update_identifier", "req_conn_capab", 465 "min_dl_bandwidth_home", "min_ul_bandwidth_home", 466 "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"] 467 for field in not_quoted: 468 if field in params: 469 self.set_cred(id, field, params[field]) 470 471 return id 472 473 def select_network(self, id, freq=None): 474 if freq: 475 extra = " freq=" + str(freq) 476 else: 477 extra = "" 478 id = self.request("SELECT_NETWORK " + str(id) + extra) 479 if "FAIL" in id: 480 raise Exception("SELECT_NETWORK failed") 481 return None 482 483 def mesh_group_add(self, id): 484 id = self.request("MESH_GROUP_ADD " + str(id)) 485 if "FAIL" in id: 486 raise Exception("MESH_GROUP_ADD failed") 487 return None 488 489 def mesh_group_remove(self): 490 id = self.request("MESH_GROUP_REMOVE " + str(self.ifname)) 491 if "FAIL" in id: 492 raise Exception("MESH_GROUP_REMOVE failed") 493 return None 494 495 def connect_network(self, id, timeout=None): 496 if timeout is None: 497 timeout = 10 if self.hostname is None else 60 498 self.dump_monitor() 499 self.select_network(id) 500 self.wait_connected(timeout=timeout) 501 self.dump_monitor() 502 503 def get_status(self, extra=None): 504 if extra: 505 extra = "-" + extra 506 else: 507 extra = "" 508 res = self.request("STATUS" + extra) 509 lines = res.splitlines() 510 vals = dict() 511 for l in lines: 512 try: 513 [name, value] = l.split('=', 1) 514 vals[name] = value 515 except ValueError as e: 516 logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) 517 return vals 518 519 def get_status_field(self, field, extra=None): 520 vals = self.get_status(extra) 521 if field in vals: 522 return vals[field] 523 return None 524 525 def get_group_status(self, extra=None): 526 if extra: 527 extra = "-" + extra 528 else: 529 extra = "" 530 res = self.group_request("STATUS" + extra) 531 lines = res.splitlines() 532 vals = dict() 533 for l in lines: 534 try: 535 [name, value] = l.split('=', 1) 536 except ValueError: 537 logger.info(self.ifname + ": Ignore unexpected status line: " + l) 538 continue 539 vals[name] = value 540 return vals 541 542 def get_group_status_field(self, field, extra=None): 543 vals = self.get_group_status(extra) 544 if field in vals: 545 return vals[field] 546 return None 547 548 def get_driver_status(self, ifname=None): 549 if ifname is None: 550 res = self.request("STATUS-DRIVER") 551 else: 552 res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname) 553 if res.startswith("FAIL"): 554 return dict() 555 lines = res.splitlines() 556 vals = dict() 557 for l in lines: 558 try: 559 [name, value] = l.split('=', 1) 560 except ValueError: 561 logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l) 562 continue 563 vals[name] = value 564 return vals 565 566 def get_driver_status_field(self, field, ifname=None): 567 vals = self.get_driver_status(ifname) 568 if field in vals: 569 return vals[field] 570 return None 571 572 def get_mcc(self): 573 mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent')) 574 return 1 if mcc < 2 else mcc 575 576 def get_mib(self): 577 res = self.request("MIB") 578 lines = res.splitlines() 579 vals = dict() 580 for l in lines: 581 try: 582 [name, value] = l.split('=', 1) 583 vals[name] = value 584 except ValueError as e: 585 logger.info(self.ifname + ": Ignore unexpected MIB line: " + l) 586 return vals 587 588 def p2p_dev_addr(self): 589 return self.get_status_field("p2p_device_address") 590 591 def p2p_interface_addr(self): 592 return self.get_group_status_field("address") 593 594 def own_addr(self): 595 try: 596 res = self.p2p_interface_addr() 597 except: 598 res = self.p2p_dev_addr() 599 return res 600 601 def get_addr(self, group=False): 602 dev_addr = self.own_addr() 603 if not group: 604 addr = self.get_status_field('address') 605 if addr: 606 dev_addr = addr 607 608 return dev_addr 609 610 def p2p_listen(self): 611 return self.global_request("P2P_LISTEN") 612 613 def p2p_ext_listen(self, period, interval): 614 return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval)) 615 616 def p2p_cancel_ext_listen(self): 617 return self.global_request("P2P_EXT_LISTEN") 618 619 def p2p_find(self, social=False, progressive=False, dev_id=None, 620 dev_type=None, delay=None, freq=None): 621 cmd = "P2P_FIND" 622 if social: 623 cmd = cmd + " type=social" 624 elif progressive: 625 cmd = cmd + " type=progressive" 626 if dev_id: 627 cmd = cmd + " dev_id=" + dev_id 628 if dev_type: 629 cmd = cmd + " dev_type=" + dev_type 630 if delay: 631 cmd = cmd + " delay=" + str(delay) 632 if freq: 633 cmd = cmd + " freq=" + str(freq) 634 return self.global_request(cmd) 635 636 def p2p_stop_find(self): 637 return self.global_request("P2P_STOP_FIND") 638 639 def wps_read_pin(self): 640 self.pin = self.request("WPS_PIN get").rstrip("\n") 641 if "FAIL" in self.pin: 642 raise Exception("Could not generate PIN") 643 return self.pin 644 645 def peer_known(self, peer, full=True): 646 res = self.global_request("P2P_PEER " + peer) 647 if peer.lower() not in res.lower(): 648 return False 649 if not full: 650 return True 651 return "[PROBE_REQ_ONLY]" not in res 652 653 def discover_peer(self, peer, full=True, timeout=15, social=True, 654 force_find=False, freq=None): 655 logger.info(self.ifname + ": Trying to discover peer " + peer) 656 if not force_find and self.peer_known(peer, full): 657 return True 658 self.p2p_find(social, freq=freq) 659 count = 0 660 while count < timeout * 4: 661 time.sleep(0.25) 662 count = count + 1 663 if self.peer_known(peer, full): 664 return True 665 return False 666 667 def get_peer(self, peer): 668 res = self.global_request("P2P_PEER " + peer) 669 if peer.lower() not in res.lower(): 670 raise Exception("Peer information not available") 671 lines = res.splitlines() 672 vals = dict() 673 for l in lines: 674 if '=' in l: 675 [name, value] = l.split('=', 1) 676 vals[name] = value 677 return vals 678 679 def group_form_result(self, ev, expect_failure=False, go_neg_res=None): 680 if expect_failure: 681 if "P2P-GROUP-STARTED" in ev: 682 raise Exception("Group formation succeeded when expecting failure") 683 exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' 684 s = re.split(exp, ev) 685 if len(s) < 3: 686 return None 687 res = {} 688 res['result'] = 'go-neg-failed' 689 res['status'] = int(s[2]) 690 return res 691 692 if "P2P-GROUP-STARTED" not in ev: 693 raise Exception("No P2P-GROUP-STARTED event seen") 694 695 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.]*)' 696 s = re.split(exp, ev) 697 if len(s) < 11: 698 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' 699 s = re.split(exp, ev) 700 if len(s) < 8: 701 raise Exception("Could not parse P2P-GROUP-STARTED") 702 res = {} 703 res['result'] = 'success' 704 res['ifname'] = s[2] 705 self.group_ifname = s[2] 706 try: 707 if self.hostname is None: 708 self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, 709 self.group_ifname)) 710 else: 711 port = self.get_ctrl_iface_port(self.group_ifname) 712 self.gctrl_mon = wpaspy.Ctrl(self.hostname, port) 713 if self.monitor: 714 self.gctrl_mon.attach() 715 except: 716 logger.debug("Could not open monitor socket for group interface") 717 self.gctrl_mon = None 718 res['role'] = s[3] 719 res['ssid'] = s[4] 720 res['freq'] = s[5] 721 if "[PERSISTENT]" in ev: 722 res['persistent'] = True 723 else: 724 res['persistent'] = False 725 p = re.match(r'psk=([0-9a-f]*)', s[6]) 726 if p: 727 res['psk'] = p.group(1) 728 p = re.match(r'passphrase="(.*)"', s[6]) 729 if p: 730 res['passphrase'] = p.group(1) 731 res['go_dev_addr'] = s[7] 732 733 if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]: 734 res['ip_addr'] = s[8] 735 if len(s) > 9: 736 res['ip_mask'] = s[9] 737 if len(s) > 10: 738 res['go_ip_addr'] = s[10] 739 740 if go_neg_res: 741 exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' 742 s = re.split(exp, go_neg_res) 743 if len(s) < 4: 744 raise Exception("Could not parse P2P-GO-NEG-SUCCESS") 745 res['go_neg_role'] = s[2] 746 res['go_neg_freq'] = s[3] 747 748 return res 749 750 def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, 751 persistent=False, freq=None, freq2=None, 752 max_oper_chwidth=None, ht40=False, vht=False): 753 if not self.discover_peer(peer): 754 raise Exception("Peer " + peer + " not found") 755 self.dump_monitor() 756 if pin: 757 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" 758 else: 759 cmd = "P2P_CONNECT " + peer + " " + method + " auth" 760 if go_intent: 761 cmd = cmd + ' go_intent=' + str(go_intent) 762 if freq: 763 cmd = cmd + ' freq=' + str(freq) 764 if freq2: 765 cmd = cmd + ' freq2=' + str(freq2) 766 if max_oper_chwidth: 767 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) 768 if ht40: 769 cmd = cmd + ' ht40' 770 if vht: 771 cmd = cmd + ' vht' 772 if persistent: 773 cmd = cmd + " persistent" 774 if "OK" in self.global_request(cmd): 775 return None 776 raise Exception("P2P_CONNECT (auth) failed") 777 778 def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False): 779 if timeout is None: 780 timeout = 1 if expect_failure else 5 781 go_neg_res = None 782 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", 783 "P2P-GO-NEG-FAILURE"], timeout) 784 if ev is None: 785 if expect_failure: 786 return None 787 raise Exception("Group formation timed out") 788 if "P2P-GO-NEG-SUCCESS" in ev: 789 go_neg_res = ev 790 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) 791 if ev is None: 792 if expect_failure: 793 return None 794 raise Exception("Group formation timed out") 795 self.dump_monitor() 796 return self.group_form_result(ev, expect_failure, go_neg_res) 797 798 def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None, 799 expect_failure=False, persistent=False, 800 persistent_id=None, freq=None, provdisc=False, 801 wait_group=True, freq2=None, max_oper_chwidth=None, 802 ht40=False, vht=False): 803 if not self.discover_peer(peer): 804 raise Exception("Peer " + peer + " not found") 805 self.dump_monitor() 806 if pin: 807 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method 808 else: 809 cmd = "P2P_CONNECT " + peer + " " + method 810 if go_intent is not None: 811 cmd = cmd + ' go_intent=' + str(go_intent) 812 if freq: 813 cmd = cmd + ' freq=' + str(freq) 814 if freq2: 815 cmd = cmd + ' freq2=' + str(freq2) 816 if max_oper_chwidth: 817 cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) 818 if ht40: 819 cmd = cmd + ' ht40' 820 if vht: 821 cmd = cmd + ' vht' 822 if persistent: 823 cmd = cmd + " persistent" 824 elif persistent_id: 825 cmd = cmd + " persistent=" + persistent_id 826 if provdisc: 827 cmd = cmd + " provdisc" 828 if "OK" in self.global_request(cmd): 829 if timeout == 0: 830 return None 831 go_neg_res = None 832 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", 833 "P2P-GO-NEG-FAILURE"], timeout) 834 if ev is None: 835 if expect_failure: 836 return None 837 raise Exception("Group formation timed out") 838 if "P2P-GO-NEG-SUCCESS" in ev: 839 if not wait_group: 840 return ev 841 go_neg_res = ev 842 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) 843 if ev is None: 844 if expect_failure: 845 return None 846 raise Exception("Group formation timed out") 847 self.dump_monitor() 848 return self.group_form_result(ev, expect_failure, go_neg_res) 849 raise Exception("P2P_CONNECT failed") 850 851 def _wait_event(self, mon, pfx, events, timeout): 852 if not isinstance(events, list): 853 raise Exception("WpaSupplicant._wait_event() called with incorrect events argument type") 854 start = os.times()[4] 855 while True: 856 while mon.pending(): 857 ev = mon.recv() 858 logger.debug(self.dbg + pfx + ev) 859 for event in events: 860 if event in ev: 861 return ev 862 now = os.times()[4] 863 remaining = start + timeout - now 864 if remaining <= 0: 865 break 866 if not mon.pending(timeout=remaining): 867 break 868 return None 869 870 def wait_event(self, events, timeout=10): 871 return self._wait_event(self.mon, ": ", events, timeout) 872 873 def wait_global_event(self, events, timeout): 874 if self.global_iface is None: 875 return self.wait_event(events, timeout) 876 return self._wait_event(self.global_mon, "(global): ", 877 events, timeout) 878 879 def wait_group_event(self, events, timeout=10): 880 if not isinstance(events, list): 881 raise Exception("WpaSupplicant.wait_group_event() called with incorrect events argument type") 882 if self.group_ifname and self.group_ifname != self.ifname: 883 if self.gctrl_mon is None: 884 return None 885 start = os.times()[4] 886 while True: 887 while self.gctrl_mon.pending(): 888 ev = self.gctrl_mon.recv() 889 logger.debug(self.group_dbg + "(group): " + ev) 890 for event in events: 891 if event in ev: 892 return ev 893 now = os.times()[4] 894 remaining = start + timeout - now 895 if remaining <= 0: 896 break 897 if not self.gctrl_mon.pending(timeout=remaining): 898 break 899 return None 900 901 return self.wait_event(events, timeout) 902 903 def wait_go_ending_session(self): 904 self.close_monitor_group() 905 timeout = 3 if self.hostname is None else 10 906 ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout) 907 if ev is None: 908 raise Exception("Group removal event timed out") 909 if "reason=GO_ENDING_SESSION" not in ev: 910 raise Exception("Unexpected group removal reason") 911 912 def dump_monitor(self, mon=True, global_mon=True): 913 count_iface = 0 914 count_global = 0 915 while mon and self.monitor and self.mon.pending(): 916 ev = self.mon.recv() 917 logger.debug(self.dbg + ": " + ev) 918 count_iface += 1 919 while global_mon and self.monitor and self.global_mon and self.global_mon.pending(): 920 ev = self.global_mon.recv() 921 logger.debug(self.global_dbg + self.ifname + "(global): " + ev) 922 count_global += 1 923 return (count_iface, count_global) 924 925 def remove_group(self, ifname=None): 926 self.close_monitor_group() 927 if ifname is None: 928 ifname = self.group_ifname if self.group_ifname else self.ifname 929 if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): 930 raise Exception("Group could not be removed") 931 self.group_ifname = None 932 933 def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False): 934 self.dump_monitor() 935 cmd = "P2P_GROUP_ADD" 936 if persistent is None: 937 pass 938 elif persistent is True: 939 cmd = cmd + " persistent" 940 else: 941 cmd = cmd + " persistent=" + str(persistent) 942 if freq: 943 cmd = cmd + " freq=" + str(freq) 944 if "OK" in self.global_request(cmd): 945 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) 946 if ev is None: 947 raise Exception("GO start up timed out") 948 if not no_event_clear: 949 self.dump_monitor() 950 return self.group_form_result(ev) 951 raise Exception("P2P_GROUP_ADD failed") 952 953 def p2p_go_authorize_client(self, pin): 954 cmd = "WPS_PIN any " + pin 955 if "FAIL" in self.group_request(cmd): 956 raise Exception("Failed to authorize client connection on GO") 957 return None 958 959 def p2p_go_authorize_client_pbc(self): 960 cmd = "WPS_PBC" 961 if "FAIL" in self.group_request(cmd): 962 raise Exception("Failed to authorize client connection on GO") 963 return None 964 965 def p2p_connect_group(self, go_addr, pin, timeout=0, social=False, 966 freq=None): 967 self.dump_monitor() 968 if not self.discover_peer(go_addr, social=social, freq=freq): 969 if social or not self.discover_peer(go_addr, social=social): 970 raise Exception("GO " + go_addr + " not found") 971 self.p2p_stop_find() 972 self.dump_monitor() 973 cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" 974 if freq: 975 cmd += " freq=" + str(freq) 976 if "OK" in self.global_request(cmd): 977 if timeout == 0: 978 self.dump_monitor() 979 return None 980 ev = self.wait_global_event(["P2P-GROUP-STARTED", 981 "P2P-GROUP-FORMATION-FAILURE"], 982 timeout) 983 if ev is None: 984 raise Exception("Joining the group timed out") 985 if "P2P-GROUP-STARTED" not in ev: 986 raise Exception("Failed to join the group") 987 self.dump_monitor() 988 return self.group_form_result(ev) 989 raise Exception("P2P_CONNECT(join) failed") 990 991 def tdls_setup(self, peer): 992 cmd = "TDLS_SETUP " + peer 993 if "FAIL" in self.group_request(cmd): 994 raise Exception("Failed to request TDLS setup") 995 return None 996 997 def tdls_teardown(self, peer): 998 cmd = "TDLS_TEARDOWN " + peer 999 if "FAIL" in self.group_request(cmd): 1000 raise Exception("Failed to request TDLS teardown") 1001 return None 1002 1003 def tdls_link_status(self, peer): 1004 cmd = "TDLS_LINK_STATUS " + peer 1005 ret = self.group_request(cmd) 1006 if "FAIL" in ret: 1007 raise Exception("Failed to request TDLS link status") 1008 return ret 1009 1010 def tspecs(self): 1011 """Return (tsid, up) tuples representing current tspecs""" 1012 res = self.request("WMM_AC_STATUS") 1013 tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res) 1014 tspecs = [tuple(map(int, tspec)) for tspec in tspecs] 1015 1016 logger.debug("tspecs: " + str(tspecs)) 1017 return tspecs 1018 1019 def add_ts(self, tsid, up, direction="downlink", expect_failure=False, 1020 extra=None): 1021 params = { 1022 "sba": 9000, 1023 "nominal_msdu_size": 1500, 1024 "min_phy_rate": 6000000, 1025 "mean_data_rate": 1500, 1026 } 1027 cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up) 1028 for (key, value) in params.items(): 1029 cmd += " %s=%d" % (key, value) 1030 if extra: 1031 cmd += " " + extra 1032 1033 if self.request(cmd).strip() != "OK": 1034 raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up)) 1035 1036 if expect_failure: 1037 ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2) 1038 if ev is None: 1039 raise Exception("ADDTS failed (time out while waiting failure)") 1040 if "tsid=%d" % (tsid) not in ev: 1041 raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED") 1042 return 1043 1044 ev = self.wait_event(["TSPEC-ADDED"], timeout=1) 1045 if ev is None: 1046 raise Exception("ADDTS failed (time out)") 1047 if "tsid=%d" % (tsid) not in ev: 1048 raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)") 1049 1050 if (tsid, up) not in self.tspecs(): 1051 raise Exception("ADDTS failed (tsid not in tspec list)") 1052 1053 def del_ts(self, tsid): 1054 if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK": 1055 raise Exception("DELTS failed") 1056 1057 ev = self.wait_event(["TSPEC-REMOVED"], timeout=1) 1058 if ev is None: 1059 raise Exception("DELTS failed (time out)") 1060 if "tsid=%d" % (tsid) not in ev: 1061 raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)") 1062 1063 tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid] 1064 if tspecs: 1065 raise Exception("DELTS failed (still in tspec list)") 1066 1067 def connect(self, ssid=None, ssid2=None, **kwargs): 1068 logger.info("Connect STA " + self.ifname + " to AP") 1069 id = self.add_network() 1070 if ssid: 1071 self.set_network_quoted(id, "ssid", ssid) 1072 elif ssid2: 1073 self.set_network(id, "ssid", ssid2) 1074 1075 quoted = ["psk", "identity", "anonymous_identity", "password", 1076 "machine_identity", "machine_password", 1077 "ca_cert", "client_cert", "private_key", 1078 "private_key_passwd", "ca_cert2", "client_cert2", 1079 "private_key2", "phase1", "phase2", "domain_suffix_match", 1080 "altsubject_match", "subject_match", "pac_file", "dh_file", 1081 "bgscan", "ht_mcs", "id_str", "openssl_ciphers", 1082 "domain_match", "dpp_connector", "sae_password", 1083 "sae_password_id", "check_cert_subject", 1084 "machine_ca_cert", "machine_client_cert", 1085 "machine_private_key", "machine_phase2"] 1086 for field in quoted: 1087 if field in kwargs and kwargs[field]: 1088 self.set_network_quoted(id, field, kwargs[field]) 1089 1090 not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise", 1091 "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3", 1092 "wep_tx_keyidx", "scan_freq", "freq_list", "eap", 1093 "eapol_flags", "fragment_size", "scan_ssid", "auth_alg", 1094 "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid", 1095 "disable_he", 1096 "disable_max_amsdu", "ampdu_factor", "ampdu_density", 1097 "disable_ht40", "disable_sgi", "disable_ldpc", 1098 "ht40_intolerant", "update_identifier", "mac_addr", 1099 "erp", "bg_scan_period", "bssid_ignore", 1100 "bssid_accept", "mem_only_psk", "eap_workaround", 1101 "engine", "fils_dh_group", "bssid_hint", 1102 "dpp_csign", "dpp_csign_expiry", 1103 "dpp_netaccesskey", "dpp_netaccesskey_expiry", "dpp_pfs", 1104 "group_mgmt", "owe_group", "owe_only", 1105 "owe_ptk_workaround", 1106 "transition_disable", "sae_pk", 1107 "roaming_consortium_selection", "ocv", 1108 "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc", 1109 "ft_eap_pmksa_caching", "beacon_prot", 1110 "wpa_deny_ptk0_rekey"] 1111 for field in not_quoted: 1112 if field in kwargs and kwargs[field]: 1113 self.set_network(id, field, kwargs[field]) 1114 1115 known_args = {"raw_psk", "password_hex", "peerkey", "okc", "ocsp", 1116 "only_add_network", "wait_connect"} 1117 unknown = set(kwargs.keys()) 1118 unknown -= set(quoted) 1119 unknown -= set(not_quoted) 1120 unknown -= known_args 1121 if unknown: 1122 raise Exception("Unknown WpaSupplicant::connect() arguments: " + str(unknown)) 1123 1124 if "raw_psk" in kwargs and kwargs['raw_psk']: 1125 self.set_network(id, "psk", kwargs['raw_psk']) 1126 if "password_hex" in kwargs and kwargs['password_hex']: 1127 self.set_network(id, "password", kwargs['password_hex']) 1128 if "peerkey" in kwargs and kwargs['peerkey']: 1129 self.set_network(id, "peerkey", "1") 1130 if "okc" in kwargs and kwargs['okc']: 1131 self.set_network(id, "proactive_key_caching", "1") 1132 if "ocsp" in kwargs and kwargs['ocsp']: 1133 self.set_network(id, "ocsp", str(kwargs['ocsp'])) 1134 if "only_add_network" in kwargs and kwargs['only_add_network']: 1135 return id 1136 if "wait_connect" not in kwargs or kwargs['wait_connect']: 1137 if "eap" in kwargs: 1138 self.connect_network(id, timeout=20) 1139 else: 1140 self.connect_network(id) 1141 else: 1142 self.dump_monitor() 1143 self.select_network(id) 1144 return id 1145 1146 def scan(self, type=None, freq=None, no_wait=False, only_new=False, 1147 passive=False): 1148 if not no_wait: 1149 self.dump_monitor() 1150 if type: 1151 cmd = "SCAN TYPE=" + type 1152 else: 1153 cmd = "SCAN" 1154 if freq: 1155 cmd = cmd + " freq=" + str(freq) 1156 if only_new: 1157 cmd += " only_new=1" 1158 if passive: 1159 cmd += " passive=1" 1160 if not no_wait: 1161 self.dump_monitor() 1162 res = self.request(cmd) 1163 if "OK" not in res: 1164 raise Exception("Failed to trigger scan: " + str(res)) 1165 if no_wait: 1166 return 1167 ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS", 1168 "CTRL-EVENT-SCAN-FAILED"], 15) 1169 if ev is None: 1170 raise Exception("Scan timed out") 1171 if "CTRL-EVENT-SCAN-FAILED" in ev: 1172 raise Exception("Scan failed: " + ev) 1173 1174 def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False, 1175 passive=False): 1176 if not force_scan and self.get_bss(bssid) is not None: 1177 return 1178 for i in range(0, 10): 1179 self.scan(freq=freq, type="ONLY", only_new=only_new, 1180 passive=passive) 1181 if self.get_bss(bssid) is not None: 1182 return 1183 raise Exception("Could not find BSS " + bssid + " in scan") 1184 1185 def flush_scan_cache(self, freq=2417): 1186 self.request("BSS_FLUSH 0") 1187 self.scan(freq=freq, only_new=True) 1188 res = self.request("SCAN_RESULTS") 1189 if len(res.splitlines()) > 1: 1190 logger.debug("Scan results remaining after first attempt to flush the results:\n" + res) 1191 self.request("BSS_FLUSH 0") 1192 self.scan(freq=2422, only_new=True) 1193 res = self.request("SCAN_RESULTS") 1194 if len(res.splitlines()) > 1: 1195 logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res) 1196 1197 def disconnect_and_stop_scan(self): 1198 self.request("DISCONNECT") 1199 res = self.request("ABORT_SCAN") 1200 for i in range(2 if "OK" in res else 1): 1201 self.wait_event(["CTRL-EVENT-DISCONNECTED", 1202 "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5) 1203 self.dump_monitor() 1204 1205 def roam(self, bssid, fail_test=False, assoc_reject_ok=False, 1206 check_bssid=True): 1207 self.dump_monitor() 1208 if "OK" not in self.request("ROAM " + bssid): 1209 raise Exception("ROAM failed") 1210 if fail_test: 1211 if assoc_reject_ok: 1212 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1213 "CTRL-EVENT-DISCONNECTED", 1214 "CTRL-EVENT-ASSOC-REJECT"], timeout=1) 1215 else: 1216 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1217 "CTRL-EVENT-DISCONNECTED"], timeout=1) 1218 if ev and "CTRL-EVENT-DISCONNECTED" in ev: 1219 self.dump_monitor() 1220 return 1221 if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev: 1222 raise Exception("Unexpected connection") 1223 self.dump_monitor() 1224 return 1225 if assoc_reject_ok: 1226 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1227 "CTRL-EVENT-DISCONNECTED"], timeout=10) 1228 else: 1229 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 1230 "CTRL-EVENT-DISCONNECTED", 1231 "CTRL-EVENT-ASSOC-REJECT"], timeout=10) 1232 if ev is None: 1233 raise Exception("Roaming with the AP timed out") 1234 if "CTRL-EVENT-ASSOC-REJECT" in ev: 1235 raise Exception("Roaming association rejected") 1236 if "CTRL-EVENT-DISCONNECTED" in ev: 1237 raise Exception("Unexpected disconnection when waiting for roam to complete") 1238 self.dump_monitor() 1239 if check_bssid and self.get_status_field('bssid') != bssid: 1240 raise Exception("Did not roam to correct BSSID") 1241 1242 def roam_over_ds(self, bssid, fail_test=False): 1243 self.dump_monitor() 1244 if "OK" not in self.request("FT_DS " + bssid): 1245 raise Exception("FT_DS failed") 1246 if fail_test: 1247 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) 1248 if ev is not None: 1249 raise Exception("Unexpected connection") 1250 self.dump_monitor() 1251 return 1252 ev = self.wait_event(["CTRL-EVENT-CONNECTED", 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 self.dump_monitor() 1259 1260 def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, 1261 new_passphrase=None, no_wait=False): 1262 self.dump_monitor() 1263 if new_ssid: 1264 self.request("WPS_REG " + bssid + " " + pin + " " + 1265 binascii.hexlify(new_ssid.encode()).decode() + " " + 1266 key_mgmt + " " + cipher + " " + 1267 binascii.hexlify(new_passphrase.encode()).decode()) 1268 if no_wait: 1269 return 1270 ev = self.wait_event(["WPS-SUCCESS"], timeout=15) 1271 else: 1272 self.request("WPS_REG " + bssid + " " + pin) 1273 if no_wait: 1274 return 1275 ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) 1276 if ev is None: 1277 raise Exception("WPS cred timed out") 1278 ev = self.wait_event(["WPS-FAIL"], timeout=15) 1279 if ev is None: 1280 raise Exception("WPS timed out") 1281 self.wait_connected(timeout=15) 1282 1283 def relog(self): 1284 self.global_request("RELOG") 1285 1286 def wait_completed(self, timeout=10): 1287 for i in range(0, timeout * 2): 1288 if self.get_status_field("wpa_state") == "COMPLETED": 1289 return 1290 time.sleep(0.5) 1291 raise Exception("Timeout while waiting for COMPLETED state") 1292 1293 def get_capability(self, field): 1294 res = self.request("GET_CAPABILITY " + field) 1295 if "FAIL" in res: 1296 return None 1297 return res.split(' ') 1298 1299 def get_bss(self, bssid, ifname=None): 1300 if not ifname or ifname == self.ifname: 1301 res = self.request("BSS " + bssid) 1302 elif ifname == self.group_ifname: 1303 res = self.group_request("BSS " + bssid) 1304 else: 1305 return None 1306 1307 if "FAIL" in res: 1308 return None 1309 lines = res.splitlines() 1310 vals = dict() 1311 for l in lines: 1312 [name, value] = l.split('=', 1) 1313 vals[name] = value 1314 if len(vals) == 0: 1315 return None 1316 return vals 1317 1318 def get_pmksa(self, bssid): 1319 res = self.request("PMKSA") 1320 lines = res.splitlines() 1321 for l in lines: 1322 if bssid not in l: 1323 continue 1324 vals = dict() 1325 try: 1326 [index, aa, pmkid, expiration, opportunistic] = l.split(' ') 1327 cache_id = None 1328 except ValueError: 1329 [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ') 1330 vals['index'] = index 1331 vals['pmkid'] = pmkid 1332 vals['expiration'] = expiration 1333 vals['opportunistic'] = opportunistic 1334 if cache_id != None: 1335 vals['cache_id'] = cache_id 1336 return vals 1337 return None 1338 1339 def get_pmk(self, network_id): 1340 bssid = self.get_status_field('bssid') 1341 res = self.request("PMKSA_GET %d" % network_id) 1342 for val in res.splitlines(): 1343 if val.startswith(bssid): 1344 return val.split(' ')[2] 1345 return None 1346 1347 def get_sta(self, addr, info=None, next=False): 1348 cmd = "STA-NEXT " if next else "STA " 1349 if addr is None: 1350 res = self.request("STA-FIRST") 1351 elif info: 1352 res = self.request(cmd + addr + " " + info) 1353 else: 1354 res = self.request(cmd + addr) 1355 lines = res.splitlines() 1356 vals = dict() 1357 first = True 1358 for l in lines: 1359 if first: 1360 vals['addr'] = l 1361 first = False 1362 else: 1363 [name, value] = l.split('=', 1) 1364 vals[name] = value 1365 return vals 1366 1367 def mgmt_rx(self, timeout=5): 1368 ev = self.wait_event(["MGMT-RX"], timeout=timeout) 1369 if ev is None: 1370 return None 1371 msg = {} 1372 items = ev.split(' ') 1373 field, val = items[1].split('=') 1374 if field != "freq": 1375 raise Exception("Unexpected MGMT-RX event format: " + ev) 1376 msg['freq'] = val 1377 1378 field, val = items[2].split('=') 1379 if field != "datarate": 1380 raise Exception("Unexpected MGMT-RX event format: " + ev) 1381 msg['datarate'] = val 1382 1383 field, val = items[3].split('=') 1384 if field != "ssi_signal": 1385 raise Exception("Unexpected MGMT-RX event format: " + ev) 1386 msg['ssi_signal'] = val 1387 1388 frame = binascii.unhexlify(items[4]) 1389 msg['frame'] = frame 1390 1391 hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) 1392 msg['fc'] = hdr[0] 1393 msg['subtype'] = (hdr[0] >> 4) & 0xf 1394 hdr = hdr[1:] 1395 msg['duration'] = hdr[0] 1396 hdr = hdr[1:] 1397 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1398 hdr = hdr[6:] 1399 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1400 hdr = hdr[6:] 1401 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 1402 hdr = hdr[6:] 1403 msg['seq_ctrl'] = hdr[0] 1404 msg['payload'] = frame[24:] 1405 1406 return msg 1407 1408 def wait_connected(self, timeout=10, error="Connection timed out"): 1409 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) 1410 if ev is None: 1411 raise Exception(error) 1412 return ev 1413 1414 def wait_disconnected(self, timeout=None, error="Disconnection timed out"): 1415 if timeout is None: 1416 timeout = 10 if self.hostname is None else 30 1417 ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout) 1418 if ev is None: 1419 raise Exception(error) 1420 return ev 1421 1422 def get_group_ifname(self): 1423 return self.group_ifname if self.group_ifname else self.ifname 1424 1425 def get_config(self): 1426 res = self.request("DUMP") 1427 if res.startswith("FAIL"): 1428 raise Exception("DUMP failed") 1429 lines = res.splitlines() 1430 vals = dict() 1431 for l in lines: 1432 [name, value] = l.split('=', 1) 1433 vals[name] = value 1434 return vals 1435 1436 def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac, 1437 method="1000", info="", status=None, cpt=None, role=None): 1438 if status is None: 1439 cmd = "P2P_ASP_PROVISION" 1440 params = "info='%s' method=%s" % (info, method) 1441 else: 1442 cmd = "P2P_ASP_PROVISION_RESP" 1443 params = "status=%d" % status 1444 1445 if role is not None: 1446 params += " role=" + role 1447 if cpt is not None: 1448 params += " cpt=" + cpt 1449 1450 if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" % 1451 (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)): 1452 raise Exception("%s request failed" % cmd) 1453 1454 def note(self, txt): 1455 self.request("NOTE " + txt) 1456 1457 def save_config(self): 1458 if "OK" not in self.request("SAVE_CONFIG"): 1459 raise Exception("Failed to save configuration file") 1460 1461 def wait_regdom(self, country_ie=False): 1462 for i in range(5): 1463 ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1) 1464 if ev is None: 1465 break 1466 if country_ie: 1467 if "init=COUNTRY_IE" in ev: 1468 break 1469 else: 1470 break 1471 1472 def dpp_qr_code(self, uri): 1473 res = self.request("DPP_QR_CODE " + uri) 1474 if "FAIL" in res: 1475 raise Exception("Failed to parse QR Code URI") 1476 return int(res) 1477 1478 def dpp_nfc_uri(self, uri): 1479 res = self.request("DPP_NFC_URI " + uri) 1480 if "FAIL" in res: 1481 raise Exception("Failed to parse NFC URI") 1482 return int(res) 1483 1484 def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None, 1485 curve=None, key=None): 1486 cmd = "DPP_BOOTSTRAP_GEN type=" + type 1487 if chan: 1488 cmd += " chan=" + chan 1489 if mac: 1490 if mac is True: 1491 mac = self.own_addr() 1492 cmd += " mac=" + mac.replace(':', '') 1493 if info: 1494 cmd += " info=" + info 1495 if curve: 1496 cmd += " curve=" + curve 1497 if key: 1498 cmd += " key=" + key 1499 res = self.request(cmd) 1500 if "FAIL" in res: 1501 raise Exception("Failed to generate bootstrapping info") 1502 return int(res) 1503 1504 def dpp_bootstrap_set(self, id, conf=None, configurator=None, ssid=None, 1505 extra=None): 1506 cmd = "DPP_BOOTSTRAP_SET %d" % id 1507 if ssid: 1508 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 1509 if extra: 1510 cmd += " " + extra 1511 if conf: 1512 cmd += " conf=" + conf 1513 if configurator is not None: 1514 cmd += " configurator=%d" % configurator 1515 if "OK" not in self.request(cmd): 1516 raise Exception("Failed to set bootstrapping parameters") 1517 1518 def dpp_listen(self, freq, netrole=None, qr=None, role=None): 1519 cmd = "DPP_LISTEN " + str(freq) 1520 if netrole: 1521 cmd += " netrole=" + netrole 1522 if qr: 1523 cmd += " qr=" + qr 1524 if role: 1525 cmd += " role=" + role 1526 if "OK" not in self.request(cmd): 1527 raise Exception("Failed to start listen operation") 1528 1529 def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None, 1530 extra=None, own=None, role=None, neg_freq=None, 1531 ssid=None, passphrase=None, expect_fail=False, 1532 tcp_addr=None, tcp_port=None, conn_status=False, 1533 ssid_charset=None, nfc_uri=None, netrole=None, 1534 csrattrs=None): 1535 cmd = "DPP_AUTH_INIT" 1536 if peer is None: 1537 if nfc_uri: 1538 peer = self.dpp_nfc_uri(nfc_uri) 1539 else: 1540 peer = self.dpp_qr_code(uri) 1541 cmd += " peer=%d" % peer 1542 if own is not None: 1543 cmd += " own=%d" % own 1544 if role: 1545 cmd += " role=" + role 1546 if extra: 1547 cmd += " " + extra 1548 if conf: 1549 cmd += " conf=" + conf 1550 if configurator is not None: 1551 cmd += " configurator=%d" % configurator 1552 if neg_freq: 1553 cmd += " neg_freq=%d" % neg_freq 1554 if ssid: 1555 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 1556 if ssid_charset: 1557 cmd += " ssid_charset=%d" % ssid_charset 1558 if passphrase: 1559 cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode() 1560 if tcp_addr: 1561 cmd += " tcp_addr=" + tcp_addr 1562 if tcp_port: 1563 cmd += " tcp_port=" + tcp_port 1564 if conn_status: 1565 cmd += " conn_status=1" 1566 if netrole: 1567 cmd += " netrole=" + netrole 1568 if csrattrs: 1569 cmd += " csrattrs=" + csrattrs 1570 res = self.request(cmd) 1571 if expect_fail: 1572 if "FAIL" not in res: 1573 raise Exception("DPP authentication started unexpectedly") 1574 return 1575 if "OK" not in res: 1576 raise Exception("Failed to initiate DPP Authentication") 1577 return int(peer) 1578 1579 def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None, 1580 extra=None, use_id=None, allow_fail=False, ver=None, 1581 tcp_addr=None, tcp_port=None): 1582 if use_id is None: 1583 id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 1584 else: 1585 id1 = use_id 1586 cmd = "own=%d " % id1 1587 if identifier: 1588 cmd += "identifier=%s " % identifier 1589 cmd += "init=1 " 1590 if ver is not None: 1591 cmd += "ver=" + str(ver) + " " 1592 if role: 1593 cmd += "role=%s " % role 1594 if tcp_addr: 1595 cmd += "tcp_addr=" + tcp_addr + " " 1596 if tcp_port: 1597 cmd += "tcp_port=" + tcp_port + " " 1598 if extra: 1599 cmd += extra + " " 1600 cmd += "code=%s" % code 1601 res = self.request("DPP_PKEX_ADD " + cmd) 1602 if allow_fail: 1603 return id1 1604 if "FAIL" in res: 1605 raise Exception("Failed to set PKEX data (initiator)") 1606 return id1 1607 1608 def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None, 1609 listen_role=None, use_id=None): 1610 if use_id is None: 1611 id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 1612 else: 1613 id0 = use_id 1614 cmd = "own=%d " % id0 1615 if identifier: 1616 cmd += "identifier=%s " % identifier 1617 cmd += "code=%s" % code 1618 res = self.request("DPP_PKEX_ADD " + cmd) 1619 if "FAIL" in res: 1620 raise Exception("Failed to set PKEX data (responder)") 1621 self.dpp_listen(freq, role=listen_role) 1622 return id0 1623 1624 def dpp_configurator_add(self, curve=None, key=None, 1625 net_access_key_curve=None): 1626 cmd = "DPP_CONFIGURATOR_ADD" 1627 if curve: 1628 cmd += " curve=" + curve 1629 if net_access_key_curve: 1630 cmd += " net_access_key_curve=" + net_access_key_curve 1631 if key: 1632 cmd += " key=" + key 1633 res = self.request(cmd) 1634 if "FAIL" in res: 1635 raise Exception("Failed to add configurator") 1636 return int(res) 1637 1638 def dpp_configurator_set(self, conf_id, net_access_key_curve=None): 1639 cmd = "DPP_CONFIGURATOR_SET %d" % conf_id 1640 if net_access_key_curve: 1641 cmd += " net_access_key_curve=" + net_access_key_curve 1642 res = self.request(cmd) 1643 if "FAIL" in res: 1644 raise Exception("Failed to set configurator") 1645 1646 def dpp_configurator_remove(self, conf_id): 1647 res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id) 1648 if "OK" not in res: 1649 raise Exception("DPP_CONFIGURATOR_REMOVE failed") 1650 1651 def get_ptksa(self, bssid, cipher): 1652 res = self.request("PTKSA_CACHE_LIST") 1653 lines = res.splitlines() 1654 for l in lines: 1655 if bssid not in l or cipher not in l: 1656 continue 1657 1658 vals = dict() 1659 [index, addr, cipher, expiration, tk, kdk] = l.split(' ', 5) 1660 vals['index'] = index 1661 vals['addr'] = addr 1662 vals['cipher'] = cipher 1663 vals['expiration'] = expiration 1664 vals['tk'] = tk 1665 vals['kdk'] = kdk 1666 return vals 1667 return None 1668