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 7import os 8import re 9import time 10import logging 11import binascii 12import struct 13import tempfile 14import wpaspy 15import remotehost 16import utils 17import subprocess 18from remotectrl import RemoteCtrl 19 20logger = logging.getLogger() 21hapd_ctrl = '/var/run/hostapd' 22hapd_global = '/var/run/hostapd-global' 23 24def mac2tuple(mac): 25 return struct.unpack('6B', binascii.unhexlify(mac.replace(':', ''))) 26 27class 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 166class 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 661def 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 722def 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 747def 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 772def 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 804def 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 819def 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 829def 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 841def 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 854def 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 864def 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 875def 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 882def 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 892def 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 902def 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 911def 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 920def 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 929def 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 941def 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 946def 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 951def 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 968def 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 985def cmd_execute(apdev, cmd, shell=False): 986 hapd_global = HostapdGlobal(apdev) 987 return hapd_global.cmd_execute(cmd, shell=shell) 988 989def send_file(apdev, src, dst): 990 hapd_global = HostapdGlobal(apdev) 991 return hapd_global.send_file(src, dst) 992 993def 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 1025def 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 1032def 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 1064idx = 0 1065def 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