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