1 # Python class for controlling hostapd 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 7 import os 8 import re 9 import time 10 import logging 11 import binascii 12 import struct 13 import tempfile 14 import wpaspy 15 import remotehost 16 import utils 17 import subprocess 18 from remotectrl import RemoteCtrl 19 20 logger = logging.getLogger() 21 hapd_ctrl = '/var/run/hostapd' 22 hapd_global = '/var/run/hostapd-global' 23 24 def mac2tuple(mac): 25 return struct.unpack('6B', binascii.unhexlify(mac.replace(':', ''))) 26 27 class HostapdGlobal: 28 def __init__(self, apdev=None, global_ctrl_override=None): 29 try: 30 hostname = apdev['hostname'] 31 port = apdev['port'] 32 if 'remote_cli' in apdev: 33 remote_cli = apdev['remote_cli'] 34 else: 35 remote_cli = False 36 except: 37 hostname = None 38 port = 8878 39 remote_cli = False 40 self.host = remotehost.Host(hostname) 41 self.hostname = hostname 42 self.port = port 43 self.remote_cli = remote_cli 44 if hostname is None: 45 global_ctrl = hapd_global 46 if global_ctrl_override: 47 global_ctrl = global_ctrl_override 48 self.ctrl = wpaspy.Ctrl(global_ctrl) 49 self.mon = wpaspy.Ctrl(global_ctrl) 50 self.dbg = "" 51 else: 52 if remote_cli: 53 global_ctrl = hapd_global 54 if global_ctrl_override: 55 global_ctrl = global_ctrl_override 56 self.ctrl = RemoteCtrl(global_ctrl, port, hostname=hostname) 57 self.mon = RemoteCtrl(global_ctrl, port, hostname=hostname) 58 self.dbg = hostname + "/global" 59 else: 60 self.ctrl = wpaspy.Ctrl(hostname, port) 61 self.mon = wpaspy.Ctrl(hostname, port) 62 self.dbg = hostname + "/" + str(port) 63 self.mon.attach() 64 65 def cmd_execute(self, cmd_array, shell=False): 66 if self.hostname is None: 67 if shell: 68 cmd = ' '.join(cmd_array) 69 else: 70 cmd = cmd_array 71 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, 72 stdout=subprocess.PIPE, shell=shell) 73 out = proc.communicate()[0] 74 ret = proc.returncode 75 return ret, out.decode() 76 else: 77 return self.host.execute(cmd_array) 78 79 def request(self, cmd, timeout=10): 80 logger.debug(self.dbg + ": CTRL(global): " + cmd) 81 return self.ctrl.request(cmd, timeout) 82 83 def wait_event(self, events, timeout): 84 start = os.times()[4] 85 while True: 86 while self.mon.pending(): 87 ev = self.mon.recv() 88 logger.debug(self.dbg + "(global): " + ev) 89 for event in events: 90 if event in ev: 91 return ev 92 now = os.times()[4] 93 remaining = start + timeout - now 94 if remaining <= 0: 95 break 96 if not self.mon.pending(timeout=remaining): 97 break 98 return None 99 100 def add(self, ifname, driver=None): 101 cmd = "ADD " + ifname + " " + hapd_ctrl 102 if driver: 103 cmd += " " + driver 104 res = self.request(cmd) 105 if "OK" not in res: 106 raise Exception("Could not add hostapd interface " + ifname) 107 108 def add_iface(self, ifname, confname): 109 res = self.request("ADD " + ifname + " config=" + confname) 110 if "OK" not in res: 111 raise Exception("Could not add hostapd interface") 112 113 def add_bss(self, phy, confname, ignore_error=False): 114 res = self.request("ADD bss_config=" + phy + ":" + confname) 115 if "OK" not in res: 116 if not ignore_error: 117 raise Exception("Could not add hostapd BSS") 118 119 def add_link(self, ifname, confname): 120 res = self.request("ADD " + ifname + " config=" + confname) 121 if "OK" not in res: 122 raise Exception("Could not add hostapd link") 123 124 def remove(self, ifname): 125 self.request("REMOVE " + ifname, timeout=30) 126 127 def relog(self): 128 self.request("RELOG") 129 130 def flush(self): 131 self.request("FLUSH") 132 133 def get_ctrl_iface_port(self, ifname): 134 if self.hostname is None: 135 return None 136 137 if self.remote_cli: 138 return None 139 140 res = self.request("INTERFACES ctrl") 141 lines = res.splitlines() 142 found = False 143 for line in lines: 144 words = line.split() 145 if words[0] == ifname: 146 found = True 147 break 148 if not found: 149 raise Exception("Could not find UDP port for " + ifname) 150 res = line.find("ctrl_iface=udp:") 151 if res == -1: 152 raise Exception("Wrong ctrl_interface format") 153 words = line.split(":") 154 return int(words[1]) 155 156 def terminate(self): 157 self.mon.detach() 158 self.mon.close() 159 self.mon = None 160 self.ctrl.terminate() 161 self.ctrl = None 162 163 def send_file(self, src, dst): 164 self.host.send_file(src, dst) 165 166 class Hostapd: 167 def __init__(self, ifname, bssidx=0, hostname=None, ctrl=hapd_ctrl, 168 port=8877, remote_cli=False, link=None): 169 self.hostname = hostname 170 self.host = remotehost.Host(hostname, ifname) 171 self.ifname = ifname 172 self.remote_cli = remote_cli 173 if hostname is None: 174 if link is not None: 175 ifname = ifname + "_link" + str(link) 176 self.ctrl = wpaspy.Ctrl(os.path.join(ctrl, ifname)) 177 self.mon = wpaspy.Ctrl(os.path.join(ctrl, ifname)) 178 self.dbg = ifname 179 else: 180 if remote_cli: 181 self.ctrl = RemoteCtrl(ctrl, port, hostname=hostname, 182 ifname=ifname) 183 self.mon = RemoteCtrl(ctrl, port, hostname=hostname, 184 ifname=ifname) 185 else: 186 self.ctrl = wpaspy.Ctrl(hostname, port) 187 self.mon = wpaspy.Ctrl(hostname, port) 188 self.dbg = hostname + "/" + ifname 189 self.mon.attach() 190 self.bssid = None 191 self.bssidx = bssidx 192 self.mld_addr = None 193 194 def cmd_execute(self, cmd_array, shell=False): 195 if self.hostname is None: 196 if shell: 197 cmd = ' '.join(cmd_array) 198 else: 199 cmd = cmd_array 200 proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, 201 stdout=subprocess.PIPE, shell=shell) 202 out = proc.communicate()[0] 203 ret = proc.returncode 204 return ret, out.decode() 205 else: 206 return self.host.execute(cmd_array) 207 208 def close_ctrl(self): 209 if self.mon is not None: 210 self.mon.detach() 211 self.mon.close() 212 self.mon = None 213 self.ctrl.close() 214 self.ctrl = None 215 216 def own_addr(self): 217 if self.bssid is None: 218 self.bssid = self.get_status_field('bssid[%d]' % self.bssidx) 219 return self.bssid 220 221 def own_mld_addr(self): 222 if self.mld_addr is None: 223 self.mld_addr = self.get_status_field('mld_addr[%d]' % self.bssidx) 224 return self.mld_addr 225 226 def get_addr(self, group=False): 227 if self.own_mld_addr() is None: 228 return self.own_addr() 229 return self.own_mld_addr() 230 231 def request(self, cmd): 232 logger.debug(self.dbg + ": CTRL: " + cmd) 233 return self.ctrl.request(cmd) 234 235 def ping(self): 236 return "PONG" in self.request("PING") 237 238 def set(self, field, value): 239 if "OK" not in self.request("SET " + field + " " + value): 240 if "TKIP" in value and (field == "wpa_pairwise" or \ 241 field == "rsn_pairwise"): 242 raise utils.HwsimSkip("Cipher TKIP not supported") 243 raise Exception("Failed to set hostapd parameter " + field) 244 245 def set_defaults(self, set_channel=True): 246 self.set("driver", "nl80211") 247 if set_channel: 248 self.set("hw_mode", "g") 249 self.set("channel", "1") 250 self.set("ieee80211n", "1") 251 self.set("logger_stdout", "-1") 252 self.set("logger_stdout_level", "0") 253 254 def set_open(self, ssid): 255 self.set_defaults() 256 self.set("ssid", ssid) 257 258 def set_wpa2_psk(self, ssid, passphrase): 259 self.set_defaults() 260 self.set("ssid", ssid) 261 self.set("wpa_passphrase", passphrase) 262 self.set("wpa", "2") 263 self.set("wpa_key_mgmt", "WPA-PSK") 264 self.set("rsn_pairwise", "CCMP") 265 266 def set_wpa_psk(self, ssid, passphrase): 267 self.set_defaults() 268 self.set("ssid", ssid) 269 self.set("wpa_passphrase", passphrase) 270 self.set("wpa", "1") 271 self.set("wpa_key_mgmt", "WPA-PSK") 272 self.set("wpa_pairwise", "TKIP") 273 274 def set_wpa_psk_mixed(self, ssid, passphrase): 275 self.set_defaults() 276 self.set("ssid", ssid) 277 self.set("wpa_passphrase", passphrase) 278 self.set("wpa", "3") 279 self.set("wpa_key_mgmt", "WPA-PSK") 280 self.set("wpa_pairwise", "TKIP") 281 self.set("rsn_pairwise", "CCMP") 282 283 def set_wep(self, ssid, key): 284 self.set_defaults() 285 self.set("ssid", ssid) 286 self.set("wep_key0", key) 287 288 def enable(self): 289 if "OK" not in self.request("ENABLE"): 290 raise Exception("Failed to enable hostapd interface " + self.ifname) 291 292 def disable(self): 293 if "OK" not in self.request("DISABLE"): 294 raise Exception("Failed to disable hostapd interface " + self.ifname) 295 296 def link_remove(self, count=10): 297 if "OK" not in self.request("LINK_REMOVE %u" % count): 298 raise Exception("Failed to remove hostapd link " + self.ifname) 299 300 def dump_monitor(self): 301 while self.mon.pending(): 302 ev = self.mon.recv() 303 logger.debug(self.dbg + ": " + ev) 304 305 def wait_event(self, events, timeout): 306 if not isinstance(events, list): 307 raise Exception("Hostapd.wait_event() called with incorrect events argument type") 308 start = os.times()[4] 309 while True: 310 while self.mon.pending(): 311 ev = self.mon.recv() 312 logger.debug(self.dbg + ": " + ev) 313 for event in events: 314 if event in ev: 315 return ev 316 now = os.times()[4] 317 remaining = start + timeout - now 318 if remaining <= 0: 319 break 320 if not self.mon.pending(timeout=remaining): 321 break 322 return None 323 324 def wait_sta(self, addr=None, timeout=2, wait_4way_hs=False): 325 ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout) 326 if ev is None: 327 raise Exception("AP did not report STA connection") 328 if addr and addr not in ev: 329 raise Exception("Unexpected STA address in connection event: " + ev) 330 if wait_4way_hs: 331 ev2 = self.wait_event(["EAPOL-4WAY-HS-COMPLETED"], 332 timeout=timeout) 333 if ev2 is None: 334 raise Exception("AP did not report 4-way handshake completion") 335 if addr and addr not in ev2: 336 raise Exception("Unexpected STA address in 4-way handshake completion event: " + ev2) 337 return ev 338 339 def wait_sta_disconnect(self, addr=None, timeout=2): 340 ev = self.wait_event(["AP-STA-DISCONNECT"], timeout=timeout) 341 if ev is None: 342 raise Exception("AP did not report STA disconnection") 343 if addr and addr not in ev: 344 raise Exception("Unexpected STA address in disconnection event: " + ev) 345 return ev 346 347 def wait_4way_hs(self, addr=None, timeout=1): 348 ev = self.wait_event(["EAPOL-4WAY-HS-COMPLETED"], timeout=timeout) 349 if ev is None: 350 raise Exception("hostapd did not report 4-way handshake completion") 351 if addr and addr not in ev: 352 raise Exception("Unexpected STA address in 4-way handshake completion event: " + ev) 353 return ev 354 355 def wait_ptkinitdone(self, addr, timeout=2): 356 while timeout > 0: 357 sta = self.get_sta(addr) 358 if 'hostapdWPAPTKState' not in sta: 359 raise Exception("GET_STA did not return hostapdWPAPTKState") 360 state = sta['hostapdWPAPTKState'] 361 if state == "11": 362 return 363 time.sleep(0.1) 364 timeout -= 0.1 365 raise Exception("Timeout while waiting for PTKINITDONE") 366 367 def get_status(self): 368 res = self.request("STATUS") 369 lines = res.splitlines() 370 vals = dict() 371 for l in lines: 372 [name, value] = l.split('=', 1) 373 vals[name] = value 374 return vals 375 376 def get_status_field(self, field): 377 vals = self.get_status() 378 if field in vals: 379 return vals[field] 380 return None 381 382 def get_driver_status(self): 383 res = self.request("STATUS-DRIVER") 384 lines = res.splitlines() 385 vals = dict() 386 for l in lines: 387 [name, value] = l.split('=', 1) 388 vals[name] = value 389 return vals 390 391 def get_driver_status_field(self, field): 392 vals = self.get_driver_status() 393 if field in vals: 394 return vals[field] 395 return None 396 397 def get_config(self): 398 res = self.request("GET_CONFIG") 399 lines = res.splitlines() 400 vals = dict() 401 for l in lines: 402 [name, value] = l.split('=', 1) 403 vals[name] = value 404 return vals 405 406 def mgmt_rx(self, timeout=5): 407 ev = self.wait_event(["MGMT-RX"], timeout=timeout) 408 if ev is None: 409 return None 410 msg = {} 411 frame = binascii.unhexlify(ev.split(' ')[1]) 412 msg['frame'] = frame 413 414 hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) 415 msg['fc'] = hdr[0] 416 msg['subtype'] = (hdr[0] >> 4) & 0xf 417 hdr = hdr[1:] 418 msg['duration'] = hdr[0] 419 hdr = hdr[1:] 420 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 421 hdr = hdr[6:] 422 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 423 hdr = hdr[6:] 424 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] 425 hdr = hdr[6:] 426 msg['seq_ctrl'] = hdr[0] 427 msg['payload'] = frame[24:] 428 429 return msg 430 431 def mgmt_tx(self, msg): 432 t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,) 433 hdr = struct.pack('<HH6B6B6BH', *t) 434 res = self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']).decode()) 435 if "OK" not in res: 436 raise Exception("MGMT_TX command to hostapd failed") 437 438 def get_sta(self, addr, info=None, next=False): 439 cmd = "STA-NEXT " if next else "STA " 440 if addr is None: 441 res = self.request("STA-FIRST") 442 elif info: 443 res = self.request(cmd + addr + " " + info) 444 else: 445 res = self.request(cmd + addr) 446 lines = res.splitlines() 447 vals = dict() 448 first = True 449 for l in lines: 450 if first and '=' not in l: 451 vals['addr'] = l 452 first = False 453 else: 454 [name, value] = l.split('=', 1) 455 vals[name] = value 456 return vals 457 458 def get_mib(self, param=None): 459 if param: 460 res = self.request("MIB " + param) 461 else: 462 res = self.request("MIB") 463 lines = res.splitlines() 464 vals = dict() 465 for l in lines: 466 name_val = l.split('=', 1) 467 if len(name_val) > 1: 468 vals[name_val[0]] = name_val[1] 469 return vals 470 471 def get_pmksa(self, addr): 472 res = self.request("PMKSA") 473 lines = res.splitlines() 474 for l in lines: 475 if addr not in l: 476 continue 477 vals = dict() 478 [index, aa, pmkid, expiration, opportunistic] = l.split(' ') 479 vals['index'] = index 480 vals['pmkid'] = pmkid 481 vals['expiration'] = expiration 482 vals['opportunistic'] = opportunistic 483 return vals 484 return None 485 486 def dpp_qr_code(self, uri): 487 res = self.request("DPP_QR_CODE " + uri) 488 if "FAIL" in res: 489 raise Exception("Failed to parse QR Code URI") 490 return int(res) 491 492 def dpp_nfc_uri(self, uri): 493 res = self.request("DPP_NFC_URI " + uri) 494 if "FAIL" in res: 495 raise Exception("Failed to parse NFC URI") 496 return int(res) 497 498 def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None, 499 curve=None, key=None, supported_curves=None, 500 host=None): 501 cmd = "DPP_BOOTSTRAP_GEN type=" + type 502 if chan: 503 cmd += " chan=" + chan 504 if mac: 505 if mac is True: 506 mac = self.own_addr() 507 cmd += " mac=" + mac.replace(':', '') 508 if info: 509 cmd += " info=" + info 510 if curve: 511 cmd += " curve=" + curve 512 if key: 513 cmd += " key=" + key 514 if supported_curves: 515 cmd += " supported_curves=" + supported_curves 516 if host: 517 cmd += " host=" + host 518 res = self.request(cmd) 519 if "FAIL" in res: 520 raise Exception("Failed to generate bootstrapping info") 521 return int(res) 522 523 def dpp_bootstrap_set(self, id, conf=None, configurator=None, ssid=None, 524 extra=None): 525 cmd = "DPP_BOOTSTRAP_SET %d" % id 526 if ssid: 527 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 528 if extra: 529 cmd += " " + extra 530 if conf: 531 cmd += " conf=" + conf 532 if configurator is not None: 533 cmd += " configurator=%d" % configurator 534 if "OK" not in self.request(cmd): 535 raise Exception("Failed to set bootstrapping parameters") 536 537 def dpp_listen(self, freq, netrole=None, qr=None, role=None): 538 cmd = "DPP_LISTEN " + str(freq) 539 if netrole: 540 cmd += " netrole=" + netrole 541 if qr: 542 cmd += " qr=" + qr 543 if role: 544 cmd += " role=" + role 545 if "OK" not in self.request(cmd): 546 raise Exception("Failed to start listen operation") 547 548 def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None, 549 extra=None, own=None, role=None, neg_freq=None, 550 ssid=None, passphrase=None, expect_fail=False, 551 conn_status=False, nfc_uri=None): 552 cmd = "DPP_AUTH_INIT" 553 if peer is None: 554 if nfc_uri: 555 peer = self.dpp_nfc_uri(nfc_uri) 556 else: 557 peer = self.dpp_qr_code(uri) 558 cmd += " peer=%d" % peer 559 if own is not None: 560 cmd += " own=%d" % own 561 if role: 562 cmd += " role=" + role 563 if extra: 564 cmd += " " + extra 565 if conf: 566 cmd += " conf=" + conf 567 if configurator is not None: 568 cmd += " configurator=%d" % configurator 569 if neg_freq: 570 cmd += " neg_freq=%d" % neg_freq 571 if ssid: 572 cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() 573 if passphrase: 574 cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode() 575 if conn_status: 576 cmd += " conn_status=1" 577 res = self.request(cmd) 578 if expect_fail: 579 if "FAIL" not in res: 580 raise Exception("DPP authentication started unexpectedly") 581 return 582 if "OK" not in res: 583 raise Exception("Failed to initiate DPP Authentication") 584 585 def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None, 586 extra=None, use_id=None, ver=None): 587 if use_id is None: 588 id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 589 else: 590 id1 = use_id 591 cmd = "own=%d " % id1 592 if identifier: 593 cmd += "identifier=%s " % identifier 594 cmd += "init=1 " 595 if ver is not None: 596 cmd += "ver=" + str(ver) + " " 597 if role: 598 cmd += "role=%s " % role 599 if extra: 600 cmd += extra + " " 601 cmd += "code=%s" % code 602 res = self.request("DPP_PKEX_ADD " + cmd) 603 if "FAIL" in res: 604 raise Exception("Failed to set PKEX data (initiator)") 605 return id1 606 607 def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None, 608 listen_role=None): 609 id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) 610 cmd = "own=%d " % id0 611 if identifier: 612 cmd += "identifier=%s " % identifier 613 cmd += "code=%s" % code 614 res = self.request("DPP_PKEX_ADD " + cmd) 615 if "FAIL" in res: 616 raise Exception("Failed to set PKEX data (responder)") 617 self.dpp_listen(freq, role=listen_role) 618 619 def dpp_configurator_add(self, curve=None, key=None, 620 net_access_key_curve=None): 621 cmd = "DPP_CONFIGURATOR_ADD" 622 if curve: 623 cmd += " curve=" + curve 624 if net_access_key_curve: 625 cmd += " net_access_key_curve=" + curve 626 if key: 627 cmd += " key=" + key 628 res = self.request(cmd) 629 if "FAIL" in res: 630 raise Exception("Failed to add configurator") 631 return int(res) 632 633 def dpp_configurator_remove(self, conf_id): 634 res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id) 635 if "OK" not in res: 636 raise Exception("DPP_CONFIGURATOR_REMOVE failed") 637 638 def note(self, txt): 639 self.request("NOTE " + txt) 640 641 def send_file(self, src, dst): 642 self.host.send_file(src, dst) 643 644 def get_ptksa(self, bssid, cipher): 645 res = self.request("PTKSA_CACHE_LIST") 646 lines = res.splitlines() 647 for l in lines: 648 if bssid not in l or cipher not in l: 649 continue 650 vals = dict() 651 [index, addr, cipher, expiration, tk, kdk] = l.split(' ', 5) 652 vals['index'] = index 653 vals['addr'] = addr 654 vals['cipher'] = cipher 655 vals['expiration'] = expiration 656 vals['tk'] = tk 657 vals['kdk'] = kdk 658 return vals 659 return None 660 661 def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30, 662 global_ctrl_override=None, driver=False, set_channel=True): 663 if isinstance(apdev, dict): 664 ifname = apdev['ifname'] 665 try: 666 hostname = apdev['hostname'] 667 port = apdev['port'] 668 if 'remote_cli' in apdev: 669 remote_cli = apdev['remote_cli'] 670 else: 671 remote_cli = False 672 if 'global_ctrl_override' in apdev: 673 global_ctrl_override = apdev['global_ctrl_override'] 674 logger.info("Starting AP " + hostname + "/" + port + " " + ifname + " remote_cli " + str(remote_cli)) 675 except: 676 logger.info("Starting AP " + ifname) 677 hostname = None 678 port = 8878 679 remote_cli = False 680 else: 681 ifname = apdev 682 logger.info("Starting AP " + ifname + " (old add_ap argument type)") 683 hostname = None 684 port = 8878 685 remote_cli = False 686 hapd_global = HostapdGlobal(apdev, 687 global_ctrl_override=global_ctrl_override) 688 hapd_global.remove(ifname) 689 hapd_global.add(ifname, driver=driver) 690 port = hapd_global.get_ctrl_iface_port(ifname) 691 hapd = Hostapd(ifname, hostname=hostname, port=port, 692 remote_cli=remote_cli) 693 if not hapd.ping(): 694 raise Exception("Could not ping hostapd") 695 hapd.set_defaults(set_channel=set_channel) 696 fields = ["ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt", 697 "wpa", "wpa_deny_ptk0_rekey", 698 "wpa_pairwise", "rsn_pairwise", "auth_server_addr", 699 "acct_server_addr"] 700 for field in fields: 701 if field in params: 702 hapd.set(field, params[field]) 703 for f, v in list(params.items()): 704 if f in fields: 705 continue 706 if isinstance(v, list): 707 for val in v: 708 hapd.set(f, val) 709 else: 710 hapd.set(f, v) 711 if no_enable: 712 return hapd 713 hapd.enable() 714 if wait_enabled: 715 ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout) 716 if ev is None: 717 raise Exception("AP startup timed out") 718 if "AP-ENABLED" not in ev: 719 raise Exception("AP startup failed") 720 return hapd 721 722 def add_bss(apdev, ifname, confname, ignore_error=False): 723 phy = utils.get_phy(apdev) 724 try: 725 hostname = apdev['hostname'] 726 port = apdev['port'] 727 if 'remote_cli' in apdev: 728 remote_cli = apdev['remote_cli'] 729 else: 730 remote_cli = False 731 logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname + " remote_cli=" + str(remote_cli)) 732 except: 733 logger.info("Starting BSS phy=" + phy + " ifname=" + ifname) 734 hostname = None 735 port = 8878 736 remote_cli = False 737 hapd_global = HostapdGlobal(apdev) 738 confname = cfg_file(apdev, confname, ifname) 739 hapd_global.send_file(confname, confname) 740 hapd_global.add_bss(phy, confname, ignore_error) 741 port = hapd_global.get_ctrl_iface_port(ifname) 742 hapd = Hostapd(ifname, hostname=hostname, port=port, remote_cli=remote_cli) 743 if not hapd.ping(): 744 raise Exception("Could not ping hostapd") 745 return hapd 746 747 def add_iface(apdev, confname): 748 ifname = apdev['ifname'] 749 try: 750 hostname = apdev['hostname'] 751 port = apdev['port'] 752 if 'remote_cli' in apdev: 753 remote_cli = apdev['remote_cli'] 754 else: 755 remote_cli = False 756 logger.info("Starting interface " + hostname + "/" + port + " " + ifname + "remote_cli=" + str(remote_cli)) 757 except: 758 logger.info("Starting interface " + ifname) 759 hostname = None 760 port = 8878 761 remote_cli = False 762 hapd_global = HostapdGlobal(apdev) 763 confname = cfg_file(apdev, confname, ifname) 764 hapd_global.send_file(confname, confname) 765 hapd_global.add_iface(ifname, confname) 766 port = hapd_global.get_ctrl_iface_port(ifname) 767 hapd = Hostapd(ifname, hostname=hostname, port=port, remote_cli=remote_cli) 768 if not hapd.ping(): 769 raise Exception("Could not ping hostapd") 770 return hapd 771 772 def add_mld_link(apdev, link_id, params): 773 if isinstance(apdev, dict): 774 ifname = apdev['ifname'] 775 try: 776 hostname = apdev['hostname'] 777 port = apdev['port'] 778 logger.info("Adding link on: " + hostname + "/" + port + " ifname=" + ifname) 779 except: 780 logger.info("Adding link on: ifname=" + ifname) 781 hostname = None 782 port = 8878 783 else: 784 ifname = apdev 785 logger.info("Adding link on: ifname=" + ifname) 786 hostname = None 787 port = 8878 788 789 hapd_global = HostapdGlobal(apdev) 790 confname, ctrl_iface = cfg_mld_link_file(ifname, params) 791 hapd_global.send_file(confname, confname) 792 try: 793 hapd_global.add_link(ifname, confname) 794 except Exception as e: 795 if str(e) == "Could not add hostapd link": 796 raise utils.HwsimSkip("No MLO support in hostapd") 797 port = hapd_global.get_ctrl_iface_port(ifname) 798 hapd = Hostapd(ifname, hostname=hostname, ctrl=ctrl_iface, port=port, 799 link=link_id) 800 if not hapd.ping(): 801 raise Exception("Could not ping hostapd") 802 return hapd 803 804 def remove_bss(apdev, ifname=None): 805 if ifname == None: 806 ifname = apdev['ifname'] 807 try: 808 hostname = apdev['hostname'] 809 port = apdev['port'] 810 logger.info("Removing BSS " + hostname + "/" + port + " " + ifname) 811 except: 812 logger.info("Removing BSS " + ifname) 813 hapd_global = HostapdGlobal(apdev) 814 hapd_global.remove(ifname) 815 816 # wait little to make sure the AP stops beaconing 817 time.sleep(0.1) 818 819 def terminate(apdev): 820 try: 821 hostname = apdev['hostname'] 822 port = apdev['port'] 823 logger.info("Terminating hostapd " + hostname + "/" + port) 824 except: 825 logger.info("Terminating hostapd") 826 hapd_global = HostapdGlobal(apdev) 827 hapd_global.terminate() 828 829 def wpa3_params(ssid=None, password=None, wpa_key_mgmt="SAE", 830 ieee80211w="2"): 831 params = {"wpa": "2", 832 "wpa_key_mgmt": wpa_key_mgmt, 833 "ieee80211w": ieee80211w, 834 "rsn_pairwise": "CCMP"} 835 if ssid: 836 params["ssid"] = ssid 837 if password: 838 params["sae_password"] = password 839 return params 840 841 def wpa2_params(ssid=None, passphrase=None, wpa_key_mgmt="WPA-PSK", 842 ieee80211w=None): 843 params = {"wpa": "2", 844 "wpa_key_mgmt": wpa_key_mgmt, 845 "rsn_pairwise": "CCMP"} 846 if ssid: 847 params["ssid"] = ssid 848 if passphrase: 849 params["wpa_passphrase"] = passphrase 850 if ieee80211w is not None: 851 params["ieee80211w"] = ieee80211w 852 return params 853 854 def wpa_params(ssid=None, passphrase=None): 855 params = {"wpa": "1", 856 "wpa_key_mgmt": "WPA-PSK", 857 "wpa_pairwise": "TKIP"} 858 if ssid: 859 params["ssid"] = ssid 860 if passphrase: 861 params["wpa_passphrase"] = passphrase 862 return params 863 864 def wpa_mixed_params(ssid=None, passphrase=None): 865 params = {"wpa": "3", 866 "wpa_key_mgmt": "WPA-PSK", 867 "wpa_pairwise": "TKIP", 868 "rsn_pairwise": "CCMP"} 869 if ssid: 870 params["ssid"] = ssid 871 if passphrase: 872 params["wpa_passphrase"] = passphrase 873 return params 874 875 def radius_params(): 876 params = {"auth_server_addr": "127.0.0.1", 877 "auth_server_port": "1812", 878 "auth_server_shared_secret": "radius", 879 "nas_identifier": "nas.w1.fi"} 880 return params 881 882 def wpa_eap_params(ssid=None): 883 params = radius_params() 884 params["wpa"] = "1" 885 params["wpa_key_mgmt"] = "WPA-EAP" 886 params["wpa_pairwise"] = "TKIP" 887 params["ieee8021x"] = "1" 888 if ssid: 889 params["ssid"] = ssid 890 return params 891 892 def wpa2_eap_params(ssid=None): 893 params = radius_params() 894 params["wpa"] = "2" 895 params["wpa_key_mgmt"] = "WPA-EAP" 896 params["rsn_pairwise"] = "CCMP" 897 params["ieee8021x"] = "1" 898 if ssid: 899 params["ssid"] = ssid 900 return params 901 902 def b_only_params(channel="1", ssid=None, country=None): 903 params = {"hw_mode": "b", 904 "channel": channel} 905 if ssid: 906 params["ssid"] = ssid 907 if country: 908 params["country_code"] = country 909 return params 910 911 def g_only_params(channel="1", ssid=None, country=None): 912 params = {"hw_mode": "g", 913 "channel": channel} 914 if ssid: 915 params["ssid"] = ssid 916 if country: 917 params["country_code"] = country 918 return params 919 920 def a_only_params(channel="36", ssid=None, country=None): 921 params = {"hw_mode": "a", 922 "channel": channel} 923 if ssid: 924 params["ssid"] = ssid 925 if country: 926 params["country_code"] = country 927 return params 928 929 def ht20_params(channel="1", ssid=None, country=None): 930 params = {"ieee80211n": "1", 931 "channel": channel, 932 "hw_mode": "g"} 933 if int(channel) > 14: 934 params["hw_mode"] = "a" 935 if ssid: 936 params["ssid"] = ssid 937 if country: 938 params["country_code"] = country 939 return params 940 941 def ht40_plus_params(channel="1", ssid=None, country=None): 942 params = ht20_params(channel, ssid, country) 943 params['ht_capab'] = "[HT40+]" 944 return params 945 946 def ht40_minus_params(channel="1", ssid=None, country=None): 947 params = ht20_params(channel, ssid, country) 948 params['ht_capab'] = "[HT40-]" 949 return params 950 951 def he_params(ssid=None): 952 params = {"ssid": "he6ghz", 953 "ieee80211n": "1", 954 "ieee80211ac": "1", 955 "wmm_enabled": "1", 956 "channel": "5", 957 "op_class": "131", 958 "ieee80211ax": "1", 959 "hw_mode": "a", 960 "he_oper_centr_freq_seg0_idx": "15", 961 "he_oper_chwidth": "2", 962 "vht_oper_chwidth": "2"} 963 if ssid: 964 params["ssid"] = ssid 965 966 return params 967 968 def he_wpa2_params(ssid=None, wpa_key_mgmt="SAE", rsn_pairwise="CCMP", 969 group_cipher="CCMP", sae_pwe="1", passphrase=None): 970 params = he_params(ssid) 971 params["wpa"] = "2" 972 params["wpa_key_mgmt"] = wpa_key_mgmt 973 params["rsn_pairwise"] = rsn_pairwise 974 params["group_cipher"] = group_cipher 975 params["ieee80211w"] = "2" 976 if "SAE" in wpa_key_mgmt: 977 params["sae_pwe"] = sae_pwe 978 params["sae_groups"] = "19" 979 980 if passphrase: 981 params["wpa_passphrase"] = passphrase 982 983 return params 984 985 def cmd_execute(apdev, cmd, shell=False): 986 hapd_global = HostapdGlobal(apdev) 987 return hapd_global.cmd_execute(cmd, shell=shell) 988 989 def send_file(apdev, src, dst): 990 hapd_global = HostapdGlobal(apdev) 991 return hapd_global.send_file(src, dst) 992 993 def acl_file(dev, apdev, conf): 994 fd, filename = tempfile.mkstemp(dir='/tmp', prefix=conf + '-') 995 f = os.fdopen(fd, 'w') 996 997 if conf == 'hostapd.macaddr': 998 mac0 = dev[0].get_status_field("address") 999 f.write(mac0 + '\n') 1000 f.write("02:00:00:00:00:12\n") 1001 f.write("02:00:00:00:00:34\n") 1002 f.write("-02:00:00:00:00:12\n") 1003 f.write("-02:00:00:00:00:34\n") 1004 f.write("01:01:01:01:01:01\n") 1005 f.write("03:01:01:01:01:03\n") 1006 elif conf == 'hostapd.accept': 1007 mac0 = dev[0].get_status_field("address") 1008 mac1 = dev[1].get_status_field("address") 1009 f.write(mac0 + " 1\n") 1010 f.write(mac1 + " 2\n") 1011 elif conf == 'hostapd.accept2': 1012 mac0 = dev[0].get_status_field("address") 1013 mac1 = dev[1].get_status_field("address") 1014 mac2 = dev[2].get_status_field("address") 1015 f.write(mac0 + " 1\n") 1016 f.write(mac1 + " 2\n") 1017 f.write(mac2 + " 3\n") 1018 else: 1019 f.close() 1020 os.unlink(filename) 1021 return conf 1022 1023 return filename 1024 1025 def bssid_inc(apdev, inc=1): 1026 parts = apdev['bssid'].split(':') 1027 parts[5] = '%02x' % (int(parts[5], 16) + int(inc)) 1028 bssid = '%s:%s:%s:%s:%s:%s' % (parts[0], parts[1], parts[2], 1029 parts[3], parts[4], parts[5]) 1030 return bssid 1031 1032 def cfg_file(apdev, conf, ifname=None): 1033 match = re.search(r'^bss-.+', conf) 1034 if match: 1035 # put cfg file in /tmp directory 1036 fd, fname = tempfile.mkstemp(dir='/tmp', prefix=conf + '-') 1037 f = os.fdopen(fd, 'w') 1038 idx = ''.join(filter(str.isdigit, conf.split('-')[-1])) 1039 if ifname is None: 1040 ifname = apdev['ifname'] 1041 if idx != '1': 1042 ifname = ifname + '-' + idx 1043 1044 f.write("driver=nl80211\n") 1045 f.write("ctrl_interface=/var/run/hostapd\n") 1046 f.write("hw_mode=g\n") 1047 f.write("channel=1\n") 1048 f.write("ieee80211n=1\n") 1049 if conf.startswith('bss-ht40-'): 1050 f.write("ht_capab=[HT40+]\n") 1051 f.write("interface=%s\n" % ifname) 1052 1053 f.write("ssid=bss-%s\n" % idx) 1054 if conf == 'bss-2-dup.conf': 1055 bssid = apdev['bssid'] 1056 else: 1057 bssid = bssid_inc(apdev, int(idx) - 1) 1058 f.write("bssid=%s\n" % bssid) 1059 1060 return fname 1061 1062 return conf 1063 1064 idx = 0 1065 def cfg_mld_link_file(ifname, params): 1066 global idx 1067 ctrl_iface="/var/run/hostapd" 1068 conf = "link-%d.conf" % idx 1069 1070 fd, fname = tempfile.mkstemp(dir='/tmp', prefix=conf + '-') 1071 f = os.fdopen(fd, 'w') 1072 1073 f.write("ctrl_interface=%s\n" % ctrl_iface) 1074 f.write("driver=nl80211\n") 1075 f.write("ieee80211n=1\n") 1076 if 'hw_mode' in params and params['hw_mode'] == 'a' and \ 1077 ('op_class' not in params or \ 1078 int(params['op_class']) not in [131, 132, 133, 134, 135, 136, 137]): 1079 f.write("ieee80211ac=1\n") 1080 f.write("ieee80211ax=1\n") 1081 f.write("ieee80211be=1\n") 1082 f.write("interface=%s\n" % ifname) 1083 f.write("mld_ap=1\n") 1084 1085 for k, v in list(params.items()): 1086 f.write("{}={}\n".format(k,v)) 1087 1088 idx = idx + 1 1089 1090 return fname, ctrl_iface 1091