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", "imsi_privacy_cert",
458                  "imsi_privacy_attr"]
459        for field in quoted:
460            if field in params:
461                self.set_cred_quoted(id, field, params[field])
462
463        not_quoted = ["eap", "roaming_consortium", "priority",
464                      "required_roaming_consortium", "sp_priority",
465                      "max_bss_load", "update_identifier", "req_conn_capab",
466                      "min_dl_bandwidth_home", "min_ul_bandwidth_home",
467                      "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"]
468        for field in not_quoted:
469            if field in params:
470                self.set_cred(id, field, params[field])
471
472        as_list = ["home_ois", "required_home_ois"]
473        for field in as_list:
474            if field in params:
475                self.set_cred_quoted(id, field, ','.join(params[field]))
476
477        return id
478
479    def select_network(self, id, freq=None):
480        if freq:
481            extra = " freq=" + str(freq)
482        else:
483            extra = ""
484        id = self.request("SELECT_NETWORK " + str(id) + extra)
485        if "FAIL" in id:
486            raise Exception("SELECT_NETWORK failed")
487        return None
488
489    def mesh_group_add(self, id):
490        id = self.request("MESH_GROUP_ADD " + str(id))
491        if "FAIL" in id:
492            raise Exception("MESH_GROUP_ADD failed")
493        return None
494
495    def mesh_group_remove(self):
496        id = self.request("MESH_GROUP_REMOVE " + str(self.ifname))
497        if "FAIL" in id:
498            raise Exception("MESH_GROUP_REMOVE failed")
499        return None
500
501    def connect_network(self, id, timeout=None):
502        if timeout is None:
503            timeout = 10 if self.hostname is None else 60
504        self.dump_monitor()
505        self.select_network(id)
506        self.wait_connected(timeout=timeout)
507
508    def get_status(self, extra=None):
509        if extra:
510            extra = "-" + extra
511        else:
512            extra = ""
513        res = self.request("STATUS" + extra)
514        lines = res.splitlines()
515        vals = dict()
516        for l in lines:
517            try:
518                [name, value] = l.split('=', 1)
519                vals[name] = value
520            except ValueError as e:
521                logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l)
522        return vals
523
524    def get_status_field(self, field, extra=None):
525        vals = self.get_status(extra)
526        if field in vals:
527            return vals[field]
528        return None
529
530    def get_group_status(self, extra=None):
531        if extra:
532            extra = "-" + extra
533        else:
534            extra = ""
535        res = self.group_request("STATUS" + extra)
536        lines = res.splitlines()
537        vals = dict()
538        for l in lines:
539            try:
540                [name, value] = l.split('=', 1)
541            except ValueError:
542                logger.info(self.ifname + ": Ignore unexpected status line: " + l)
543                continue
544            vals[name] = value
545        return vals
546
547    def get_group_status_field(self, field, extra=None):
548        vals = self.get_group_status(extra)
549        if field in vals:
550            return vals[field]
551        return None
552
553    def get_driver_status(self, ifname=None):
554        if ifname is None:
555            res = self.request("STATUS-DRIVER")
556        else:
557            res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname)
558            if res.startswith("FAIL"):
559                return dict()
560        lines = res.splitlines()
561        vals = dict()
562        for l in lines:
563            try:
564                [name, value] = l.split('=', 1)
565            except ValueError:
566                logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l)
567                continue
568            vals[name] = value
569        return vals
570
571    def get_driver_status_field(self, field, ifname=None):
572        vals = self.get_driver_status(ifname)
573        if field in vals:
574            return vals[field]
575        return None
576
577    def get_mcc(self):
578        mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent'))
579        return 1 if mcc < 2 else mcc
580
581    def get_mib(self):
582        res = self.request("MIB")
583        lines = res.splitlines()
584        vals = dict()
585        for l in lines:
586            try:
587                [name, value] = l.split('=', 1)
588                vals[name] = value
589            except ValueError as e:
590                logger.info(self.ifname + ": Ignore unexpected MIB line: " + l)
591        return vals
592
593    def p2p_dev_addr(self):
594        return self.get_status_field("p2p_device_address")
595
596    def p2p_interface_addr(self):
597        return self.get_group_status_field("address")
598
599    def own_addr(self):
600        try:
601            res = self.p2p_interface_addr()
602        except:
603            res = self.p2p_dev_addr()
604        return res
605
606    def get_addr(self, group=False):
607        dev_addr = self.own_addr()
608        if not group:
609            addr = self.get_status_field('address')
610            if addr:
611                dev_addr = addr
612
613        return dev_addr
614
615    def p2p_listen(self):
616        return self.global_request("P2P_LISTEN")
617
618    def p2p_ext_listen(self, period, interval):
619        return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval))
620
621    def p2p_cancel_ext_listen(self):
622        return self.global_request("P2P_EXT_LISTEN")
623
624    def p2p_find(self, social=False, progressive=False, dev_id=None,
625                 dev_type=None, delay=None, freq=None):
626        cmd = "P2P_FIND"
627        if social:
628            cmd = cmd + " type=social"
629        elif progressive:
630            cmd = cmd + " type=progressive"
631        if dev_id:
632            cmd = cmd + " dev_id=" + dev_id
633        if dev_type:
634            cmd = cmd + " dev_type=" + dev_type
635        if delay:
636            cmd = cmd + " delay=" + str(delay)
637        if freq:
638            cmd = cmd + " freq=" + str(freq)
639        return self.global_request(cmd)
640
641    def p2p_stop_find(self):
642        return self.global_request("P2P_STOP_FIND")
643
644    def wps_read_pin(self):
645        self.pin = self.request("WPS_PIN get").rstrip("\n")
646        if "FAIL" in self.pin:
647            raise Exception("Could not generate PIN")
648        return self.pin
649
650    def peer_known(self, peer, full=True):
651        res = self.global_request("P2P_PEER " + peer)
652        if peer.lower() not in res.lower():
653            return False
654        if not full:
655            return True
656        return "[PROBE_REQ_ONLY]" not in res
657
658    def discover_peer(self, peer, full=True, timeout=15, social=True,
659                      force_find=False, freq=None):
660        logger.info(self.ifname + ": Trying to discover peer " + peer)
661        if not force_find and self.peer_known(peer, full):
662            return True
663        self.p2p_find(social, freq=freq)
664        count = 0
665        while count < timeout * 4:
666            time.sleep(0.25)
667            count = count + 1
668            if self.peer_known(peer, full):
669                return True
670        return False
671
672    def get_peer(self, peer):
673        res = self.global_request("P2P_PEER " + peer)
674        if peer.lower() not in res.lower():
675            raise Exception("Peer information not available")
676        lines = res.splitlines()
677        vals = dict()
678        for l in lines:
679            if '=' in l:
680                [name, value] = l.split('=', 1)
681                vals[name] = value
682        return vals
683
684    def group_form_result(self, ev, expect_failure=False, go_neg_res=None):
685        if expect_failure:
686            if "P2P-GROUP-STARTED" in ev:
687                raise Exception("Group formation succeeded when expecting failure")
688            exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)'
689            s = re.split(exp, ev)
690            if len(s) < 3:
691                return None
692            res = {}
693            res['result'] = 'go-neg-failed'
694            res['status'] = int(s[2])
695            return res
696
697        if "P2P-GROUP-STARTED" not in ev:
698            raise Exception("No P2P-GROUP-STARTED event seen")
699
700        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.]*)'
701        s = re.split(exp, ev)
702        if len(s) < 11:
703            exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)'
704            s = re.split(exp, ev)
705            if len(s) < 8:
706                raise Exception("Could not parse P2P-GROUP-STARTED")
707        res = {}
708        res['result'] = 'success'
709        res['ifname'] = s[2]
710        self.group_ifname = s[2]
711        try:
712            if self.hostname is None:
713                self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl,
714                                                          self.group_ifname))
715            else:
716                port = self.get_ctrl_iface_port(self.group_ifname)
717                self.gctrl_mon = wpaspy.Ctrl(self.hostname, port)
718            if self.monitor:
719                self.gctrl_mon.attach()
720        except:
721            logger.debug("Could not open monitor socket for group interface")
722            self.gctrl_mon = None
723        res['role'] = s[3]
724        res['ssid'] = s[4]
725        res['freq'] = s[5]
726        if "[PERSISTENT]" in ev:
727            res['persistent'] = True
728        else:
729            res['persistent'] = False
730        p = re.match(r'psk=([0-9a-f]*)', s[6])
731        if p:
732            res['psk'] = p.group(1)
733        p = re.match(r'passphrase="(.*)"', s[6])
734        if p:
735            res['passphrase'] = p.group(1)
736        res['go_dev_addr'] = s[7]
737
738        if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]:
739            res['ip_addr'] = s[8]
740        if len(s) > 9:
741            res['ip_mask'] = s[9]
742        if len(s) > 10:
743            res['go_ip_addr'] = s[10]
744
745        if go_neg_res:
746            exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)'
747            s = re.split(exp, go_neg_res)
748            if len(s) < 4:
749                raise Exception("Could not parse P2P-GO-NEG-SUCCESS")
750            res['go_neg_role'] = s[2]
751            res['go_neg_freq'] = s[3]
752
753        return res
754
755    def p2p_go_neg_auth(self, peer, pin, method, go_intent=None,
756                        persistent=False, freq=None, freq2=None,
757                        max_oper_chwidth=None, ht40=False, vht=False):
758        if not self.discover_peer(peer):
759            raise Exception("Peer " + peer + " not found")
760        self.dump_monitor()
761        if pin:
762            cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth"
763        else:
764            cmd = "P2P_CONNECT " + peer + " " + method + " auth"
765        if go_intent:
766            cmd = cmd + ' go_intent=' + str(go_intent)
767        if freq:
768            cmd = cmd + ' freq=' + str(freq)
769        if freq2:
770            cmd = cmd + ' freq2=' + str(freq2)
771        if max_oper_chwidth:
772            cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
773        if ht40:
774            cmd = cmd + ' ht40'
775        if vht:
776            cmd = cmd + ' vht'
777        if persistent:
778            cmd = cmd + " persistent"
779        if "OK" in self.global_request(cmd):
780            return None
781        raise Exception("P2P_CONNECT (auth) failed")
782
783    def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False):
784        if timeout is None:
785            timeout = 1 if expect_failure else 5
786        go_neg_res = None
787        ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
788                                     "P2P-GO-NEG-FAILURE"], timeout)
789        if ev is None:
790            if expect_failure:
791                return None
792            raise Exception("Group formation timed out")
793        if "P2P-GO-NEG-SUCCESS" in ev:
794            go_neg_res = ev
795            ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
796            if ev is None:
797                if expect_failure:
798                    return None
799                raise Exception("Group formation timed out")
800        return self.group_form_result(ev, expect_failure, go_neg_res)
801
802    def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None,
803                        expect_failure=False, persistent=False,
804                        persistent_id=None, freq=None, provdisc=False,
805                        wait_group=True, freq2=None, max_oper_chwidth=None,
806                        ht40=False, vht=False):
807        if not self.discover_peer(peer,timeout=timeout if timeout else 15):
808            raise Exception("Peer " + peer + " not found")
809        self.dump_monitor()
810        if pin:
811            cmd = "P2P_CONNECT " + peer + " " + pin + " " + method
812        else:
813            cmd = "P2P_CONNECT " + peer + " " + method
814        if go_intent is not None:
815            cmd = cmd + ' go_intent=' + str(go_intent)
816        if freq:
817            cmd = cmd + ' freq=' + str(freq)
818        if freq2:
819            cmd = cmd + ' freq2=' + str(freq2)
820        if max_oper_chwidth:
821            cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth)
822        if ht40:
823            cmd = cmd + ' ht40'
824        if vht:
825            cmd = cmd + ' vht'
826        if persistent:
827            cmd = cmd + " persistent"
828        elif persistent_id:
829            cmd = cmd + " persistent=" + persistent_id
830        if provdisc:
831            cmd = cmd + " provdisc"
832        if "OK" in self.global_request(cmd):
833            if timeout == 0:
834                return None
835            go_neg_res = None
836            ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
837                                         "P2P-GO-NEG-FAILURE"], timeout)
838            if ev is None:
839                if expect_failure:
840                    return None
841                raise Exception("Group formation timed out")
842            if "P2P-GO-NEG-SUCCESS" in ev:
843                if not wait_group:
844                    return ev
845                go_neg_res = ev
846                ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
847                if ev is None:
848                    if expect_failure:
849                        return None
850                    raise Exception("Group formation timed out")
851            self.dump_monitor()
852            return self.group_form_result(ev, expect_failure, go_neg_res)
853        raise Exception("P2P_CONNECT failed")
854
855    def _wait_event(self, mon, pfx, events, timeout):
856        if not isinstance(events, list):
857            raise Exception("WpaSupplicant._wait_event() called with incorrect events argument type")
858        start = os.times()[4]
859        while True:
860            while mon.pending():
861                ev = mon.recv()
862                logger.debug(self.dbg + pfx + ev)
863                for event in events:
864                    if event in ev:
865                        return ev
866            now = os.times()[4]
867            remaining = start + timeout - now
868            if remaining <= 0:
869                break
870            if not mon.pending(timeout=remaining):
871                break
872        return None
873
874    def wait_event(self, events, timeout=10):
875        return self._wait_event(self.mon, ": ", events, timeout)
876
877    def wait_global_event(self, events, timeout):
878        if self.global_iface is None:
879            return self.wait_event(events, timeout)
880        return self._wait_event(self.global_mon, "(global): ",
881                                events, timeout)
882
883    def wait_group_event(self, events, timeout=10):
884        if not isinstance(events, list):
885            raise Exception("WpaSupplicant.wait_group_event() called with incorrect events argument type")
886        if self.group_ifname and self.group_ifname != self.ifname:
887            if self.gctrl_mon is None:
888                return None
889            start = os.times()[4]
890            while True:
891                while self.gctrl_mon.pending():
892                    ev = self.gctrl_mon.recv()
893                    logger.debug(self.group_dbg + "(group): " + ev)
894                    for event in events:
895                        if event in ev:
896                            return ev
897                now = os.times()[4]
898                remaining = start + timeout - now
899                if remaining <= 0:
900                    break
901                if not self.gctrl_mon.pending(timeout=remaining):
902                    break
903            return None
904
905        return self.wait_event(events, timeout)
906
907    def wait_go_ending_session(self):
908        self.close_monitor_group()
909        timeout = 3 if self.hostname is None else 10
910        ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout)
911        if ev is None:
912            raise Exception("Group removal event timed out")
913        if "reason=GO_ENDING_SESSION" not in ev:
914            raise Exception("Unexpected group removal reason")
915
916    def dump_monitor(self, mon=True, global_mon=True):
917        count_iface = 0
918        count_global = 0
919        while mon and self.monitor and self.mon.pending():
920            ev = self.mon.recv()
921            logger.debug(self.dbg + ": " + ev)
922            count_iface += 1
923        while global_mon and self.monitor and self.global_mon and self.global_mon.pending():
924            ev = self.global_mon.recv()
925            logger.debug(self.global_dbg + self.ifname + "(global): " + ev)
926            count_global += 1
927        return (count_iface, count_global)
928
929    def remove_group(self, ifname=None):
930        self.close_monitor_group()
931        if ifname is None:
932            ifname = self.group_ifname if self.group_ifname else self.ifname
933        if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname):
934            raise Exception("Group could not be removed")
935        self.group_ifname = None
936
937    def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False):
938        self.dump_monitor()
939        cmd = "P2P_GROUP_ADD"
940        if persistent is None:
941            pass
942        elif persistent is True:
943            cmd = cmd + " persistent"
944        else:
945            cmd = cmd + " persistent=" + str(persistent)
946        if freq:
947            cmd = cmd + " freq=" + str(freq)
948        if "OK" in self.global_request(cmd):
949            ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5)
950            if ev is None:
951                raise Exception("GO start up timed out")
952            if not no_event_clear:
953                self.dump_monitor()
954            return self.group_form_result(ev)
955        raise Exception("P2P_GROUP_ADD failed")
956
957    def p2p_go_authorize_client(self, pin):
958        cmd = "WPS_PIN any " + pin
959        if "FAIL" in self.group_request(cmd):
960            raise Exception("Failed to authorize client connection on GO")
961        return None
962
963    def p2p_go_authorize_client_pbc(self):
964        cmd = "WPS_PBC"
965        if "FAIL" in self.group_request(cmd):
966            raise Exception("Failed to authorize client connection on GO")
967        return None
968
969    def p2p_connect_group(self, go_addr, pin, timeout=0, social=False,
970                          freq=None):
971        self.dump_monitor()
972        if not self.discover_peer(go_addr, social=social, freq=freq):
973            if social or not self.discover_peer(go_addr, social=social):
974                raise Exception("GO " + go_addr + " not found")
975        self.p2p_stop_find()
976        self.dump_monitor()
977        cmd = "P2P_CONNECT " + go_addr + " " + pin + " join"
978        if freq:
979            cmd += " freq=" + str(freq)
980        if "OK" in self.global_request(cmd):
981            if timeout == 0:
982                self.dump_monitor()
983                return None
984            ev = self.wait_global_event(["P2P-GROUP-STARTED",
985                                         "P2P-GROUP-FORMATION-FAILURE"],
986                                        timeout)
987            if ev is None:
988                raise Exception("Joining the group timed out")
989            if "P2P-GROUP-STARTED" not in ev:
990                raise Exception("Failed to join the group")
991            self.dump_monitor()
992            return self.group_form_result(ev)
993        raise Exception("P2P_CONNECT(join) failed")
994
995    def tdls_setup(self, peer):
996        cmd = "TDLS_SETUP " + peer
997        if "FAIL" in self.group_request(cmd):
998            raise Exception("Failed to request TDLS setup")
999        return None
1000
1001    def tdls_teardown(self, peer):
1002        cmd = "TDLS_TEARDOWN " + peer
1003        if "FAIL" in self.group_request(cmd):
1004            raise Exception("Failed to request TDLS teardown")
1005        return None
1006
1007    def tdls_link_status(self, peer):
1008        cmd = "TDLS_LINK_STATUS " + peer
1009        ret = self.group_request(cmd)
1010        if "FAIL" in ret:
1011            raise Exception("Failed to request TDLS link status")
1012        return ret
1013
1014    def tspecs(self):
1015        """Return (tsid, up) tuples representing current tspecs"""
1016        res = self.request("WMM_AC_STATUS")
1017        tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res)
1018        tspecs = [tuple(map(int, tspec)) for tspec in tspecs]
1019
1020        logger.debug("tspecs: " + str(tspecs))
1021        return tspecs
1022
1023    def add_ts(self, tsid, up, direction="downlink", expect_failure=False,
1024               extra=None):
1025        params = {
1026            "sba": 9000,
1027            "nominal_msdu_size": 1500,
1028            "min_phy_rate": 6000000,
1029            "mean_data_rate": 1500,
1030        }
1031        cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up)
1032        for (key, value) in params.items():
1033            cmd += " %s=%d" % (key, value)
1034        if extra:
1035            cmd += " " + extra
1036
1037        if self.request(cmd).strip() != "OK":
1038            raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up))
1039
1040        if expect_failure:
1041            ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2)
1042            if ev is None:
1043                raise Exception("ADDTS failed (time out while waiting failure)")
1044            if "tsid=%d" % (tsid) not in ev:
1045                raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED")
1046            return
1047
1048        ev = self.wait_event(["TSPEC-ADDED"], timeout=1)
1049        if ev is None:
1050            raise Exception("ADDTS failed (time out)")
1051        if "tsid=%d" % (tsid) not in ev:
1052            raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)")
1053
1054        if (tsid, up) not in self.tspecs():
1055            raise Exception("ADDTS failed (tsid not in tspec list)")
1056
1057    def del_ts(self, tsid):
1058        if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK":
1059            raise Exception("DELTS failed")
1060
1061        ev = self.wait_event(["TSPEC-REMOVED"], timeout=1)
1062        if ev is None:
1063            raise Exception("DELTS failed (time out)")
1064        if "tsid=%d" % (tsid) not in ev:
1065            raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)")
1066
1067        tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid]
1068        if tspecs:
1069            raise Exception("DELTS failed (still in tspec list)")
1070
1071    def connect(self, ssid=None, ssid2=None, timeout=None, **kwargs):
1072        logger.info("Connect STA " + self.ifname + " to AP")
1073        id = self.add_network()
1074        if ssid:
1075            self.set_network_quoted(id, "ssid", ssid)
1076        elif ssid2:
1077            self.set_network(id, "ssid", ssid2)
1078
1079        quoted = ["psk", "identity", "anonymous_identity", "password",
1080                  "machine_identity", "machine_password",
1081                  "ca_cert", "client_cert", "private_key",
1082                  "private_key_passwd", "ca_cert2", "client_cert2",
1083                  "private_key2", "phase1", "phase2", "domain_suffix_match",
1084                  "altsubject_match", "subject_match", "pac_file",
1085                  "bgscan", "ht_mcs", "id_str", "openssl_ciphers",
1086                  "domain_match", "dpp_connector", "sae_password",
1087                  "sae_password_id", "check_cert_subject",
1088                  "machine_ca_cert", "machine_client_cert",
1089                  "machine_private_key", "machine_phase2",
1090                  "imsi_identity", "imsi_privacy_cert", "imsi_privacy_attr"]
1091        for field in quoted:
1092            if field in kwargs and kwargs[field]:
1093                self.set_network_quoted(id, field, kwargs[field])
1094
1095        not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise",
1096                      "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
1097                      "wep_tx_keyidx", "scan_freq", "freq_list", "eap",
1098                      "eapol_flags", "fragment_size", "scan_ssid", "auth_alg",
1099                      "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid",
1100                      "disable_he", "disable_eht",
1101                      "disable_max_amsdu", "ampdu_factor", "ampdu_density",
1102                      "disable_ht40", "disable_sgi", "disable_ldpc",
1103                      "ht40_intolerant", "update_identifier", "mac_addr",
1104                      "erp", "bg_scan_period", "bssid_ignore",
1105                      "bssid_accept", "mem_only_psk", "eap_workaround",
1106                      "engine", "fils_dh_group", "bssid_hint",
1107                      "dpp_csign", "dpp_csign_expiry",
1108                      "dpp_netaccesskey", "dpp_netaccesskey_expiry", "dpp_pfs",
1109                      "dpp_connector_privacy",
1110                      "group_mgmt", "owe_group", "owe_only",
1111                      "owe_ptk_workaround",
1112                      "transition_disable", "sae_pk",
1113                      "roaming_consortium_selection", "ocv",
1114                      "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc",
1115                      "ft_eap_pmksa_caching", "beacon_prot",
1116                      "mac_value",
1117                      "wpa_deny_ptk0_rekey",
1118                      "max_idle",
1119                      "ssid_protection",
1120                      "enable_4addr_mode"]
1121        for field in not_quoted:
1122            if field in kwargs and kwargs[field]:
1123                self.set_network(id, field, kwargs[field])
1124
1125        if timeout is None:
1126            if "eap" in kwargs:
1127                timeout=20
1128            else:
1129                timeout=15
1130
1131        known_args = {"raw_psk", "password_hex", "peerkey", "okc", "ocsp",
1132                      "only_add_network", "wait_connect", "raw_identity"}
1133        unknown = set(kwargs.keys())
1134        unknown -= set(quoted)
1135        unknown -= set(not_quoted)
1136        unknown -= known_args
1137        if unknown:
1138            raise Exception("Unknown WpaSupplicant::connect() arguments: " + str(unknown))
1139
1140        if "raw_identity" in kwargs and kwargs['raw_identity']:
1141            self.set_network(id, "identity", kwargs['raw_identity'])
1142        if "raw_psk" in kwargs and kwargs['raw_psk']:
1143            self.set_network(id, "psk", kwargs['raw_psk'])
1144        if "password_hex" in kwargs and kwargs['password_hex']:
1145            self.set_network(id, "password", kwargs['password_hex'])
1146        if "peerkey" in kwargs and kwargs['peerkey']:
1147            self.set_network(id, "peerkey", "1")
1148        if "okc" in kwargs and kwargs['okc']:
1149            self.set_network(id, "proactive_key_caching", "1")
1150        if "ocsp" in kwargs and kwargs['ocsp']:
1151            self.set_network(id, "ocsp", str(kwargs['ocsp']))
1152        if "only_add_network" in kwargs and kwargs['only_add_network']:
1153            return id
1154        if "wait_connect" not in kwargs or kwargs['wait_connect']:
1155            self.connect_network(id, timeout=timeout)
1156        else:
1157            self.dump_monitor()
1158            self.select_network(id)
1159        return id
1160
1161    def scan(self, type=None, freq=None, no_wait=False, only_new=False,
1162             passive=False, timeout=15):
1163        if not no_wait:
1164            self.dump_monitor()
1165        if type:
1166            cmd = "SCAN TYPE=" + type
1167        else:
1168            cmd = "SCAN"
1169        if freq:
1170            cmd = cmd + " freq=" + str(freq)
1171        if only_new:
1172            cmd += " only_new=1"
1173        if passive:
1174            cmd += " passive=1"
1175        if not no_wait:
1176            self.dump_monitor()
1177        res = self.request(cmd)
1178        if "OK" not in res:
1179            raise Exception("Failed to trigger scan: " + str(res))
1180        if no_wait:
1181            return
1182        ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS",
1183                              "CTRL-EVENT-SCAN-FAILED"], timeout)
1184        if ev is None:
1185            raise Exception("Scan timed out")
1186        if "CTRL-EVENT-SCAN-FAILED" in ev:
1187            raise Exception("Scan failed: " + ev)
1188
1189    def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False,
1190                     passive=False):
1191        if not force_scan and self.get_bss(bssid) is not None:
1192            return
1193        for i in range(0, 10):
1194            self.scan(freq=freq, type="ONLY", only_new=only_new,
1195                      passive=passive)
1196            if self.get_bss(bssid) is not None:
1197                return
1198        raise Exception("Could not find BSS " + bssid + " in scan")
1199
1200    def flush_scan_cache(self, freq=2417):
1201        for i in range(3):
1202            self.request("BSS_FLUSH 0")
1203            try:
1204                self.scan(freq=freq, only_new=True)
1205            except Exception as e:
1206                if i < 2:
1207                    logger.info("flush_scan_cache: Failed to start scan: " + str(e))
1208                    self.request("ABORT_SCAN")
1209                    time.sleep(0.1)
1210        res = self.request("SCAN_RESULTS")
1211        if len(res.splitlines()) > 1:
1212            logger.debug("Scan results remaining after first attempt to flush the results:\n" + res)
1213            self.request("BSS_FLUSH 0")
1214            self.scan(freq=2422, only_new=True)
1215            res = self.request("SCAN_RESULTS")
1216            if len(res.splitlines()) > 1:
1217                logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res)
1218
1219    def disconnect_and_stop_scan(self):
1220        self.request("DISCONNECT")
1221        res = self.request("ABORT_SCAN")
1222        for i in range(2 if "OK" in res else 1):
1223                self.wait_event(["CTRL-EVENT-DISCONNECTED",
1224                                 "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5)
1225        self.dump_monitor()
1226
1227    def roam(self, bssid, fail_test=False, assoc_reject_ok=False,
1228             check_bssid=True):
1229        self.dump_monitor()
1230        if "OK" not in self.request("ROAM " + bssid):
1231            raise Exception("ROAM failed")
1232        if fail_test:
1233            if assoc_reject_ok:
1234                ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1235                                      "CTRL-EVENT-DISCONNECTED",
1236                                      "CTRL-EVENT-ASSOC-REJECT"], timeout=1)
1237            else:
1238                ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1239                                      "CTRL-EVENT-DISCONNECTED"], timeout=1)
1240            if ev and "CTRL-EVENT-DISCONNECTED" in ev:
1241                self.dump_monitor()
1242                return
1243            if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev:
1244                raise Exception("Unexpected connection")
1245            self.dump_monitor()
1246            return
1247        if assoc_reject_ok:
1248            ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1249                                  "CTRL-EVENT-DISCONNECTED"], timeout=10)
1250        else:
1251            ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1252                                      "CTRL-EVENT-DISCONNECTED",
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        if "CTRL-EVENT-DISCONNECTED" in ev:
1259            raise Exception("Unexpected disconnection when waiting for roam to complete")
1260        self.dump_monitor()
1261        if check_bssid and self.get_status_field('bssid') != bssid:
1262            raise Exception("Did not roam to correct BSSID")
1263
1264    def roam_over_ds(self, bssid, fail_test=False, force=False):
1265        self.dump_monitor()
1266        cmd = "FT_DS " + bssid
1267        if force:
1268            cmd += " force"
1269        if "OK" not in self.request(cmd):
1270            raise Exception("FT_DS failed")
1271        if fail_test:
1272            ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
1273            if ev is not None:
1274                raise Exception("Unexpected connection")
1275            self.dump_monitor()
1276            return
1277        ev = self.wait_event(["CTRL-EVENT-CONNECTED",
1278                              "CTRL-EVENT-ASSOC-REJECT"], timeout=10)
1279        if ev is None:
1280            raise Exception("Roaming with the AP timed out")
1281        if "CTRL-EVENT-ASSOC-REJECT" in ev:
1282            raise Exception("Roaming association rejected")
1283        self.dump_monitor()
1284
1285    def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None,
1286                new_passphrase=None, no_wait=False):
1287        self.dump_monitor()
1288        if new_ssid:
1289            self.request("WPS_REG " + bssid + " " + pin + " " +
1290                         binascii.hexlify(new_ssid.encode()).decode() + " " +
1291                         key_mgmt + " " + cipher + " " +
1292                         binascii.hexlify(new_passphrase.encode()).decode())
1293            if no_wait:
1294                return
1295            ev = self.wait_event(["WPS-SUCCESS"], timeout=15)
1296        else:
1297            self.request("WPS_REG " + bssid + " " + pin)
1298            if no_wait:
1299                return
1300            ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15)
1301            if ev is None:
1302                raise Exception("WPS cred timed out")
1303            ev = self.wait_event(["WPS-FAIL"], timeout=15)
1304        if ev is None:
1305            raise Exception("WPS timed out")
1306        self.wait_connected(timeout=15)
1307
1308    def relog(self):
1309        self.global_request("RELOG")
1310
1311    def wait_completed(self, timeout=10):
1312        for i in range(0, timeout * 2):
1313            if self.get_status_field("wpa_state") == "COMPLETED":
1314                return
1315            time.sleep(0.5)
1316        raise Exception("Timeout while waiting for COMPLETED state")
1317
1318    def get_capability(self, field):
1319        res = self.request("GET_CAPABILITY " + field)
1320        if "FAIL" in res:
1321            return None
1322        return res.split(' ')
1323
1324    def get_bss(self, bssid, ifname=None):
1325        if not ifname or ifname == self.ifname:
1326            res = self.request("BSS " + bssid)
1327        elif ifname == self.group_ifname:
1328            res = self.group_request("BSS " + bssid)
1329        else:
1330            return None
1331
1332        if "FAIL" in res:
1333            return None
1334        lines = res.splitlines()
1335        vals = dict()
1336        for l in lines:
1337            [name, value] = l.split('=', 1)
1338            vals[name] = value
1339        if len(vals) == 0:
1340            return None
1341        return vals
1342
1343    def get_pmksa(self, bssid):
1344        res = self.request("PMKSA")
1345        lines = res.splitlines()
1346        for l in lines:
1347            if bssid not in l:
1348                continue
1349            vals = dict()
1350            try:
1351                [index, aa, pmkid, expiration, opportunistic] = l.split(' ')
1352                cache_id = None
1353            except ValueError:
1354                [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ')
1355            vals['index'] = index
1356            vals['pmkid'] = pmkid
1357            vals['expiration'] = expiration
1358            vals['opportunistic'] = opportunistic
1359            if cache_id != None:
1360                vals['cache_id'] = cache_id
1361            return vals
1362        return None
1363
1364    def get_pmk(self, network_id):
1365        bssid = self.get_status_field('bssid')
1366        res = self.request("PMKSA_GET %d" % network_id)
1367        for val in res.splitlines():
1368            if val.startswith(bssid):
1369                return val.split(' ')[2]
1370        return None
1371
1372    def get_sta(self, addr, info=None, next=False):
1373        cmd = "STA-NEXT " if next else "STA "
1374        if addr is None:
1375            res = self.request("STA-FIRST")
1376        elif info:
1377            res = self.request(cmd + addr + " " + info)
1378        else:
1379            res = self.request(cmd + addr)
1380        lines = res.splitlines()
1381        vals = dict()
1382        first = True
1383        for l in lines:
1384            if first:
1385                vals['addr'] = l
1386                first = False
1387            else:
1388                [name, value] = l.split('=', 1)
1389                vals[name] = value
1390        return vals
1391
1392    def mgmt_rx(self, timeout=5):
1393        ev = self.wait_event(["MGMT-RX"], timeout=timeout)
1394        if ev is None:
1395            return None
1396        return self.mgmt_rx_parse(ev)
1397
1398    def mgmt_rx_parse(self, ev):
1399        msg = {}
1400        items = ev.split(' ')
1401        field, val = items[1].split('=')
1402        if field != "freq":
1403            raise Exception("Unexpected MGMT-RX event format: " + ev)
1404        msg['freq'] = val
1405
1406        field, val = items[2].split('=')
1407        if field != "datarate":
1408            raise Exception("Unexpected MGMT-RX event format: " + ev)
1409        msg['datarate'] = val
1410
1411        field, val = items[3].split('=')
1412        if field != "ssi_signal":
1413            raise Exception("Unexpected MGMT-RX event format: " + ev)
1414        msg['ssi_signal'] = val
1415
1416        frame = binascii.unhexlify(items[4])
1417        msg['frame'] = frame
1418
1419        hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
1420        msg['fc'] = hdr[0]
1421        msg['subtype'] = (hdr[0] >> 4) & 0xf
1422        hdr = hdr[1:]
1423        msg['duration'] = hdr[0]
1424        hdr = hdr[1:]
1425        msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1426        hdr = hdr[6:]
1427        msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1428        hdr = hdr[6:]
1429        msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
1430        hdr = hdr[6:]
1431        msg['seq_ctrl'] = hdr[0]
1432        msg['payload'] = frame[24:]
1433
1434        return msg
1435
1436    def wait_connected(self, timeout=10, error="Connection timed out"):
1437        ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout)
1438        if ev is None:
1439            raise Exception(error)
1440        return ev
1441
1442    def wait_disconnected(self, timeout=None, error="Disconnection timed out"):
1443        if timeout is None:
1444            timeout = 10 if self.hostname is None else 30
1445        ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout)
1446        if ev is None:
1447            raise Exception(error)
1448        return ev
1449
1450    def get_group_ifname(self):
1451        return self.group_ifname if self.group_ifname else self.ifname
1452
1453    def get_config(self):
1454        res = self.request("DUMP")
1455        if res.startswith("FAIL"):
1456            raise Exception("DUMP failed")
1457        lines = res.splitlines()
1458        vals = dict()
1459        for l in lines:
1460            [name, value] = l.split('=', 1)
1461            vals[name] = value
1462        return vals
1463
1464    def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac,
1465                      method="1000", info="", status=None, cpt=None, role=None):
1466        if status is None:
1467            cmd = "P2P_ASP_PROVISION"
1468            params = "info='%s' method=%s" % (info, method)
1469        else:
1470            cmd = "P2P_ASP_PROVISION_RESP"
1471            params = "status=%d" % status
1472
1473        if role is not None:
1474            params += " role=" + role
1475        if cpt is not None:
1476            params += " cpt=" + cpt
1477
1478        if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" %
1479                                           (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)):
1480            raise Exception("%s request failed" % cmd)
1481
1482    def note(self, txt):
1483        self.request("NOTE " + txt)
1484
1485    def save_config(self):
1486        if "OK" not in self.request("SAVE_CONFIG"):
1487            raise Exception("Failed to save configuration file")
1488
1489    def wait_regdom(self, country_ie=False):
1490        for i in range(5):
1491            ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1)
1492            if ev is None:
1493                break
1494            if country_ie:
1495                if "init=COUNTRY_IE" in ev:
1496                    break
1497            else:
1498                break
1499
1500    def dpp_qr_code(self, uri):
1501        res = self.request("DPP_QR_CODE " + uri)
1502        if "FAIL" in res:
1503            raise Exception("Failed to parse QR Code URI")
1504        return int(res)
1505
1506    def dpp_nfc_uri(self, uri):
1507        res = self.request("DPP_NFC_URI " + uri)
1508        if "FAIL" in res:
1509            raise Exception("Failed to parse NFC URI")
1510        return int(res)
1511
1512    def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None,
1513                          curve=None, key=None, supported_curves=None,
1514                          host=None):
1515        cmd = "DPP_BOOTSTRAP_GEN type=" + type
1516        if chan:
1517            cmd += " chan=" + chan
1518        if mac:
1519            if mac is True:
1520                mac = self.own_addr()
1521            cmd += " mac=" + mac.replace(':', '')
1522        if info:
1523            cmd += " info=" + info
1524        if curve:
1525            cmd += " curve=" + curve
1526        if key:
1527            cmd += " key=" + key
1528        if supported_curves:
1529            cmd += " supported_curves=" + supported_curves
1530        if host:
1531            cmd += " host=" + host
1532        res = self.request(cmd)
1533        if "FAIL" in res:
1534            raise Exception("Failed to generate bootstrapping info")
1535        return int(res)
1536
1537    def dpp_bootstrap_set(self, id, conf=None, configurator=None, ssid=None,
1538                          extra=None):
1539        cmd = "DPP_BOOTSTRAP_SET %d" % id
1540        if ssid:
1541            cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
1542        if extra:
1543            cmd += " " + extra
1544        if conf:
1545            cmd += " conf=" + conf
1546        if configurator is not None:
1547            cmd += " configurator=%d" % configurator
1548        if "OK" not in self.request(cmd):
1549            raise Exception("Failed to set bootstrapping parameters")
1550
1551    def dpp_listen(self, freq, netrole=None, qr=None, role=None):
1552        cmd = "DPP_LISTEN " + str(freq)
1553        if netrole:
1554            cmd += " netrole=" + netrole
1555        if qr:
1556            cmd += " qr=" + qr
1557        if role:
1558            cmd += " role=" + role
1559        if "OK" not in self.request(cmd):
1560            raise Exception("Failed to start listen operation")
1561        # Since DPP listen is a radio work, make sure it has started
1562        # by the time we return and continue with the test, since it
1563        # usually will send something this side should receive.
1564        work_started = "dpp-listen@" + self.ifname + ":" + str(freq) + ":1"
1565        for i in range(10):
1566            if work_started in self.request("RADIO_WORK show"):
1567                time.sleep(0.0005)
1568                return
1569            time.sleep(0.01)
1570        raise Exception("Failed to start DPP listen work")
1571
1572    def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None,
1573                      extra=None, own=None, role=None, neg_freq=None,
1574                      ssid=None, passphrase=None, expect_fail=False,
1575                      tcp_addr=None, tcp_port=None, conn_status=False,
1576                      ssid_charset=None, nfc_uri=None, netrole=None,
1577                      csrattrs=None):
1578        cmd = "DPP_AUTH_INIT"
1579        if peer is None:
1580            if nfc_uri:
1581                peer = self.dpp_nfc_uri(nfc_uri)
1582            else:
1583                peer = self.dpp_qr_code(uri)
1584        cmd += " peer=%d" % peer
1585        if own is not None:
1586            cmd += " own=%d" % own
1587        if role:
1588            cmd += " role=" + role
1589        if extra:
1590            cmd += " " + extra
1591        if conf:
1592            cmd += " conf=" + conf
1593        if configurator is not None:
1594            cmd += " configurator=%d" % configurator
1595        if neg_freq:
1596            cmd += " neg_freq=%d" % neg_freq
1597        if ssid:
1598            cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode()
1599        if ssid_charset:
1600            cmd += " ssid_charset=%d" % ssid_charset
1601        if passphrase:
1602            cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode()
1603        if tcp_addr:
1604            cmd += " tcp_addr=" + tcp_addr
1605        if tcp_port:
1606            cmd += " tcp_port=" + tcp_port
1607        if conn_status:
1608            cmd += " conn_status=1"
1609        if netrole:
1610            cmd += " netrole=" + netrole
1611        if csrattrs:
1612            cmd += " csrattrs=" + csrattrs
1613        res = self.request(cmd)
1614        if expect_fail:
1615            if "FAIL" not in res:
1616                raise Exception("DPP authentication started unexpectedly")
1617            return
1618        if "OK" not in res:
1619            raise Exception("Failed to initiate DPP Authentication")
1620        return int(peer)
1621
1622    def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None,
1623                      extra=None, use_id=None, allow_fail=False, ver=None,
1624                      tcp_addr=None, tcp_port=None):
1625        if use_id is None:
1626            id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1627        else:
1628            id1 = use_id
1629        cmd = "own=%d " % id1
1630        if identifier:
1631            cmd += "identifier=%s " % identifier
1632        cmd += "init=1 "
1633        if ver is not None:
1634            cmd += "ver=" + str(ver) + " "
1635        if role:
1636            cmd += "role=%s " % role
1637        if tcp_addr:
1638            cmd += "tcp_addr=" + tcp_addr + " "
1639        if tcp_port:
1640            cmd += "tcp_port=" + tcp_port + " "
1641        if extra:
1642            cmd += extra + " "
1643        cmd += "code=%s" % code
1644        res = self.request("DPP_PKEX_ADD " + cmd)
1645        if allow_fail:
1646            return id1
1647        if "FAIL" in res:
1648            raise Exception("Failed to set PKEX data (initiator)")
1649        return id1
1650
1651    def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None,
1652                      listen_role=None, use_id=None):
1653        if use_id is None:
1654            id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve)
1655        else:
1656            id0 = use_id
1657        cmd = "own=%d " % id0
1658        if identifier:
1659            cmd += "identifier=%s " % identifier
1660        cmd += "code=%s" % code
1661        res = self.request("DPP_PKEX_ADD " + cmd)
1662        if "FAIL" in res:
1663            raise Exception("Failed to set PKEX data (responder)")
1664        self.dpp_listen(freq, role=listen_role)
1665        return id0
1666
1667    def dpp_configurator_add(self, curve=None, key=None,
1668                             net_access_key_curve=None):
1669        cmd = "DPP_CONFIGURATOR_ADD"
1670        if curve:
1671            cmd += " curve=" + curve
1672        if net_access_key_curve:
1673            cmd += " net_access_key_curve=" + net_access_key_curve
1674        if key:
1675            cmd += " key=" + key
1676        res = self.request(cmd)
1677        if "FAIL" in res:
1678            raise Exception("Failed to add configurator")
1679        return int(res)
1680
1681    def dpp_configurator_set(self, conf_id, net_access_key_curve=None):
1682        cmd = "DPP_CONFIGURATOR_SET %d" % conf_id
1683        if net_access_key_curve:
1684            cmd += " net_access_key_curve=" + net_access_key_curve
1685        res = self.request(cmd)
1686        if "FAIL" in res:
1687            raise Exception("Failed to set configurator")
1688
1689    def dpp_configurator_remove(self, conf_id):
1690        res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id)
1691        if "OK" not in res:
1692            raise Exception("DPP_CONFIGURATOR_REMOVE failed")
1693
1694    def get_ptksa(self, bssid, cipher):
1695        res = self.request("PTKSA_CACHE_LIST")
1696        lines = res.splitlines()
1697        for l in lines:
1698            if bssid not in l or cipher not in l:
1699                continue
1700
1701            vals = dict()
1702            [index, addr, cipher, expiration, tk, kdk] = l.split(' ', 5)
1703            vals['index'] = index
1704            vals['addr'] = addr
1705            vals['cipher'] = cipher
1706            vals['expiration'] = expiration
1707            vals['tk'] = tk
1708            vals['kdk'] = kdk
1709            return vals
1710        return None
1711
1712    def wait_sta(self, addr=None, timeout=2, wait_4way_hs=False):
1713        ev = self.wait_group_event(["AP-STA-CONNECT"], timeout=timeout)
1714        if ev is None:
1715            ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout)
1716            if ev is None:
1717                raise Exception("AP did not report STA connection")
1718        if addr and addr not in ev:
1719            raise Exception("Unexpected STA address in connection event: " + ev)
1720        if wait_4way_hs:
1721            ev2 = self.wait_group_event(["EAPOL-4WAY-HS-COMPLETED"],
1722                                        timeout=timeout)
1723            if ev2 is None:
1724                raise Exception("AP did not report 4-way handshake completion")
1725            if addr and addr not in ev2:
1726                raise Exception("Unexpected STA address in 4-way handshake completion event: " + ev2)
1727        return ev
1728
1729    def wait_sta_disconnect(self, addr=None, timeout=2):
1730        ev = self.wait_group_event(["AP-STA-DISCONNECT"], timeout=timeout)
1731        if ev is None:
1732            ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout)
1733            if ev is None:
1734                raise Exception("AP did not report STA disconnection")
1735        if addr and addr not in ev:
1736            raise Exception("Unexpected STA address in disconnection event: " + ev)
1737        return ev
1738