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