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