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