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 
7 import os
8 import time
9 import logging
10 import binascii
11 import re
12 import struct
13 import wpaspy
14 import remotehost
15 import subprocess
16 from remotectrl import RemoteCtrl
17 
18 logger = logging.getLogger()
19 wpas_ctrl = '/var/run/wpa_supplicant'
20 
21 class 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