]>
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 |
9489637b JM |
22 | if ifname: |
23 | self.set_ifname(ifname) | |
24 | else: | |
25 | self.ifname = None | |
1ae73b03 | 26 | |
0fa28afe JM |
27 | self.global_iface = global_iface |
28 | if global_iface: | |
29 | self.global_ctrl = wpaspy.Ctrl(global_iface) | |
30 | self.global_mon = wpaspy.Ctrl(global_iface) | |
31 | self.global_mon.attach() | |
32 | ||
9489637b JM |
33 | def set_ifname(self, ifname): |
34 | self.ifname = ifname | |
35 | self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
36 | self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
37 | self.mon.attach() | |
38 | ||
39 | def remove_ifname(self): | |
40 | if self.ifname: | |
41 | self.mon.detach() | |
42 | self.mon = None | |
43 | self.ctrl = None | |
44 | self.ifname = None | |
45 | ||
6e917c3e | 46 | def interface_add(self, ifname, driver="nl80211", drv_params=None): |
9489637b JM |
47 | try: |
48 | groups = subprocess.check_output(["id"]) | |
49 | group = "admin" if "(admin)" in groups else "adm" | |
50 | except Exception, e: | |
51 | group = "admin" | |
52 | cmd = "INTERFACE_ADD " + ifname + "\t\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group | |
6e917c3e JM |
53 | if drv_params: |
54 | cmd = cmd + '\t' + drv_params | |
9489637b JM |
55 | if "FAIL" in self.global_request(cmd): |
56 | raise Exception("Failed to add a dynamic wpa_supplicant interface") | |
57 | self.set_ifname(ifname) | |
58 | ||
59 | def interface_remove(self, ifname): | |
60 | self.remove_ifname() | |
61 | self.global_request("INTERFACE_REMOVE " + ifname) | |
62 | ||
1ae73b03 JM |
63 | def request(self, cmd): |
64 | logger.debug(self.ifname + ": CTRL: " + cmd) | |
65 | return self.ctrl.request(cmd) | |
66 | ||
0fa28afe JM |
67 | def global_request(self, cmd): |
68 | if self.global_iface is None: | |
69 | self.request(cmd) | |
70 | else: | |
9489637b JM |
71 | ifname = self.ifname or self.global_iface |
72 | logger.debug(ifname + ": CTRL: " + cmd) | |
0fa28afe JM |
73 | return self.global_ctrl.request(cmd) |
74 | ||
f3f8ee88 JM |
75 | def group_request(self, cmd): |
76 | if self.group_ifname and self.group_ifname != self.ifname: | |
77 | logger.debug(self.group_ifname + ": CTRL: " + cmd) | |
78 | gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname)) | |
79 | return gctrl.request(cmd) | |
80 | return self.request(cmd) | |
81 | ||
1ae73b03 JM |
82 | def ping(self): |
83 | return "PONG" in self.request("PING") | |
84 | ||
85 | def reset(self): | |
ea0e92ee JM |
86 | res = self.request("FLUSH") |
87 | if not "OK" in res: | |
88 | logger.info("FLUSH to " + self.ifname + " failed: " + res) | |
b2210bd2 | 89 | self.request("WPS_ER_STOP") |
d369bdca | 90 | self.request("SET pmf 0") |
efd43d85 | 91 | self.request("SET external_sim 0") |
715bf904 JM |
92 | self.request("SET hessid 00:00:00:00:00:00") |
93 | self.request("SET access_network_type 15") | |
46f2cfce JM |
94 | self.request("SET p2p_add_cli_chan 0") |
95 | self.request("SET p2p_no_go_freq ") | |
96 | self.request("SET p2p_pref_chan ") | |
19ae1d07 | 97 | self.request("SET p2p_no_group_iface 1") |
c9dc5623 | 98 | self.request("SET p2p_go_intent 7") |
f3f8ee88 | 99 | self.group_ifname = None |
6edaee9c | 100 | self.dump_monitor() |
6ca3a98b JM |
101 | |
102 | iter = 0 | |
103 | while iter < 60: | |
104 | state = self.get_driver_status_field("scan_state") | |
105 | if "SCAN_STARTED" in state or "SCAN_REQUESTED" in state: | |
106 | logger.info(self.ifname + ": Waiting for scan operation to complete before continuing") | |
107 | time.sleep(1) | |
108 | else: | |
109 | break | |
110 | iter = iter + 1 | |
111 | if iter == 60: | |
112 | logger.error(self.ifname + ": Driver scan state did not clear") | |
113 | print "Trying to clear cfg80211/mac80211 scan state" | |
114 | try: | |
115 | cmd = ["sudo", "ifconfig", self.ifname, "down"] | |
116 | subprocess.call(cmd) | |
117 | except subprocess.CalledProcessError, e: | |
118 | logger.info("ifconfig failed: " + str(e.returncode)) | |
119 | logger.info(e.output) | |
120 | try: | |
121 | cmd = ["sudo", "ifconfig", self.ifname, "up"] | |
122 | subprocess.call(cmd) | |
123 | except subprocess.CalledProcessError, e: | |
124 | logger.info("ifconfig failed: " + str(e.returncode)) | |
125 | logger.info(e.output) | |
c57c1ed6 JM |
126 | if iter > 0: |
127 | # The ongoing scan could have discovered BSSes or P2P peers | |
128 | logger.info("Run FLUSH again since scan was in progress") | |
129 | self.request("FLUSH") | |
b21df6e7 | 130 | self.dump_monitor() |
6ca3a98b | 131 | |
ea0e92ee JM |
132 | if not self.ping(): |
133 | logger.info("No PING response from " + self.ifname + " after reset") | |
1ae73b03 | 134 | |
07a2e61b JM |
135 | def add_network(self): |
136 | id = self.request("ADD_NETWORK") | |
137 | if "FAIL" in id: | |
138 | raise Exception("ADD_NETWORK failed") | |
139 | return int(id) | |
140 | ||
141 | def remove_network(self, id): | |
142 | id = self.request("REMOVE_NETWORK " + str(id)) | |
143 | if "FAIL" in id: | |
144 | raise Exception("REMOVE_NETWORK failed") | |
145 | return None | |
146 | ||
ca5b81a5 JM |
147 | def get_network(self, id, field): |
148 | res = self.request("GET_NETWORK " + str(id) + " " + field) | |
149 | if res == "FAIL\n": | |
150 | return None | |
151 | return res | |
152 | ||
07a2e61b JM |
153 | def set_network(self, id, field, value): |
154 | res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value) | |
155 | if "FAIL" in res: | |
156 | raise Exception("SET_NETWORK failed") | |
157 | return None | |
158 | ||
159 | def set_network_quoted(self, id, field, value): | |
160 | res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') | |
161 | if "FAIL" in res: | |
162 | raise Exception("SET_NETWORK failed") | |
163 | return None | |
164 | ||
6a0b4002 JM |
165 | def list_networks(self): |
166 | res = self.request("LIST_NETWORKS") | |
167 | lines = res.splitlines() | |
168 | networks = [] | |
169 | for l in lines: | |
170 | if "network id" in l: | |
171 | continue | |
172 | [id,ssid,bssid,flags] = l.split('\t') | |
173 | network = {} | |
174 | network['id'] = id | |
175 | network['ssid'] = ssid | |
176 | network['bssid'] = bssid | |
177 | network['flags'] = flags | |
178 | networks.append(network) | |
179 | return networks | |
180 | ||
543f9f7e | 181 | def hs20_enable(self, auto_interworking=False): |
bbe86767 JM |
182 | self.request("SET interworking 1") |
183 | self.request("SET hs20 1") | |
543f9f7e JM |
184 | if auto_interworking: |
185 | self.request("SET auto_interworking 1") | |
186 | else: | |
187 | self.request("SET auto_interworking 0") | |
bbe86767 | 188 | |
93a06242 JM |
189 | def add_cred(self): |
190 | id = self.request("ADD_CRED") | |
191 | if "FAIL" in id: | |
192 | raise Exception("ADD_CRED failed") | |
193 | return int(id) | |
194 | ||
195 | def remove_cred(self, id): | |
196 | id = self.request("REMOVE_CRED " + str(id)) | |
197 | if "FAIL" in id: | |
198 | raise Exception("REMOVE_CRED failed") | |
199 | return None | |
200 | ||
201 | def set_cred(self, id, field, value): | |
202 | res = self.request("SET_CRED " + str(id) + " " + field + " " + value) | |
203 | if "FAIL" in res: | |
204 | raise Exception("SET_CRED failed") | |
205 | return None | |
206 | ||
207 | def set_cred_quoted(self, id, field, value): | |
208 | res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"') | |
209 | if "FAIL" in res: | |
210 | raise Exception("SET_CRED failed") | |
211 | return None | |
212 | ||
aa45859e JM |
213 | def get_cred(self, id, field): |
214 | return self.request("GET_CRED " + str(id) + " " + field) | |
215 | ||
2232edf8 | 216 | def add_cred_values(self, params): |
bbe86767 | 217 | id = self.add_cred() |
2232edf8 | 218 | |
d355372c | 219 | quoted = [ "realm", "username", "password", "domain", "imsi", |
dcd68168 | 220 | "excluded_ssid", "milenage", "ca_cert", "client_cert", |
2253ea44 | 221 | "private_key", "domain_suffix_match", "provisioning_sp", |
e2afdef2 | 222 | "roaming_partner", "phase1", "phase2" ] |
2232edf8 JM |
223 | for field in quoted: |
224 | if field in params: | |
225 | self.set_cred_quoted(id, field, params[field]) | |
226 | ||
a96066a5 | 227 | not_quoted = [ "eap", "roaming_consortium", "priority", |
19839f8e | 228 | "required_roaming_consortium", "sp_priority", |
9714fbcd JM |
229 | "max_bss_load", "update_identifier", "req_conn_capab", |
230 | "min_dl_bandwidth_home", "min_ul_bandwidth_home", | |
231 | "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming" ] | |
2232edf8 JM |
232 | for field in not_quoted: |
233 | if field in params: | |
234 | self.set_cred(id, field, params[field]) | |
235 | ||
bbe86767 JM |
236 | return id; |
237 | ||
ca4fd182 JM |
238 | def select_network(self, id, freq=None): |
239 | if freq: | |
240 | extra = " freq=" + freq | |
241 | else: | |
242 | extra = "" | |
243 | id = self.request("SELECT_NETWORK " + str(id) + extra) | |
81266da7 JM |
244 | if "FAIL" in id: |
245 | raise Exception("SELECT_NETWORK failed") | |
246 | return None | |
247 | ||
7559ad7a | 248 | def connect_network(self, id, timeout=10): |
81266da7 JM |
249 | self.dump_monitor() |
250 | self.select_network(id) | |
7559ad7a | 251 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) |
81266da7 JM |
252 | if ev is None: |
253 | raise Exception("Association with the AP timed out") | |
254 | self.dump_monitor() | |
255 | ||
f44c45ac JM |
256 | def get_status(self, extra=None): |
257 | if extra: | |
258 | extra = "-" + extra | |
259 | else: | |
260 | extra = "" | |
261 | res = self.request("STATUS" + extra) | |
1ae73b03 | 262 | lines = res.splitlines() |
302b7a1b | 263 | vals = dict() |
1ae73b03 | 264 | for l in lines: |
e01929c6 JM |
265 | try: |
266 | [name,value] = l.split('=', 1) | |
267 | vals[name] = value | |
268 | except ValueError, e: | |
269 | logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) | |
302b7a1b JM |
270 | return vals |
271 | ||
f44c45ac JM |
272 | def get_status_field(self, field, extra=None): |
273 | vals = self.get_status(extra) | |
302b7a1b JM |
274 | if field in vals: |
275 | return vals[field] | |
1ae73b03 JM |
276 | return None |
277 | ||
f44c45ac JM |
278 | def get_group_status(self, extra=None): |
279 | if extra: | |
280 | extra = "-" + extra | |
281 | else: | |
282 | extra = "" | |
283 | res = self.group_request("STATUS" + extra) | |
7cb08cdb | 284 | lines = res.splitlines() |
302b7a1b | 285 | vals = dict() |
7cb08cdb JM |
286 | for l in lines: |
287 | [name,value] = l.split('=', 1) | |
302b7a1b JM |
288 | vals[name] = value |
289 | return vals | |
290 | ||
f44c45ac JM |
291 | def get_group_status_field(self, field, extra=None): |
292 | vals = self.get_group_status(extra) | |
302b7a1b JM |
293 | if field in vals: |
294 | return vals[field] | |
7cb08cdb JM |
295 | return None |
296 | ||
6ca3a98b JM |
297 | def get_driver_status(self): |
298 | res = self.request("STATUS-DRIVER") | |
299 | lines = res.splitlines() | |
300 | vals = dict() | |
301 | for l in lines: | |
302 | [name,value] = l.split('=', 1) | |
303 | vals[name] = value | |
304 | return vals | |
305 | ||
306 | def get_driver_status_field(self, field): | |
307 | vals = self.get_driver_status() | |
308 | if field in vals: | |
309 | return vals[field] | |
310 | return None | |
311 | ||
44bb9106 JM |
312 | def get_mib(self): |
313 | res = self.request("MIB") | |
314 | lines = res.splitlines() | |
315 | vals = dict() | |
316 | for l in lines: | |
317 | try: | |
318 | [name,value] = l.split('=', 1) | |
319 | vals[name] = value | |
320 | except ValueError, e: | |
321 | logger.info(self.ifname + ": Ignore unexpected MIB line: " + l) | |
322 | return vals | |
323 | ||
1ae73b03 | 324 | def p2p_dev_addr(self): |
302b7a1b | 325 | return self.get_status_field("p2p_device_address") |
1ae73b03 | 326 | |
7cb08cdb | 327 | def p2p_interface_addr(self): |
302b7a1b | 328 | return self.get_group_status_field("address") |
7cb08cdb | 329 | |
1ae73b03 | 330 | def p2p_listen(self): |
0fa28afe | 331 | return self.global_request("P2P_LISTEN") |
1ae73b03 | 332 | |
5070b14a | 333 | def p2p_find(self, social=False, progressive=False, dev_id=None, dev_type=None): |
c70ebce0 | 334 | cmd = "P2P_FIND" |
1ae73b03 | 335 | if social: |
c70ebce0 | 336 | cmd = cmd + " type=social" |
5070b14a JM |
337 | elif progressive: |
338 | cmd = cmd + " type=progressive" | |
c70ebce0 JM |
339 | if dev_id: |
340 | cmd = cmd + " dev_id=" + dev_id | |
341 | if dev_type: | |
342 | cmd = cmd + " dev_type=" + dev_type | |
343 | return self.global_request(cmd) | |
1ae73b03 | 344 | |
5743006d | 345 | def p2p_stop_find(self): |
0fa28afe | 346 | return self.global_request("P2P_STOP_FIND") |
5743006d | 347 | |
1ae73b03 | 348 | def wps_read_pin(self): |
861671b6 JM |
349 | self.pin = self.request("WPS_PIN get").rstrip("\n") |
350 | if "FAIL" in self.pin: | |
351 | raise Exception("Could not generate PIN") | |
1ae73b03 JM |
352 | return self.pin |
353 | ||
354 | def peer_known(self, peer, full=True): | |
0fa28afe | 355 | res = self.global_request("P2P_PEER " + peer) |
1ae73b03 JM |
356 | if peer.lower() not in res.lower(): |
357 | return False | |
358 | if not full: | |
359 | return True | |
360 | return "[PROBE_REQ_ONLY]" not in res | |
361 | ||
d4b21766 | 362 | def discover_peer(self, peer, full=True, timeout=15, social=True, force_find=False): |
1ae73b03 | 363 | logger.info(self.ifname + ": Trying to discover peer " + peer) |
d4b21766 | 364 | if not force_find and self.peer_known(peer, full): |
1ae73b03 | 365 | return True |
d963f037 | 366 | self.p2p_find(social) |
1ae73b03 JM |
367 | count = 0 |
368 | while count < timeout: | |
369 | time.sleep(1) | |
370 | count = count + 1 | |
371 | if self.peer_known(peer, full): | |
372 | return True | |
373 | return False | |
374 | ||
451afb4f JM |
375 | def get_peer(self, peer): |
376 | res = self.global_request("P2P_PEER " + peer) | |
377 | if peer.lower() not in res.lower(): | |
378 | raise Exception("Peer information not available") | |
379 | lines = res.splitlines() | |
380 | vals = dict() | |
381 | for l in lines: | |
382 | if '=' in l: | |
383 | [name,value] = l.split('=', 1) | |
384 | vals[name] = value | |
385 | return vals | |
386 | ||
46f2cfce | 387 | def group_form_result(self, ev, expect_failure=False, go_neg_res=None): |
f7b1a750 JM |
388 | if expect_failure: |
389 | if "P2P-GROUP-STARTED" in ev: | |
390 | raise Exception("Group formation succeeded when expecting failure") | |
391 | exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' | |
392 | s = re.split(exp, ev) | |
393 | if len(s) < 3: | |
394 | return None | |
395 | res = {} | |
396 | res['result'] = 'go-neg-failed' | |
397 | res['status'] = int(s[2]) | |
398 | return res | |
399 | ||
400 | if "P2P-GROUP-STARTED" not in ev: | |
401 | raise Exception("No P2P-GROUP-STARTED event seen") | |
402 | ||
c9dc5623 | 403 | 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 | 404 | s = re.split(exp, ev) |
c9dc5623 JM |
405 | if len(s) < 11: |
406 | exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' | |
407 | s = re.split(exp, ev) | |
408 | if len(s) < 8: | |
409 | raise Exception("Could not parse P2P-GROUP-STARTED") | |
c68f9a61 JM |
410 | res = {} |
411 | res['result'] = 'success' | |
412 | res['ifname'] = s[2] | |
f3f8ee88 | 413 | self.group_ifname = s[2] |
c68f9a61 JM |
414 | res['role'] = s[3] |
415 | res['ssid'] = s[4] | |
416 | res['freq'] = s[5] | |
451afb4f JM |
417 | if "[PERSISTENT]" in ev: |
418 | res['persistent'] = True | |
419 | else: | |
420 | res['persistent'] = False | |
c68f9a61 JM |
421 | p = re.match(r'psk=([0-9a-f]*)', s[6]) |
422 | if p: | |
423 | res['psk'] = p.group(1) | |
424 | p = re.match(r'passphrase="(.*)"', s[6]) | |
425 | if p: | |
426 | res['passphrase'] = p.group(1) | |
427 | res['go_dev_addr'] = s[7] | |
46f2cfce | 428 | |
c9dc5623 JM |
429 | if len(s) > 8 and len(s[8]) > 0: |
430 | res['ip_addr'] = s[8] | |
431 | if len(s) > 9: | |
432 | res['ip_mask'] = s[9] | |
433 | if len(s) > 10: | |
434 | res['go_ip_addr'] = s[10] | |
435 | ||
46f2cfce JM |
436 | if go_neg_res: |
437 | exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' | |
438 | s = re.split(exp, go_neg_res) | |
439 | if len(s) < 4: | |
440 | raise Exception("Could not parse P2P-GO-NEG-SUCCESS") | |
441 | res['go_neg_role'] = s[2] | |
442 | res['go_neg_freq'] = s[3] | |
443 | ||
c68f9a61 JM |
444 | return res |
445 | ||
46f2cfce | 446 | def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, persistent=False, freq=None): |
1ae73b03 JM |
447 | if not self.discover_peer(peer): |
448 | raise Exception("Peer " + peer + " not found") | |
449 | self.dump_monitor() | |
450 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" | |
809079d3 JM |
451 | if go_intent: |
452 | cmd = cmd + ' go_intent=' + str(go_intent) | |
46f2cfce JM |
453 | if freq: |
454 | cmd = cmd + ' freq=' + str(freq) | |
451afb4f JM |
455 | if persistent: |
456 | cmd = cmd + " persistent" | |
0fa28afe | 457 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
458 | return None |
459 | raise Exception("P2P_CONNECT (auth) failed") | |
460 | ||
809079d3 | 461 | def p2p_go_neg_auth_result(self, timeout=1, expect_failure=False): |
46f2cfce JM |
462 | go_neg_res = None |
463 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
464 | "P2P-GO-NEG-FAILURE"], timeout); | |
c68f9a61 | 465 | if ev is None: |
809079d3 JM |
466 | if expect_failure: |
467 | return None | |
c68f9a61 | 468 | raise Exception("Group formation timed out") |
46f2cfce JM |
469 | if "P2P-GO-NEG-SUCCESS" in ev: |
470 | go_neg_res = ev | |
471 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout); | |
472 | if ev is None: | |
473 | if expect_failure: | |
474 | return None | |
475 | raise Exception("Group formation timed out") | |
c68f9a61 | 476 | self.dump_monitor() |
46f2cfce | 477 | return self.group_form_result(ev, expect_failure, go_neg_res) |
c68f9a61 | 478 | |
bdc44764 | 479 | 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): |
1ae73b03 JM |
480 | if not self.discover_peer(peer): |
481 | raise Exception("Peer " + peer + " not found") | |
482 | self.dump_monitor() | |
1a4d80b8 JM |
483 | if pin: |
484 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method | |
485 | else: | |
486 | cmd = "P2P_CONNECT " + peer + " " + method | |
809079d3 JM |
487 | if go_intent: |
488 | cmd = cmd + ' go_intent=' + str(go_intent) | |
ef2bd5a3 JM |
489 | if freq: |
490 | cmd = cmd + ' freq=' + str(freq) | |
451afb4f JM |
491 | if persistent: |
492 | cmd = cmd + " persistent" | |
bdc44764 JM |
493 | elif persistent_id: |
494 | cmd = cmd + " persistent=" + persistent_id | |
08e4bd87 JM |
495 | if provdisc: |
496 | cmd = cmd + " provdisc" | |
0fa28afe | 497 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
498 | if timeout == 0: |
499 | self.dump_monitor() | |
500 | return None | |
46f2cfce JM |
501 | go_neg_res = None |
502 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
503 | "P2P-GO-NEG-FAILURE"], timeout) | |
c68f9a61 | 504 | if ev is None: |
809079d3 JM |
505 | if expect_failure: |
506 | return None | |
c68f9a61 | 507 | raise Exception("Group formation timed out") |
46f2cfce JM |
508 | if "P2P-GO-NEG-SUCCESS" in ev: |
509 | go_neg_res = ev | |
510 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) | |
511 | if ev is None: | |
512 | if expect_failure: | |
513 | return None | |
514 | raise Exception("Group formation timed out") | |
c68f9a61 | 515 | self.dump_monitor() |
46f2cfce | 516 | return self.group_form_result(ev, expect_failure, go_neg_res) |
1ae73b03 JM |
517 | raise Exception("P2P_CONNECT failed") |
518 | ||
cf9189b9 | 519 | def wait_event(self, events, timeout=10): |
36408936 JM |
520 | start = os.times()[4] |
521 | while True: | |
1ae73b03 JM |
522 | while self.mon.pending(): |
523 | ev = self.mon.recv() | |
809079d3 | 524 | logger.debug(self.ifname + ": " + ev) |
f7b1a750 JM |
525 | for event in events: |
526 | if event in ev: | |
527 | return ev | |
36408936 JM |
528 | now = os.times()[4] |
529 | remaining = start + timeout - now | |
530 | if remaining <= 0: | |
531 | break | |
532 | if not self.mon.pending(timeout=remaining): | |
533 | break | |
c68f9a61 | 534 | return None |
1ae73b03 | 535 | |
0fa28afe JM |
536 | def wait_global_event(self, events, timeout): |
537 | if self.global_iface is None: | |
538 | self.wait_event(events, timeout) | |
539 | else: | |
36408936 JM |
540 | start = os.times()[4] |
541 | while True: | |
0fa28afe JM |
542 | while self.global_mon.pending(): |
543 | ev = self.global_mon.recv() | |
9d507452 | 544 | logger.debug(self.ifname + "(global): " + ev) |
0fa28afe JM |
545 | for event in events: |
546 | if event in ev: | |
547 | return ev | |
36408936 JM |
548 | now = os.times()[4] |
549 | remaining = start + timeout - now | |
550 | if remaining <= 0: | |
551 | break | |
552 | if not self.global_mon.pending(timeout=remaining): | |
553 | break | |
0fa28afe JM |
554 | return None |
555 | ||
2c914e24 JM |
556 | def wait_go_ending_session(self): |
557 | ev = self.wait_event(["P2P-GROUP-REMOVED"], timeout=3) | |
558 | if ev is None: | |
559 | raise Exception("Group removal event timed out") | |
560 | if "reason=GO_ENDING_SESSION" not in ev: | |
561 | raise Exception("Unexpected group removal reason") | |
562 | ||
1ae73b03 JM |
563 | def dump_monitor(self): |
564 | while self.mon.pending(): | |
565 | ev = self.mon.recv() | |
566 | logger.debug(self.ifname + ": " + ev) | |
9d507452 JM |
567 | while self.global_mon.pending(): |
568 | ev = self.global_mon.recv() | |
569 | logger.debug(self.ifname + "(global): " + ev) | |
3eb29b7b | 570 | |
a311c61d JM |
571 | def remove_group(self, ifname=None): |
572 | if ifname is None: | |
451afb4f | 573 | ifname = self.group_ifname if self.group_ifname else self.ifname |
0fa28afe | 574 | if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): |
3eb29b7b | 575 | raise Exception("Group could not be removed") |
f3f8ee88 | 576 | self.group_ifname = None |
4ea8d3b5 | 577 | |
5924d4c1 | 578 | def p2p_start_go(self, persistent=None, freq=None): |
4ea8d3b5 JM |
579 | self.dump_monitor() |
580 | cmd = "P2P_GROUP_ADD" | |
07a2e61b JM |
581 | if persistent is None: |
582 | pass | |
583 | elif persistent is True: | |
584 | cmd = cmd + " persistent" | |
585 | else: | |
586 | cmd = cmd + " persistent=" + str(persistent) | |
5924d4c1 | 587 | if freq: |
ef2bd5a3 | 588 | cmd = cmd + " freq=" + str(freq) |
0fa28afe JM |
589 | if "OK" in self.global_request(cmd): |
590 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) | |
4ea8d3b5 JM |
591 | if ev is None: |
592 | raise Exception("GO start up timed out") | |
593 | self.dump_monitor() | |
594 | return self.group_form_result(ev) | |
595 | raise Exception("P2P_GROUP_ADD failed") | |
596 | ||
597 | def p2p_go_authorize_client(self, pin): | |
598 | cmd = "WPS_PIN any " + pin | |
f3f8ee88 | 599 | if "FAIL" in self.group_request(cmd): |
4ea8d3b5 JM |
600 | raise Exception("Failed to authorize client connection on GO") |
601 | return None | |
602 | ||
b162675f JM |
603 | def p2p_go_authorize_client_pbc(self): |
604 | cmd = "WPS_PBC" | |
605 | if "FAIL" in self.group_request(cmd): | |
606 | raise Exception("Failed to authorize client connection on GO") | |
607 | return None | |
608 | ||
41af1305 | 609 | def p2p_connect_group(self, go_addr, pin, timeout=0, social=False): |
4ea8d3b5 | 610 | self.dump_monitor() |
41af1305 | 611 | if not self.discover_peer(go_addr, social=social): |
4ea8d3b5 JM |
612 | raise Exception("GO " + go_addr + " not found") |
613 | self.dump_monitor() | |
614 | cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" | |
0fa28afe | 615 | if "OK" in self.global_request(cmd): |
4ea8d3b5 JM |
616 | if timeout == 0: |
617 | self.dump_monitor() | |
618 | return None | |
0fa28afe | 619 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) |
4ea8d3b5 JM |
620 | if ev is None: |
621 | raise Exception("Joining the group timed out") | |
622 | self.dump_monitor() | |
623 | return self.group_form_result(ev) | |
624 | raise Exception("P2P_CONNECT(join) failed") | |
7cb08cdb JM |
625 | |
626 | def tdls_setup(self, peer): | |
627 | cmd = "TDLS_SETUP " + peer | |
628 | if "FAIL" in self.group_request(cmd): | |
629 | raise Exception("Failed to request TDLS setup") | |
630 | return None | |
631 | ||
632 | def tdls_teardown(self, peer): | |
633 | cmd = "TDLS_TEARDOWN " + peer | |
634 | if "FAIL" in self.group_request(cmd): | |
635 | raise Exception("Failed to request TDLS teardown") | |
636 | return None | |
b61e418c | 637 | |
6f939e59 | 638 | def connect(self, ssid=None, ssid2=None, **kwargs): |
b61e418c JM |
639 | logger.info("Connect STA " + self.ifname + " to AP") |
640 | id = self.add_network() | |
d78f3303 JM |
641 | if ssid: |
642 | self.set_network_quoted(id, "ssid", ssid) | |
643 | elif ssid2: | |
644 | self.set_network(id, "ssid", ssid2) | |
6f939e59 JM |
645 | |
646 | quoted = [ "psk", "identity", "anonymous_identity", "password", | |
647 | "ca_cert", "client_cert", "private_key", | |
648 | "private_key_passwd", "ca_cert2", "client_cert2", | |
649 | "private_key2", "phase1", "phase2", "domain_suffix_match", | |
31ec9faf | 650 | "altsubject_match", "subject_match", "pac_file", "dh_file", |
08429720 | 651 | "bgscan", "ht_mcs" ] |
6f939e59 JM |
652 | for field in quoted: |
653 | if field in kwargs and kwargs[field]: | |
654 | self.set_network_quoted(id, field, kwargs[field]) | |
655 | ||
656 | not_quoted = [ "proto", "key_mgmt", "ieee80211w", "pairwise", | |
657 | "group", "wep_key0", "scan_freq", "eap", | |
fb5c8cea | 658 | "eapol_flags", "fragment_size", "scan_ssid", "auth_alg", |
08429720 JM |
659 | "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid", |
660 | "disable_max_amsdu", "ampdu_factor", "ampdu_density", | |
661 | "disable_ht40", "disable_sgi", "disable_ldpc" ] | |
6f939e59 JM |
662 | for field in not_quoted: |
663 | if field in kwargs and kwargs[field]: | |
664 | self.set_network(id, field, kwargs[field]) | |
665 | ||
666 | if "raw_psk" in kwargs and kwargs['raw_psk']: | |
667 | self.set_network(id, "psk", kwargs['raw_psk']) | |
668 | if "password_hex" in kwargs and kwargs['password_hex']: | |
669 | self.set_network(id, "password", kwargs['password_hex']) | |
670 | if "peerkey" in kwargs and kwargs['peerkey']: | |
4a5a5792 | 671 | self.set_network(id, "peerkey", "1") |
6f939e59 | 672 | if "okc" in kwargs and kwargs['okc']: |
0fab9ce6 | 673 | self.set_network(id, "proactive_key_caching", "1") |
6f939e59 JM |
674 | if "ocsp" in kwargs and kwargs['ocsp']: |
675 | self.set_network(id, "ocsp", str(kwargs['ocsp'])) | |
676 | if "only_add_network" in kwargs and kwargs['only_add_network']: | |
a6cf5cd6 | 677 | return id |
6f939e59 JM |
678 | if "wait_connect" not in kwargs or kwargs['wait_connect']: |
679 | if "eap" in kwargs: | |
7559ad7a JM |
680 | self.connect_network(id, timeout=20) |
681 | else: | |
682 | self.connect_network(id) | |
9626962d JM |
683 | else: |
684 | self.dump_monitor() | |
685 | self.select_network(id) | |
709f18d5 | 686 | return id |
5126138c | 687 | |
b3ec107c | 688 | def scan(self, type=None, freq=None, no_wait=False, only_new=False): |
5126138c JM |
689 | if type: |
690 | cmd = "SCAN TYPE=" + type | |
691 | else: | |
692 | cmd = "SCAN" | |
0589f401 JM |
693 | if freq: |
694 | cmd = cmd + " freq=" + freq | |
b3ec107c JM |
695 | if only_new: |
696 | cmd += " only_new=1" | |
d7a99700 JM |
697 | if not no_wait: |
698 | self.dump_monitor() | |
5126138c JM |
699 | if not "OK" in self.request(cmd): |
700 | raise Exception("Failed to trigger scan") | |
d7a99700 JM |
701 | if no_wait: |
702 | return | |
5126138c JM |
703 | ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS"], 15) |
704 | if ev is None: | |
705 | raise Exception("Scan timed out") | |
706 | ||
3b808945 | 707 | def roam(self, bssid, fail_test=False): |
5126138c JM |
708 | self.dump_monitor() |
709 | self.request("ROAM " + bssid) | |
3b808945 JM |
710 | if fail_test: |
711 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
712 | if ev is not None: | |
713 | raise Exception("Unexpected connection") | |
714 | self.dump_monitor() | |
715 | return | |
5126138c JM |
716 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10) |
717 | if ev is None: | |
718 | raise Exception("Roaming with the AP timed out") | |
719 | self.dump_monitor() | |
6edaee9c | 720 | |
3b808945 | 721 | def roam_over_ds(self, bssid, fail_test=False): |
b553eab1 JM |
722 | self.dump_monitor() |
723 | self.request("FT_DS " + bssid) | |
3b808945 JM |
724 | if fail_test: |
725 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
726 | if ev is not None: | |
727 | raise Exception("Unexpected connection") | |
728 | self.dump_monitor() | |
729 | return | |
b553eab1 JM |
730 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10) |
731 | if ev is None: | |
732 | raise Exception("Roaming with the AP timed out") | |
733 | self.dump_monitor() | |
734 | ||
6edaee9c | 735 | def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, |
6645ff50 | 736 | new_passphrase=None, no_wait=False): |
6edaee9c JM |
737 | self.dump_monitor() |
738 | if new_ssid: | |
739 | self.request("WPS_REG " + bssid + " " + pin + " " + | |
740 | new_ssid.encode("hex") + " " + key_mgmt + " " + | |
741 | cipher + " " + new_passphrase.encode("hex")) | |
6645ff50 JM |
742 | if no_wait: |
743 | return | |
6edaee9c JM |
744 | ev = self.wait_event(["WPS-SUCCESS"], timeout=15) |
745 | else: | |
746 | self.request("WPS_REG " + bssid + " " + pin) | |
6645ff50 JM |
747 | if no_wait: |
748 | return | |
6edaee9c JM |
749 | ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) |
750 | if ev is None: | |
751 | raise Exception("WPS cred timed out") | |
752 | ev = self.wait_event(["WPS-FAIL"], timeout=15) | |
753 | if ev is None: | |
754 | raise Exception("WPS timed out") | |
755 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=15) | |
756 | if ev is None: | |
757 | raise Exception("Association with the AP timed out") | |
57661377 JM |
758 | |
759 | def relog(self): | |
760 | self.request("RELOG") | |
41af1305 JM |
761 | |
762 | def wait_completed(self, timeout=10): | |
763 | for i in range(0, timeout * 2): | |
764 | if self.get_status_field("wpa_state") == "COMPLETED": | |
765 | return | |
766 | time.sleep(0.5) | |
767 | raise Exception("Timeout while waiting for COMPLETED state") | |
0eff1ab3 JM |
768 | |
769 | def get_capability(self, field): | |
770 | res = self.request("GET_CAPABILITY " + field) | |
771 | if "FAIL" in res: | |
772 | return None | |
773 | return res.split(' ') | |
2cdd91d8 JM |
774 | |
775 | def get_bss(self, bssid): | |
776 | res = self.request("BSS " + bssid) | |
777 | lines = res.splitlines() | |
778 | vals = dict() | |
779 | for l in lines: | |
780 | [name,value] = l.split('=', 1) | |
781 | vals[name] = value | |
782 | return vals | |
0fab9ce6 JM |
783 | |
784 | def get_pmksa(self, bssid): | |
785 | res = self.request("PMKSA") | |
786 | lines = res.splitlines() | |
787 | for l in lines: | |
788 | if bssid not in l: | |
789 | continue | |
790 | vals = dict() | |
791 | [index,aa,pmkid,expiration,opportunistic] = l.split(' ') | |
792 | vals['index'] = index | |
793 | vals['pmkid'] = pmkid | |
794 | vals['expiration'] = expiration | |
795 | vals['opportunistic'] = opportunistic | |
796 | return vals | |
797 | return None | |
e1a5e09a JM |
798 | |
799 | def get_sta(self, addr, info=None, next=False): | |
800 | cmd = "STA-NEXT " if next else "STA " | |
801 | if addr is None: | |
802 | res = self.request("STA-FIRST") | |
803 | elif info: | |
804 | res = self.request(cmd + addr + " " + info) | |
805 | else: | |
806 | res = self.request(cmd + addr) | |
807 | lines = res.splitlines() | |
808 | vals = dict() | |
809 | first = True | |
810 | for l in lines: | |
811 | if first: | |
812 | vals['addr'] = l | |
813 | first = False | |
814 | else: | |
815 | [name,value] = l.split('=', 1) | |
816 | vals[name] = value | |
817 | return vals | |
daf3806d JM |
818 | |
819 | def mgmt_rx(self, timeout=5): | |
820 | ev = self.wait_event(["MGMT-RX"], timeout=timeout) | |
821 | if ev is None: | |
822 | return None | |
823 | msg = {} | |
824 | items = ev.split(' ') | |
825 | field,val = items[1].split('=') | |
826 | if field != "freq": | |
827 | raise Exception("Unexpected MGMT-RX event format: " + ev) | |
828 | msg['freq'] = val | |
829 | frame = binascii.unhexlify(items[4]) | |
830 | msg['frame'] = frame | |
831 | ||
832 | hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) | |
833 | msg['fc'] = hdr[0] | |
834 | msg['subtype'] = (hdr[0] >> 4) & 0xf | |
835 | hdr = hdr[1:] | |
836 | msg['duration'] = hdr[0] | |
837 | hdr = hdr[1:] | |
838 | msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
839 | hdr = hdr[6:] | |
840 | msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
841 | hdr = hdr[6:] | |
842 | msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
843 | hdr = hdr[6:] | |
844 | msg['seq_ctrl'] = hdr[0] | |
845 | msg['payload'] = frame[24:] | |
846 | ||
847 | return msg |