1# Python class for controlling wpa_supplicant
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 time
9import logging
10import binascii
11import re
12import struct
13import wpaspy
14import remotehost
15import subprocess
16
17logger = logging.getLogger()
18wpas_ctrl = '/var/run/wpa_supplicant'
19
20class WpaSupplicant:
21    def __init__(self, ifname=None, global_iface=None, hostname=None,
22                 port=9877, global_port=9878, monitor=True):
23        self.monitor = monitor
24        self.hostname = hostname
25        self.group_ifname = None
26        self.global_mon = None
27        self.global_ctrl = None
28        self.gctrl_mon = None
29        self.ctrl = None
30        self.mon = None
31        self.ifname = None
32        self.host = remotehost.Host(hostname, ifname)
33        self._group_dbg = None
34        if ifname:
35            self.set_ifname(ifname, hostname, port)
36            res = self.get_driver_status()
37            if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000:
38                self.p2p_dev_ifname = 'p2p-dev-' + self.ifname
39            else:
40                self.p2p_dev_ifname = ifname
41
42        self.global_iface = global_iface
43        if global_iface:
44            if hostname != None:
45                self.global_ctrl = wpaspy.Ctrl(hostname, global_port)
46                if self.monitor:
47                    self.global_mon = wpaspy.Ctrl(hostname, global_port)
48                self.global_dbg = hostname + "/" + str(global_port) + "/"
49            else:
50                self.global_ctrl = wpaspy.Ctrl(global_iface)
51                if self.monitor:
52                    self.global_mon = wpaspy.Ctrl(global_iface)
53                self.global_dbg = ""
54            if self.monitor:
55                self.global_mon.attach()
56
57    def __del__(self):
58        self.close_monitor()
59        self.close_control()
60
61    def close_control_ctrl(self):
62        if self.ctrl:
63            del self.ctrl
64            self.ctrl = None
65
66    def close_control_global(self):
67        if self.global_ctrl:
68            del self.global_ctrl
69            self.global_ctrl = None
70
71    def close_control(self):
72        self.close_control_ctrl()
73        self.close_control_global()
74
75    def close_monitor_mon(self):
76        if not self.mon:
77            return
78        try:
79            while self.mon.pending():
80                ev = self.mon.recv()
81                logger.debug(self.dbg + ": " + ev)
82        except:
83            pass
84        try:
85            self.mon.detach()
86        except ConnectionRefusedError:
87            pass
88        except Exception as e:
89            if str(e) == "DETACH failed":
90                pass
91            else:
92                raise
93        del self.mon
94        self.mon = None
95
96    def close_monitor_global(self):
97        if not self.global_mon:
98            return
99        try:
100            while self.global_mon.pending():
101                ev = self.global_mon.recv()
102                logger.debug(self.global_dbg + ": " + ev)
103        except:
104            pass
105        try:
106            self.global_mon.detach()
107        except ConnectionRefusedError:
108            pass
109        except Exception as e:
110            if str(e) == "DETACH failed":
111                pass
112            else:
113                raise
114        del self.global_mon
115        self.global_mon = None
116
117    def close_monitor_group(self):
118        if not self.gctrl_mon:
119            return
120        try:
121            while self.gctrl_mon.pending():
122                ev = self.gctrl_mon.recv()
123                logger.debug(self.dbg + ": " + ev)
124        except:
125            pass
126        try:
127            self.gctrl_mon.detach()
128        except:
129            pass
130        del self.gctrl_mon
131        self.gctrl_mon = None
132
133    def close_monitor(self):
134        self.close_monitor_mon()
135        self.close_monitor_global()
136        self.close_monitor_group()
137
138    def cmd_execute(self, cmd_array, shell=False):
139        if self.hostname is None:
140            if shell:
141                cmd = ' '.join(cmd_array)
142            else:
143                cmd = cmd_array
144            proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT,
145                                    stdout=subprocess.PIPE, shell=shell)
146            out = proc.communicate()[0]
147            ret = proc.returncode
148            return ret, out.decode()
149        else:
150            return self.host.execute(cmd_array)
151
152    def terminate(self):
153        if self.global_mon:
154            self.close_monitor_global()
155            self.global_ctrl.terminate()
156            self.global_ctrl = None
157
158    def close_ctrl(self):
159        self.close_monitor_global()
160        self.close_control_global()
161        self.remove_ifname()
162
163    def set_ifname(self, ifname, hostname=None, port=9877):
164        self.remove_ifname()
165        self.ifname = ifname
166        if hostname != None:
167            self.ctrl = wpaspy.Ctrl(hostname, port)
168            if self.monitor:
169                self.mon = wpaspy.Ctrl(hostname, port)
170            self.host = remotehost.Host(hostname, ifname)
171            self.dbg = hostname + "/" + ifname
172        else:
173            self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
174            if self.monitor:
175                self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
176            self.dbg = ifname
177        if self.monitor:
178            self.mon.attach()
179
180    def remove_ifname(self):
181        self.close_monitor_mon()
182        self.close_control_ctrl()
183        self.ifname = None
184
185    def get_ctrl_iface_port(self, ifname):
186        if self.hostname is None:
187            return None
188
189        res = self.global_request("INTERFACES ctrl")
190        lines = res.splitlines()
191        found = False
192        for line in lines:
193            words = line.split()
194            if words[0] == ifname:
195                found = True
196                break
197        if not found:
198            raise Exception("Could not find UDP port for " + ifname)
199        res = line.find("ctrl_iface=udp:")
200        if res == -1:
201            raise Exception("Wrong ctrl_interface format")
202        words = line.split(":")
203        return int(words[1])
204
205    def interface_add(self, ifname, config="", driver="nl80211",
206                      drv_params=None, br_ifname=None, create=False,
207                      set_ifname=True, all_params=False, if_type=None):
208        status, groups = self.host.execute(["id"])
209        if status != 0:
210            group = "admin"
211        group = "admin" if "(admin)" in groups else "adm"
212        cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group
213        if drv_params:
214            cmd = cmd + '\t' + drv_params
215        if br_ifname:
216            if not drv_params:
217                cmd += '\t'
218            cmd += '\t' + br_ifname
219        if create:
220            if not br_ifname:
221                cmd += '\t'
222                if not drv_params:
223                    cmd += '\t'
224            cmd += '\tcreate'
225            if if_type:
226                cmd += '\t' + if_type
227        if all_params and not create:
228            if not br_ifname:
229                cmd += '\t'
230                if not drv_params:
231                    cmd += '\t'
232            cmd += '\t'
233        if "FAIL" in self.global_request(cmd):
234            raise Exception("Failed to add a dynamic wpa_supplicant interface")
235        if not create and set_ifname:
236            port = self.get_ctrl_iface_port(ifname)
237            self.set_ifname(ifname, self.hostname, port)
238            res = self.get_driver_status()
239            if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000:
240                self.p2p_dev_ifname = 'p2p-dev-' + self.ifname
241            else:
242                self.p2p_dev_ifname = ifname
243
244    def interface_remove(self, ifname):
245        self.remove_ifname()
246        self.global_request("INTERFACE_REMOVE " + ifname)
247
248    def request(self, cmd, timeout=10):
249        logger.debug(self.dbg + ": CTRL: " + cmd)
250        return self.ctrl.request(cmd, timeout=timeout)
251
252    def global_request(self, cmd):
253        if self.global_iface is None:
254            return self.request(cmd)
255        else:
256            ifname = self.ifname or self.global_iface
257            logger.debug(self.global_dbg + ifname + ": CTRL(global): " + cmd)
258            return self.global_ctrl.request(cmd)
259
260    @property
261    def group_dbg(self):
262        if self._group_dbg is not None:
263            return self._group_dbg
264        if self.group_ifname is None:
265            raise Exception("Cannot have group_dbg without group_ifname")
266        if self.hostname is None:
267            self._group_dbg = self.group_ifname
268        else:
269            self._group_dbg = self.hostname + "/" + self.group_ifname
270        return self._group_dbg
271
272    def group_request(self, cmd):
273        if self.group_ifname and self.group_ifname != self.ifname:
274            if self.hostname is None:
275                gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname))
276            else:
277                port = self.get_ctrl_iface_port(self.group_ifname)
278                gctrl = wpaspy.Ctrl(self.hostname, port)
279            logger.debug(self.group_dbg + ": CTRL(group): " + cmd)
280            return gctrl.request(cmd)
281        return self.request(cmd)
282
283    def ping(self):
284        return "PONG" in self.request("PING")
285
286    def global_ping(self):
287        return "PONG" in self.global_request("PING")
288
289    def reset(self):
290        self.dump_monitor()
291        res = self.request("FLUSH")
292        if "OK" not in res:
293            logger.info("FLUSH to " + self.ifname + " failed: " + res)
294        self.global_request("REMOVE_NETWORK all")
295        self.global_request("SET p2p_no_group_iface 1")
296        self.global_request("P2P_FLUSH")
297        self.close_monitor_group()
298        self.group_ifname = None
299        self.dump_monitor()
300
301        iter = 0
302        while iter < 60:
303            state1 = self.get_driver_status_field("scan_state")
304            p2pdev = "p2p-dev-" + self.ifname
305            state2 = self.get_driver_status_field("scan_state", ifname=p2pdev)
306            states = str(state1) + " " + str(state2)
307            if "SCAN_STARTED" in states or "SCAN_REQUESTED" in states:
308                logger.info(self.ifname + ": Waiting for scan operation to complete before continuing")
309                time.sleep(1)
310            else:
311                break
312            iter = iter + 1
313        if iter == 60:
314            logger.error(self.ifname + ": Driver scan state did not clear")
315            print("Trying to clear cfg80211/mac80211 scan state")
316            status, buf = self.host.execute(["ifconfig", self.ifname, "down"])
317            if status != 0:
318                logger.info("ifconfig failed: " + buf)
319                logger.info(status)
320            status, buf = self.host.execute(["ifconfig", self.ifname, "up"])
321            if status != 0:
322                logger.info("ifconfig failed: " + buf)
323                logger.info(status)
324        if iter > 0:
325            # The ongoing scan could have discovered BSSes or P2P peers
326            logger.info("Run FLUSH again since scan was in progress")
327            self.request("FLUSH")
328            self.dump_monitor()
329
330        if not self.ping():
331            logger.info("No PING response from " + self.ifname + " after reset")
332
333    def set(self, field, value, allow_fail=False):
334        if "OK" not in self.request("SET " + field + " " + value):
335            if allow_fail:
336                return
337            raise Exception("Failed to set wpa_supplicant parameter " + field)
338
339    def add_network(self):
340        id = self.request("ADD_NETWORK")
341        if "FAIL" in id:
342            raise Exception("ADD_NETWORK failed")
343        return int(id)
344
345    def remove_network(self, id):
346        id = self.request("REMOVE_NETWORK " + str(id))
347        if "FAIL" in id:
348            raise Exception("REMOVE_NETWORK failed")
349        return None
350
351    def get_network(self, id, field):
352        res = self.request("GET_NETWORK " + str(id) + " " + field)
353        if res == "FAIL\n":
354            return None
355        return res
356
357    def set_network(self, id, field, value):
358        res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value)
359        if "FAIL" in res:
360            raise Exception("SET_NETWORK failed")
361        return None
362
363    def set_network_quoted(self, id, field, value):
364        res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"')
365        if "FAIL" in res:
366            raise Exception("SET_NETWORK failed")
367        return None
368
369    def p2pdev_request(self, cmd):
370        return self.global_request("IFNAME=" + self.p2p_dev_ifname + " " + cmd)
371
372    def p2pdev_add_network(self):
373        id = self.p2pdev_request("ADD_NETWORK")
374        if "FAIL" in id:
375            raise Exception("p2pdev ADD_NETWORK failed")
376        return int(id)
377
378    def p2pdev_set_network(self, id, field, value):
379        res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + " " + value)
380        if "FAIL" in res:
381            raise Exception("p2pdev SET_NETWORK failed")
382        return None
383
384    def p2pdev_set_network_quoted(self, id, field, value):
385        res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"')
386        if "FAIL" in res:
387            raise Exception("p2pdev SET_NETWORK failed")
388        return None
389
390    def list_networks(self, p2p=False):
391        if p2p:
392            res = self.global_request("LIST_NETWORKS")
393        else:
394            res = self.request("LIST_NETWORKS")
395        lines = res.splitlines()
396        networks = []
397        for l in lines:
398            if "network id" in l:
399                continue
400            [id, ssid, bssid, flags] = l.split('\t')
401            network = {}
402            network['id'] = id
403            network['ssid'] = ssid
404            network['bssid'] = bssid
405            network['flags'] = flags
406            networks.append(network)
407        return networks
408
409    def hs20_enable(self, auto_interworking=False):
410        self.request("SET interworking 1")
411        self.request("SET hs20 1")
412        if auto_interworking:
413            self.request("SET auto_interworking 1")
414        else:
415            self.request("SET auto_interworking 0")
416
417    def interworking_add_network(self, bssid):
418        id = self.request("INTERWORKING_ADD_NETWORK " + bssid)
419        if "FAIL" in id or "OK" in id:
420            raise Exception("INTERWORKING_ADD_NETWORK failed")
421        return int(id)
422
423    def add_cred(self):
424        id = self.request("ADD_CRED")
425        if "FAIL" in id:
426            raise Exception("ADD_CRED failed")
427        return int(id)
428
429    def remove_cred(self, id):
430        id = self.request("REMOVE_CRED " + str(id))
431        if "FAIL" in id:
432            raise Exception("REMOVE_CRED failed")
433        return None
434
435    def set_cred(self, id, field, value):
436        res = self.request("SET_CRED " + str(id) + " " + field + " " + value)
437        if "FAIL" in res:
438            raise Exception("SET_CRED failed")
439        return None
440
441    def set_cred_quoted(self, id, field, value):
442        res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"')
443        if "FAIL" in res:
444            raise Exception("SET_CRED failed")
445        return None
446
447    def get_cred(self, id, field):
448        return self.request("GET_CRED " + str(id) + " " + field)
449
450    def add_cred_values(self, params):
451        id = self.add_cred()
452
453        quoted = ["realm", "username", "password", "domain", "imsi",
454                  "excluded_ssid", "milenage", "ca_cert", "client_cert",
455                  "private_key", "domain_suffix_match", "provisioning_sp",
456                  "roaming_partner", "phase1", "phase2", "private_key_passwd",
457                  "roaming_consortiums"]
458        for field in quoted:
459            if field in params:
460                self.set_cred_quoted(id, field, params[field])
461
462        not_quoted = ["eap", "roaming_consortium", "priority",
463                      "required_roaming_consortium", "sp_priority",
464                      "max_bss_load", "update_identifier", "req_conn_capab",
465                      "min_dl_bandwidth_home", "min_ul_bandwidth_home",
466                      "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"]
467        for field in not_quoted:
468            if field in params:
469                self.set_cred(id, field, params[field])
470
471        return id
472
473    def select_network(self, id, freq=None):
474        if freq:
475            extra = " freq=" + str(freq)
476        else:
477            extra = ""
478        id = self.request("SELECT_NETWORK " + str(id) + extra)
479        if "FAIL" in id:
480            raise Exception("SELECT_NETWORK failed")
481        return None
482
483    def mesh_group_add(self, id):
484        id = self.request("MESH_GROUP_ADD " + str(id))
485        if "FAIL" in id:
486            raise Exception("MESH_GROUP_ADD failed")
487        return None
488
489    def mesh_group_remove(self):
490        id = self.request("MESH_GROUP_REMOVE " + str(self.ifname))
491        if "FAIL" in id:
492            raise Exception("MESH_GROUP_REMOVE failed")
493        return None
494
495    def connect_network(self, id, timeout=None):
496        if timeout is None:
497            timeout = 10 if self.hostname is None else 60
498        self.dump_monitor()
499        self.select_network(id)
500        self.wait_connected(timeout=timeout)
501        self.dump_monitor()
502
503    def get_status(self, extra=None):
504        if extra:
505            extra = "-" + extra
506        else:
507            extra = ""
508        res = self.request("STATUS" + extra)
509        lines = res.splitlines()
510        vals = dict()
511        for l in lines:
512            try:
513                [name, value] = l.split('=', 1)
514                vals[name] = value
515            except ValueError as e:
516                logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l)
517        return vals
518
519    def get_status_field(self, field, extra=None):
520        vals = self.get_status(extra)
521        if field in vals:
522            return vals[field]
523        return None
524
525    def get_group_status(self, extra=None):
526        if extra:
527            extra = "-" + extra
528        else:
529            extra = ""
530        res = self.group_request("STATUS" + extra)
531        lines = res.splitlines()
532        vals = dict()
533        for l in lines:
534            try:
535                [name, value] = l.split('=', 1)
536            except ValueError:
537                logger.info(self.ifname + ": Ignore unexpected status line: " + l)
538                continue
539            vals[name] = value
540        return vals
541
542    def get_group_status_field(self, field, extra=None):
543        vals = self.get_group_status(extra)
544        if field in vals:
545            return vals[field]
546        return None
547
548    def get_driver_status(self, ifname=None):
549        if ifname is None:
550            res = self.request("STATUS-DRIVER")
551        else:
552            res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname)
553            if res.startswith("FAIL"):
554                return dict()
555        lines = res.splitlines()
556        vals = dict()
557        for l in lines:
558            try:
559                [name, value] = l.split('=', 1)
560            except ValueError:
561                logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l)
562                continue
563            vals[name] = value
564        return vals
565
566    def get_driver_status_field(self, field, ifname=None):
567        vals = self.get_driver_status(ifname)
568        if field in vals:
569            return vals[field]
570        return None
571
572    def get_mcc(self):
573        mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent'))
574        return 1 if mcc < 2 else mcc
575
576    def get_mib(self):
577        res = self.request("MIB")
578        lines = res.splitlines()
579        vals = dict()
580        for l in lines:
581            try:
582                [name, value] = l.split('=', 1)
583                vals[name] = value
584            except ValueError as e:
585                logger.info(self.ifname + ": Ignore unexpected MIB line: " + l)
586        return vals
587
588    def p2p_dev_addr(self):
589        return self.get_status_field("p2p_device_address")
590
591    def p2p_interface_addr(self):
592        return self.get_group_status_field("address")
593
594    def own_addr(self):
595        try:
596            res = self.p2p_interface_addr()
597        except:
598            res = self.p2p_dev_addr()
599        return res
600
601    def get_addr(self, group=False):
602        dev_addr = self.own_addr()
603        if not group:
604            addr = self.get_status_field('address')
605            if addr:
606                dev_addr = addr
607
608        return dev_addr
609
610    def p2p_listen(self):
611        return self.global_request("P2P_LISTEN")
612
613    def p2p_ext_listen(self, period, interval):
614        return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval))
615
616    def p2p_cancel_ext_listen(self):
617        return self.global_request("P2P_EXT_LISTEN")
618
619    def p2p_find(self, social=False, progressive=False, dev_id=None,
620                 dev_type=None, delay=None, freq=None):
621        cmd = "P2P_FIND"
622        if social:
623            cmd = cmd + " type=social"
624        elif progressive:
625            cmd = cmd + " type=progressive"
626        if dev_id:
627            cmd = cmd + " dev_id=" + dev_id
628        if dev_type:
629            cmd = cmd + " dev_type=" + dev_type
630        if delay:
631            cmd = cmd + " delay=" + str(delay)
632        if freq:
633            cmd = cmd + " freq=" + str(freq)
634        return self.global_request(cmd)
635
636    def p2p_stop_find(self):
637        return self.global_request("P2P_STOP_FIND")
638
639    def wps_read_pin(self):
640        self.pin = self.request("WPS_PIN get").rstrip("\n")
641        if "FAIL" in self.pin:
642            raise Exception("Could not generate PIN")
643        return self.pin
644
645    def peer_known(self, peer, full=True):
646        res = self.global_request("P2P_PEER " + peer)
647        if peer.lower() not in res.lower():
648            return False
649        if not full:
650            return True
651        return "[PROBE_REQ_ONLY]" not in res
652
653    def discover_peer(self, peer, full=True, timeout=15, social=True,
654                      force_find=False, freq=None):
655        logger.info(self.ifname + ": Trying to discover peer " + peer)
656        if not force_find and self.peer_known(peer, full):
657            return True
658        self.p2p_find(social, freq=freq)
659        count = 0
660        while count < timeout * 4:
661            time.sleep(0.25)
662            count = count + 1
663            if self.peer_known(peer, full):
664                return True
665        return False
666
667    def get_peer(self, peer):
668        res = self.global_request("P2P_PEER " + peer)
669        if peer.lower() not in res.lower():
670            raise Exception("Peer information not available")
671        lines = res.splitlines()
672        vals = dict()
673        for l in lines:
674            if '=' in l:
675                [name, value] = l.split('=', 1)
676                vals[name] = value
677        return vals
678
679    def group_form_result(self, ev, expect_failure=False, go_neg_res=None):
680        if expect_failure:
681            if "P2P-GROUP-STARTED" in ev:
682                raise Exception("Group formation succeeded when expecting failure")
683            exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)'
684            s = re.split(exp, ev)
685            if len(s) < 3:
686                return None
687            res = {}
688            res['result'] = 'go-neg-failed'
689            res['status'] = int(s[2])
690            return res
691
692        if "P2P-GROUP-STARTED" not in ev:
693            raise Exception("No P2P-GROUP-STARTED event seen")
694
695        exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*) ip_addr=([0-9.]*) ip_mask=([0-9.]*) go_ip_addr=([0-9.]*)'
696        s = re.split(exp, ev)
697        if len(s) < 11:
698            exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)'
699            s = re.split(exp, ev)
700            if len(s) < 8:
701                raise Exception("Could not parse P2P-GROUP-STARTED")
702        res = {}
703        res['result'] = 'success'
704        res['ifname'] = s[2]
705        self.group_ifname = s[2]
706        try:
707            if self.hostname is None:
708                self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl,
709                                                          self.group_ifname))
710            else:
711                port = self.get_ctrl_iface_port(self.group_ifname)
712                self.gctrl_mon = wpaspy.Ctrl(self.hostname, port)
713            if self.monitor:
714                self.gctrl_mon.attach()
715        except:
716            logger.debug("Could not open monitor socket for group interface")
717            self.gctrl_mon = None
718        res['role'] = s[3]
719        res['ssid'] = s[4]
720        res['freq'] = s[5]
721        if "[PERSISTENT]" in ev:
722            res['persistent'] = True
723        else:
724            res['persistent'] = False
725        p = re.match(r'psk=([0-9a-f]*)', s[6])
726        if p:
727            res['psk'] = p.group(1)
728        p = re.match(r'passphrase="(.*)"', s[6])
729        if p:
730            res['passphrase'] = p.group(1)
731        res['go_dev_addr'] = s[7]
732
733        if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]:
734            res['ip_addr'] = s[8]
735        if len(s) > 9:
736            res['ip_mask'] = s[9]
737        if len(s) > 10:
738            res['go_ip_addr'] = s[10]
739
740        if go_neg_res:
741            exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)'
742            s = re.split(exp, go_neg_res)
743            if len(s) < 4:
744                raise Exception("Could not parse P2P-GO-NEG-SUCCESS")
745            res['go_neg_role'] = s[2]
746            res['go_neg_freq'] = s[3]
747
748        return res
749
750    def p2p_go_neg_auth(self, peer, pin, method, go_intent=None,
751                        persistent=False, freq=None, freq2=None,
752                        max_oper_chwidth=None, ht40=False, vht=False):
753        if not self.discover_peer(peer):
754            raise Exception("Peer " + peer + " not found")
755        self.dump_monitor()
756        if pin:
757            cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth"
758        else:
759            cmd = "P2P_CONNECT " + peer + " " + method + " auth"
760        if go_intent:
761            cmd = cmd + ' go_intent=' + str(go_intent)
762        if freq:
763            cmd = cmd + ' freq=' + str(freq)
764        if freq2:
765            cmd = cmd + ' freq2=' + str(freq2)
766        if max_oper_chwidth:
767            cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
768        if ht40:
769            cmd = cmd + ' ht40'
770        if vht:
771            cmd = cmd + ' vht'
772        if persistent:
773            cmd = cmd + " persistent"
774        if "OK" in self.global_request(cmd):
775            return None
776        raise Exception("P2P_CONNECT (auth) failed")
777
778    def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False):
779        if timeout is None:
780            timeout = 1 if expect_failure else 5
781        go_neg_res = None
782        ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
783                                     "P2P-GO-NEG-FAILURE"], timeout)
784        if ev is None:
785            if expect_failure:
786                return None
787            raise Exception("Group formation timed out")
788        if "P2P-GO-NEG-SUCCESS" in ev:
789            go_neg_res = ev
790            ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
791            if ev is None:
792                if expect_failure:
793                    return None
794                raise Exception("Group formation timed out")
795        self.dump_monitor()
796        return self.group_form_result(ev, expect_failure, go_neg_res)
797
798    def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None,
799                        expect_failure=False, persistent=False,
800                        persistent_id=None, freq=None, provdisc=False,
801                        wait_group=True, freq2=None, max_oper_chwidth=None,
802                        ht40=False, vht=False):
803        if not self.discover_peer(peer):
804            raise Exception("Peer " + peer + " not found")
805        self.dump_monitor()
806        if pin:
807            cmd = "P2P_CONNECT " + peer + " " + pin + " " + method
808        else:
809            cmd = "P2P_CONNECT " + peer + " " + method
810        if go_intent is not None:
811            cmd = cmd + ' go_intent=' + str(go_intent)
812        if freq:
813            cmd = cmd + ' freq=' + str(freq)
814        if freq2:
815            cmd = cmd + ' freq2=' + str(freq2)
816        if max_oper_chwidth:
817            cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
818        if ht40:
819            cmd = cmd + ' ht40'
820        if vht:
821            cmd = cmd + ' vht'
822        if persistent:
823            cmd = cmd + " persistent"
824        elif persistent_id:
825            cmd = cmd + " persistent=" + persistent_id
826        if provdisc:
827            cmd = cmd + " provdisc"
828        if "OK" in self.global_request(cmd):
829            if timeout == 0:
830                return None
831            go_neg_res = None
832            ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
833                                         "P2P-GO-NEG-FAILURE"], timeout)
834            if ev is None:
835                if expect_failure:
836                    return None
837                raise Exception("Group formation timed out")
838            if "P2P-GO-NEG-SUCCESS" in ev:
839                if not wait_group:
840                    return ev
841                go_neg_res = ev
842                ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
843                if ev is None:
844                    if expect_failure:
845                        return None
846                    raise Exception("Group formation timed out")
847            self.dump_monitor()
848            return self.group_form_result(ev, expect_failure, go_neg_res)
849        raise Exception("P2P_CONNECT failed")
850
851    def _wait_event(self, mon, pfx, events, timeout):
852        if not isinstance(events, list):
853            raise Exception("WpaSupplicant._wait_event() called with incorrect events argument type")
854        start = os.times()[4]
855        while True:
856            while mon.pending():
857                ev = mon.recv()
858                logger.debug(self.dbg + pfx + ev)
859                for event in events:
860                    if event in ev:
861                        return ev
862            now = os.times()[4]
863            remaining = start + timeout - now
864            if remaining <= 0:
865                break
866            if not mon.pending(timeout=remaining):
867                break
868        return None
869
870    def wait_event(self, events, timeout=10):
871        return self._wait_event(self.mon, ": ", events, timeout)
872
873    def wait_global_event(self, events, timeout):
874        if self.global_iface is None:
875            return self.wait_event(events, timeout)
876        return self._wait_event(self.global_mon, "(global): ",
877                                events, timeout)
878
879    def wait_group_event(self, events, timeout=10):
880        if not isinstance(events, list):
881            raise Exception("WpaSupplicant.wait_group_event() called with incorrect events argument type")
882        if self.group_ifname and self.group_ifname != self.ifname:
883            if self.gctrl_mon is None:
884                return None
885            start = os.times()[4]
886            while True:
887                while self.gctrl_mon.pending():
888                    ev = self.gctrl_mon.recv()
889                    logger.debug(self.group_dbg + "(group): " + ev)
890                    for event in events:
891                        if event in ev:
892                            return ev
893                now = os.times()[4]
894                remaining = start + timeout - now
895                if remaining <= 0:
896                    break
897                if not self.gctrl_mon.pending(timeout=remaining):
898                    break
899            return None
900
901        return self.wait_event(events, timeout)
902
903    def wait_go_ending_session(self):
904        self.close_monitor_group()
905        timeout = 3 if self.hostname is None else 10
906        ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout)
907        if ev is None:
908            raise Exception("Group removal event timed out")
909        if "reason=GO_ENDING_SESSION" not in ev:
910            raise Exception("Unexpected group removal reason")
911
912    def dump_monitor(self, mon=True, global_mon=True):
913        count_iface = 0
914        count_global = 0
915        while mon and self.monitor and self.mon.pending():
916            ev = self.mon.recv()
917            logger.debug(self.dbg + ": " + ev)
918            count_iface += 1
919        while global_mon and self.monitor and self.global_mon and self.global_mon.pending():
920            ev = self.global_mon.recv()
921            logger.debug(self.global_dbg + self.ifname + "(global): " + ev)
922            count_global += 1
923        return (count_iface, count_global)
924
925    def remove_group(self, ifname=None):
926        self.close_monitor_group()
927        if ifname is None:
928            ifname = self.group_ifname if self.group_ifname else self.ifname
929        if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname):
930            raise Exception("Group could not be removed")
931        self.group_ifname = None
932
933    def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False):
934        self.dump_monitor()
935        cmd = "P2P_GROUP_ADD"
936        if persistent is None:
937            pass
938        elif persistent is True:
939            cmd = cmd + " persistent"
940        else:
941            cmd = cmd + " persistent=" + str(persistent)
942        if freq:
943            cmd = cmd + " freq=" + str(freq)
944        if "OK" in self.global_request(cmd):
945            ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5)
946            if ev is None:
947                raise Exception("GO start up timed out")
948            if not no_event_clear:
949                self.dump_monitor()
950            return self.group_form_result(ev)
951        raise Exception("P2P_GROUP_ADD failed")
952
953    def p2p_go_authorize_client(self, pin):
954        cmd = "WPS_PIN any " + pin
955        if "FAIL" in self.group_request(cmd):
956            raise Exception("Failed to authorize client connection on GO")
957        return None
958
959    def p2p_go_authorize_client_pbc(self):
960        cmd = "WPS_PBC"
961        if "FAIL" in self.group_request(cmd):
962            raise Exception("Failed to authorize client connection on GO")
963        return None
964
965    def p2p_connect_group(self, go_addr, pin, timeout=0, social=False,
966                          freq=None):
967        self.dump_monitor()
968        if not self.discover_peer(go_addr, social=social, freq=freq):
969            if social or not self.discover_peer(go_addr, social=social):
970                raise Exception("GO " + go_addr + " not found")
971        self.p2p_stop_find()
972        self.dump_monitor()
973        cmd = "P2P_CONNECT " + go_addr + " " + pin + " join"
974        if freq:
975            cmd += " freq=" + str(freq)
976        if "OK" in self.global_request(cmd):
977            if timeout == 0:
978                self.dump_monitor()
979                return None
980            ev = self.wait_global_event(["P2P-GROUP-STARTED",
981                                         "P2P-GROUP-FORMATION-FAILURE"],
982                                        timeout)
983            if ev is None:
984                raise Exception("Joining the group timed out")
985            if "P2P-GROUP-STARTED" not in ev:
986                raise Exception("Failed to join the group")
987            self.dump_monitor()
988            return self.group_form_result(ev)
989        raise Exception("P2P_CONNECT(join) failed")
990
991    def tdls_setup(self, peer):
992        cmd = "TDLS_SETUP " + peer
993        if "FAIL" in self.group_request(cmd):
994            raise Exception("Failed to request TDLS setup")
995        return None
996
997    def tdls_teardown(self, peer):
998        cmd = "TDLS_TEARDOWN " + peer
999        if "FAIL" in self.group_request(cmd):
1000            raise Exception("Failed to request TDLS teardown")
1001        return None
1002
1003    def tdls_link_status(self, peer):
1004        cmd = "TDLS_LINK_STATUS " + peer
1005        ret = self.group_request(cmd)
1006        if "FAIL" in ret:
1007            raise Exception("Failed to request TDLS link status")
1008        return ret
1009
1010    def tspecs(self):
1011        """Return (tsid, up) tuples representing current tspecs"""
1012        res = self.request("WMM_AC_STATUS")
1013        tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res)
1014        tspecs = [tuple(map(int, tspec)) for tspec in tspecs]
1015
1016        logger.debug("tspecs: " + str(tspecs))
1017        return tspecs
1018
1019    def add_ts(self, tsid, up, direction="downlink", expect_failure=False,
1020               extra=None):
1021        params = {
1022            "sba": 9000,
1023            "nominal_msdu_size": 1500,
1024            "min_phy_rate": 6000000,
1025            "mean_data_rate": 1500,
1026        }
1027        cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up)
1028        for (key, value) in params.items():
1029            cmd += " %s=%d" % (key, value)
1030        if extra:
1031            cmd += " " + extra
1032
1033        if self.request(cmd).strip() != "OK":
1034            raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up))
1035
1036        if expect_failure:
1037            ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2)
1038            if ev is None:
1039                raise Exception("ADDTS failed (time out while waiting failure)")
1040            if "tsid=%d" % (tsid) not in ev:
1041                raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED")
1042            return
1043
1044        ev = self.wait_event(["TSPEC-ADDED"], timeout=1)
1045        if ev is None:
1046            raise Exception("ADDTS failed (time out)")
1047        if "tsid=%d" % (tsid) not in ev:
1048            raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)")
1049
1050        if (tsid, up) not in self.tspecs():
1051            raise Exception("ADDTS failed (tsid not in tspec list)")
1052
1053    def del_ts(self, tsid):
1054        if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK":
1055            raise Exception("DELTS failed")
1056
1057        ev = self.wait_event(["TSPEC-REMOVED"], timeout=1)
1058        if ev is None:
1059            raise Exception("DELTS failed (time out)")
1060        if "tsid=%d" % (tsid) not in ev:
1061            raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)")
1062
1063        tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid]
1064        if tspecs:
1065            raise Exception("DELTS failed (still in tspec list)")
1066
1067    def connect(self, ssid=None, ssid2=None, **kwargs):
1068        logger.info("Connect STA " + self.ifname + " to AP")
1069        id = self.add_network()
1070        if ssid:
1071            self.set_network_quoted(id, "ssid", ssid)
1072        elif ssid2:
1073            self.set_network(id, "ssid", ssid2)
1074
1075        quoted = ["psk", "identity", "anonymous_identity", "password",
1076                  "machine_identity", "machine_password",
1077                  "ca_cert", "client_cert", "private_key",
1078                  "private_key_passwd", "ca_cert2", "client_cert2",
1079                  "private_key2", "phase1", "phase2", "domain_suffix_match",
1080                  "altsubject_match", "subject_match", "pac_file", "dh_file",
1081                  "bgscan", "ht_mcs", "id_str", "openssl_ciphers",
1082                  "domain_match", "dpp_connector", "sae_password",
1083                  "sae_password_id", "check_cert_subject",
1084                  "machine_ca_cert", "machine_client_cert",
1085                  "machine_private_key", "machine_phase2"]
1086        for field in quoted:
1087            if field in kwargs and kwargs[field]:
1088                self.set_network_quoted(id, field, kwargs[field])
1089
1090        not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise",
1091                      "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
1092                      "wep_tx_keyidx", "scan_freq", "freq_list", "eap",
1093                      "eapol_flags", "fragment_size", "scan_ssid", "auth_alg",
1094                      "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid",
1095                      "disable_he",
1096                      "disable_max_amsdu", "ampdu_factor", "ampdu_density",
1097                      "disable_ht40", "disable_sgi", "disable_ldpc",
1098                      "ht40_intolerant", "update_identifier", "mac_addr",
1099                      "erp", "bg_scan_period", "bssid_ignore",
1100                      "bssid_accept", "mem_only_psk", "eap_workaround",
1101                      "engine", "fils_dh_group", "bssid_hint",
1102                      "dpp_csign", "dpp_csign_expiry",
1103                      "dpp_netaccesskey", "dpp_netaccesskey_expiry", "dpp_pfs",
1104                      "group_mgmt", "owe_group", "owe_only",
1105                      "owe_ptk_workaround",
1106                      "transition_disable", "sae_pk",
1107                      "roaming_consortium_selection", "ocv",
1108                      "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc",
1109                      "ft_eap_pmksa_caching", "beacon_prot",
1110                      "wpa_deny_ptk0_rekey"]
1111        for field in not_quoted:
1112            if field in kwargs and kwargs[field]:
1113                self.set_network(id, field, kwargs[field])
1114
1115        known_args = {"raw_psk", "password_hex", "peerkey", "okc", "ocsp",
1116                      "only_add_network", "wait_connect"}
1117        unknown = set(kwargs.keys())
1118        unknown -= set(quoted)
1119        unknown -= set(not_quoted)
1120        unknown -= known_args
1121        if unknown:
1122            raise Exception("Unknown WpaSupplicant::connect() arguments: " + str(unknown))
1123
1124        if "raw_psk" in kwargs and kwargs['raw_psk']:
1125            self.set_network(id, "psk", kwargs['raw_psk'])
1126        if "password_hex" in kwargs and kwargs['password_hex']:
1127            self.set_network(id, "password", kwargs['password_hex'])
1128        if "peerkey" in kwargs and kwargs['peerkey']:
1129            self.set_network(id, "peerkey", "1")
1130        if "okc" in kwargs and kwargs['okc']:
1131            self.set_network(id, "proactive_key_caching", "1")
1132        if "ocsp" in kwargs and kwargs['ocsp']:
1133            self.set_network(id, "ocsp", str(kwargs['ocsp']))
1134        if "only_add_network" in kwargs and kwargs['only_add_network']:
1135            return id
1136        if "wait_connect" not in kwargs or kwargs['wait_connect']:
1137            if "eap" in kwargs:
1138                self.connect_network(id, timeout=20)
1139            else:
1140                self.connect_network(id)
1141        else:
1142            self.dump_monitor()
1143            self.select_network(id)
1144        return id
1145
1146    def scan(self, type=None, freq=None, no_wait=False, only_new=False,
1147             passive=False):
1148        if not no_wait:
1149            self.dump_monitor()
1150        if type:
1151            cmd = "SCAN TYPE=" + type
1152        else:
1153            cmd = "SCAN"
1154        if freq:
1155            cmd = cmd + " freq=" + str(freq)
1156        if only_new:
1157            cmd += " only_new=1"
1158        if passive:
1159            cmd += " passive=1"
1160        if not no_wait:
1161            self.dump_monitor()
1162        res = self.request(cmd)
1163        if "OK" not in res:
1164            raise Exception("Failed to trigger scan: " + str(res))
1165        if no_wait:
1166            return
1167        ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS",
1168                              "CTRL-EVENT-SCAN-FAILED"], 15)
1169        if ev is None:
1170            raise Exception("Scan timed out")
1171        if "CTRL-EVENT-SCAN-FAILED" in ev:
1172            raise Exception("Scan failed: " + ev)
1173
1174    def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False,
1175                     passive=False):
1176        if not force_scan and self.get_bss(bssid) is not None:
1177            return
1178        for i in range(0, 10):
1179            self.scan(freq=freq, type="ONLY", only_new=only_new,
1180                      passive=passive)
1181            if self.get_bss(bssid) is not None:
1182                return
1183        raise Exception("Could not find BSS " + bssid + " in scan")
1184
1185    def flush_scan_cache(self, freq=2417):
1186        self.request("BSS_FLUSH 0")
1187        self.scan(freq=freq, only_new=True)
1188        res = self.request("SCAN_RESULTS")
1189        if len(res.splitlines()) > 1:
1190            logger.debug("Scan results remaining after first attempt to flush the results:\n" + res)
1191            self.request("BSS_FLUSH 0")
1192            self.scan(freq=2422, only_new=True)
1193            res = self.request("SCAN_RESULTS")
1194            if len(res.splitlines()) > 1:
1195                logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res)
1196
1197    def disconnect_and_stop_scan(self):
1198        self.request("DISCONNECT")
1199        res = self.request("ABORT_SCAN")
1200        for i in range(2 if "OK" in res else 1):
1201                self.wait_event(["CTRL-EVENT-DISCONNECTED",
1202                                 "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5)
1203        self.dump_monitor()
1204
1205    def roam(self, bssid, fail_test=False, assoc_reject_ok=False,
1206             check_bssid=True):
1207        self.dump_monitor()
1208        if "OK" not in self.request("ROAM " + bssid):
1209            raise Exception("ROAM failed")
1210        if fail_test:
1211            if assoc_reject_ok:
1212                ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1213                                      "CTRL-EVENT-DISCONNECTED",
1214                                      "CTRL-EVENT-ASSOC-REJECT"], timeout=1)
1215            else:
1216                ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1217                                      "CTRL-EVENT-DISCONNECTED"], timeout=1)
1218            if ev and "CTRL-EVENT-DISCONNECTED" in ev:
1219                self.dump_monitor()
1220                return
1221            if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev:
1222                raise Exception("Unexpected connection")
1223            self.dump_monitor()
1224            return
1225        if assoc_reject_ok:
1226            ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1227                                  "CTRL-EVENT-DISCONNECTED"], timeout=10)
1228        else:
1229            ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1230                                      "CTRL-EVENT-DISCONNECTED",
1231                                  "CTRL-EVENT-ASSOC-REJECT"], timeout=10)
1232        if ev is None:
1233            raise Exception("Roaming with the AP timed out")
1234        if "CTRL-EVENT-ASSOC-REJECT" in ev:
1235            raise Exception("Roaming association rejected")
1236        if "CTRL-EVENT-DISCONNECTED" in ev:
1237            raise Exception("Unexpected disconnection when waiting for roam to complete")
1238        self.dump_monitor()
1239        if check_bssid and self.get_status_field('bssid') != bssid:
1240            raise Exception("Did not roam to correct BSSID")
1241
1242    def roam_over_ds(self, bssid, fail_test=False):
1243        self.dump_monitor()
1244        if "OK" not in self.request("FT_DS " + bssid):
1245            raise Exception("FT_DS failed")
1246        if fail_test:
1247            ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
1248            if ev is not None:
1249                raise Exception("Unexpected connection")
1250            self.dump_monitor()
1251            return
1252        ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1253                              "CTRL-EVENT-ASSOC-REJECT"], timeout=10)
1254        if ev is None:
1255            raise Exception("Roaming with the AP timed out")
1256        if "CTRL-EVENT-ASSOC-REJECT" in ev:
1257            raise Exception("Roaming association rejected")
1258        self.dump_monitor()
1259
1260    def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None,
1261                new_passphrase=None, no_wait=False):
1262        self.dump_monitor()
1263        if new_ssid:
1264            self.request("WPS_REG " + bssid + " " + pin + " " +
1265                         binascii.hexlify(new_ssid.encode()).decode() + " " +
1266                         key_mgmt + " " + cipher + " " +
1267                         binascii.hexlify(new_passphrase.encode()).decode())
1268            if no_wait:
1269                return
1270            ev = self.wait_event(["WPS-SUCCESS"], timeout=15)
1271        else:
1272            self.request("WPS_REG " + bssid + " " + pin)
1273            if no_wait:
1274                return
1275            ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15)
1276            if ev is None:
1277                raise Exception("WPS cred timed out")
1278            ev = self.wait_event(["WPS-FAIL"], timeout=15)
1279        if ev is None:
1280            raise Exception("WPS timed out")
1281        self.wait_connected(timeout=15)
1282
1283    def relog(self):
1284        self.global_request("RELOG")
1285
1286    def wait_completed(self, timeout=10):
1287        for i in range(0, timeout * 2):
1288            if self.get_status_field("wpa_state") == "COMPLETED":
1289                return
1290            time.sleep(0.5)
1291        raise Exception("Timeout while waiting for COMPLETED state")
1292
1293    def get_capability(self, field):
1294        res = self.request("GET_CAPABILITY " + field)
1295        if "FAIL" in res:
1296            return None
1297        return res.split(' ')
1298
1299    def get_bss(self, bssid, ifname=None):
1300        if not ifname or ifname == self.ifname:
1301            res = self.request("BSS " + bssid)
1302        elif ifname == self.group_ifname:
1303            res = self.group_request("BSS " + bssid)
1304        else:
1305            return None
1306
1307        if "FAIL" in res:
1308            return None
1309        lines = res.splitlines()
1310        vals = dict()
1311        for l in lines:
1312            [name, value] = l.split('=', 1)
1313            vals[name] = value
1314        if len(vals) == 0:
1315            return None
1316        return vals
1317
1318    def get_pmksa(self, bssid):
1319        res = self.request("PMKSA")
1320        lines = res.splitlines()
1321        for l in lines:
1322            if bssid not in l:
1323                continue
1324            vals = dict()
1325            try:
1326                [index, aa, pmkid, expiration, opportunistic] = l.split(' ')
1327                cache_id = None
1328            except ValueError:
1329                [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ')
1330            vals['index'] = index
1331            vals['pmkid'] = pmkid
1332            vals['expiration'] = expiration
1333            vals['opportunistic'] = opportunistic
1334            if cache_id != None:
1335                vals['cache_id'] = cache_id
1336            return vals
1337        return None
1338
1339    def get_pmk(self, network_id):
1340        bssid = self.get_status_field('bssid')
1341        res = self.request("PMKSA_GET %d" % network_id)
1342        for val in res.splitlines():
1343            if val.startswith(bssid):
1344                return val.split(' ')[2]
1345        return None
1346
1347    def get_sta(self, addr, info=None, next=False):
1348        cmd = "STA-NEXT " if next else "STA "
1349        if addr is None:
1350            res = self.request("STA-FIRST")
1351        elif info:
1352            res = self.request(cmd + addr + " " + info)
1353        else:
1354            res = self.request(cmd + addr)
1355        lines = res.splitlines()
1356        vals = dict()
1357        first = True
1358        for l in lines:
1359            if first:
1360                vals['addr'] = l
1361                first = False
1362            else:
1363                [name, value] = l.split('=', 1)
1364                vals[name] = value
1365        return vals
1366
1367    def mgmt_rx(self, timeout=5):
1368        ev = self.wait_event(["MGMT-RX"], timeout=timeout)
1369        if ev is None:
1370            return None
1371        msg = {}
1372        items = ev.split(' ')
1373        field, val = items[1].split('=')
1374        if field != "freq":
1375            raise Exception("Unexpected MGMT-RX event format: " + ev)
1376        msg['freq'] = val
1377
1378        field, val = items[2].split('=')
1379        if field != "datarate":
1380            raise Exception("Unexpected MGMT-RX event format: " + ev)
1381        msg['datarate'] = val
1382
1383        field, val = items[3].split('=')
1384        if field != "ssi_signal":
1385            raise Exception("Unexpected MGMT-RX event format: " + ev)
1386        msg['ssi_signal'] = val
1387
1388        frame = binascii.unhexlify(items[4])
1389        msg['frame'] = frame
1390
1391        hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
1392        msg['fc'] = hdr[0]
1393        msg['subtype'] = (hdr[0] >> 4) & 0xf
1394        hdr = hdr[1:]
1395        msg['duration'] = hdr[0]
1396        hdr = hdr[1:]
1397        msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1398        hdr = hdr[6:]
1399        msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1400        hdr = hdr[6:]
1401        msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1402        hdr = hdr[6:]
1403        msg['seq_ctrl'] = hdr[0]
1404        msg['payload'] = frame[24:]
1405
1406        return msg
1407
1408    def wait_connected(self, timeout=10, error="Connection timed out"):
1409        ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout)
1410        if ev is None:
1411            raise Exception(error)
1412        return ev
1413
1414    def wait_disconnected(self, timeout=None, error="Disconnection timed out"):
1415        if timeout is None:
1416            timeout = 10 if self.hostname is None else 30
1417        ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout)
1418        if ev is None:
1419            raise Exception(error)
1420        return ev
1421
1422    def get_group_ifname(self):
1423        return self.group_ifname if self.group_ifname else self.ifname
1424
1425    def get_config(self):
1426        res = self.request("DUMP")
1427        if res.startswith("FAIL"):
1428            raise Exception("DUMP failed")
1429        lines = res.splitlines()
1430        vals = dict()
1431        for l in lines:
1432            [name, value] = l.split('=', 1)
1433            vals[name] = value
1434        return vals
1435
1436    def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac,
1437                      method="1000", info="", status=None, cpt=None, role=None):
1438        if status is None:
1439            cmd = "P2P_ASP_PROVISION"
1440            params = "info='%s' method=%s" % (info, method)
1441        else:
1442            cmd = "P2P_ASP_PROVISION_RESP"
1443            params = "status=%d" % status
1444
1445        if role is not None:
1446            params += " role=" + role
1447        if cpt is not None:
1448            params += " cpt=" + cpt
1449
1450        if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" %
1451                                           (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)):
1452            raise Exception("%s request failed" % cmd)
1453
1454    def note(self, txt):
1455        self.request("NOTE " + txt)
1456
1457    def save_config(self):
1458        if "OK" not in self.request("SAVE_CONFIG"):
1459            raise Exception("Failed to save configuration file")
1460
1461    def wait_regdom(self, country_ie=False):
1462        for i in range(5):
1463            ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1)
1464            if ev is None:
1465                break
1466            if country_ie:
1467                if "init=COUNTRY_IE" in ev:
1468                    break
1469            else:
1470                break
1471
1472    def dpp_qr_code(self, uri):
1473        res = self.request("DPP_QR_CODE " + uri)
1474        if "FAIL" in res:
1475            raise Exception("Failed to parse QR Code URI")
1476        return int(res)
1477
1478    def dpp_nfc_uri(self, uri):
1479        res = self.request("DPP_NFC_URI " + uri)
1480        if "FAIL" in res:
1481            raise Exception("Failed to parse NFC URI")
1482        return int(res)
1483
1484    def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None,
1485                          curve=None, key=None):
1486        cmd = "DPP_BOOTSTRAP_GEN type=" + type
1487        if chan:
1488            cmd += " chan=" + chan
1489        if mac:
1490            if mac is True:
1491                mac = self.own_addr()
1492            cmd += " mac=" + mac.replace(':', '')
1493        if info:
1494            cmd += " info=" + info
1495        if curve:
1496            cmd += " curve=" + curve
1497        if key:
1498            cmd += " key=" + key
1499        res = self.request(cmd)
1500        if "FAIL" in res:
1501            raise Exception("Failed to generate bootstrapping info")
1502        return int(res)
1503
1504    def dpp_bootstrap_set(self, id, conf=None, configurator=None, ssid=None,
1505                          extra=None):
1506        cmd = "DPP_BOOTSTRAP_SET %d" % id
1507        if ssid:
1508            cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
1509        if extra:
1510            cmd += " " + extra
1511        if conf:
1512            cmd += " conf=" + conf
1513        if configurator is not None:
1514            cmd += " configurator=%d" % configurator
1515        if "OK" not in self.request(cmd):
1516            raise Exception("Failed to set bootstrapping parameters")
1517
1518    def dpp_listen(self, freq, netrole=None, qr=None, role=None):
1519        cmd = "DPP_LISTEN " + str(freq)
1520        if netrole:
1521            cmd += " netrole=" + netrole
1522        if qr:
1523            cmd += " qr=" + qr
1524        if role:
1525            cmd += " role=" + role
1526        if "OK" not in self.request(cmd):
1527            raise Exception("Failed to start listen operation")
1528
1529    def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None,
1530                      extra=None, own=None, role=None, neg_freq=None,
1531                      ssid=None, passphrase=None, expect_fail=False,
1532                      tcp_addr=None, tcp_port=None, conn_status=False,
1533                      ssid_charset=None, nfc_uri=None, netrole=None,
1534                      csrattrs=None):
1535        cmd = "DPP_AUTH_INIT"
1536        if peer is None:
1537            if nfc_uri:
1538                peer = self.dpp_nfc_uri(nfc_uri)
1539            else:
1540                peer = self.dpp_qr_code(uri)
1541        cmd += " peer=%d" % peer
1542        if own is not None:
1543            cmd += " own=%d" % own
1544        if role:
1545            cmd += " role=" + role
1546        if extra:
1547            cmd += " " + extra
1548        if conf:
1549            cmd += " conf=" + conf
1550        if configurator is not None:
1551            cmd += " configurator=%d" % configurator
1552        if neg_freq:
1553            cmd += " neg_freq=%d" % neg_freq
1554        if ssid:
1555            cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
1556        if ssid_charset:
1557            cmd += " ssid_charset=%d" % ssid_charset
1558        if passphrase:
1559            cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode()
1560        if tcp_addr:
1561            cmd += " tcp_addr=" + tcp_addr
1562        if tcp_port:
1563            cmd += " tcp_port=" + tcp_port
1564        if conn_status:
1565            cmd += " conn_status=1"
1566        if netrole:
1567            cmd += " netrole=" + netrole
1568        if csrattrs:
1569            cmd += " csrattrs=" + csrattrs
1570        res = self.request(cmd)
1571        if expect_fail:
1572            if "FAIL" not in res:
1573                raise Exception("DPP authentication started unexpectedly")
1574            return
1575        if "OK" not in res:
1576            raise Exception("Failed to initiate DPP Authentication")
1577        return int(peer)
1578
1579    def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None,
1580                      extra=None, use_id=None, allow_fail=False, ver=None,
1581                      tcp_addr=None, tcp_port=None):
1582        if use_id is None:
1583            id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1584        else:
1585            id1 = use_id
1586        cmd = "own=%d " % id1
1587        if identifier:
1588            cmd += "identifier=%s " % identifier
1589        cmd += "init=1 "
1590        if ver is not None:
1591            cmd += "ver=" + str(ver) + " "
1592        if role:
1593            cmd += "role=%s " % role
1594        if tcp_addr:
1595            cmd += "tcp_addr=" + tcp_addr + " "
1596        if tcp_port:
1597            cmd += "tcp_port=" + tcp_port + " "
1598        if extra:
1599            cmd += extra + " "
1600        cmd += "code=%s" % code
1601        res = self.request("DPP_PKEX_ADD " + cmd)
1602        if allow_fail:
1603            return id1
1604        if "FAIL" in res:
1605            raise Exception("Failed to set PKEX data (initiator)")
1606        return id1
1607
1608    def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None,
1609                      listen_role=None, use_id=None):
1610        if use_id is None:
1611            id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1612        else:
1613            id0 = use_id
1614        cmd = "own=%d " % id0
1615        if identifier:
1616            cmd += "identifier=%s " % identifier
1617        cmd += "code=%s" % code
1618        res = self.request("DPP_PKEX_ADD " + cmd)
1619        if "FAIL" in res:
1620            raise Exception("Failed to set PKEX data (responder)")
1621        self.dpp_listen(freq, role=listen_role)
1622        return id0
1623
1624    def dpp_configurator_add(self, curve=None, key=None,
1625                             net_access_key_curve=None):
1626        cmd = "DPP_CONFIGURATOR_ADD"
1627        if curve:
1628            cmd += " curve=" + curve
1629        if net_access_key_curve:
1630            cmd += " net_access_key_curve=" + net_access_key_curve
1631        if key:
1632            cmd += " key=" + key
1633        res = self.request(cmd)
1634        if "FAIL" in res:
1635            raise Exception("Failed to add configurator")
1636        return int(res)
1637
1638    def dpp_configurator_set(self, conf_id, net_access_key_curve=None):
1639        cmd = "DPP_CONFIGURATOR_SET %d" % conf_id
1640        if net_access_key_curve:
1641            cmd += " net_access_key_curve=" + net_access_key_curve
1642        res = self.request(cmd)
1643        if "FAIL" in res:
1644            raise Exception("Failed to set configurator")
1645
1646    def dpp_configurator_remove(self, conf_id):
1647        res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id)
1648        if "OK" not in res:
1649            raise Exception("DPP_CONFIGURATOR_REMOVE failed")
1650
1651    def get_ptksa(self, bssid, cipher):
1652        res = self.request("PTKSA_CACHE_LIST")
1653        lines = res.splitlines()
1654        for l in lines:
1655            if bssid not in l or cipher not in l:
1656                continue
1657
1658            vals = dict()
1659            [index, addr, cipher, expiration, tk, kdk] = l.split(' ', 5)
1660            vals['index'] = index
1661            vals['addr'] = addr
1662            vals['cipher'] = cipher
1663            vals['expiration'] = expiration
1664            vals['tk'] = tk
1665            vals['kdk'] = kdk
1666            return vals
1667        return None
1668