]>
Commit | Line | Data |
---|---|---|
1ae73b03 | 1 | # Python class for controlling wpa_supplicant |
d7a99700 | 2 | # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi> |
1ae73b03 JM |
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 | |
daf3806d | 10 | import binascii |
c68f9a61 | 11 | import re |
daf3806d | 12 | import struct |
6ca3a98b | 13 | import subprocess |
1ae73b03 JM |
14 | import wpaspy |
15 | ||
c9aa4308 | 16 | logger = logging.getLogger() |
1ae73b03 JM |
17 | wpas_ctrl = '/var/run/wpa_supplicant' |
18 | ||
19 | class WpaSupplicant: | |
9489637b | 20 | def __init__(self, ifname=None, global_iface=None): |
f3f8ee88 | 21 | self.group_ifname = None |
4823566c | 22 | self.gctrl_mon = None |
9489637b JM |
23 | if ifname: |
24 | self.set_ifname(ifname) | |
25 | else: | |
26 | self.ifname = None | |
1ae73b03 | 27 | |
0fa28afe JM |
28 | self.global_iface = global_iface |
29 | if global_iface: | |
30 | self.global_ctrl = wpaspy.Ctrl(global_iface) | |
31 | self.global_mon = wpaspy.Ctrl(global_iface) | |
32 | self.global_mon.attach() | |
c8836a4f JM |
33 | else: |
34 | self.global_mon = None | |
0fa28afe | 35 | |
a66d2248 JM |
36 | def close_ctrl(self): |
37 | if self.global_mon: | |
38 | self.global_mon.detach() | |
39 | self.global_mon = None | |
40 | self.global_ctrl = None | |
41 | self.remove_ifname() | |
42 | ||
9489637b JM |
43 | def set_ifname(self, ifname): |
44 | self.ifname = ifname | |
45 | self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
46 | self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
47 | self.mon.attach() | |
48 | ||
49 | def remove_ifname(self): | |
50 | if self.ifname: | |
51 | self.mon.detach() | |
52 | self.mon = None | |
53 | self.ctrl = None | |
54 | self.ifname = None | |
55 | ||
a1512a0c | 56 | def interface_add(self, ifname, config="", driver="nl80211", |
138bf118 JM |
57 | drv_params=None, br_ifname=None, create=False, |
58 | set_ifname=True, all_params=False): | |
9489637b JM |
59 | try: |
60 | groups = subprocess.check_output(["id"]) | |
61 | group = "admin" if "(admin)" in groups else "adm" | |
62 | except Exception, e: | |
63 | group = "admin" | |
117caa4a | 64 | cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group |
6e917c3e JM |
65 | if drv_params: |
66 | cmd = cmd + '\t' + drv_params | |
a1512a0c JM |
67 | if br_ifname: |
68 | if not drv_params: | |
69 | cmd += '\t' | |
70 | cmd += '\t' + br_ifname | |
25f2cb61 JM |
71 | if create: |
72 | if not br_ifname: | |
73 | cmd += '\t' | |
74 | if not drv_params: | |
75 | cmd += '\t' | |
76 | cmd += '\tcreate' | |
138bf118 JM |
77 | if all_params and not create: |
78 | if not br_ifname: | |
79 | cmd += '\t' | |
80 | if not drv_params: | |
81 | cmd += '\t' | |
82 | cmd += '\t' | |
9489637b JM |
83 | if "FAIL" in self.global_request(cmd): |
84 | raise Exception("Failed to add a dynamic wpa_supplicant interface") | |
138bf118 | 85 | if not create and set_ifname: |
25f2cb61 | 86 | self.set_ifname(ifname) |
9489637b JM |
87 | |
88 | def interface_remove(self, ifname): | |
89 | self.remove_ifname() | |
90 | self.global_request("INTERFACE_REMOVE " + ifname) | |
91 | ||
874057da | 92 | def request(self, cmd, timeout=10): |
1ae73b03 | 93 | logger.debug(self.ifname + ": CTRL: " + cmd) |
874057da | 94 | return self.ctrl.request(cmd, timeout=timeout) |
1ae73b03 | 95 | |
0fa28afe JM |
96 | def global_request(self, cmd): |
97 | if self.global_iface is None: | |
98 | self.request(cmd) | |
99 | else: | |
9489637b | 100 | ifname = self.ifname or self.global_iface |
521b7e79 | 101 | logger.debug(ifname + ": CTRL(global): " + cmd) |
0fa28afe JM |
102 | return self.global_ctrl.request(cmd) |
103 | ||
f3f8ee88 JM |
104 | def group_request(self, cmd): |
105 | if self.group_ifname and self.group_ifname != self.ifname: | |
106 | logger.debug(self.group_ifname + ": CTRL: " + cmd) | |
107 | gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname)) | |
108 | return gctrl.request(cmd) | |
109 | return self.request(cmd) | |
110 | ||
1ae73b03 JM |
111 | def ping(self): |
112 | return "PONG" in self.request("PING") | |
113 | ||
521b7e79 JM |
114 | def global_ping(self): |
115 | return "PONG" in self.global_request("PING") | |
116 | ||
1ae73b03 | 117 | def reset(self): |
521b7e79 | 118 | self.dump_monitor() |
ea0e92ee JM |
119 | res = self.request("FLUSH") |
120 | if not "OK" in res: | |
121 | logger.info("FLUSH to " + self.ifname + " failed: " + res) | |
09f60224 | 122 | self.global_request("REMOVE_NETWORK all") |
a2168cf4 IP |
123 | self.global_request("SET p2p_add_cli_chan 0") |
124 | self.global_request("SET p2p_no_go_freq ") | |
125 | self.global_request("SET p2p_pref_chan ") | |
126 | self.global_request("SET p2p_no_group_iface 1") | |
127 | self.global_request("SET p2p_go_intent 7") | |
128 | self.global_request("P2P_FLUSH") | |
eec0cc8d | 129 | self.request("SET ignore_old_scan_res 0") |
4823566c JM |
130 | if self.gctrl_mon: |
131 | try: | |
132 | self.gctrl_mon.detach() | |
133 | except: | |
134 | pass | |
135 | self.gctrl_mon = None | |
f3f8ee88 | 136 | self.group_ifname = None |
6edaee9c | 137 | self.dump_monitor() |
6ca3a98b JM |
138 | |
139 | iter = 0 | |
140 | while iter < 60: | |
141 | state = self.get_driver_status_field("scan_state") | |
142 | if "SCAN_STARTED" in state or "SCAN_REQUESTED" in state: | |
143 | logger.info(self.ifname + ": Waiting for scan operation to complete before continuing") | |
144 | time.sleep(1) | |
145 | else: | |
146 | break | |
147 | iter = iter + 1 | |
148 | if iter == 60: | |
149 | logger.error(self.ifname + ": Driver scan state did not clear") | |
150 | print "Trying to clear cfg80211/mac80211 scan state" | |
151 | try: | |
c4668009 | 152 | cmd = ["ifconfig", self.ifname, "down"] |
6ca3a98b JM |
153 | subprocess.call(cmd) |
154 | except subprocess.CalledProcessError, e: | |
155 | logger.info("ifconfig failed: " + str(e.returncode)) | |
156 | logger.info(e.output) | |
157 | try: | |
c4668009 | 158 | cmd = ["ifconfig", self.ifname, "up"] |
6ca3a98b JM |
159 | subprocess.call(cmd) |
160 | except subprocess.CalledProcessError, e: | |
161 | logger.info("ifconfig failed: " + str(e.returncode)) | |
162 | logger.info(e.output) | |
c57c1ed6 JM |
163 | if iter > 0: |
164 | # The ongoing scan could have discovered BSSes or P2P peers | |
165 | logger.info("Run FLUSH again since scan was in progress") | |
166 | self.request("FLUSH") | |
b21df6e7 | 167 | self.dump_monitor() |
6ca3a98b | 168 | |
ea0e92ee JM |
169 | if not self.ping(): |
170 | logger.info("No PING response from " + self.ifname + " after reset") | |
1ae73b03 | 171 | |
07a2e61b JM |
172 | def add_network(self): |
173 | id = self.request("ADD_NETWORK") | |
174 | if "FAIL" in id: | |
175 | raise Exception("ADD_NETWORK failed") | |
176 | return int(id) | |
177 | ||
178 | def remove_network(self, id): | |
179 | id = self.request("REMOVE_NETWORK " + str(id)) | |
180 | if "FAIL" in id: | |
181 | raise Exception("REMOVE_NETWORK failed") | |
182 | return None | |
183 | ||
ca5b81a5 JM |
184 | def get_network(self, id, field): |
185 | res = self.request("GET_NETWORK " + str(id) + " " + field) | |
186 | if res == "FAIL\n": | |
187 | return None | |
188 | return res | |
189 | ||
07a2e61b JM |
190 | def set_network(self, id, field, value): |
191 | res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value) | |
192 | if "FAIL" in res: | |
193 | raise Exception("SET_NETWORK failed") | |
194 | return None | |
195 | ||
196 | def set_network_quoted(self, id, field, value): | |
197 | res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') | |
198 | if "FAIL" in res: | |
199 | raise Exception("SET_NETWORK failed") | |
200 | return None | |
201 | ||
8e751cfa BR |
202 | def list_networks(self, p2p=False): |
203 | if p2p: | |
204 | res = self.global_request("LIST_NETWORKS") | |
205 | else: | |
206 | res = self.request("LIST_NETWORKS") | |
6a0b4002 JM |
207 | lines = res.splitlines() |
208 | networks = [] | |
209 | for l in lines: | |
210 | if "network id" in l: | |
211 | continue | |
212 | [id,ssid,bssid,flags] = l.split('\t') | |
213 | network = {} | |
214 | network['id'] = id | |
215 | network['ssid'] = ssid | |
216 | network['bssid'] = bssid | |
217 | network['flags'] = flags | |
218 | networks.append(network) | |
219 | return networks | |
220 | ||
543f9f7e | 221 | def hs20_enable(self, auto_interworking=False): |
bbe86767 JM |
222 | self.request("SET interworking 1") |
223 | self.request("SET hs20 1") | |
543f9f7e JM |
224 | if auto_interworking: |
225 | self.request("SET auto_interworking 1") | |
226 | else: | |
227 | self.request("SET auto_interworking 0") | |
bbe86767 | 228 | |
22653762 JM |
229 | def interworking_add_network(self, bssid): |
230 | id = self.request("INTERWORKING_ADD_NETWORK " + bssid) | |
231 | if "FAIL" in id or "OK" in id: | |
232 | raise Exception("INTERWORKING_ADD_NETWORK failed") | |
233 | return int(id) | |
234 | ||
93a06242 JM |
235 | def add_cred(self): |
236 | id = self.request("ADD_CRED") | |
237 | if "FAIL" in id: | |
238 | raise Exception("ADD_CRED failed") | |
239 | return int(id) | |
240 | ||
241 | def remove_cred(self, id): | |
242 | id = self.request("REMOVE_CRED " + str(id)) | |
243 | if "FAIL" in id: | |
244 | raise Exception("REMOVE_CRED failed") | |
245 | return None | |
246 | ||
247 | def set_cred(self, id, field, value): | |
248 | res = self.request("SET_CRED " + str(id) + " " + field + " " + value) | |
249 | if "FAIL" in res: | |
250 | raise Exception("SET_CRED failed") | |
251 | return None | |
252 | ||
253 | def set_cred_quoted(self, id, field, value): | |
254 | res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"') | |
255 | if "FAIL" in res: | |
256 | raise Exception("SET_CRED failed") | |
257 | return None | |
258 | ||
aa45859e JM |
259 | def get_cred(self, id, field): |
260 | return self.request("GET_CRED " + str(id) + " " + field) | |
261 | ||
2232edf8 | 262 | def add_cred_values(self, params): |
bbe86767 | 263 | id = self.add_cred() |
2232edf8 | 264 | |
d355372c | 265 | quoted = [ "realm", "username", "password", "domain", "imsi", |
dcd68168 | 266 | "excluded_ssid", "milenage", "ca_cert", "client_cert", |
2253ea44 | 267 | "private_key", "domain_suffix_match", "provisioning_sp", |
e2afdef2 | 268 | "roaming_partner", "phase1", "phase2" ] |
2232edf8 JM |
269 | for field in quoted: |
270 | if field in params: | |
271 | self.set_cred_quoted(id, field, params[field]) | |
272 | ||
a96066a5 | 273 | not_quoted = [ "eap", "roaming_consortium", "priority", |
19839f8e | 274 | "required_roaming_consortium", "sp_priority", |
9714fbcd JM |
275 | "max_bss_load", "update_identifier", "req_conn_capab", |
276 | "min_dl_bandwidth_home", "min_ul_bandwidth_home", | |
277 | "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming" ] | |
2232edf8 JM |
278 | for field in not_quoted: |
279 | if field in params: | |
280 | self.set_cred(id, field, params[field]) | |
281 | ||
bbe86767 JM |
282 | return id; |
283 | ||
ca4fd182 JM |
284 | def select_network(self, id, freq=None): |
285 | if freq: | |
22653762 | 286 | extra = " freq=" + str(freq) |
ca4fd182 JM |
287 | else: |
288 | extra = "" | |
289 | id = self.request("SELECT_NETWORK " + str(id) + extra) | |
81266da7 JM |
290 | if "FAIL" in id: |
291 | raise Exception("SELECT_NETWORK failed") | |
292 | return None | |
293 | ||
68157c06 JL |
294 | def mesh_group_add(self, id): |
295 | id = self.request("MESH_GROUP_ADD " + str(id)) | |
296 | if "FAIL" in id: | |
297 | raise Exception("MESH_GROUP_ADD failed") | |
298 | return None | |
299 | ||
300 | def mesh_group_remove(self): | |
301 | id = self.request("MESH_GROUP_REMOVE " + str(self.ifname)) | |
302 | if "FAIL" in id: | |
303 | raise Exception("MESH_GROUP_REMOVE failed") | |
304 | return None | |
305 | ||
7559ad7a | 306 | def connect_network(self, id, timeout=10): |
81266da7 JM |
307 | self.dump_monitor() |
308 | self.select_network(id) | |
5f35a5e2 | 309 | self.wait_connected(timeout=timeout) |
81266da7 JM |
310 | self.dump_monitor() |
311 | ||
f44c45ac JM |
312 | def get_status(self, extra=None): |
313 | if extra: | |
314 | extra = "-" + extra | |
315 | else: | |
316 | extra = "" | |
317 | res = self.request("STATUS" + extra) | |
1ae73b03 | 318 | lines = res.splitlines() |
302b7a1b | 319 | vals = dict() |
1ae73b03 | 320 | for l in lines: |
e01929c6 JM |
321 | try: |
322 | [name,value] = l.split('=', 1) | |
323 | vals[name] = value | |
324 | except ValueError, e: | |
325 | logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) | |
302b7a1b JM |
326 | return vals |
327 | ||
f44c45ac JM |
328 | def get_status_field(self, field, extra=None): |
329 | vals = self.get_status(extra) | |
302b7a1b JM |
330 | if field in vals: |
331 | return vals[field] | |
1ae73b03 JM |
332 | return None |
333 | ||
f44c45ac JM |
334 | def get_group_status(self, extra=None): |
335 | if extra: | |
336 | extra = "-" + extra | |
337 | else: | |
338 | extra = "" | |
339 | res = self.group_request("STATUS" + extra) | |
7cb08cdb | 340 | lines = res.splitlines() |
302b7a1b | 341 | vals = dict() |
7cb08cdb | 342 | for l in lines: |
6bb9d861 JB |
343 | try: |
344 | [name,value] = l.split('=', 1) | |
345 | except ValueError: | |
346 | logger.info(self.ifname + ": Ignore unexpected status line: " + l) | |
347 | continue | |
302b7a1b JM |
348 | vals[name] = value |
349 | return vals | |
350 | ||
f44c45ac JM |
351 | def get_group_status_field(self, field, extra=None): |
352 | vals = self.get_group_status(extra) | |
302b7a1b JM |
353 | if field in vals: |
354 | return vals[field] | |
7cb08cdb JM |
355 | return None |
356 | ||
6ca3a98b JM |
357 | def get_driver_status(self): |
358 | res = self.request("STATUS-DRIVER") | |
359 | lines = res.splitlines() | |
360 | vals = dict() | |
361 | for l in lines: | |
6bb9d861 JB |
362 | try: |
363 | [name,value] = l.split('=', 1) | |
364 | except ValueError: | |
365 | logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l) | |
366 | continue | |
6ca3a98b JM |
367 | vals[name] = value |
368 | return vals | |
369 | ||
370 | def get_driver_status_field(self, field): | |
371 | vals = self.get_driver_status() | |
372 | if field in vals: | |
373 | return vals[field] | |
374 | return None | |
375 | ||
5fe7a426 HD |
376 | def get_mcc(self): |
377 | mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent')) | |
378 | return 1 if mcc < 2 else mcc | |
379 | ||
44bb9106 JM |
380 | def get_mib(self): |
381 | res = self.request("MIB") | |
382 | lines = res.splitlines() | |
383 | vals = dict() | |
384 | for l in lines: | |
385 | try: | |
386 | [name,value] = l.split('=', 1) | |
387 | vals[name] = value | |
388 | except ValueError, e: | |
389 | logger.info(self.ifname + ": Ignore unexpected MIB line: " + l) | |
390 | return vals | |
391 | ||
1ae73b03 | 392 | def p2p_dev_addr(self): |
302b7a1b | 393 | return self.get_status_field("p2p_device_address") |
1ae73b03 | 394 | |
7cb08cdb | 395 | def p2p_interface_addr(self): |
302b7a1b | 396 | return self.get_group_status_field("address") |
7cb08cdb | 397 | |
f6420942 JM |
398 | def own_addr(self): |
399 | try: | |
400 | res = self.p2p_interface_addr() | |
401 | except: | |
402 | res = self.p2p_dev_addr() | |
403 | return res | |
404 | ||
1ae73b03 | 405 | def p2p_listen(self): |
0fa28afe | 406 | return self.global_request("P2P_LISTEN") |
1ae73b03 | 407 | |
7457c635 | 408 | def p2p_find(self, social=False, progressive=False, dev_id=None, |
6d0b4474 | 409 | dev_type=None, delay=None, freq=None): |
c70ebce0 | 410 | cmd = "P2P_FIND" |
1ae73b03 | 411 | if social: |
c70ebce0 | 412 | cmd = cmd + " type=social" |
5070b14a JM |
413 | elif progressive: |
414 | cmd = cmd + " type=progressive" | |
c70ebce0 JM |
415 | if dev_id: |
416 | cmd = cmd + " dev_id=" + dev_id | |
417 | if dev_type: | |
418 | cmd = cmd + " dev_type=" + dev_type | |
7457c635 JM |
419 | if delay: |
420 | cmd = cmd + " delay=" + str(delay) | |
6d0b4474 JM |
421 | if freq: |
422 | cmd = cmd + " freq=" + str(freq) | |
c70ebce0 | 423 | return self.global_request(cmd) |
1ae73b03 | 424 | |
5743006d | 425 | def p2p_stop_find(self): |
0fa28afe | 426 | return self.global_request("P2P_STOP_FIND") |
5743006d | 427 | |
1ae73b03 | 428 | def wps_read_pin(self): |
861671b6 JM |
429 | self.pin = self.request("WPS_PIN get").rstrip("\n") |
430 | if "FAIL" in self.pin: | |
431 | raise Exception("Could not generate PIN") | |
1ae73b03 JM |
432 | return self.pin |
433 | ||
434 | def peer_known(self, peer, full=True): | |
0fa28afe | 435 | res = self.global_request("P2P_PEER " + peer) |
1ae73b03 JM |
436 | if peer.lower() not in res.lower(): |
437 | return False | |
438 | if not full: | |
439 | return True | |
440 | return "[PROBE_REQ_ONLY]" not in res | |
441 | ||
d4b21766 | 442 | def discover_peer(self, peer, full=True, timeout=15, social=True, force_find=False): |
1ae73b03 | 443 | logger.info(self.ifname + ": Trying to discover peer " + peer) |
d4b21766 | 444 | if not force_find and self.peer_known(peer, full): |
1ae73b03 | 445 | return True |
d963f037 | 446 | self.p2p_find(social) |
1ae73b03 | 447 | count = 0 |
ee3f9f38 JM |
448 | while count < timeout * 4: |
449 | time.sleep(0.25) | |
1ae73b03 JM |
450 | count = count + 1 |
451 | if self.peer_known(peer, full): | |
452 | return True | |
453 | return False | |
454 | ||
451afb4f JM |
455 | def get_peer(self, peer): |
456 | res = self.global_request("P2P_PEER " + peer) | |
457 | if peer.lower() not in res.lower(): | |
458 | raise Exception("Peer information not available") | |
459 | lines = res.splitlines() | |
460 | vals = dict() | |
461 | for l in lines: | |
462 | if '=' in l: | |
463 | [name,value] = l.split('=', 1) | |
464 | vals[name] = value | |
465 | return vals | |
466 | ||
46f2cfce | 467 | def group_form_result(self, ev, expect_failure=False, go_neg_res=None): |
f7b1a750 JM |
468 | if expect_failure: |
469 | if "P2P-GROUP-STARTED" in ev: | |
470 | raise Exception("Group formation succeeded when expecting failure") | |
471 | exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' | |
472 | s = re.split(exp, ev) | |
473 | if len(s) < 3: | |
474 | return None | |
475 | res = {} | |
476 | res['result'] = 'go-neg-failed' | |
477 | res['status'] = int(s[2]) | |
478 | return res | |
479 | ||
480 | if "P2P-GROUP-STARTED" not in ev: | |
481 | raise Exception("No P2P-GROUP-STARTED event seen") | |
482 | ||
c9dc5623 | 483 | 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.]*)' |
c68f9a61 | 484 | s = re.split(exp, ev) |
c9dc5623 JM |
485 | if len(s) < 11: |
486 | exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' | |
487 | s = re.split(exp, ev) | |
488 | if len(s) < 8: | |
489 | raise Exception("Could not parse P2P-GROUP-STARTED") | |
c68f9a61 JM |
490 | res = {} |
491 | res['result'] = 'success' | |
492 | res['ifname'] = s[2] | |
f3f8ee88 | 493 | self.group_ifname = s[2] |
4823566c JM |
494 | try: |
495 | self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname)) | |
496 | self.gctrl_mon.attach() | |
497 | except: | |
498 | logger.debug("Could not open monitor socket for group interface") | |
499 | self.gctrl_mon = None | |
c68f9a61 JM |
500 | res['role'] = s[3] |
501 | res['ssid'] = s[4] | |
502 | res['freq'] = s[5] | |
451afb4f JM |
503 | if "[PERSISTENT]" in ev: |
504 | res['persistent'] = True | |
505 | else: | |
506 | res['persistent'] = False | |
c68f9a61 JM |
507 | p = re.match(r'psk=([0-9a-f]*)', s[6]) |
508 | if p: | |
509 | res['psk'] = p.group(1) | |
510 | p = re.match(r'passphrase="(.*)"', s[6]) | |
511 | if p: | |
512 | res['passphrase'] = p.group(1) | |
513 | res['go_dev_addr'] = s[7] | |
46f2cfce | 514 | |
c9dc5623 JM |
515 | if len(s) > 8 and len(s[8]) > 0: |
516 | res['ip_addr'] = s[8] | |
517 | if len(s) > 9: | |
518 | res['ip_mask'] = s[9] | |
519 | if len(s) > 10: | |
520 | res['go_ip_addr'] = s[10] | |
521 | ||
46f2cfce JM |
522 | if go_neg_res: |
523 | exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' | |
524 | s = re.split(exp, go_neg_res) | |
525 | if len(s) < 4: | |
526 | raise Exception("Could not parse P2P-GO-NEG-SUCCESS") | |
527 | res['go_neg_role'] = s[2] | |
528 | res['go_neg_freq'] = s[3] | |
529 | ||
c68f9a61 JM |
530 | return res |
531 | ||
46f2cfce | 532 | def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, persistent=False, freq=None): |
1ae73b03 JM |
533 | if not self.discover_peer(peer): |
534 | raise Exception("Peer " + peer + " not found") | |
535 | self.dump_monitor() | |
1f53fe03 JM |
536 | if pin: |
537 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" | |
538 | else: | |
539 | cmd = "P2P_CONNECT " + peer + " " + method + " auth" | |
809079d3 JM |
540 | if go_intent: |
541 | cmd = cmd + ' go_intent=' + str(go_intent) | |
46f2cfce JM |
542 | if freq: |
543 | cmd = cmd + ' freq=' + str(freq) | |
451afb4f JM |
544 | if persistent: |
545 | cmd = cmd + " persistent" | |
0fa28afe | 546 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
547 | return None |
548 | raise Exception("P2P_CONNECT (auth) failed") | |
549 | ||
809079d3 | 550 | def p2p_go_neg_auth_result(self, timeout=1, expect_failure=False): |
46f2cfce JM |
551 | go_neg_res = None |
552 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
553 | "P2P-GO-NEG-FAILURE"], timeout); | |
c68f9a61 | 554 | if ev is None: |
809079d3 JM |
555 | if expect_failure: |
556 | return None | |
c68f9a61 | 557 | raise Exception("Group formation timed out") |
46f2cfce JM |
558 | if "P2P-GO-NEG-SUCCESS" in ev: |
559 | go_neg_res = ev | |
560 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout); | |
561 | if ev is None: | |
562 | if expect_failure: | |
563 | return None | |
564 | raise Exception("Group formation timed out") | |
c68f9a61 | 565 | self.dump_monitor() |
46f2cfce | 566 | return self.group_form_result(ev, expect_failure, go_neg_res) |
c68f9a61 | 567 | |
b43ac5d5 | 568 | 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): |
1ae73b03 JM |
569 | if not self.discover_peer(peer): |
570 | raise Exception("Peer " + peer + " not found") | |
571 | self.dump_monitor() | |
1a4d80b8 JM |
572 | if pin: |
573 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method | |
574 | else: | |
575 | cmd = "P2P_CONNECT " + peer + " " + method | |
809079d3 JM |
576 | if go_intent: |
577 | cmd = cmd + ' go_intent=' + str(go_intent) | |
ef2bd5a3 JM |
578 | if freq: |
579 | cmd = cmd + ' freq=' + str(freq) | |
451afb4f JM |
580 | if persistent: |
581 | cmd = cmd + " persistent" | |
bdc44764 JM |
582 | elif persistent_id: |
583 | cmd = cmd + " persistent=" + persistent_id | |
08e4bd87 JM |
584 | if provdisc: |
585 | cmd = cmd + " provdisc" | |
0fa28afe | 586 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
587 | if timeout == 0: |
588 | self.dump_monitor() | |
589 | return None | |
46f2cfce JM |
590 | go_neg_res = None |
591 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
592 | "P2P-GO-NEG-FAILURE"], timeout) | |
c68f9a61 | 593 | if ev is None: |
809079d3 JM |
594 | if expect_failure: |
595 | return None | |
c68f9a61 | 596 | raise Exception("Group formation timed out") |
46f2cfce | 597 | if "P2P-GO-NEG-SUCCESS" in ev: |
b43ac5d5 JM |
598 | if not wait_group: |
599 | return ev | |
46f2cfce JM |
600 | go_neg_res = ev |
601 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) | |
602 | if ev is None: | |
603 | if expect_failure: | |
604 | return None | |
605 | raise Exception("Group formation timed out") | |
c68f9a61 | 606 | self.dump_monitor() |
46f2cfce | 607 | return self.group_form_result(ev, expect_failure, go_neg_res) |
1ae73b03 JM |
608 | raise Exception("P2P_CONNECT failed") |
609 | ||
cf9189b9 | 610 | def wait_event(self, events, timeout=10): |
36408936 JM |
611 | start = os.times()[4] |
612 | while True: | |
1ae73b03 JM |
613 | while self.mon.pending(): |
614 | ev = self.mon.recv() | |
809079d3 | 615 | logger.debug(self.ifname + ": " + ev) |
f7b1a750 JM |
616 | for event in events: |
617 | if event in ev: | |
618 | return ev | |
36408936 JM |
619 | now = os.times()[4] |
620 | remaining = start + timeout - now | |
621 | if remaining <= 0: | |
622 | break | |
623 | if not self.mon.pending(timeout=remaining): | |
624 | break | |
c68f9a61 | 625 | return None |
1ae73b03 | 626 | |
0fa28afe JM |
627 | def wait_global_event(self, events, timeout): |
628 | if self.global_iface is None: | |
629 | self.wait_event(events, timeout) | |
630 | else: | |
36408936 JM |
631 | start = os.times()[4] |
632 | while True: | |
0fa28afe JM |
633 | while self.global_mon.pending(): |
634 | ev = self.global_mon.recv() | |
9d507452 | 635 | logger.debug(self.ifname + "(global): " + ev) |
0fa28afe JM |
636 | for event in events: |
637 | if event in ev: | |
638 | return ev | |
36408936 JM |
639 | now = os.times()[4] |
640 | remaining = start + timeout - now | |
641 | if remaining <= 0: | |
642 | break | |
643 | if not self.global_mon.pending(timeout=remaining): | |
644 | break | |
0fa28afe JM |
645 | return None |
646 | ||
4823566c JM |
647 | def wait_group_event(self, events, timeout=10): |
648 | if self.group_ifname and self.group_ifname != self.ifname: | |
649 | if self.gctrl_mon is None: | |
650 | return None | |
651 | start = os.times()[4] | |
652 | while True: | |
653 | while self.gctrl_mon.pending(): | |
654 | ev = self.gctrl_mon.recv() | |
655 | logger.debug(self.group_ifname + ": " + ev) | |
656 | for event in events: | |
657 | if event in ev: | |
658 | return ev | |
659 | now = os.times()[4] | |
660 | remaining = start + timeout - now | |
661 | if remaining <= 0: | |
662 | break | |
663 | if not self.gctrl_mon.pending(timeout=remaining): | |
664 | break | |
665 | return None | |
666 | ||
667 | return self.wait_event(events, timeout) | |
668 | ||
2c914e24 | 669 | def wait_go_ending_session(self): |
4823566c JM |
670 | if self.gctrl_mon: |
671 | try: | |
672 | self.gctrl_mon.detach() | |
673 | except: | |
674 | pass | |
675 | self.gctrl_mon = None | |
b0d697be | 676 | ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=3) |
2c914e24 JM |
677 | if ev is None: |
678 | raise Exception("Group removal event timed out") | |
679 | if "reason=GO_ENDING_SESSION" not in ev: | |
680 | raise Exception("Unexpected group removal reason") | |
681 | ||
1ae73b03 JM |
682 | def dump_monitor(self): |
683 | while self.mon.pending(): | |
684 | ev = self.mon.recv() | |
685 | logger.debug(self.ifname + ": " + ev) | |
c8836a4f | 686 | while self.global_mon and self.global_mon.pending(): |
9d507452 JM |
687 | ev = self.global_mon.recv() |
688 | logger.debug(self.ifname + "(global): " + ev) | |
3eb29b7b | 689 | |
a311c61d | 690 | def remove_group(self, ifname=None): |
4823566c JM |
691 | if self.gctrl_mon: |
692 | try: | |
693 | self.gctrl_mon.detach() | |
694 | except: | |
695 | pass | |
696 | self.gctrl_mon = None | |
a311c61d | 697 | if ifname is None: |
451afb4f | 698 | ifname = self.group_ifname if self.group_ifname else self.ifname |
0fa28afe | 699 | if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): |
3eb29b7b | 700 | raise Exception("Group could not be removed") |
f3f8ee88 | 701 | self.group_ifname = None |
4ea8d3b5 | 702 | |
25057d92 | 703 | def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False): |
4ea8d3b5 JM |
704 | self.dump_monitor() |
705 | cmd = "P2P_GROUP_ADD" | |
07a2e61b JM |
706 | if persistent is None: |
707 | pass | |
708 | elif persistent is True: | |
709 | cmd = cmd + " persistent" | |
710 | else: | |
711 | cmd = cmd + " persistent=" + str(persistent) | |
5924d4c1 | 712 | if freq: |
ef2bd5a3 | 713 | cmd = cmd + " freq=" + str(freq) |
0fa28afe JM |
714 | if "OK" in self.global_request(cmd): |
715 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) | |
4ea8d3b5 JM |
716 | if ev is None: |
717 | raise Exception("GO start up timed out") | |
25057d92 JM |
718 | if not no_event_clear: |
719 | self.dump_monitor() | |
4ea8d3b5 JM |
720 | return self.group_form_result(ev) |
721 | raise Exception("P2P_GROUP_ADD failed") | |
722 | ||
723 | def p2p_go_authorize_client(self, pin): | |
724 | cmd = "WPS_PIN any " + pin | |
f3f8ee88 | 725 | if "FAIL" in self.group_request(cmd): |
4ea8d3b5 JM |
726 | raise Exception("Failed to authorize client connection on GO") |
727 | return None | |
728 | ||
b162675f JM |
729 | def p2p_go_authorize_client_pbc(self): |
730 | cmd = "WPS_PBC" | |
731 | if "FAIL" in self.group_request(cmd): | |
732 | raise Exception("Failed to authorize client connection on GO") | |
733 | return None | |
734 | ||
ee3f9f38 JM |
735 | def p2p_connect_group(self, go_addr, pin, timeout=0, social=False, |
736 | freq=None): | |
4ea8d3b5 | 737 | self.dump_monitor() |
41af1305 | 738 | if not self.discover_peer(go_addr, social=social): |
54c20c9b JM |
739 | if social or not self.discover_peer(go_addr, social=social): |
740 | raise Exception("GO " + go_addr + " not found") | |
4ea8d3b5 JM |
741 | self.dump_monitor() |
742 | cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" | |
ee3f9f38 JM |
743 | if freq: |
744 | cmd += " freq=" + str(freq) | |
0fa28afe | 745 | if "OK" in self.global_request(cmd): |
4ea8d3b5 JM |
746 | if timeout == 0: |
747 | self.dump_monitor() | |
748 | return None | |
0fa28afe | 749 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) |
4ea8d3b5 JM |
750 | if ev is None: |
751 | raise Exception("Joining the group timed out") | |
752 | self.dump_monitor() | |
753 | return self.group_form_result(ev) | |
754 | raise Exception("P2P_CONNECT(join) failed") | |
7cb08cdb JM |
755 | |
756 | def tdls_setup(self, peer): | |
757 | cmd = "TDLS_SETUP " + peer | |
758 | if "FAIL" in self.group_request(cmd): | |
759 | raise Exception("Failed to request TDLS setup") | |
760 | return None | |
761 | ||
762 | def tdls_teardown(self, peer): | |
763 | cmd = "TDLS_TEARDOWN " + peer | |
764 | if "FAIL" in self.group_request(cmd): | |
765 | raise Exception("Failed to request TDLS teardown") | |
766 | return None | |
b61e418c | 767 | |
2380d804 OG |
768 | def tdls_link_status(self, peer): |
769 | cmd = "TDLS_LINK_STATUS " + peer | |
770 | ret = self.group_request(cmd) | |
771 | if "FAIL" in ret: | |
772 | raise Exception("Failed to request TDLS link status") | |
773 | return ret | |
774 | ||
bc32f830 EP |
775 | def tspecs(self): |
776 | """Return (tsid, up) tuples representing current tspecs""" | |
777 | res = self.request("WMM_AC_STATUS") | |
778 | tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res) | |
779 | tspecs = [tuple(map(int, tspec)) for tspec in tspecs] | |
780 | ||
781 | logger.debug("tspecs: " + str(tspecs)) | |
782 | return tspecs | |
783 | ||
ceb767d5 JM |
784 | def add_ts(self, tsid, up, direction="downlink", expect_failure=False, |
785 | extra=None): | |
76133458 EP |
786 | params = { |
787 | "sba": 9000, | |
788 | "nominal_msdu_size": 1500, | |
789 | "min_phy_rate": 6000000, | |
790 | "mean_data_rate": 1500, | |
791 | } | |
ceb767d5 | 792 | cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up) |
76133458 EP |
793 | for (key, value) in params.iteritems(): |
794 | cmd += " %s=%d" % (key, value) | |
ceb767d5 JM |
795 | if extra: |
796 | cmd += " " + extra | |
76133458 EP |
797 | |
798 | if self.request(cmd).strip() != "OK": | |
799 | raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up)) | |
800 | ||
ceb767d5 JM |
801 | if expect_failure: |
802 | ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2) | |
803 | if ev is None: | |
804 | raise Exception("ADDTS failed (time out while waiting failure)") | |
805 | if "tsid=%d" % (tsid) not in ev: | |
806 | raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED") | |
807 | return | |
808 | ||
76133458 EP |
809 | ev = self.wait_event(["TSPEC-ADDED"], timeout=1) |
810 | if ev is None: | |
811 | raise Exception("ADDTS failed (time out)") | |
812 | if "tsid=%d" % (tsid) not in ev: | |
813 | raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)") | |
814 | ||
bc32f830 EP |
815 | if not (tsid, up) in self.tspecs(): |
816 | raise Exception("ADDTS failed (tsid not in tspec list)") | |
817 | ||
76133458 EP |
818 | def del_ts(self, tsid): |
819 | if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK": | |
820 | raise Exception("DELTS failed") | |
821 | ||
822 | ev = self.wait_event(["TSPEC-REMOVED"], timeout=1) | |
823 | if ev is None: | |
824 | raise Exception("DELTS failed (time out)") | |
825 | if "tsid=%d" % (tsid) not in ev: | |
826 | raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)") | |
827 | ||
bc32f830 EP |
828 | tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid] |
829 | if tspecs: | |
830 | raise Exception("DELTS failed (still in tspec list)") | |
831 | ||
6f939e59 | 832 | def connect(self, ssid=None, ssid2=None, **kwargs): |
b61e418c JM |
833 | logger.info("Connect STA " + self.ifname + " to AP") |
834 | id = self.add_network() | |
d78f3303 JM |
835 | if ssid: |
836 | self.set_network_quoted(id, "ssid", ssid) | |
837 | elif ssid2: | |
838 | self.set_network(id, "ssid", ssid2) | |
6f939e59 JM |
839 | |
840 | quoted = [ "psk", "identity", "anonymous_identity", "password", | |
841 | "ca_cert", "client_cert", "private_key", | |
842 | "private_key_passwd", "ca_cert2", "client_cert2", | |
843 | "private_key2", "phase1", "phase2", "domain_suffix_match", | |
31ec9faf | 844 | "altsubject_match", "subject_match", "pac_file", "dh_file", |
061cbb25 JM |
845 | "bgscan", "ht_mcs", "id_str", "openssl_ciphers", |
846 | "domain_match" ] | |
6f939e59 JM |
847 | for field in quoted: |
848 | if field in kwargs and kwargs[field]: | |
849 | self.set_network_quoted(id, field, kwargs[field]) | |
850 | ||
851 | not_quoted = [ "proto", "key_mgmt", "ieee80211w", "pairwise", | |
168d4b09 JM |
852 | "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3", |
853 | "wep_tx_keyidx", "scan_freq", "eap", | |
fb5c8cea | 854 | "eapol_flags", "fragment_size", "scan_ssid", "auth_alg", |
08429720 JM |
855 | "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid", |
856 | "disable_max_amsdu", "ampdu_factor", "ampdu_density", | |
bd6bb3e3 | 857 | "disable_ht40", "disable_sgi", "disable_ldpc", |
acc9a635 | 858 | "ht40_intolerant", "update_identifier", "mac_addr", |
c2096d99 | 859 | "erp", "bg_scan_period", "bssid_blacklist", |
febf5752 | 860 | "bssid_whitelist", "mem_only_psk", "eap_workaround" ] |
6f939e59 JM |
861 | for field in not_quoted: |
862 | if field in kwargs and kwargs[field]: | |
863 | self.set_network(id, field, kwargs[field]) | |
864 | ||
865 | if "raw_psk" in kwargs and kwargs['raw_psk']: | |
866 | self.set_network(id, "psk", kwargs['raw_psk']) | |
867 | if "password_hex" in kwargs and kwargs['password_hex']: | |
868 | self.set_network(id, "password", kwargs['password_hex']) | |
869 | if "peerkey" in kwargs and kwargs['peerkey']: | |
4a5a5792 | 870 | self.set_network(id, "peerkey", "1") |
6f939e59 | 871 | if "okc" in kwargs and kwargs['okc']: |
0fab9ce6 | 872 | self.set_network(id, "proactive_key_caching", "1") |
6f939e59 JM |
873 | if "ocsp" in kwargs and kwargs['ocsp']: |
874 | self.set_network(id, "ocsp", str(kwargs['ocsp'])) | |
875 | if "only_add_network" in kwargs and kwargs['only_add_network']: | |
a6cf5cd6 | 876 | return id |
6f939e59 JM |
877 | if "wait_connect" not in kwargs or kwargs['wait_connect']: |
878 | if "eap" in kwargs: | |
7559ad7a JM |
879 | self.connect_network(id, timeout=20) |
880 | else: | |
881 | self.connect_network(id) | |
9626962d JM |
882 | else: |
883 | self.dump_monitor() | |
884 | self.select_network(id) | |
709f18d5 | 885 | return id |
5126138c | 886 | |
b3ec107c | 887 | def scan(self, type=None, freq=None, no_wait=False, only_new=False): |
5126138c JM |
888 | if type: |
889 | cmd = "SCAN TYPE=" + type | |
890 | else: | |
891 | cmd = "SCAN" | |
0589f401 | 892 | if freq: |
243dcc4a | 893 | cmd = cmd + " freq=" + str(freq) |
b3ec107c JM |
894 | if only_new: |
895 | cmd += " only_new=1" | |
d7a99700 JM |
896 | if not no_wait: |
897 | self.dump_monitor() | |
5126138c JM |
898 | if not "OK" in self.request(cmd): |
899 | raise Exception("Failed to trigger scan") | |
d7a99700 JM |
900 | if no_wait: |
901 | return | |
5126138c JM |
902 | ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS"], 15) |
903 | if ev is None: | |
904 | raise Exception("Scan timed out") | |
905 | ||
d2432b8b | 906 | def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False): |
841bed04 JM |
907 | if not force_scan and self.get_bss(bssid) is not None: |
908 | return | |
909 | for i in range(0, 10): | |
d2432b8b | 910 | self.scan(freq=freq, type="ONLY", only_new=only_new) |
841bed04 JM |
911 | if self.get_bss(bssid) is not None: |
912 | return | |
913 | raise Exception("Could not find BSS " + bssid + " in scan") | |
914 | ||
970a23f6 | 915 | def flush_scan_cache(self, freq=2417): |
243dcc4a | 916 | self.request("BSS_FLUSH 0") |
970a23f6 | 917 | self.scan(freq=freq, only_new=True) |
c1e38fec JM |
918 | res = self.request("SCAN_RESULTS") |
919 | if len(res.splitlines()) > 1: | |
920 | self.request("BSS_FLUSH 0") | |
921 | self.scan(freq=2422, only_new=True) | |
922 | res = self.request("SCAN_RESULTS") | |
923 | if len(res.splitlines()) > 1: | |
924 | logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res) | |
243dcc4a | 925 | |
3b808945 | 926 | def roam(self, bssid, fail_test=False): |
5126138c | 927 | self.dump_monitor() |
655bc8bf JM |
928 | if "OK" not in self.request("ROAM " + bssid): |
929 | raise Exception("ROAM failed") | |
3b808945 JM |
930 | if fail_test: |
931 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
932 | if ev is not None: | |
933 | raise Exception("Unexpected connection") | |
934 | self.dump_monitor() | |
935 | return | |
5f35a5e2 | 936 | self.wait_connected(timeout=10, error="Roaming with the AP timed out") |
5126138c | 937 | self.dump_monitor() |
6edaee9c | 938 | |
3b808945 | 939 | def roam_over_ds(self, bssid, fail_test=False): |
b553eab1 | 940 | self.dump_monitor() |
655bc8bf JM |
941 | if "OK" not in self.request("FT_DS " + bssid): |
942 | raise Exception("FT_DS failed") | |
3b808945 JM |
943 | if fail_test: |
944 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
945 | if ev is not None: | |
946 | raise Exception("Unexpected connection") | |
947 | self.dump_monitor() | |
948 | return | |
5f35a5e2 | 949 | self.wait_connected(timeout=10, error="Roaming with the AP timed out") |
b553eab1 JM |
950 | self.dump_monitor() |
951 | ||
6edaee9c | 952 | def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, |
6645ff50 | 953 | new_passphrase=None, no_wait=False): |
6edaee9c JM |
954 | self.dump_monitor() |
955 | if new_ssid: | |
956 | self.request("WPS_REG " + bssid + " " + pin + " " + | |
957 | new_ssid.encode("hex") + " " + key_mgmt + " " + | |
958 | cipher + " " + new_passphrase.encode("hex")) | |
6645ff50 JM |
959 | if no_wait: |
960 | return | |
6edaee9c JM |
961 | ev = self.wait_event(["WPS-SUCCESS"], timeout=15) |
962 | else: | |
963 | self.request("WPS_REG " + bssid + " " + pin) | |
6645ff50 JM |
964 | if no_wait: |
965 | return | |
6edaee9c JM |
966 | ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) |
967 | if ev is None: | |
968 | raise Exception("WPS cred timed out") | |
969 | ev = self.wait_event(["WPS-FAIL"], timeout=15) | |
970 | if ev is None: | |
971 | raise Exception("WPS timed out") | |
5f35a5e2 | 972 | self.wait_connected(timeout=15) |
57661377 JM |
973 | |
974 | def relog(self): | |
5f797376 | 975 | self.global_request("RELOG") |
41af1305 JM |
976 | |
977 | def wait_completed(self, timeout=10): | |
978 | for i in range(0, timeout * 2): | |
979 | if self.get_status_field("wpa_state") == "COMPLETED": | |
980 | return | |
981 | time.sleep(0.5) | |
982 | raise Exception("Timeout while waiting for COMPLETED state") | |
0eff1ab3 JM |
983 | |
984 | def get_capability(self, field): | |
985 | res = self.request("GET_CAPABILITY " + field) | |
986 | if "FAIL" in res: | |
987 | return None | |
988 | return res.split(' ') | |
2cdd91d8 | 989 | |
fb9cf82e IP |
990 | def get_bss(self, bssid, ifname=None): |
991 | if not ifname or ifname == self.ifname: | |
992 | res = self.request("BSS " + bssid) | |
993 | elif ifname == self.group_ifname: | |
994 | res = self.group_request("BSS " + bssid) | |
995 | else: | |
996 | return None | |
997 | ||
c126cb4d JM |
998 | if "FAIL" in res: |
999 | return None | |
2cdd91d8 JM |
1000 | lines = res.splitlines() |
1001 | vals = dict() | |
1002 | for l in lines: | |
1003 | [name,value] = l.split('=', 1) | |
1004 | vals[name] = value | |
c126cb4d JM |
1005 | if len(vals) == 0: |
1006 | return None | |
2cdd91d8 | 1007 | return vals |
0fab9ce6 JM |
1008 | |
1009 | def get_pmksa(self, bssid): | |
1010 | res = self.request("PMKSA") | |
1011 | lines = res.splitlines() | |
1012 | for l in lines: | |
1013 | if bssid not in l: | |
1014 | continue | |
1015 | vals = dict() | |
1016 | [index,aa,pmkid,expiration,opportunistic] = l.split(' ') | |
1017 | vals['index'] = index | |
1018 | vals['pmkid'] = pmkid | |
1019 | vals['expiration'] = expiration | |
1020 | vals['opportunistic'] = opportunistic | |
1021 | return vals | |
1022 | return None | |
e1a5e09a JM |
1023 | |
1024 | def get_sta(self, addr, info=None, next=False): | |
1025 | cmd = "STA-NEXT " if next else "STA " | |
1026 | if addr is None: | |
1027 | res = self.request("STA-FIRST") | |
1028 | elif info: | |
1029 | res = self.request(cmd + addr + " " + info) | |
1030 | else: | |
1031 | res = self.request(cmd + addr) | |
1032 | lines = res.splitlines() | |
1033 | vals = dict() | |
1034 | first = True | |
1035 | for l in lines: | |
1036 | if first: | |
1037 | vals['addr'] = l | |
1038 | first = False | |
1039 | else: | |
1040 | [name,value] = l.split('=', 1) | |
1041 | vals[name] = value | |
1042 | return vals | |
daf3806d JM |
1043 | |
1044 | def mgmt_rx(self, timeout=5): | |
1045 | ev = self.wait_event(["MGMT-RX"], timeout=timeout) | |
1046 | if ev is None: | |
1047 | return None | |
1048 | msg = {} | |
1049 | items = ev.split(' ') | |
1050 | field,val = items[1].split('=') | |
1051 | if field != "freq": | |
1052 | raise Exception("Unexpected MGMT-RX event format: " + ev) | |
1053 | msg['freq'] = val | |
1054 | frame = binascii.unhexlify(items[4]) | |
1055 | msg['frame'] = frame | |
1056 | ||
1057 | hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) | |
1058 | msg['fc'] = hdr[0] | |
1059 | msg['subtype'] = (hdr[0] >> 4) & 0xf | |
1060 | hdr = hdr[1:] | |
1061 | msg['duration'] = hdr[0] | |
1062 | hdr = hdr[1:] | |
1063 | msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1064 | hdr = hdr[6:] | |
1065 | msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1066 | hdr = hdr[6:] | |
1067 | msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1068 | hdr = hdr[6:] | |
1069 | msg['seq_ctrl'] = hdr[0] | |
1070 | msg['payload'] = frame[24:] | |
1071 | ||
1072 | return msg | |
5f35a5e2 JM |
1073 | |
1074 | def wait_connected(self, timeout=10, error="Connection timed out"): | |
1075 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) | |
1076 | if ev is None: | |
1077 | raise Exception(error) | |
1078 | return ev | |
1079 | ||
1080 | def wait_disconnected(self, timeout=10, error="Disconnection timed out"): | |
1081 | ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout) | |
1082 | if ev is None: | |
1083 | raise Exception(error) | |
1084 | return ev | |
bbcbbd37 DS |
1085 | |
1086 | def get_group_ifname(self): | |
1087 | return self.group_ifname if self.group_ifname else self.ifname | |
7ebc7e8f JM |
1088 | |
1089 | def get_config(self): | |
1090 | res = self.request("DUMP") | |
1091 | if res.startswith("FAIL"): | |
1092 | raise Exception("DUMP failed") | |
1093 | lines = res.splitlines() | |
1094 | vals = dict() | |
1095 | for l in lines: | |
1096 | [name,value] = l.split('=', 1) | |
1097 | vals[name] = value | |
1098 | return vals |