1 # Python class for controlling wpa_supplicant
2 # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi>
4 # This software may be distributed under the terms of the BSD license.
5 # See README for more details.
16 logger = logging.getLogger()
17 wpas_ctrl = '/var/run/wpa_supplicant'
20 def __init__(self, ifname=None, global_iface=None):
21 self.group_ifname = None
24 self.set_ifname(ifname)
28 self.global_iface = global_iface
30 self.global_ctrl = wpaspy.Ctrl(global_iface)
31 self.global_mon = wpaspy.Ctrl(global_iface)
32 self.global_mon.attach()
34 def set_ifname(self, ifname):
36 self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
37 self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname))
40 def remove_ifname(self):
47 def interface_add(self, ifname, config="", driver="nl80211", drv_params=None):
49 groups = subprocess.check_output(["id"])
50 group = "admin" if "(admin)" in groups else "adm"
53 cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group
55 cmd = cmd + '\t' + drv_params
56 if "FAIL" in self.global_request(cmd):
57 raise Exception("Failed to add a dynamic wpa_supplicant interface")
58 self.set_ifname(ifname)
60 def interface_remove(self, ifname):
62 self.global_request("INTERFACE_REMOVE " + ifname)
64 def request(self, cmd):
65 logger.debug(self.ifname + ": CTRL: " + cmd)
66 return self.ctrl.request(cmd)
68 def global_request(self, cmd):
69 if self.global_iface is None:
72 ifname = self.ifname or self.global_iface
73 logger.debug(ifname + ": CTRL(global): " + cmd)
74 return self.global_ctrl.request(cmd)
76 def group_request(self, cmd):
77 if self.group_ifname and self.group_ifname != self.ifname:
78 logger.debug(self.group_ifname + ": CTRL: " + cmd)
79 gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname))
80 return gctrl.request(cmd)
81 return self.request(cmd)
84 return "PONG" in self.request("PING")
86 def global_ping(self):
87 return "PONG" in self.global_request("PING")
91 res = self.request("FLUSH")
93 logger.info("FLUSH to " + self.ifname + " failed: " + res)
94 self.request("WPS_ER_STOP")
95 self.request("SET pmf 0")
96 self.request("SET external_sim 0")
97 self.request("SET hessid 00:00:00:00:00:00")
98 self.request("SET access_network_type 15")
99 self.request("SET p2p_add_cli_chan 0")
100 self.request("SET p2p_no_go_freq ")
101 self.request("SET p2p_pref_chan ")
102 self.request("SET p2p_no_group_iface 1")
103 self.request("SET p2p_go_intent 7")
106 self.gctrl_mon.detach()
109 self.gctrl_mon = None
110 self.group_ifname = None
115 state = self.get_driver_status_field("scan_state")
116 if "SCAN_STARTED" in state or "SCAN_REQUESTED" in state:
117 logger.info(self.ifname + ": Waiting for scan operation to complete before continuing")
123 logger.error(self.ifname + ": Driver scan state did not clear")
124 print "Trying to clear cfg80211/mac80211 scan state"
126 cmd = ["sudo", "ifconfig", self.ifname, "down"]
128 except subprocess.CalledProcessError, e:
129 logger.info("ifconfig failed: " + str(e.returncode))
130 logger.info(e.output)
132 cmd = ["sudo", "ifconfig", self.ifname, "up"]
134 except subprocess.CalledProcessError, e:
135 logger.info("ifconfig failed: " + str(e.returncode))
136 logger.info(e.output)
138 # The ongoing scan could have discovered BSSes or P2P peers
139 logger.info("Run FLUSH again since scan was in progress")
140 self.request("FLUSH")
144 logger.info("No PING response from " + self.ifname + " after reset")
146 def add_network(self):
147 id = self.request("ADD_NETWORK")
149 raise Exception("ADD_NETWORK failed")
152 def remove_network(self, id):
153 id = self.request("REMOVE_NETWORK " + str(id))
155 raise Exception("REMOVE_NETWORK failed")
158 def get_network(self, id, field):
159 res = self.request("GET_NETWORK " + str(id) + " " + field)
164 def set_network(self, id, field, value):
165 res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value)
167 raise Exception("SET_NETWORK failed")
170 def set_network_quoted(self, id, field, value):
171 res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"')
173 raise Exception("SET_NETWORK failed")
176 def list_networks(self):
177 res = self.request("LIST_NETWORKS")
178 lines = res.splitlines()
181 if "network id" in l:
183 [id,ssid,bssid,flags] = l.split('\t')
186 network['ssid'] = ssid
187 network['bssid'] = bssid
188 network['flags'] = flags
189 networks.append(network)
192 def hs20_enable(self, auto_interworking=False):
193 self.request("SET interworking 1")
194 self.request("SET hs20 1")
195 if auto_interworking:
196 self.request("SET auto_interworking 1")
198 self.request("SET auto_interworking 0")
201 id = self.request("ADD_CRED")
203 raise Exception("ADD_CRED failed")
206 def remove_cred(self, id):
207 id = self.request("REMOVE_CRED " + str(id))
209 raise Exception("REMOVE_CRED failed")
212 def set_cred(self, id, field, value):
213 res = self.request("SET_CRED " + str(id) + " " + field + " " + value)
215 raise Exception("SET_CRED failed")
218 def set_cred_quoted(self, id, field, value):
219 res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"')
221 raise Exception("SET_CRED failed")
224 def get_cred(self, id, field):
225 return self.request("GET_CRED " + str(id) + " " + field)
227 def add_cred_values(self, params):
230 quoted = [ "realm", "username", "password", "domain", "imsi",
231 "excluded_ssid", "milenage", "ca_cert", "client_cert",
232 "private_key", "domain_suffix_match", "provisioning_sp",
233 "roaming_partner", "phase1", "phase2" ]
236 self.set_cred_quoted(id, field, params[field])
238 not_quoted = [ "eap", "roaming_consortium", "priority",
239 "required_roaming_consortium", "sp_priority",
240 "max_bss_load", "update_identifier", "req_conn_capab",
241 "min_dl_bandwidth_home", "min_ul_bandwidth_home",
242 "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming" ]
243 for field in not_quoted:
245 self.set_cred(id, field, params[field])
249 def select_network(self, id, freq=None):
251 extra = " freq=" + freq
254 id = self.request("SELECT_NETWORK " + str(id) + extra)
256 raise Exception("SELECT_NETWORK failed")
259 def connect_network(self, id, timeout=10):
261 self.select_network(id)
262 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout)
264 raise Exception("Association with the AP timed out")
267 def get_status(self, extra=None):
272 res = self.request("STATUS" + extra)
273 lines = res.splitlines()
277 [name,value] = l.split('=', 1)
279 except ValueError, e:
280 logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l)
283 def get_status_field(self, field, extra=None):
284 vals = self.get_status(extra)
289 def get_group_status(self, extra=None):
294 res = self.group_request("STATUS" + extra)
295 lines = res.splitlines()
298 [name,value] = l.split('=', 1)
302 def get_group_status_field(self, field, extra=None):
303 vals = self.get_group_status(extra)
308 def get_driver_status(self):
309 res = self.request("STATUS-DRIVER")
310 lines = res.splitlines()
313 [name,value] = l.split('=', 1)
317 def get_driver_status_field(self, field):
318 vals = self.get_driver_status()
324 mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent'))
325 return 1 if mcc < 2 else mcc
328 res = self.request("MIB")
329 lines = res.splitlines()
333 [name,value] = l.split('=', 1)
335 except ValueError, e:
336 logger.info(self.ifname + ": Ignore unexpected MIB line: " + l)
339 def p2p_dev_addr(self):
340 return self.get_status_field("p2p_device_address")
342 def p2p_interface_addr(self):
343 return self.get_group_status_field("address")
347 res = self.p2p_interface_addr()
349 res = self.p2p_dev_addr()
352 def p2p_listen(self):
353 return self.global_request("P2P_LISTEN")
355 def p2p_find(self, social=False, progressive=False, dev_id=None, dev_type=None):
358 cmd = cmd + " type=social"
360 cmd = cmd + " type=progressive"
362 cmd = cmd + " dev_id=" + dev_id
364 cmd = cmd + " dev_type=" + dev_type
365 return self.global_request(cmd)
367 def p2p_stop_find(self):
368 return self.global_request("P2P_STOP_FIND")
370 def wps_read_pin(self):
371 self.pin = self.request("WPS_PIN get").rstrip("\n")
372 if "FAIL" in self.pin:
373 raise Exception("Could not generate PIN")
376 def peer_known(self, peer, full=True):
377 res = self.global_request("P2P_PEER " + peer)
378 if peer.lower() not in res.lower():
382 return "[PROBE_REQ_ONLY]" not in res
384 def discover_peer(self, peer, full=True, timeout=15, social=True, force_find=False):
385 logger.info(self.ifname + ": Trying to discover peer " + peer)
386 if not force_find and self.peer_known(peer, full):
388 self.p2p_find(social)
390 while count < timeout:
393 if self.peer_known(peer, full):
397 def get_peer(self, peer):
398 res = self.global_request("P2P_PEER " + peer)
399 if peer.lower() not in res.lower():
400 raise Exception("Peer information not available")
401 lines = res.splitlines()
405 [name,value] = l.split('=', 1)
409 def group_form_result(self, ev, expect_failure=False, go_neg_res=None):
411 if "P2P-GROUP-STARTED" in ev:
412 raise Exception("Group formation succeeded when expecting failure")
413 exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)'
414 s = re.split(exp, ev)
418 res['result'] = 'go-neg-failed'
419 res['status'] = int(s[2])
422 if "P2P-GROUP-STARTED" not in ev:
423 raise Exception("No P2P-GROUP-STARTED event seen")
425 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*) ip_addr=([0-9.]*) ip_mask=([0-9.]*) go_ip_addr=([0-9.]*)'
426 s = re.split(exp, ev)
428 exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)'
429 s = re.split(exp, ev)
431 raise Exception("Could not parse P2P-GROUP-STARTED")
433 res['result'] = 'success'
435 self.group_ifname = s[2]
437 self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname))
438 self.gctrl_mon.attach()
440 logger.debug("Could not open monitor socket for group interface")
441 self.gctrl_mon = None
445 if "[PERSISTENT]" in ev:
446 res['persistent'] = True
448 res['persistent'] = False
449 p = re.match(r'psk=([0-9a-f]*)', s[6])
451 res['psk'] = p.group(1)
452 p = re.match(r'passphrase="(.*)"', s[6])
454 res['passphrase'] = p.group(1)
455 res['go_dev_addr'] = s[7]
457 if len(s) > 8 and len(s[8]) > 0:
458 res['ip_addr'] = s[8]
460 res['ip_mask'] = s[9]
462 res['go_ip_addr'] = s[10]
465 exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)'
466 s = re.split(exp, go_neg_res)
468 raise Exception("Could not parse P2P-GO-NEG-SUCCESS")
469 res['go_neg_role'] = s[2]
470 res['go_neg_freq'] = s[3]
474 def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, persistent=False, freq=None):
475 if not self.discover_peer(peer):
476 raise Exception("Peer " + peer + " not found")
478 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth"
480 cmd = cmd + ' go_intent=' + str(go_intent)
482 cmd = cmd + ' freq=' + str(freq)
484 cmd = cmd + " persistent"
485 if "OK" in self.global_request(cmd):
487 raise Exception("P2P_CONNECT (auth) failed")
489 def p2p_go_neg_auth_result(self, timeout=1, expect_failure=False):
491 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
492 "P2P-GO-NEG-FAILURE"], timeout);
496 raise Exception("Group formation timed out")
497 if "P2P-GO-NEG-SUCCESS" in ev:
499 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout);
503 raise Exception("Group formation timed out")
505 return self.group_form_result(ev, expect_failure, go_neg_res)
507 def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None, expect_failure=False, persistent=False, persistent_id=None, freq=None, provdisc=False, wait_group=True):
508 if not self.discover_peer(peer):
509 raise Exception("Peer " + peer + " not found")
512 cmd = "P2P_CONNECT " + peer + " " + pin + " " + method
514 cmd = "P2P_CONNECT " + peer + " " + method
516 cmd = cmd + ' go_intent=' + str(go_intent)
518 cmd = cmd + ' freq=' + str(freq)
520 cmd = cmd + " persistent"
522 cmd = cmd + " persistent=" + persistent_id
524 cmd = cmd + " provdisc"
525 if "OK" in self.global_request(cmd):
530 ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS",
531 "P2P-GO-NEG-FAILURE"], timeout)
535 raise Exception("Group formation timed out")
536 if "P2P-GO-NEG-SUCCESS" in ev:
540 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
544 raise Exception("Group formation timed out")
546 return self.group_form_result(ev, expect_failure, go_neg_res)
547 raise Exception("P2P_CONNECT failed")
549 def wait_event(self, events, timeout=10):
550 start = os.times()[4]
552 while self.mon.pending():
554 logger.debug(self.ifname + ": " + ev)
559 remaining = start + timeout - now
562 if not self.mon.pending(timeout=remaining):
566 def wait_global_event(self, events, timeout):
567 if self.global_iface is None:
568 self.wait_event(events, timeout)
570 start = os.times()[4]
572 while self.global_mon.pending():
573 ev = self.global_mon.recv()
574 logger.debug(self.ifname + "(global): " + ev)
579 remaining = start + timeout - now
582 if not self.global_mon.pending(timeout=remaining):
586 def wait_group_event(self, events, timeout=10):
587 if self.group_ifname and self.group_ifname != self.ifname:
588 if self.gctrl_mon is None:
590 start = os.times()[4]
592 while self.gctrl_mon.pending():
593 ev = self.gctrl_mon.recv()
594 logger.debug(self.group_ifname + ": " + ev)
599 remaining = start + timeout - now
602 if not self.gctrl_mon.pending(timeout=remaining):
606 return self.wait_event(events, timeout)
608 def wait_go_ending_session(self):
611 self.gctrl_mon.detach()
614 self.gctrl_mon = None
615 ev = self.wait_event(["P2P-GROUP-REMOVED"], timeout=3)
617 raise Exception("Group removal event timed out")
618 if "reason=GO_ENDING_SESSION" not in ev:
619 raise Exception("Unexpected group removal reason")
621 def dump_monitor(self):
622 while self.mon.pending():
624 logger.debug(self.ifname + ": " + ev)
625 while self.global_mon.pending():
626 ev = self.global_mon.recv()
627 logger.debug(self.ifname + "(global): " + ev)
629 def remove_group(self, ifname=None):
632 self.gctrl_mon.detach()
635 self.gctrl_mon = None
637 ifname = self.group_ifname if self.group_ifname else self.ifname
638 if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname):
639 raise Exception("Group could not be removed")
640 self.group_ifname = None
642 def p2p_start_go(self, persistent=None, freq=None):
644 cmd = "P2P_GROUP_ADD"
645 if persistent is None:
647 elif persistent is True:
648 cmd = cmd + " persistent"
650 cmd = cmd + " persistent=" + str(persistent)
652 cmd = cmd + " freq=" + str(freq)
653 if "OK" in self.global_request(cmd):
654 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5)
656 raise Exception("GO start up timed out")
658 return self.group_form_result(ev)
659 raise Exception("P2P_GROUP_ADD failed")
661 def p2p_go_authorize_client(self, pin):
662 cmd = "WPS_PIN any " + pin
663 if "FAIL" in self.group_request(cmd):
664 raise Exception("Failed to authorize client connection on GO")
667 def p2p_go_authorize_client_pbc(self):
669 if "FAIL" in self.group_request(cmd):
670 raise Exception("Failed to authorize client connection on GO")
673 def p2p_connect_group(self, go_addr, pin, timeout=0, social=False):
675 if not self.discover_peer(go_addr, social=social):
676 if social or not self.discover_peer(go_addr, social=social):
677 raise Exception("GO " + go_addr + " not found")
679 cmd = "P2P_CONNECT " + go_addr + " " + pin + " join"
680 if "OK" in self.global_request(cmd):
684 ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout)
686 raise Exception("Joining the group timed out")
688 return self.group_form_result(ev)
689 raise Exception("P2P_CONNECT(join) failed")
691 def tdls_setup(self, peer):
692 cmd = "TDLS_SETUP " + peer
693 if "FAIL" in self.group_request(cmd):
694 raise Exception("Failed to request TDLS setup")
697 def tdls_teardown(self, peer):
698 cmd = "TDLS_TEARDOWN " + peer
699 if "FAIL" in self.group_request(cmd):
700 raise Exception("Failed to request TDLS teardown")
703 def connect(self, ssid=None, ssid2=None, **kwargs):
704 logger.info("Connect STA " + self.ifname + " to AP")
705 id = self.add_network()
707 self.set_network_quoted(id, "ssid", ssid)
709 self.set_network(id, "ssid", ssid2)
711 quoted = [ "psk", "identity", "anonymous_identity", "password",
712 "ca_cert", "client_cert", "private_key",
713 "private_key_passwd", "ca_cert2", "client_cert2",
714 "private_key2", "phase1", "phase2", "domain_suffix_match",
715 "altsubject_match", "subject_match", "pac_file", "dh_file",
716 "bgscan", "ht_mcs", "id_str", "openssl_ciphers" ]
718 if field in kwargs and kwargs[field]:
719 self.set_network_quoted(id, field, kwargs[field])
721 not_quoted = [ "proto", "key_mgmt", "ieee80211w", "pairwise",
722 "group", "wep_key0", "scan_freq", "eap",
723 "eapol_flags", "fragment_size", "scan_ssid", "auth_alg",
724 "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid",
725 "disable_max_amsdu", "ampdu_factor", "ampdu_density",
726 "disable_ht40", "disable_sgi", "disable_ldpc",
727 "ht40_intolerant", "update_identifier", "mac_addr" ]
728 for field in not_quoted:
729 if field in kwargs and kwargs[field]:
730 self.set_network(id, field, kwargs[field])
732 if "raw_psk" in kwargs and kwargs['raw_psk']:
733 self.set_network(id, "psk", kwargs['raw_psk'])
734 if "password_hex" in kwargs and kwargs['password_hex']:
735 self.set_network(id, "password", kwargs['password_hex'])
736 if "peerkey" in kwargs and kwargs['peerkey']:
737 self.set_network(id, "peerkey", "1")
738 if "okc" in kwargs and kwargs['okc']:
739 self.set_network(id, "proactive_key_caching", "1")
740 if "ocsp" in kwargs and kwargs['ocsp']:
741 self.set_network(id, "ocsp", str(kwargs['ocsp']))
742 if "only_add_network" in kwargs and kwargs['only_add_network']:
744 if "wait_connect" not in kwargs or kwargs['wait_connect']:
746 self.connect_network(id, timeout=20)
748 self.connect_network(id)
751 self.select_network(id)
754 def scan(self, type=None, freq=None, no_wait=False, only_new=False):
756 cmd = "SCAN TYPE=" + type
760 cmd = cmd + " freq=" + freq
765 if not "OK" in self.request(cmd):
766 raise Exception("Failed to trigger scan")
769 ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS"], 15)
771 raise Exception("Scan timed out")
773 def scan_for_bss(self, bssid, freq=None, force_scan=False):
774 if not force_scan and self.get_bss(bssid) is not None:
776 for i in range(0, 10):
777 self.scan(freq=freq, type="ONLY")
778 if self.get_bss(bssid) is not None:
780 raise Exception("Could not find BSS " + bssid + " in scan")
782 def roam(self, bssid, fail_test=False):
784 if "OK" not in self.request("ROAM " + bssid):
785 raise Exception("ROAM failed")
787 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
789 raise Exception("Unexpected connection")
792 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10)
794 raise Exception("Roaming with the AP timed out")
797 def roam_over_ds(self, bssid, fail_test=False):
799 if "OK" not in self.request("FT_DS " + bssid):
800 raise Exception("FT_DS failed")
802 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1)
804 raise Exception("Unexpected connection")
807 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10)
809 raise Exception("Roaming with the AP timed out")
812 def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None,
813 new_passphrase=None, no_wait=False):
816 self.request("WPS_REG " + bssid + " " + pin + " " +
817 new_ssid.encode("hex") + " " + key_mgmt + " " +
818 cipher + " " + new_passphrase.encode("hex"))
821 ev = self.wait_event(["WPS-SUCCESS"], timeout=15)
823 self.request("WPS_REG " + bssid + " " + pin)
826 ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15)
828 raise Exception("WPS cred timed out")
829 ev = self.wait_event(["WPS-FAIL"], timeout=15)
831 raise Exception("WPS timed out")
832 ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=15)
834 raise Exception("Association with the AP timed out")
837 self.request("RELOG")
839 def wait_completed(self, timeout=10):
840 for i in range(0, timeout * 2):
841 if self.get_status_field("wpa_state") == "COMPLETED":
844 raise Exception("Timeout while waiting for COMPLETED state")
846 def get_capability(self, field):
847 res = self.request("GET_CAPABILITY " + field)
850 return res.split(' ')
852 def get_bss(self, bssid):
853 res = self.request("BSS " + bssid)
856 lines = res.splitlines()
859 [name,value] = l.split('=', 1)
865 def get_pmksa(self, bssid):
866 res = self.request("PMKSA")
867 lines = res.splitlines()
872 [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
873 vals['index'] = index
874 vals['pmkid'] = pmkid
875 vals['expiration'] = expiration
876 vals['opportunistic'] = opportunistic
880 def get_sta(self, addr, info=None, next=False):
881 cmd = "STA-NEXT " if next else "STA "
883 res = self.request("STA-FIRST")
885 res = self.request(cmd + addr + " " + info)
887 res = self.request(cmd + addr)
888 lines = res.splitlines()
896 [name,value] = l.split('=', 1)
900 def mgmt_rx(self, timeout=5):
901 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
905 items = ev.split(' ')
906 field,val = items[1].split('=')
908 raise Exception("Unexpected MGMT-RX event format: " + ev)
910 frame = binascii.unhexlify(items[4])
913 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
915 msg['subtype'] = (hdr[0] >> 4) & 0xf
917 msg['duration'] = hdr[0]
919 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
921 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
923 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
925 msg['seq_ctrl'] = hdr[0]
926 msg['payload'] = frame[24:]