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