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