]>
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 | 212 | "private_key", "domain_suffix_match", "provisioning_sp", |
e2afdef2 | 213 | "roaming_partner", "phase1", "phase2" ] |
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", |
9714fbcd JM |
220 | "max_bss_load", "update_identifier", "req_conn_capab", |
221 | "min_dl_bandwidth_home", "min_ul_bandwidth_home", | |
222 | "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming" ] | |
2232edf8 JM |
223 | for field in not_quoted: |
224 | if field in params: | |
225 | self.set_cred(id, field, params[field]) | |
226 | ||
bbe86767 JM |
227 | return id; |
228 | ||
81266da7 JM |
229 | def select_network(self, id): |
230 | id = self.request("SELECT_NETWORK " + str(id)) | |
231 | if "FAIL" in id: | |
232 | raise Exception("SELECT_NETWORK failed") | |
233 | return None | |
234 | ||
7559ad7a | 235 | def connect_network(self, id, timeout=10): |
81266da7 JM |
236 | self.dump_monitor() |
237 | self.select_network(id) | |
7559ad7a | 238 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) |
81266da7 JM |
239 | if ev is None: |
240 | raise Exception("Association with the AP timed out") | |
241 | self.dump_monitor() | |
242 | ||
302b7a1b | 243 | def get_status(self): |
1ae73b03 JM |
244 | res = self.request("STATUS") |
245 | lines = res.splitlines() | |
302b7a1b | 246 | vals = dict() |
1ae73b03 | 247 | for l in lines: |
e01929c6 JM |
248 | try: |
249 | [name,value] = l.split('=', 1) | |
250 | vals[name] = value | |
251 | except ValueError, e: | |
252 | logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) | |
302b7a1b JM |
253 | return vals |
254 | ||
255 | def get_status_field(self, field): | |
256 | vals = self.get_status() | |
257 | if field in vals: | |
258 | return vals[field] | |
1ae73b03 JM |
259 | return None |
260 | ||
302b7a1b | 261 | def get_group_status(self): |
7cb08cdb JM |
262 | res = self.group_request("STATUS") |
263 | lines = res.splitlines() | |
302b7a1b | 264 | vals = dict() |
7cb08cdb JM |
265 | for l in lines: |
266 | [name,value] = l.split('=', 1) | |
302b7a1b JM |
267 | vals[name] = value |
268 | return vals | |
269 | ||
270 | def get_group_status_field(self, field): | |
271 | vals = self.get_group_status() | |
272 | if field in vals: | |
273 | return vals[field] | |
7cb08cdb JM |
274 | return None |
275 | ||
6ca3a98b JM |
276 | def get_driver_status(self): |
277 | res = self.request("STATUS-DRIVER") | |
278 | lines = res.splitlines() | |
279 | vals = dict() | |
280 | for l in lines: | |
281 | [name,value] = l.split('=', 1) | |
282 | vals[name] = value | |
283 | return vals | |
284 | ||
285 | def get_driver_status_field(self, field): | |
286 | vals = self.get_driver_status() | |
287 | if field in vals: | |
288 | return vals[field] | |
289 | return None | |
290 | ||
1ae73b03 | 291 | def p2p_dev_addr(self): |
302b7a1b | 292 | return self.get_status_field("p2p_device_address") |
1ae73b03 | 293 | |
7cb08cdb | 294 | def p2p_interface_addr(self): |
302b7a1b | 295 | return self.get_group_status_field("address") |
7cb08cdb | 296 | |
1ae73b03 | 297 | def p2p_listen(self): |
0fa28afe | 298 | return self.global_request("P2P_LISTEN") |
1ae73b03 | 299 | |
c70ebce0 JM |
300 | def p2p_find(self, social=False, dev_id=None, dev_type=None): |
301 | cmd = "P2P_FIND" | |
1ae73b03 | 302 | if social: |
c70ebce0 JM |
303 | cmd = cmd + " type=social" |
304 | if dev_id: | |
305 | cmd = cmd + " dev_id=" + dev_id | |
306 | if dev_type: | |
307 | cmd = cmd + " dev_type=" + dev_type | |
308 | return self.global_request(cmd) | |
1ae73b03 | 309 | |
5743006d | 310 | def p2p_stop_find(self): |
0fa28afe | 311 | return self.global_request("P2P_STOP_FIND") |
5743006d | 312 | |
1ae73b03 JM |
313 | def wps_read_pin(self): |
314 | #TODO: make this random | |
315 | self.pin = "12345670" | |
316 | return self.pin | |
317 | ||
318 | def peer_known(self, peer, full=True): | |
0fa28afe | 319 | res = self.global_request("P2P_PEER " + peer) |
1ae73b03 JM |
320 | if peer.lower() not in res.lower(): |
321 | return False | |
322 | if not full: | |
323 | return True | |
324 | return "[PROBE_REQ_ONLY]" not in res | |
325 | ||
d4b21766 | 326 | def discover_peer(self, peer, full=True, timeout=15, social=True, force_find=False): |
1ae73b03 | 327 | logger.info(self.ifname + ": Trying to discover peer " + peer) |
d4b21766 | 328 | if not force_find and self.peer_known(peer, full): |
1ae73b03 | 329 | return True |
d963f037 | 330 | self.p2p_find(social) |
1ae73b03 JM |
331 | count = 0 |
332 | while count < timeout: | |
333 | time.sleep(1) | |
334 | count = count + 1 | |
335 | if self.peer_known(peer, full): | |
336 | return True | |
337 | return False | |
338 | ||
451afb4f JM |
339 | def get_peer(self, peer): |
340 | res = self.global_request("P2P_PEER " + peer) | |
341 | if peer.lower() not in res.lower(): | |
342 | raise Exception("Peer information not available") | |
343 | lines = res.splitlines() | |
344 | vals = dict() | |
345 | for l in lines: | |
346 | if '=' in l: | |
347 | [name,value] = l.split('=', 1) | |
348 | vals[name] = value | |
349 | return vals | |
350 | ||
46f2cfce | 351 | def group_form_result(self, ev, expect_failure=False, go_neg_res=None): |
f7b1a750 JM |
352 | if expect_failure: |
353 | if "P2P-GROUP-STARTED" in ev: | |
354 | raise Exception("Group formation succeeded when expecting failure") | |
355 | exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' | |
356 | s = re.split(exp, ev) | |
357 | if len(s) < 3: | |
358 | return None | |
359 | res = {} | |
360 | res['result'] = 'go-neg-failed' | |
361 | res['status'] = int(s[2]) | |
362 | return res | |
363 | ||
364 | if "P2P-GROUP-STARTED" not in ev: | |
365 | raise Exception("No P2P-GROUP-STARTED event seen") | |
366 | ||
c9dc5623 | 367 | 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 | 368 | s = re.split(exp, ev) |
c9dc5623 JM |
369 | if len(s) < 11: |
370 | exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' | |
371 | s = re.split(exp, ev) | |
372 | if len(s) < 8: | |
373 | raise Exception("Could not parse P2P-GROUP-STARTED") | |
c68f9a61 JM |
374 | res = {} |
375 | res['result'] = 'success' | |
376 | res['ifname'] = s[2] | |
f3f8ee88 | 377 | self.group_ifname = s[2] |
c68f9a61 JM |
378 | res['role'] = s[3] |
379 | res['ssid'] = s[4] | |
380 | res['freq'] = s[5] | |
451afb4f JM |
381 | if "[PERSISTENT]" in ev: |
382 | res['persistent'] = True | |
383 | else: | |
384 | res['persistent'] = False | |
c68f9a61 JM |
385 | p = re.match(r'psk=([0-9a-f]*)', s[6]) |
386 | if p: | |
387 | res['psk'] = p.group(1) | |
388 | p = re.match(r'passphrase="(.*)"', s[6]) | |
389 | if p: | |
390 | res['passphrase'] = p.group(1) | |
391 | res['go_dev_addr'] = s[7] | |
46f2cfce | 392 | |
c9dc5623 JM |
393 | if len(s) > 8 and len(s[8]) > 0: |
394 | res['ip_addr'] = s[8] | |
395 | if len(s) > 9: | |
396 | res['ip_mask'] = s[9] | |
397 | if len(s) > 10: | |
398 | res['go_ip_addr'] = s[10] | |
399 | ||
46f2cfce JM |
400 | if go_neg_res: |
401 | exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' | |
402 | s = re.split(exp, go_neg_res) | |
403 | if len(s) < 4: | |
404 | raise Exception("Could not parse P2P-GO-NEG-SUCCESS") | |
405 | res['go_neg_role'] = s[2] | |
406 | res['go_neg_freq'] = s[3] | |
407 | ||
c68f9a61 JM |
408 | return res |
409 | ||
46f2cfce | 410 | def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, persistent=False, freq=None): |
1ae73b03 JM |
411 | if not self.discover_peer(peer): |
412 | raise Exception("Peer " + peer + " not found") | |
413 | self.dump_monitor() | |
414 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" | |
809079d3 JM |
415 | if go_intent: |
416 | cmd = cmd + ' go_intent=' + str(go_intent) | |
46f2cfce JM |
417 | if freq: |
418 | cmd = cmd + ' freq=' + str(freq) | |
451afb4f JM |
419 | if persistent: |
420 | cmd = cmd + " persistent" | |
0fa28afe | 421 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
422 | return None |
423 | raise Exception("P2P_CONNECT (auth) failed") | |
424 | ||
809079d3 | 425 | def p2p_go_neg_auth_result(self, timeout=1, expect_failure=False): |
46f2cfce JM |
426 | go_neg_res = None |
427 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
428 | "P2P-GO-NEG-FAILURE"], timeout); | |
c68f9a61 | 429 | if ev is None: |
809079d3 JM |
430 | if expect_failure: |
431 | return None | |
c68f9a61 | 432 | raise Exception("Group formation timed out") |
46f2cfce JM |
433 | if "P2P-GO-NEG-SUCCESS" in ev: |
434 | go_neg_res = ev | |
435 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout); | |
436 | if ev is None: | |
437 | if expect_failure: | |
438 | return None | |
439 | raise Exception("Group formation timed out") | |
c68f9a61 | 440 | self.dump_monitor() |
46f2cfce | 441 | return self.group_form_result(ev, expect_failure, go_neg_res) |
c68f9a61 | 442 | |
ef2bd5a3 | 443 | def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None, expect_failure=False, persistent=False, freq=None): |
1ae73b03 JM |
444 | if not self.discover_peer(peer): |
445 | raise Exception("Peer " + peer + " not found") | |
446 | self.dump_monitor() | |
1a4d80b8 JM |
447 | if pin: |
448 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method | |
449 | else: | |
450 | cmd = "P2P_CONNECT " + peer + " " + method | |
809079d3 JM |
451 | if go_intent: |
452 | cmd = cmd + ' go_intent=' + str(go_intent) | |
ef2bd5a3 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 | if timeout == 0: |
459 | self.dump_monitor() | |
460 | return None | |
46f2cfce JM |
461 | go_neg_res = None |
462 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
463 | "P2P-GO-NEG-FAILURE"], timeout) | |
c68f9a61 | 464 | if ev is None: |
809079d3 JM |
465 | if expect_failure: |
466 | return None | |
c68f9a61 | 467 | raise Exception("Group formation timed out") |
46f2cfce JM |
468 | if "P2P-GO-NEG-SUCCESS" in ev: |
469 | go_neg_res = ev | |
470 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) | |
471 | if ev is None: | |
472 | if expect_failure: | |
473 | return None | |
474 | raise Exception("Group formation timed out") | |
c68f9a61 | 475 | self.dump_monitor() |
46f2cfce | 476 | return self.group_form_result(ev, expect_failure, go_neg_res) |
1ae73b03 JM |
477 | raise Exception("P2P_CONNECT failed") |
478 | ||
cf9189b9 | 479 | def wait_event(self, events, timeout=10): |
36408936 JM |
480 | start = os.times()[4] |
481 | while True: | |
1ae73b03 JM |
482 | while self.mon.pending(): |
483 | ev = self.mon.recv() | |
809079d3 | 484 | logger.debug(self.ifname + ": " + ev) |
f7b1a750 JM |
485 | for event in events: |
486 | if event in ev: | |
487 | return ev | |
36408936 JM |
488 | now = os.times()[4] |
489 | remaining = start + timeout - now | |
490 | if remaining <= 0: | |
491 | break | |
492 | if not self.mon.pending(timeout=remaining): | |
493 | break | |
c68f9a61 | 494 | return None |
1ae73b03 | 495 | |
0fa28afe JM |
496 | def wait_global_event(self, events, timeout): |
497 | if self.global_iface is None: | |
498 | self.wait_event(events, timeout) | |
499 | else: | |
36408936 JM |
500 | start = os.times()[4] |
501 | while True: | |
0fa28afe JM |
502 | while self.global_mon.pending(): |
503 | ev = self.global_mon.recv() | |
9d507452 | 504 | logger.debug(self.ifname + "(global): " + ev) |
0fa28afe JM |
505 | for event in events: |
506 | if event in ev: | |
507 | return ev | |
36408936 JM |
508 | now = os.times()[4] |
509 | remaining = start + timeout - now | |
510 | if remaining <= 0: | |
511 | break | |
512 | if not self.global_mon.pending(timeout=remaining): | |
513 | break | |
0fa28afe JM |
514 | return None |
515 | ||
2c914e24 JM |
516 | def wait_go_ending_session(self): |
517 | ev = self.wait_event(["P2P-GROUP-REMOVED"], timeout=3) | |
518 | if ev is None: | |
519 | raise Exception("Group removal event timed out") | |
520 | if "reason=GO_ENDING_SESSION" not in ev: | |
521 | raise Exception("Unexpected group removal reason") | |
522 | ||
1ae73b03 JM |
523 | def dump_monitor(self): |
524 | while self.mon.pending(): | |
525 | ev = self.mon.recv() | |
526 | logger.debug(self.ifname + ": " + ev) | |
9d507452 JM |
527 | while self.global_mon.pending(): |
528 | ev = self.global_mon.recv() | |
529 | logger.debug(self.ifname + "(global): " + ev) | |
3eb29b7b | 530 | |
a311c61d JM |
531 | def remove_group(self, ifname=None): |
532 | if ifname is None: | |
451afb4f | 533 | ifname = self.group_ifname if self.group_ifname else self.ifname |
0fa28afe | 534 | if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): |
3eb29b7b | 535 | raise Exception("Group could not be removed") |
f3f8ee88 | 536 | self.group_ifname = None |
4ea8d3b5 | 537 | |
5924d4c1 | 538 | def p2p_start_go(self, persistent=None, freq=None): |
4ea8d3b5 JM |
539 | self.dump_monitor() |
540 | cmd = "P2P_GROUP_ADD" | |
07a2e61b JM |
541 | if persistent is None: |
542 | pass | |
543 | elif persistent is True: | |
544 | cmd = cmd + " persistent" | |
545 | else: | |
546 | cmd = cmd + " persistent=" + str(persistent) | |
5924d4c1 | 547 | if freq: |
ef2bd5a3 | 548 | cmd = cmd + " freq=" + str(freq) |
0fa28afe JM |
549 | if "OK" in self.global_request(cmd): |
550 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) | |
4ea8d3b5 JM |
551 | if ev is None: |
552 | raise Exception("GO start up timed out") | |
553 | self.dump_monitor() | |
554 | return self.group_form_result(ev) | |
555 | raise Exception("P2P_GROUP_ADD failed") | |
556 | ||
557 | def p2p_go_authorize_client(self, pin): | |
558 | cmd = "WPS_PIN any " + pin | |
f3f8ee88 | 559 | if "FAIL" in self.group_request(cmd): |
4ea8d3b5 JM |
560 | raise Exception("Failed to authorize client connection on GO") |
561 | return None | |
562 | ||
b162675f JM |
563 | def p2p_go_authorize_client_pbc(self): |
564 | cmd = "WPS_PBC" | |
565 | if "FAIL" in self.group_request(cmd): | |
566 | raise Exception("Failed to authorize client connection on GO") | |
567 | return None | |
568 | ||
41af1305 | 569 | def p2p_connect_group(self, go_addr, pin, timeout=0, social=False): |
4ea8d3b5 | 570 | self.dump_monitor() |
41af1305 | 571 | if not self.discover_peer(go_addr, social=social): |
4ea8d3b5 JM |
572 | raise Exception("GO " + go_addr + " not found") |
573 | self.dump_monitor() | |
574 | cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" | |
0fa28afe | 575 | if "OK" in self.global_request(cmd): |
4ea8d3b5 JM |
576 | if timeout == 0: |
577 | self.dump_monitor() | |
578 | return None | |
0fa28afe | 579 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) |
4ea8d3b5 JM |
580 | if ev is None: |
581 | raise Exception("Joining the group timed out") | |
582 | self.dump_monitor() | |
583 | return self.group_form_result(ev) | |
584 | raise Exception("P2P_CONNECT(join) failed") | |
7cb08cdb JM |
585 | |
586 | def tdls_setup(self, peer): | |
587 | cmd = "TDLS_SETUP " + peer | |
588 | if "FAIL" in self.group_request(cmd): | |
589 | raise Exception("Failed to request TDLS setup") | |
590 | return None | |
591 | ||
592 | def tdls_teardown(self, peer): | |
593 | cmd = "TDLS_TEARDOWN " + peer | |
594 | if "FAIL" in self.group_request(cmd): | |
595 | raise Exception("Failed to request TDLS teardown") | |
596 | return None | |
b61e418c | 597 | |
6f939e59 | 598 | def connect(self, ssid=None, ssid2=None, **kwargs): |
b61e418c JM |
599 | logger.info("Connect STA " + self.ifname + " to AP") |
600 | id = self.add_network() | |
d78f3303 JM |
601 | if ssid: |
602 | self.set_network_quoted(id, "ssid", ssid) | |
603 | elif ssid2: | |
604 | self.set_network(id, "ssid", ssid2) | |
6f939e59 JM |
605 | |
606 | quoted = [ "psk", "identity", "anonymous_identity", "password", | |
607 | "ca_cert", "client_cert", "private_key", | |
608 | "private_key_passwd", "ca_cert2", "client_cert2", | |
609 | "private_key2", "phase1", "phase2", "domain_suffix_match", | |
610 | "altsubject_match", "subject_match", "pac_file", "dh_file" ] | |
611 | for field in quoted: | |
612 | if field in kwargs and kwargs[field]: | |
613 | self.set_network_quoted(id, field, kwargs[field]) | |
614 | ||
615 | not_quoted = [ "proto", "key_mgmt", "ieee80211w", "pairwise", | |
616 | "group", "wep_key0", "scan_freq", "eap", | |
617 | "eapol_flags", "fragment_size", "scan_ssid", "auth_alg" ] | |
618 | for field in not_quoted: | |
619 | if field in kwargs and kwargs[field]: | |
620 | self.set_network(id, field, kwargs[field]) | |
621 | ||
622 | if "raw_psk" in kwargs and kwargs['raw_psk']: | |
623 | self.set_network(id, "psk", kwargs['raw_psk']) | |
624 | if "password_hex" in kwargs and kwargs['password_hex']: | |
625 | self.set_network(id, "password", kwargs['password_hex']) | |
626 | if "peerkey" in kwargs and kwargs['peerkey']: | |
4a5a5792 | 627 | self.set_network(id, "peerkey", "1") |
6f939e59 | 628 | if "okc" in kwargs and kwargs['okc']: |
0fab9ce6 | 629 | self.set_network(id, "proactive_key_caching", "1") |
6f939e59 JM |
630 | if "ocsp" in kwargs and kwargs['ocsp']: |
631 | self.set_network(id, "ocsp", str(kwargs['ocsp'])) | |
632 | if "only_add_network" in kwargs and kwargs['only_add_network']: | |
a6cf5cd6 | 633 | return id |
6f939e59 JM |
634 | if "wait_connect" not in kwargs or kwargs['wait_connect']: |
635 | if "eap" in kwargs: | |
7559ad7a JM |
636 | self.connect_network(id, timeout=20) |
637 | else: | |
638 | self.connect_network(id) | |
9626962d JM |
639 | else: |
640 | self.dump_monitor() | |
641 | self.select_network(id) | |
709f18d5 | 642 | return id |
5126138c | 643 | |
d7a99700 | 644 | def scan(self, type=None, freq=None, no_wait=False): |
5126138c JM |
645 | if type: |
646 | cmd = "SCAN TYPE=" + type | |
647 | else: | |
648 | cmd = "SCAN" | |
0589f401 JM |
649 | if freq: |
650 | cmd = cmd + " freq=" + freq | |
d7a99700 JM |
651 | if not no_wait: |
652 | self.dump_monitor() | |
5126138c JM |
653 | if not "OK" in self.request(cmd): |
654 | raise Exception("Failed to trigger scan") | |
d7a99700 JM |
655 | if no_wait: |
656 | return | |
5126138c JM |
657 | ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS"], 15) |
658 | if ev is None: | |
659 | raise Exception("Scan timed out") | |
660 | ||
661 | def roam(self, bssid): | |
662 | self.dump_monitor() | |
663 | self.request("ROAM " + bssid) | |
664 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10) | |
665 | if ev is None: | |
666 | raise Exception("Roaming with the AP timed out") | |
667 | self.dump_monitor() | |
6edaee9c | 668 | |
b553eab1 JM |
669 | def roam_over_ds(self, bssid): |
670 | self.dump_monitor() | |
671 | self.request("FT_DS " + bssid) | |
672 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=10) | |
673 | if ev is None: | |
674 | raise Exception("Roaming with the AP timed out") | |
675 | self.dump_monitor() | |
676 | ||
6edaee9c | 677 | def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, |
6645ff50 | 678 | new_passphrase=None, no_wait=False): |
6edaee9c JM |
679 | self.dump_monitor() |
680 | if new_ssid: | |
681 | self.request("WPS_REG " + bssid + " " + pin + " " + | |
682 | new_ssid.encode("hex") + " " + key_mgmt + " " + | |
683 | cipher + " " + new_passphrase.encode("hex")) | |
6645ff50 JM |
684 | if no_wait: |
685 | return | |
6edaee9c JM |
686 | ev = self.wait_event(["WPS-SUCCESS"], timeout=15) |
687 | else: | |
688 | self.request("WPS_REG " + bssid + " " + pin) | |
6645ff50 JM |
689 | if no_wait: |
690 | return | |
6edaee9c JM |
691 | ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) |
692 | if ev is None: | |
693 | raise Exception("WPS cred timed out") | |
694 | ev = self.wait_event(["WPS-FAIL"], timeout=15) | |
695 | if ev is None: | |
696 | raise Exception("WPS timed out") | |
697 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=15) | |
698 | if ev is None: | |
699 | raise Exception("Association with the AP timed out") | |
57661377 JM |
700 | |
701 | def relog(self): | |
702 | self.request("RELOG") | |
41af1305 JM |
703 | |
704 | def wait_completed(self, timeout=10): | |
705 | for i in range(0, timeout * 2): | |
706 | if self.get_status_field("wpa_state") == "COMPLETED": | |
707 | return | |
708 | time.sleep(0.5) | |
709 | raise Exception("Timeout while waiting for COMPLETED state") | |
0eff1ab3 JM |
710 | |
711 | def get_capability(self, field): | |
712 | res = self.request("GET_CAPABILITY " + field) | |
713 | if "FAIL" in res: | |
714 | return None | |
715 | return res.split(' ') | |
2cdd91d8 JM |
716 | |
717 | def get_bss(self, bssid): | |
718 | res = self.request("BSS " + bssid) | |
719 | lines = res.splitlines() | |
720 | vals = dict() | |
721 | for l in lines: | |
722 | [name,value] = l.split('=', 1) | |
723 | vals[name] = value | |
724 | return vals | |
0fab9ce6 JM |
725 | |
726 | def get_pmksa(self, bssid): | |
727 | res = self.request("PMKSA") | |
728 | lines = res.splitlines() | |
729 | for l in lines: | |
730 | if bssid not in l: | |
731 | continue | |
732 | vals = dict() | |
733 | [index,aa,pmkid,expiration,opportunistic] = l.split(' ') | |
734 | vals['index'] = index | |
735 | vals['pmkid'] = pmkid | |
736 | vals['expiration'] = expiration | |
737 | vals['opportunistic'] = opportunistic | |
738 | return vals | |
739 | return None | |
e1a5e09a JM |
740 | |
741 | def get_sta(self, addr, info=None, next=False): | |
742 | cmd = "STA-NEXT " if next else "STA " | |
743 | if addr is None: | |
744 | res = self.request("STA-FIRST") | |
745 | elif info: | |
746 | res = self.request(cmd + addr + " " + info) | |
747 | else: | |
748 | res = self.request(cmd + addr) | |
749 | lines = res.splitlines() | |
750 | vals = dict() | |
751 | first = True | |
752 | for l in lines: | |
753 | if first: | |
754 | vals['addr'] = l | |
755 | first = False | |
756 | else: | |
757 | [name,value] = l.split('=', 1) | |
758 | vals[name] = value | |
759 | return vals |