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