]>
Commit | Line | Data |
---|---|---|
1ae73b03 | 1 | # Python class for controlling wpa_supplicant |
04e17e50 | 2 | # Copyright (c) 2013-2019, 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 |
1ae73b03 | 13 | import wpaspy |
8ce4855b | 14 | import remotehost |
7fd9fbc2 | 15 | import subprocess |
1ae73b03 | 16 | |
c9aa4308 | 17 | logger = logging.getLogger() |
1ae73b03 JM |
18 | wpas_ctrl = '/var/run/wpa_supplicant' |
19 | ||
20 | class WpaSupplicant: | |
0fd4792b | 21 | def __init__(self, ifname=None, global_iface=None, hostname=None, |
bb5d761c JM |
22 | port=9877, global_port=9878, monitor=True): |
23 | self.monitor = monitor | |
0fd4792b | 24 | self.hostname = hostname |
f3f8ee88 | 25 | self.group_ifname = None |
b21540e6 JM |
26 | self.global_mon = None |
27 | self.global_ctrl = None | |
4823566c | 28 | self.gctrl_mon = None |
b21540e6 JM |
29 | self.ctrl = None |
30 | self.mon = None | |
31 | self.ifname = None | |
8ce4855b | 32 | self.host = remotehost.Host(hostname, ifname) |
d28cfc2a | 33 | self._group_dbg = None |
9489637b | 34 | if ifname: |
0fd4792b | 35 | self.set_ifname(ifname, hostname, port) |
afb2e8b8 | 36 | res = self.get_driver_status() |
50d06776 | 37 | if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000: |
afb2e8b8 AS |
38 | self.p2p_dev_ifname = 'p2p-dev-' + self.ifname |
39 | else: | |
40 | self.p2p_dev_ifname = ifname | |
1ae73b03 | 41 | |
0fa28afe JM |
42 | self.global_iface = global_iface |
43 | if global_iface: | |
0fd4792b JD |
44 | if hostname != None: |
45 | self.global_ctrl = wpaspy.Ctrl(hostname, global_port) | |
bb5d761c JM |
46 | if self.monitor: |
47 | self.global_mon = wpaspy.Ctrl(hostname, global_port) | |
d4944fad | 48 | self.global_dbg = hostname + "/" + str(global_port) + "/" |
0fd4792b JD |
49 | else: |
50 | self.global_ctrl = wpaspy.Ctrl(global_iface) | |
bb5d761c JM |
51 | if self.monitor: |
52 | self.global_mon = wpaspy.Ctrl(global_iface) | |
d4944fad | 53 | self.global_dbg = "" |
bb5d761c JM |
54 | if self.monitor: |
55 | self.global_mon.attach() | |
b21540e6 JM |
56 | |
57 | def __del__(self): | |
58 | self.close_monitor() | |
59 | self.close_control() | |
60 | ||
61 | def close_control_ctrl(self): | |
62 | if self.ctrl: | |
63 | del self.ctrl | |
64 | self.ctrl = None | |
65 | ||
66 | def close_control_global(self): | |
67 | if self.global_ctrl: | |
68 | del self.global_ctrl | |
69 | self.global_ctrl = None | |
70 | ||
71 | def close_control(self): | |
72 | self.close_control_ctrl() | |
73 | self.close_control_global() | |
74 | ||
75 | def close_monitor_mon(self): | |
76 | if not self.mon: | |
77 | return | |
78 | try: | |
79 | while self.mon.pending(): | |
80 | ev = self.mon.recv() | |
81 | logger.debug(self.dbg + ": " + ev) | |
82 | except: | |
83 | pass | |
84 | try: | |
85 | self.mon.detach() | |
86 | except ConnectionRefusedError: | |
87 | pass | |
6541b9db JM |
88 | except Exception as e: |
89 | if str(e) == "DETACH failed": | |
90 | pass | |
91 | else: | |
92 | raise | |
b21540e6 JM |
93 | del self.mon |
94 | self.mon = None | |
95 | ||
96 | def close_monitor_global(self): | |
97 | if not self.global_mon: | |
98 | return | |
99 | try: | |
100 | while self.global_mon.pending(): | |
101 | ev = self.global_mon.recv() | |
102 | logger.debug(self.global_dbg + ": " + ev) | |
103 | except: | |
104 | pass | |
105 | try: | |
106 | self.global_mon.detach() | |
107 | except ConnectionRefusedError: | |
108 | pass | |
6541b9db JM |
109 | except Exception as e: |
110 | if str(e) == "DETACH failed": | |
111 | pass | |
112 | else: | |
113 | raise | |
b21540e6 JM |
114 | del self.global_mon |
115 | self.global_mon = None | |
116 | ||
117 | def close_monitor_group(self): | |
118 | if not self.gctrl_mon: | |
119 | return | |
120 | try: | |
121 | while self.gctrl_mon.pending(): | |
122 | ev = self.gctrl_mon.recv() | |
123 | logger.debug(self.dbg + ": " + ev) | |
124 | except: | |
125 | pass | |
126 | try: | |
127 | self.gctrl_mon.detach() | |
128 | except: | |
129 | pass | |
130 | del self.gctrl_mon | |
131 | self.gctrl_mon = None | |
132 | ||
133 | def close_monitor(self): | |
134 | self.close_monitor_mon() | |
135 | self.close_monitor_global() | |
136 | self.close_monitor_group() | |
0fa28afe | 137 | |
e9f2d54f | 138 | def cmd_execute(self, cmd_array, shell=False): |
7fd9fbc2 | 139 | if self.hostname is None: |
e9f2d54f JM |
140 | if shell: |
141 | cmd = ' '.join(cmd_array) | |
142 | else: | |
143 | cmd = cmd_array | |
7fd9fbc2 | 144 | proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, |
e9f2d54f | 145 | stdout=subprocess.PIPE, shell=shell) |
7fd9fbc2 JA |
146 | out = proc.communicate()[0] |
147 | ret = proc.returncode | |
45b0b88f | 148 | return ret, out.decode() |
7fd9fbc2 JA |
149 | else: |
150 | return self.host.execute(cmd_array) | |
151 | ||
e3b36d42 JD |
152 | def terminate(self): |
153 | if self.global_mon: | |
b21540e6 | 154 | self.close_monitor_global() |
e3b36d42 JD |
155 | self.global_ctrl.terminate() |
156 | self.global_ctrl = None | |
157 | ||
a66d2248 | 158 | def close_ctrl(self): |
b21540e6 JM |
159 | self.close_monitor_global() |
160 | self.close_control_global() | |
a66d2248 JM |
161 | self.remove_ifname() |
162 | ||
0fd4792b | 163 | def set_ifname(self, ifname, hostname=None, port=9877): |
b21540e6 | 164 | self.remove_ifname() |
9489637b | 165 | self.ifname = ifname |
0fd4792b JD |
166 | if hostname != None: |
167 | self.ctrl = wpaspy.Ctrl(hostname, port) | |
bb5d761c JM |
168 | if self.monitor: |
169 | self.mon = wpaspy.Ctrl(hostname, port) | |
8ce4855b | 170 | self.host = remotehost.Host(hostname, ifname) |
d4944fad | 171 | self.dbg = hostname + "/" + ifname |
0fd4792b JD |
172 | else: |
173 | self.ctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
bb5d761c JM |
174 | if self.monitor: |
175 | self.mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, ifname)) | |
d4944fad | 176 | self.dbg = ifname |
bb5d761c JM |
177 | if self.monitor: |
178 | self.mon.attach() | |
9489637b JM |
179 | |
180 | def remove_ifname(self): | |
b21540e6 JM |
181 | self.close_monitor_mon() |
182 | self.close_control_ctrl() | |
183 | self.ifname = None | |
9489637b | 184 | |
0fd4792b JD |
185 | def get_ctrl_iface_port(self, ifname): |
186 | if self.hostname is None: | |
187 | return None | |
188 | ||
189 | res = self.global_request("INTERFACES ctrl") | |
190 | lines = res.splitlines() | |
191 | found = False | |
192 | for line in lines: | |
193 | words = line.split() | |
194 | if words[0] == ifname: | |
195 | found = True | |
196 | break | |
197 | if not found: | |
198 | raise Exception("Could not find UDP port for " + ifname) | |
199 | res = line.find("ctrl_iface=udp:") | |
200 | if res == -1: | |
201 | raise Exception("Wrong ctrl_interface format") | |
202 | words = line.split(":") | |
203 | return int(words[1]) | |
204 | ||
a1512a0c | 205 | def interface_add(self, ifname, config="", driver="nl80211", |
138bf118 | 206 | drv_params=None, br_ifname=None, create=False, |
06119030 | 207 | set_ifname=True, all_params=False, if_type=None): |
5a766acc | 208 | status, groups = self.host.execute(["id"]) |
8ce4855b | 209 | if status != 0: |
9489637b | 210 | group = "admin" |
8ce4855b | 211 | group = "admin" if "(admin)" in groups else "adm" |
117caa4a | 212 | cmd = "INTERFACE_ADD " + ifname + "\t" + config + "\t" + driver + "\tDIR=/var/run/wpa_supplicant GROUP=" + group |
6e917c3e JM |
213 | if drv_params: |
214 | cmd = cmd + '\t' + drv_params | |
a1512a0c JM |
215 | if br_ifname: |
216 | if not drv_params: | |
217 | cmd += '\t' | |
218 | cmd += '\t' + br_ifname | |
25f2cb61 JM |
219 | if create: |
220 | if not br_ifname: | |
221 | cmd += '\t' | |
222 | if not drv_params: | |
223 | cmd += '\t' | |
224 | cmd += '\tcreate' | |
06119030 IP |
225 | if if_type: |
226 | cmd += '\t' + if_type | |
138bf118 JM |
227 | if all_params and not create: |
228 | if not br_ifname: | |
229 | cmd += '\t' | |
230 | if not drv_params: | |
231 | cmd += '\t' | |
232 | cmd += '\t' | |
9489637b JM |
233 | if "FAIL" in self.global_request(cmd): |
234 | raise Exception("Failed to add a dynamic wpa_supplicant interface") | |
138bf118 | 235 | if not create and set_ifname: |
0fd4792b JD |
236 | port = self.get_ctrl_iface_port(ifname) |
237 | self.set_ifname(ifname, self.hostname, port) | |
afb2e8b8 | 238 | res = self.get_driver_status() |
50d06776 | 239 | if 'capa.flags' in res and int(res['capa.flags'], 0) & 0x20000000: |
afb2e8b8 AS |
240 | self.p2p_dev_ifname = 'p2p-dev-' + self.ifname |
241 | else: | |
242 | self.p2p_dev_ifname = ifname | |
9489637b JM |
243 | |
244 | def interface_remove(self, ifname): | |
245 | self.remove_ifname() | |
246 | self.global_request("INTERFACE_REMOVE " + ifname) | |
247 | ||
874057da | 248 | def request(self, cmd, timeout=10): |
d4944fad | 249 | logger.debug(self.dbg + ": CTRL: " + cmd) |
874057da | 250 | return self.ctrl.request(cmd, timeout=timeout) |
1ae73b03 | 251 | |
0fa28afe JM |
252 | def global_request(self, cmd): |
253 | if self.global_iface is None: | |
2b5488a4 | 254 | return self.request(cmd) |
0fa28afe | 255 | else: |
9489637b | 256 | ifname = self.ifname or self.global_iface |
d4944fad | 257 | logger.debug(self.global_dbg + ifname + ": CTRL(global): " + cmd) |
0fa28afe JM |
258 | return self.global_ctrl.request(cmd) |
259 | ||
d28cfc2a JA |
260 | @property |
261 | def group_dbg(self): | |
262 | if self._group_dbg is not None: | |
263 | return self._group_dbg | |
264 | if self.group_ifname is None: | |
265 | raise Exception("Cannot have group_dbg without group_ifname") | |
266 | if self.hostname is None: | |
267 | self._group_dbg = self.group_ifname | |
268 | else: | |
269 | self._group_dbg = self.hostname + "/" + self.group_ifname | |
270 | return self._group_dbg | |
271 | ||
f3f8ee88 JM |
272 | def group_request(self, cmd): |
273 | if self.group_ifname and self.group_ifname != self.ifname: | |
d28cfc2a JA |
274 | if self.hostname is None: |
275 | gctrl = wpaspy.Ctrl(os.path.join(wpas_ctrl, self.group_ifname)) | |
276 | else: | |
277 | port = self.get_ctrl_iface_port(self.group_ifname) | |
278 | gctrl = wpaspy.Ctrl(self.hostname, port) | |
279 | logger.debug(self.group_dbg + ": CTRL(group): " + cmd) | |
f3f8ee88 JM |
280 | return gctrl.request(cmd) |
281 | return self.request(cmd) | |
282 | ||
1ae73b03 JM |
283 | def ping(self): |
284 | return "PONG" in self.request("PING") | |
285 | ||
521b7e79 JM |
286 | def global_ping(self): |
287 | return "PONG" in self.global_request("PING") | |
288 | ||
1ae73b03 | 289 | def reset(self): |
521b7e79 | 290 | self.dump_monitor() |
ea0e92ee | 291 | res = self.request("FLUSH") |
a8b8da11 | 292 | if "OK" not in res: |
ea0e92ee | 293 | logger.info("FLUSH to " + self.ifname + " failed: " + res) |
09f60224 | 294 | self.global_request("REMOVE_NETWORK all") |
a2168cf4 | 295 | self.global_request("SET p2p_no_group_iface 1") |
a2168cf4 | 296 | self.global_request("P2P_FLUSH") |
b21540e6 | 297 | self.close_monitor_group() |
f3f8ee88 | 298 | self.group_ifname = None |
6edaee9c | 299 | self.dump_monitor() |
6ca3a98b JM |
300 | |
301 | iter = 0 | |
302 | while iter < 60: | |
53606b10 AO |
303 | state1 = self.get_driver_status_field("scan_state") |
304 | p2pdev = "p2p-dev-" + self.ifname | |
305 | state2 = self.get_driver_status_field("scan_state", ifname=p2pdev) | |
306 | states = str(state1) + " " + str(state2) | |
307 | if "SCAN_STARTED" in states or "SCAN_REQUESTED" in states: | |
6ca3a98b JM |
308 | logger.info(self.ifname + ": Waiting for scan operation to complete before continuing") |
309 | time.sleep(1) | |
310 | else: | |
311 | break | |
312 | iter = iter + 1 | |
313 | if iter == 60: | |
314 | logger.error(self.ifname + ": Driver scan state did not clear") | |
89896c00 | 315 | print("Trying to clear cfg80211/mac80211 scan state") |
5a766acc | 316 | status, buf = self.host.execute(["ifconfig", self.ifname, "down"]) |
8ce4855b JD |
317 | if status != 0: |
318 | logger.info("ifconfig failed: " + buf) | |
319 | logger.info(status) | |
5a766acc | 320 | status, buf = self.host.execute(["ifconfig", self.ifname, "up"]) |
8ce4855b JD |
321 | if status != 0: |
322 | logger.info("ifconfig failed: " + buf) | |
323 | logger.info(status) | |
c57c1ed6 JM |
324 | if iter > 0: |
325 | # The ongoing scan could have discovered BSSes or P2P peers | |
326 | logger.info("Run FLUSH again since scan was in progress") | |
327 | self.request("FLUSH") | |
b21df6e7 | 328 | self.dump_monitor() |
6ca3a98b | 329 | |
ea0e92ee JM |
330 | if not self.ping(): |
331 | logger.info("No PING response from " + self.ifname + " after reset") | |
1ae73b03 | 332 | |
79124c7b | 333 | def set(self, field, value): |
a8b8da11 | 334 | if "OK" not in self.request("SET " + field + " " + value): |
79124c7b JM |
335 | raise Exception("Failed to set wpa_supplicant parameter " + field) |
336 | ||
07a2e61b JM |
337 | def add_network(self): |
338 | id = self.request("ADD_NETWORK") | |
339 | if "FAIL" in id: | |
340 | raise Exception("ADD_NETWORK failed") | |
341 | return int(id) | |
342 | ||
343 | def remove_network(self, id): | |
344 | id = self.request("REMOVE_NETWORK " + str(id)) | |
345 | if "FAIL" in id: | |
346 | raise Exception("REMOVE_NETWORK failed") | |
347 | return None | |
348 | ||
ca5b81a5 JM |
349 | def get_network(self, id, field): |
350 | res = self.request("GET_NETWORK " + str(id) + " " + field) | |
351 | if res == "FAIL\n": | |
352 | return None | |
353 | return res | |
354 | ||
07a2e61b JM |
355 | def set_network(self, id, field, value): |
356 | res = self.request("SET_NETWORK " + str(id) + " " + field + " " + value) | |
357 | if "FAIL" in res: | |
358 | raise Exception("SET_NETWORK failed") | |
359 | return None | |
360 | ||
361 | def set_network_quoted(self, id, field, value): | |
362 | res = self.request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') | |
363 | if "FAIL" in res: | |
364 | raise Exception("SET_NETWORK failed") | |
365 | return None | |
366 | ||
5e65346a AS |
367 | def p2pdev_request(self, cmd): |
368 | return self.global_request("IFNAME=" + self.p2p_dev_ifname + " " + cmd) | |
369 | ||
370 | def p2pdev_add_network(self): | |
371 | id = self.p2pdev_request("ADD_NETWORK") | |
372 | if "FAIL" in id: | |
373 | raise Exception("p2pdev ADD_NETWORK failed") | |
374 | return int(id) | |
375 | ||
376 | def p2pdev_set_network(self, id, field, value): | |
377 | res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + " " + value) | |
378 | if "FAIL" in res: | |
379 | raise Exception("p2pdev SET_NETWORK failed") | |
380 | return None | |
381 | ||
382 | def p2pdev_set_network_quoted(self, id, field, value): | |
383 | res = self.p2pdev_request("SET_NETWORK " + str(id) + " " + field + ' "' + value + '"') | |
384 | if "FAIL" in res: | |
385 | raise Exception("p2pdev SET_NETWORK failed") | |
386 | return None | |
387 | ||
8e751cfa BR |
388 | def list_networks(self, p2p=False): |
389 | if p2p: | |
390 | res = self.global_request("LIST_NETWORKS") | |
391 | else: | |
392 | res = self.request("LIST_NETWORKS") | |
6a0b4002 JM |
393 | lines = res.splitlines() |
394 | networks = [] | |
395 | for l in lines: | |
396 | if "network id" in l: | |
397 | continue | |
fab49f61 | 398 | [id, ssid, bssid, flags] = l.split('\t') |
6a0b4002 JM |
399 | network = {} |
400 | network['id'] = id | |
401 | network['ssid'] = ssid | |
402 | network['bssid'] = bssid | |
403 | network['flags'] = flags | |
404 | networks.append(network) | |
405 | return networks | |
406 | ||
543f9f7e | 407 | def hs20_enable(self, auto_interworking=False): |
bbe86767 JM |
408 | self.request("SET interworking 1") |
409 | self.request("SET hs20 1") | |
543f9f7e JM |
410 | if auto_interworking: |
411 | self.request("SET auto_interworking 1") | |
412 | else: | |
413 | self.request("SET auto_interworking 0") | |
bbe86767 | 414 | |
22653762 JM |
415 | def interworking_add_network(self, bssid): |
416 | id = self.request("INTERWORKING_ADD_NETWORK " + bssid) | |
417 | if "FAIL" in id or "OK" in id: | |
418 | raise Exception("INTERWORKING_ADD_NETWORK failed") | |
419 | return int(id) | |
420 | ||
93a06242 JM |
421 | def add_cred(self): |
422 | id = self.request("ADD_CRED") | |
423 | if "FAIL" in id: | |
424 | raise Exception("ADD_CRED failed") | |
425 | return int(id) | |
426 | ||
427 | def remove_cred(self, id): | |
428 | id = self.request("REMOVE_CRED " + str(id)) | |
429 | if "FAIL" in id: | |
430 | raise Exception("REMOVE_CRED failed") | |
431 | return None | |
432 | ||
433 | def set_cred(self, id, field, value): | |
434 | res = self.request("SET_CRED " + str(id) + " " + field + " " + value) | |
435 | if "FAIL" in res: | |
436 | raise Exception("SET_CRED failed") | |
437 | return None | |
438 | ||
439 | def set_cred_quoted(self, id, field, value): | |
440 | res = self.request("SET_CRED " + str(id) + " " + field + ' "' + value + '"') | |
441 | if "FAIL" in res: | |
442 | raise Exception("SET_CRED failed") | |
443 | return None | |
444 | ||
aa45859e JM |
445 | def get_cred(self, id, field): |
446 | return self.request("GET_CRED " + str(id) + " " + field) | |
447 | ||
2232edf8 | 448 | def add_cred_values(self, params): |
bbe86767 | 449 | id = self.add_cred() |
2232edf8 | 450 | |
fab49f61 JM |
451 | quoted = ["realm", "username", "password", "domain", "imsi", |
452 | "excluded_ssid", "milenage", "ca_cert", "client_cert", | |
453 | "private_key", "domain_suffix_match", "provisioning_sp", | |
454 | "roaming_partner", "phase1", "phase2", "private_key_passwd", | |
455 | "roaming_consortiums"] | |
2232edf8 JM |
456 | for field in quoted: |
457 | if field in params: | |
458 | self.set_cred_quoted(id, field, params[field]) | |
459 | ||
fab49f61 JM |
460 | not_quoted = ["eap", "roaming_consortium", "priority", |
461 | "required_roaming_consortium", "sp_priority", | |
462 | "max_bss_load", "update_identifier", "req_conn_capab", | |
463 | "min_dl_bandwidth_home", "min_ul_bandwidth_home", | |
464 | "min_dl_bandwidth_roaming", "min_ul_bandwidth_roaming"] | |
2232edf8 JM |
465 | for field in not_quoted: |
466 | if field in params: | |
467 | self.set_cred(id, field, params[field]) | |
468 | ||
bc6e3288 | 469 | return id |
bbe86767 | 470 | |
ca4fd182 JM |
471 | def select_network(self, id, freq=None): |
472 | if freq: | |
22653762 | 473 | extra = " freq=" + str(freq) |
ca4fd182 JM |
474 | else: |
475 | extra = "" | |
476 | id = self.request("SELECT_NETWORK " + str(id) + extra) | |
81266da7 JM |
477 | if "FAIL" in id: |
478 | raise Exception("SELECT_NETWORK failed") | |
479 | return None | |
480 | ||
68157c06 JL |
481 | def mesh_group_add(self, id): |
482 | id = self.request("MESH_GROUP_ADD " + str(id)) | |
483 | if "FAIL" in id: | |
484 | raise Exception("MESH_GROUP_ADD failed") | |
485 | return None | |
486 | ||
487 | def mesh_group_remove(self): | |
488 | id = self.request("MESH_GROUP_REMOVE " + str(self.ifname)) | |
489 | if "FAIL" in id: | |
490 | raise Exception("MESH_GROUP_REMOVE failed") | |
491 | return None | |
492 | ||
67a0d4f9 JA |
493 | def connect_network(self, id, timeout=None): |
494 | if timeout is None: | |
495 | timeout = 10 if self.hostname is None else 60 | |
81266da7 JM |
496 | self.dump_monitor() |
497 | self.select_network(id) | |
5f35a5e2 | 498 | self.wait_connected(timeout=timeout) |
81266da7 JM |
499 | self.dump_monitor() |
500 | ||
f44c45ac JM |
501 | def get_status(self, extra=None): |
502 | if extra: | |
503 | extra = "-" + extra | |
504 | else: | |
505 | extra = "" | |
506 | res = self.request("STATUS" + extra) | |
1ae73b03 | 507 | lines = res.splitlines() |
302b7a1b | 508 | vals = dict() |
1ae73b03 | 509 | for l in lines: |
e01929c6 | 510 | try: |
fab49f61 | 511 | [name, value] = l.split('=', 1) |
e01929c6 | 512 | vals[name] = value |
bab493b9 | 513 | except ValueError as e: |
e01929c6 | 514 | logger.info(self.ifname + ": Ignore unexpected STATUS line: " + l) |
302b7a1b JM |
515 | return vals |
516 | ||
f44c45ac JM |
517 | def get_status_field(self, field, extra=None): |
518 | vals = self.get_status(extra) | |
302b7a1b JM |
519 | if field in vals: |
520 | return vals[field] | |
1ae73b03 JM |
521 | return None |
522 | ||
f44c45ac JM |
523 | def get_group_status(self, extra=None): |
524 | if extra: | |
525 | extra = "-" + extra | |
526 | else: | |
527 | extra = "" | |
528 | res = self.group_request("STATUS" + extra) | |
7cb08cdb | 529 | lines = res.splitlines() |
302b7a1b | 530 | vals = dict() |
7cb08cdb | 531 | for l in lines: |
6bb9d861 | 532 | try: |
fab49f61 | 533 | [name, value] = l.split('=', 1) |
6bb9d861 JB |
534 | except ValueError: |
535 | logger.info(self.ifname + ": Ignore unexpected status line: " + l) | |
536 | continue | |
302b7a1b JM |
537 | vals[name] = value |
538 | return vals | |
539 | ||
f44c45ac JM |
540 | def get_group_status_field(self, field, extra=None): |
541 | vals = self.get_group_status(extra) | |
302b7a1b JM |
542 | if field in vals: |
543 | return vals[field] | |
7cb08cdb JM |
544 | return None |
545 | ||
53606b10 AO |
546 | def get_driver_status(self, ifname=None): |
547 | if ifname is None: | |
548 | res = self.request("STATUS-DRIVER") | |
549 | else: | |
550 | res = self.global_request("IFNAME=%s STATUS-DRIVER" % ifname) | |
c0183847 JM |
551 | if res.startswith("FAIL"): |
552 | return dict() | |
6ca3a98b JM |
553 | lines = res.splitlines() |
554 | vals = dict() | |
555 | for l in lines: | |
6bb9d861 | 556 | try: |
fab49f61 | 557 | [name, value] = l.split('=', 1) |
6bb9d861 JB |
558 | except ValueError: |
559 | logger.info(self.ifname + ": Ignore unexpected status-driver line: " + l) | |
560 | continue | |
6ca3a98b JM |
561 | vals[name] = value |
562 | return vals | |
563 | ||
53606b10 AO |
564 | def get_driver_status_field(self, field, ifname=None): |
565 | vals = self.get_driver_status(ifname) | |
6ca3a98b JM |
566 | if field in vals: |
567 | return vals[field] | |
568 | return None | |
569 | ||
5fe7a426 | 570 | def get_mcc(self): |
0663ae22 JM |
571 | mcc = int(self.get_driver_status_field('capa.num_multichan_concurrent')) |
572 | return 1 if mcc < 2 else mcc | |
5fe7a426 | 573 | |
44bb9106 JM |
574 | def get_mib(self): |
575 | res = self.request("MIB") | |
576 | lines = res.splitlines() | |
577 | vals = dict() | |
578 | for l in lines: | |
579 | try: | |
fab49f61 | 580 | [name, value] = l.split('=', 1) |
44bb9106 | 581 | vals[name] = value |
bab493b9 | 582 | except ValueError as e: |
44bb9106 JM |
583 | logger.info(self.ifname + ": Ignore unexpected MIB line: " + l) |
584 | return vals | |
585 | ||
1ae73b03 | 586 | def p2p_dev_addr(self): |
302b7a1b | 587 | return self.get_status_field("p2p_device_address") |
1ae73b03 | 588 | |
7cb08cdb | 589 | def p2p_interface_addr(self): |
302b7a1b | 590 | return self.get_group_status_field("address") |
7cb08cdb | 591 | |
f6420942 JM |
592 | def own_addr(self): |
593 | try: | |
594 | res = self.p2p_interface_addr() | |
595 | except: | |
596 | res = self.p2p_dev_addr() | |
597 | return res | |
598 | ||
1ae73b03 | 599 | def p2p_listen(self): |
0fa28afe | 600 | return self.global_request("P2P_LISTEN") |
1ae73b03 | 601 | |
a3dc7502 AO |
602 | def p2p_ext_listen(self, period, interval): |
603 | return self.global_request("P2P_EXT_LISTEN %d %d" % (period, interval)) | |
604 | ||
605 | def p2p_cancel_ext_listen(self): | |
606 | return self.global_request("P2P_EXT_LISTEN") | |
607 | ||
7457c635 | 608 | def p2p_find(self, social=False, progressive=False, dev_id=None, |
6d0b4474 | 609 | dev_type=None, delay=None, freq=None): |
c70ebce0 | 610 | cmd = "P2P_FIND" |
1ae73b03 | 611 | if social: |
c70ebce0 | 612 | cmd = cmd + " type=social" |
5070b14a JM |
613 | elif progressive: |
614 | cmd = cmd + " type=progressive" | |
c70ebce0 JM |
615 | if dev_id: |
616 | cmd = cmd + " dev_id=" + dev_id | |
617 | if dev_type: | |
618 | cmd = cmd + " dev_type=" + dev_type | |
7457c635 JM |
619 | if delay: |
620 | cmd = cmd + " delay=" + str(delay) | |
6d0b4474 JM |
621 | if freq: |
622 | cmd = cmd + " freq=" + str(freq) | |
c70ebce0 | 623 | return self.global_request(cmd) |
1ae73b03 | 624 | |
5743006d | 625 | def p2p_stop_find(self): |
0fa28afe | 626 | return self.global_request("P2P_STOP_FIND") |
5743006d | 627 | |
1ae73b03 | 628 | def wps_read_pin(self): |
861671b6 JM |
629 | self.pin = self.request("WPS_PIN get").rstrip("\n") |
630 | if "FAIL" in self.pin: | |
631 | raise Exception("Could not generate PIN") | |
1ae73b03 JM |
632 | return self.pin |
633 | ||
634 | def peer_known(self, peer, full=True): | |
0fa28afe | 635 | res = self.global_request("P2P_PEER " + peer) |
1ae73b03 JM |
636 | if peer.lower() not in res.lower(): |
637 | return False | |
638 | if not full: | |
639 | return True | |
640 | return "[PROBE_REQ_ONLY]" not in res | |
641 | ||
7f2fea59 JM |
642 | def discover_peer(self, peer, full=True, timeout=15, social=True, |
643 | force_find=False, freq=None): | |
1ae73b03 | 644 | logger.info(self.ifname + ": Trying to discover peer " + peer) |
d4b21766 | 645 | if not force_find and self.peer_known(peer, full): |
1ae73b03 | 646 | return True |
7f2fea59 | 647 | self.p2p_find(social, freq=freq) |
1ae73b03 | 648 | count = 0 |
ee3f9f38 JM |
649 | while count < timeout * 4: |
650 | time.sleep(0.25) | |
1ae73b03 JM |
651 | count = count + 1 |
652 | if self.peer_known(peer, full): | |
653 | return True | |
654 | return False | |
655 | ||
451afb4f JM |
656 | def get_peer(self, peer): |
657 | res = self.global_request("P2P_PEER " + peer) | |
658 | if peer.lower() not in res.lower(): | |
659 | raise Exception("Peer information not available") | |
660 | lines = res.splitlines() | |
661 | vals = dict() | |
662 | for l in lines: | |
663 | if '=' in l: | |
fab49f61 | 664 | [name, value] = l.split('=', 1) |
451afb4f JM |
665 | vals[name] = value |
666 | return vals | |
667 | ||
46f2cfce | 668 | def group_form_result(self, ev, expect_failure=False, go_neg_res=None): |
f7b1a750 JM |
669 | if expect_failure: |
670 | if "P2P-GROUP-STARTED" in ev: | |
671 | raise Exception("Group formation succeeded when expecting failure") | |
672 | exp = r'<.>(P2P-GO-NEG-FAILURE) status=([0-9]*)' | |
673 | s = re.split(exp, ev) | |
674 | if len(s) < 3: | |
675 | return None | |
676 | res = {} | |
677 | res['result'] = 'go-neg-failed' | |
678 | res['status'] = int(s[2]) | |
679 | return res | |
680 | ||
681 | if "P2P-GROUP-STARTED" not in ev: | |
682 | raise Exception("No P2P-GROUP-STARTED event seen") | |
683 | ||
c9dc5623 | 684 | 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 | 685 | s = re.split(exp, ev) |
c9dc5623 JM |
686 | if len(s) < 11: |
687 | exp = r'<.>(P2P-GROUP-STARTED) ([^ ]*) ([^ ]*) ssid="(.*)" freq=([0-9]*) ((?:psk=.*)|(?:passphrase=".*")) go_dev_addr=([0-9a-f:]*)' | |
688 | s = re.split(exp, ev) | |
689 | if len(s) < 8: | |
690 | raise Exception("Could not parse P2P-GROUP-STARTED") | |
c68f9a61 JM |
691 | res = {} |
692 | res['result'] = 'success' | |
693 | res['ifname'] = s[2] | |
f3f8ee88 | 694 | self.group_ifname = s[2] |
4823566c | 695 | try: |
d28cfc2a JA |
696 | if self.hostname is None: |
697 | self.gctrl_mon = wpaspy.Ctrl(os.path.join(wpas_ctrl, | |
698 | self.group_ifname)) | |
699 | else: | |
700 | port = self.get_ctrl_iface_port(self.group_ifname) | |
701 | self.gctrl_mon = wpaspy.Ctrl(self.hostname, port) | |
4d916a7f JM |
702 | if self.monitor: |
703 | self.gctrl_mon.attach() | |
4823566c JM |
704 | except: |
705 | logger.debug("Could not open monitor socket for group interface") | |
706 | self.gctrl_mon = None | |
c68f9a61 JM |
707 | res['role'] = s[3] |
708 | res['ssid'] = s[4] | |
709 | res['freq'] = s[5] | |
451afb4f JM |
710 | if "[PERSISTENT]" in ev: |
711 | res['persistent'] = True | |
712 | else: | |
713 | res['persistent'] = False | |
c68f9a61 JM |
714 | p = re.match(r'psk=([0-9a-f]*)', s[6]) |
715 | if p: | |
716 | res['psk'] = p.group(1) | |
717 | p = re.match(r'passphrase="(.*)"', s[6]) | |
718 | if p: | |
719 | res['passphrase'] = p.group(1) | |
720 | res['go_dev_addr'] = s[7] | |
46f2cfce | 721 | |
ebf25480 | 722 | if len(s) > 8 and len(s[8]) > 0 and "[PERSISTENT]" not in s[8]: |
c9dc5623 JM |
723 | res['ip_addr'] = s[8] |
724 | if len(s) > 9: | |
725 | res['ip_mask'] = s[9] | |
726 | if len(s) > 10: | |
727 | res['go_ip_addr'] = s[10] | |
728 | ||
46f2cfce JM |
729 | if go_neg_res: |
730 | exp = r'<.>(P2P-GO-NEG-SUCCESS) role=(GO|client) freq=([0-9]*)' | |
731 | s = re.split(exp, go_neg_res) | |
732 | if len(s) < 4: | |
733 | raise Exception("Could not parse P2P-GO-NEG-SUCCESS") | |
734 | res['go_neg_role'] = s[2] | |
735 | res['go_neg_freq'] = s[3] | |
736 | ||
c68f9a61 JM |
737 | return res |
738 | ||
35b61a49 JM |
739 | def p2p_go_neg_auth(self, peer, pin, method, go_intent=None, |
740 | persistent=False, freq=None, freq2=None, | |
741 | max_oper_chwidth=None, ht40=False, vht=False): | |
1ae73b03 JM |
742 | if not self.discover_peer(peer): |
743 | raise Exception("Peer " + peer + " not found") | |
744 | self.dump_monitor() | |
1f53fe03 JM |
745 | if pin: |
746 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method + " auth" | |
747 | else: | |
748 | cmd = "P2P_CONNECT " + peer + " " + method + " auth" | |
809079d3 JM |
749 | if go_intent: |
750 | cmd = cmd + ' go_intent=' + str(go_intent) | |
46f2cfce JM |
751 | if freq: |
752 | cmd = cmd + ' freq=' + str(freq) | |
35b61a49 JM |
753 | if freq2: |
754 | cmd = cmd + ' freq2=' + str(freq2) | |
755 | if max_oper_chwidth: | |
756 | cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) | |
757 | if ht40: | |
758 | cmd = cmd + ' ht40' | |
759 | if vht: | |
760 | cmd = cmd + ' vht' | |
451afb4f JM |
761 | if persistent: |
762 | cmd = cmd + " persistent" | |
0fa28afe | 763 | if "OK" in self.global_request(cmd): |
1ae73b03 JM |
764 | return None |
765 | raise Exception("P2P_CONNECT (auth) failed") | |
766 | ||
10f312d4 JM |
767 | def p2p_go_neg_auth_result(self, timeout=None, expect_failure=False): |
768 | if timeout is None: | |
769 | timeout = 1 if expect_failure else 5 | |
46f2cfce JM |
770 | go_neg_res = None |
771 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
bc6e3288 | 772 | "P2P-GO-NEG-FAILURE"], timeout) |
c68f9a61 | 773 | if ev is None: |
809079d3 JM |
774 | if expect_failure: |
775 | return None | |
c68f9a61 | 776 | raise Exception("Group formation timed out") |
46f2cfce JM |
777 | if "P2P-GO-NEG-SUCCESS" in ev: |
778 | go_neg_res = ev | |
bc6e3288 | 779 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) |
46f2cfce JM |
780 | if ev is None: |
781 | if expect_failure: | |
782 | return None | |
783 | raise Exception("Group formation timed out") | |
c68f9a61 | 784 | self.dump_monitor() |
46f2cfce | 785 | return self.group_form_result(ev, expect_failure, go_neg_res) |
c68f9a61 | 786 | |
35b61a49 JM |
787 | def p2p_go_neg_init(self, peer, pin, method, timeout=0, go_intent=None, |
788 | expect_failure=False, persistent=False, | |
789 | persistent_id=None, freq=None, provdisc=False, | |
790 | wait_group=True, freq2=None, max_oper_chwidth=None, | |
791 | ht40=False, vht=False): | |
1ae73b03 JM |
792 | if not self.discover_peer(peer): |
793 | raise Exception("Peer " + peer + " not found") | |
794 | self.dump_monitor() | |
1a4d80b8 JM |
795 | if pin: |
796 | cmd = "P2P_CONNECT " + peer + " " + pin + " " + method | |
797 | else: | |
798 | cmd = "P2P_CONNECT " + peer + " " + method | |
a68a7493 | 799 | if go_intent is not None: |
809079d3 | 800 | cmd = cmd + ' go_intent=' + str(go_intent) |
ef2bd5a3 JM |
801 | if freq: |
802 | cmd = cmd + ' freq=' + str(freq) | |
35b61a49 JM |
803 | if freq2: |
804 | cmd = cmd + ' freq2=' + str(freq2) | |
805 | if max_oper_chwidth: | |
806 | cmd = cmd + ' max_oper_chwidth=' + str(max_oper_chwidth) | |
807 | if ht40: | |
808 | cmd = cmd + ' ht40' | |
809 | if vht: | |
810 | cmd = cmd + ' vht' | |
451afb4f JM |
811 | if persistent: |
812 | cmd = cmd + " persistent" | |
bdc44764 JM |
813 | elif persistent_id: |
814 | cmd = cmd + " persistent=" + persistent_id | |
08e4bd87 JM |
815 | if provdisc: |
816 | cmd = cmd + " provdisc" | |
0fa28afe | 817 | if "OK" in self.global_request(cmd): |
1ae73b03 | 818 | if timeout == 0: |
1ae73b03 | 819 | return None |
46f2cfce JM |
820 | go_neg_res = None |
821 | ev = self.wait_global_event(["P2P-GO-NEG-SUCCESS", | |
822 | "P2P-GO-NEG-FAILURE"], timeout) | |
c68f9a61 | 823 | if ev is None: |
809079d3 JM |
824 | if expect_failure: |
825 | return None | |
c68f9a61 | 826 | raise Exception("Group formation timed out") |
46f2cfce | 827 | if "P2P-GO-NEG-SUCCESS" in ev: |
b43ac5d5 JM |
828 | if not wait_group: |
829 | return ev | |
46f2cfce JM |
830 | go_neg_res = ev |
831 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout) | |
832 | if ev is None: | |
833 | if expect_failure: | |
834 | return None | |
835 | raise Exception("Group formation timed out") | |
c68f9a61 | 836 | self.dump_monitor() |
46f2cfce | 837 | return self.group_form_result(ev, expect_failure, go_neg_res) |
1ae73b03 JM |
838 | raise Exception("P2P_CONNECT failed") |
839 | ||
a2674fd9 | 840 | def _wait_event(self, mon, pfx, events, timeout): |
36408936 JM |
841 | start = os.times()[4] |
842 | while True: | |
a2674fd9 JB |
843 | while mon.pending(): |
844 | ev = mon.recv() | |
845 | logger.debug(self.dbg + pfx + ev) | |
f7b1a750 JM |
846 | for event in events: |
847 | if event in ev: | |
848 | return ev | |
36408936 JM |
849 | now = os.times()[4] |
850 | remaining = start + timeout - now | |
851 | if remaining <= 0: | |
852 | break | |
a2674fd9 | 853 | if not mon.pending(timeout=remaining): |
36408936 | 854 | break |
c68f9a61 | 855 | return None |
1ae73b03 | 856 | |
a2674fd9 JB |
857 | def wait_event(self, events, timeout=10): |
858 | return self._wait_event(self.mon, ": ", events, timeout) | |
859 | ||
0fa28afe JM |
860 | def wait_global_event(self, events, timeout): |
861 | if self.global_iface is None: | |
a2674fd9 JB |
862 | return self.wait_event(events, timeout) |
863 | return self._wait_event(self.global_mon, "(global): ", | |
864 | events, timeout) | |
0fa28afe | 865 | |
4823566c JM |
866 | def wait_group_event(self, events, timeout=10): |
867 | if self.group_ifname and self.group_ifname != self.ifname: | |
868 | if self.gctrl_mon is None: | |
869 | return None | |
870 | start = os.times()[4] | |
871 | while True: | |
872 | while self.gctrl_mon.pending(): | |
873 | ev = self.gctrl_mon.recv() | |
d28cfc2a | 874 | logger.debug(self.group_dbg + "(group): " + ev) |
4823566c JM |
875 | for event in events: |
876 | if event in ev: | |
877 | return ev | |
878 | now = os.times()[4] | |
879 | remaining = start + timeout - now | |
880 | if remaining <= 0: | |
881 | break | |
882 | if not self.gctrl_mon.pending(timeout=remaining): | |
883 | break | |
884 | return None | |
885 | ||
886 | return self.wait_event(events, timeout) | |
887 | ||
2c914e24 | 888 | def wait_go_ending_session(self): |
b21540e6 | 889 | self.close_monitor_group() |
c7ca843d JA |
890 | timeout = 3 if self.hostname is None else 10 |
891 | ev = self.wait_global_event(["P2P-GROUP-REMOVED"], timeout=timeout) | |
2c914e24 JM |
892 | if ev is None: |
893 | raise Exception("Group removal event timed out") | |
894 | if "reason=GO_ENDING_SESSION" not in ev: | |
895 | raise Exception("Unexpected group removal reason") | |
896 | ||
3a88b7e6 | 897 | def dump_monitor(self, mon=True, global_mon=True): |
d9052150 JM |
898 | count_iface = 0 |
899 | count_global = 0 | |
3a88b7e6 | 900 | while mon and self.monitor and self.mon.pending(): |
1ae73b03 | 901 | ev = self.mon.recv() |
d4944fad | 902 | logger.debug(self.dbg + ": " + ev) |
d9052150 | 903 | count_iface += 1 |
3a88b7e6 | 904 | while global_mon and self.monitor and self.global_mon and self.global_mon.pending(): |
9d507452 | 905 | ev = self.global_mon.recv() |
d4944fad | 906 | logger.debug(self.global_dbg + self.ifname + "(global): " + ev) |
d9052150 JM |
907 | count_global += 1 |
908 | return (count_iface, count_global) | |
3eb29b7b | 909 | |
a311c61d | 910 | def remove_group(self, ifname=None): |
b21540e6 | 911 | self.close_monitor_group() |
a311c61d | 912 | if ifname is None: |
451afb4f | 913 | ifname = self.group_ifname if self.group_ifname else self.ifname |
0fa28afe | 914 | if "OK" not in self.global_request("P2P_GROUP_REMOVE " + ifname): |
3eb29b7b | 915 | raise Exception("Group could not be removed") |
f3f8ee88 | 916 | self.group_ifname = None |
4ea8d3b5 | 917 | |
25057d92 | 918 | def p2p_start_go(self, persistent=None, freq=None, no_event_clear=False): |
4ea8d3b5 JM |
919 | self.dump_monitor() |
920 | cmd = "P2P_GROUP_ADD" | |
07a2e61b JM |
921 | if persistent is None: |
922 | pass | |
923 | elif persistent is True: | |
924 | cmd = cmd + " persistent" | |
925 | else: | |
926 | cmd = cmd + " persistent=" + str(persistent) | |
5924d4c1 | 927 | if freq: |
ef2bd5a3 | 928 | cmd = cmd + " freq=" + str(freq) |
0fa28afe JM |
929 | if "OK" in self.global_request(cmd): |
930 | ev = self.wait_global_event(["P2P-GROUP-STARTED"], timeout=5) | |
4ea8d3b5 JM |
931 | if ev is None: |
932 | raise Exception("GO start up timed out") | |
25057d92 JM |
933 | if not no_event_clear: |
934 | self.dump_monitor() | |
4ea8d3b5 JM |
935 | return self.group_form_result(ev) |
936 | raise Exception("P2P_GROUP_ADD failed") | |
937 | ||
938 | def p2p_go_authorize_client(self, pin): | |
939 | cmd = "WPS_PIN any " + pin | |
f3f8ee88 | 940 | if "FAIL" in self.group_request(cmd): |
4ea8d3b5 JM |
941 | raise Exception("Failed to authorize client connection on GO") |
942 | return None | |
943 | ||
b162675f JM |
944 | def p2p_go_authorize_client_pbc(self): |
945 | cmd = "WPS_PBC" | |
946 | if "FAIL" in self.group_request(cmd): | |
947 | raise Exception("Failed to authorize client connection on GO") | |
948 | return None | |
949 | ||
ee3f9f38 JM |
950 | def p2p_connect_group(self, go_addr, pin, timeout=0, social=False, |
951 | freq=None): | |
4ea8d3b5 | 952 | self.dump_monitor() |
7f2fea59 | 953 | if not self.discover_peer(go_addr, social=social, freq=freq): |
54c20c9b JM |
954 | if social or not self.discover_peer(go_addr, social=social): |
955 | raise Exception("GO " + go_addr + " not found") | |
8251be17 | 956 | self.p2p_stop_find() |
4ea8d3b5 JM |
957 | self.dump_monitor() |
958 | cmd = "P2P_CONNECT " + go_addr + " " + pin + " join" | |
ee3f9f38 JM |
959 | if freq: |
960 | cmd += " freq=" + str(freq) | |
0fa28afe | 961 | if "OK" in self.global_request(cmd): |
4ea8d3b5 JM |
962 | if timeout == 0: |
963 | self.dump_monitor() | |
964 | return None | |
8251be17 JM |
965 | ev = self.wait_global_event(["P2P-GROUP-STARTED", |
966 | "P2P-GROUP-FORMATION-FAILURE"], | |
967 | timeout) | |
4ea8d3b5 JM |
968 | if ev is None: |
969 | raise Exception("Joining the group timed out") | |
8251be17 JM |
970 | if "P2P-GROUP-STARTED" not in ev: |
971 | raise Exception("Failed to join the group") | |
4ea8d3b5 JM |
972 | self.dump_monitor() |
973 | return self.group_form_result(ev) | |
974 | raise Exception("P2P_CONNECT(join) failed") | |
7cb08cdb JM |
975 | |
976 | def tdls_setup(self, peer): | |
977 | cmd = "TDLS_SETUP " + peer | |
978 | if "FAIL" in self.group_request(cmd): | |
979 | raise Exception("Failed to request TDLS setup") | |
980 | return None | |
981 | ||
982 | def tdls_teardown(self, peer): | |
983 | cmd = "TDLS_TEARDOWN " + peer | |
984 | if "FAIL" in self.group_request(cmd): | |
985 | raise Exception("Failed to request TDLS teardown") | |
986 | return None | |
b61e418c | 987 | |
2380d804 OG |
988 | def tdls_link_status(self, peer): |
989 | cmd = "TDLS_LINK_STATUS " + peer | |
990 | ret = self.group_request(cmd) | |
991 | if "FAIL" in ret: | |
992 | raise Exception("Failed to request TDLS link status") | |
993 | return ret | |
994 | ||
bc32f830 EP |
995 | def tspecs(self): |
996 | """Return (tsid, up) tuples representing current tspecs""" | |
997 | res = self.request("WMM_AC_STATUS") | |
998 | tspecs = re.findall(r"TSID=(\d+) UP=(\d+)", res) | |
999 | tspecs = [tuple(map(int, tspec)) for tspec in tspecs] | |
1000 | ||
1001 | logger.debug("tspecs: " + str(tspecs)) | |
1002 | return tspecs | |
1003 | ||
ceb767d5 JM |
1004 | def add_ts(self, tsid, up, direction="downlink", expect_failure=False, |
1005 | extra=None): | |
76133458 EP |
1006 | params = { |
1007 | "sba": 9000, | |
1008 | "nominal_msdu_size": 1500, | |
1009 | "min_phy_rate": 6000000, | |
1010 | "mean_data_rate": 1500, | |
1011 | } | |
ceb767d5 | 1012 | cmd = "WMM_AC_ADDTS %s tsid=%d up=%d" % (direction, tsid, up) |
35d8c254 | 1013 | for (key, value) in params.items(): |
76133458 | 1014 | cmd += " %s=%d" % (key, value) |
ceb767d5 JM |
1015 | if extra: |
1016 | cmd += " " + extra | |
76133458 EP |
1017 | |
1018 | if self.request(cmd).strip() != "OK": | |
1019 | raise Exception("ADDTS failed (tsid=%d up=%d)" % (tsid, up)) | |
1020 | ||
ceb767d5 JM |
1021 | if expect_failure: |
1022 | ev = self.wait_event(["TSPEC-REQ-FAILED"], timeout=2) | |
1023 | if ev is None: | |
1024 | raise Exception("ADDTS failed (time out while waiting failure)") | |
1025 | if "tsid=%d" % (tsid) not in ev: | |
1026 | raise Exception("ADDTS failed (invalid tsid in TSPEC-REQ-FAILED") | |
1027 | return | |
1028 | ||
76133458 EP |
1029 | ev = self.wait_event(["TSPEC-ADDED"], timeout=1) |
1030 | if ev is None: | |
1031 | raise Exception("ADDTS failed (time out)") | |
1032 | if "tsid=%d" % (tsid) not in ev: | |
1033 | raise Exception("ADDTS failed (invalid tsid in TSPEC-ADDED)") | |
1034 | ||
a8b8da11 | 1035 | if (tsid, up) not in self.tspecs(): |
bc32f830 EP |
1036 | raise Exception("ADDTS failed (tsid not in tspec list)") |
1037 | ||
76133458 EP |
1038 | def del_ts(self, tsid): |
1039 | if self.request("WMM_AC_DELTS %d" % (tsid)).strip() != "OK": | |
1040 | raise Exception("DELTS failed") | |
1041 | ||
1042 | ev = self.wait_event(["TSPEC-REMOVED"], timeout=1) | |
1043 | if ev is None: | |
1044 | raise Exception("DELTS failed (time out)") | |
1045 | if "tsid=%d" % (tsid) not in ev: | |
1046 | raise Exception("DELTS failed (invalid tsid in TSPEC-REMOVED)") | |
1047 | ||
bc32f830 EP |
1048 | tspecs = [(t, u) for (t, u) in self.tspecs() if t == tsid] |
1049 | if tspecs: | |
1050 | raise Exception("DELTS failed (still in tspec list)") | |
1051 | ||
6f939e59 | 1052 | def connect(self, ssid=None, ssid2=None, **kwargs): |
b61e418c JM |
1053 | logger.info("Connect STA " + self.ifname + " to AP") |
1054 | id = self.add_network() | |
d78f3303 JM |
1055 | if ssid: |
1056 | self.set_network_quoted(id, "ssid", ssid) | |
1057 | elif ssid2: | |
1058 | self.set_network(id, "ssid", ssid2) | |
6f939e59 | 1059 | |
fab49f61 JM |
1060 | quoted = ["psk", "identity", "anonymous_identity", "password", |
1061 | "ca_cert", "client_cert", "private_key", | |
1062 | "private_key_passwd", "ca_cert2", "client_cert2", | |
1063 | "private_key2", "phase1", "phase2", "domain_suffix_match", | |
1064 | "altsubject_match", "subject_match", "pac_file", "dh_file", | |
1065 | "bgscan", "ht_mcs", "id_str", "openssl_ciphers", | |
1066 | "domain_match", "dpp_connector", "sae_password", | |
1067 | "sae_password_id", "check_cert_subject"] | |
6f939e59 JM |
1068 | for field in quoted: |
1069 | if field in kwargs and kwargs[field]: | |
1070 | self.set_network_quoted(id, field, kwargs[field]) | |
1071 | ||
fab49f61 JM |
1072 | not_quoted = ["proto", "key_mgmt", "ieee80211w", "pairwise", |
1073 | "group", "wep_key0", "wep_key1", "wep_key2", "wep_key3", | |
1074 | "wep_tx_keyidx", "scan_freq", "freq_list", "eap", | |
1075 | "eapol_flags", "fragment_size", "scan_ssid", "auth_alg", | |
1076 | "wpa_ptk_rekey", "disable_ht", "disable_vht", "bssid", | |
1077 | "disable_max_amsdu", "ampdu_factor", "ampdu_density", | |
1078 | "disable_ht40", "disable_sgi", "disable_ldpc", | |
1079 | "ht40_intolerant", "update_identifier", "mac_addr", | |
1080 | "erp", "bg_scan_period", "bssid_blacklist", | |
1081 | "bssid_whitelist", "mem_only_psk", "eap_workaround", | |
1082 | "engine", "fils_dh_group", "bssid_hint", | |
1083 | "dpp_csign", "dpp_csign_expiry", | |
1084 | "dpp_netaccesskey", "dpp_netaccesskey_expiry", | |
1085 | "group_mgmt", "owe_group", | |
1086 | "roaming_consortium_selection", "ocv", | |
1087 | "multi_ap_backhaul_sta", "rx_stbc", "tx_stbc"] | |
6f939e59 JM |
1088 | for field in not_quoted: |
1089 | if field in kwargs and kwargs[field]: | |
1090 | self.set_network(id, field, kwargs[field]) | |
1091 | ||
1092 | if "raw_psk" in kwargs and kwargs['raw_psk']: | |
1093 | self.set_network(id, "psk", kwargs['raw_psk']) | |
1094 | if "password_hex" in kwargs and kwargs['password_hex']: | |
1095 | self.set_network(id, "password", kwargs['password_hex']) | |
1096 | if "peerkey" in kwargs and kwargs['peerkey']: | |
4a5a5792 | 1097 | self.set_network(id, "peerkey", "1") |
6f939e59 | 1098 | if "okc" in kwargs and kwargs['okc']: |
0fab9ce6 | 1099 | self.set_network(id, "proactive_key_caching", "1") |
6f939e59 JM |
1100 | if "ocsp" in kwargs and kwargs['ocsp']: |
1101 | self.set_network(id, "ocsp", str(kwargs['ocsp'])) | |
1102 | if "only_add_network" in kwargs and kwargs['only_add_network']: | |
a6cf5cd6 | 1103 | return id |
6f939e59 JM |
1104 | if "wait_connect" not in kwargs or kwargs['wait_connect']: |
1105 | if "eap" in kwargs: | |
7559ad7a JM |
1106 | self.connect_network(id, timeout=20) |
1107 | else: | |
1108 | self.connect_network(id) | |
9626962d JM |
1109 | else: |
1110 | self.dump_monitor() | |
1111 | self.select_network(id) | |
709f18d5 | 1112 | return id |
5126138c | 1113 | |
c0dd37f5 JM |
1114 | def scan(self, type=None, freq=None, no_wait=False, only_new=False, |
1115 | passive=False): | |
043a29ec JM |
1116 | if not no_wait: |
1117 | self.dump_monitor() | |
5126138c JM |
1118 | if type: |
1119 | cmd = "SCAN TYPE=" + type | |
1120 | else: | |
1121 | cmd = "SCAN" | |
0589f401 | 1122 | if freq: |
243dcc4a | 1123 | cmd = cmd + " freq=" + str(freq) |
b3ec107c JM |
1124 | if only_new: |
1125 | cmd += " only_new=1" | |
c0dd37f5 JM |
1126 | if passive: |
1127 | cmd += " passive=1" | |
d7a99700 JM |
1128 | if not no_wait: |
1129 | self.dump_monitor() | |
64766a77 | 1130 | res = self.request(cmd) |
a8b8da11 | 1131 | if "OK" not in res: |
64766a77 | 1132 | raise Exception("Failed to trigger scan: " + str(res)) |
d7a99700 JM |
1133 | if no_wait: |
1134 | return | |
dff690b8 JM |
1135 | ev = self.wait_event(["CTRL-EVENT-SCAN-RESULTS", |
1136 | "CTRL-EVENT-SCAN-FAILED"], 15) | |
5126138c JM |
1137 | if ev is None: |
1138 | raise Exception("Scan timed out") | |
dff690b8 JM |
1139 | if "CTRL-EVENT-SCAN-FAILED" in ev: |
1140 | raise Exception("Scan failed: " + ev) | |
5126138c | 1141 | |
c0dd37f5 JM |
1142 | def scan_for_bss(self, bssid, freq=None, force_scan=False, only_new=False, |
1143 | passive=False): | |
841bed04 JM |
1144 | if not force_scan and self.get_bss(bssid) is not None: |
1145 | return | |
1146 | for i in range(0, 10): | |
c0dd37f5 JM |
1147 | self.scan(freq=freq, type="ONLY", only_new=only_new, |
1148 | passive=passive) | |
841bed04 JM |
1149 | if self.get_bss(bssid) is not None: |
1150 | return | |
1151 | raise Exception("Could not find BSS " + bssid + " in scan") | |
1152 | ||
970a23f6 | 1153 | def flush_scan_cache(self, freq=2417): |
243dcc4a | 1154 | self.request("BSS_FLUSH 0") |
970a23f6 | 1155 | self.scan(freq=freq, only_new=True) |
c1e38fec JM |
1156 | res = self.request("SCAN_RESULTS") |
1157 | if len(res.splitlines()) > 1: | |
64766a77 | 1158 | logger.debug("Scan results remaining after first attempt to flush the results:\n" + res) |
c1e38fec JM |
1159 | self.request("BSS_FLUSH 0") |
1160 | self.scan(freq=2422, only_new=True) | |
1161 | res = self.request("SCAN_RESULTS") | |
1162 | if len(res.splitlines()) > 1: | |
1163 | logger.info("flush_scan_cache: Could not clear all BSS entries. These remain:\n" + res) | |
243dcc4a | 1164 | |
e01a492c JM |
1165 | def disconnect_and_stop_scan(self): |
1166 | self.request("DISCONNECT") | |
1167 | res = self.request("ABORT_SCAN") | |
1168 | for i in range(2 if "OK" in res else 1): | |
1169 | self.wait_event(["CTRL-EVENT-DISCONNECTED", | |
1170 | "CTRL-EVENT-SCAN-RESULTS"], timeout=0.5) | |
1171 | self.dump_monitor() | |
1172 | ||
63a7683b | 1173 | def roam(self, bssid, fail_test=False, assoc_reject_ok=False): |
5126138c | 1174 | self.dump_monitor() |
655bc8bf JM |
1175 | if "OK" not in self.request("ROAM " + bssid): |
1176 | raise Exception("ROAM failed") | |
3b808945 | 1177 | if fail_test: |
63a7683b JM |
1178 | if assoc_reject_ok: |
1179 | ev = self.wait_event(["CTRL-EVENT-CONNECTED", | |
1180 | "CTRL-EVENT-ASSOC-REJECT"], timeout=1) | |
1181 | else: | |
1182 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
1183 | if ev is not None and "CTRL-EVENT-ASSOC-REJECT" not in ev: | |
3b808945 JM |
1184 | raise Exception("Unexpected connection") |
1185 | self.dump_monitor() | |
1186 | return | |
04e17e50 JM |
1187 | ev = self.wait_event(["CTRL-EVENT-CONNECTED", |
1188 | "CTRL-EVENT-ASSOC-REJECT"], timeout=10) | |
1189 | if ev is None: | |
1190 | raise Exception("Roaming with the AP timed out") | |
1191 | if "CTRL-EVENT-ASSOC-REJECT" in ev: | |
1192 | raise Exception("Roaming association rejected") | |
5126138c | 1193 | self.dump_monitor() |
6edaee9c | 1194 | |
3b808945 | 1195 | def roam_over_ds(self, bssid, fail_test=False): |
b553eab1 | 1196 | self.dump_monitor() |
655bc8bf JM |
1197 | if "OK" not in self.request("FT_DS " + bssid): |
1198 | raise Exception("FT_DS failed") | |
3b808945 JM |
1199 | if fail_test: |
1200 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=1) | |
1201 | if ev is not None: | |
1202 | raise Exception("Unexpected connection") | |
1203 | self.dump_monitor() | |
1204 | return | |
04e17e50 JM |
1205 | ev = self.wait_event(["CTRL-EVENT-CONNECTED", |
1206 | "CTRL-EVENT-ASSOC-REJECT"], timeout=10) | |
1207 | if ev is None: | |
1208 | raise Exception("Roaming with the AP timed out") | |
1209 | if "CTRL-EVENT-ASSOC-REJECT" in ev: | |
1210 | raise Exception("Roaming association rejected") | |
b553eab1 JM |
1211 | self.dump_monitor() |
1212 | ||
6edaee9c | 1213 | def wps_reg(self, bssid, pin, new_ssid=None, key_mgmt=None, cipher=None, |
6645ff50 | 1214 | new_passphrase=None, no_wait=False): |
6edaee9c JM |
1215 | self.dump_monitor() |
1216 | if new_ssid: | |
1217 | self.request("WPS_REG " + bssid + " " + pin + " " + | |
54c58f29 MH |
1218 | binascii.hexlify(new_ssid.encode()).decode() + " " + |
1219 | key_mgmt + " " + cipher + " " + | |
1220 | binascii.hexlify(new_passphrase.encode()).decode()) | |
6645ff50 JM |
1221 | if no_wait: |
1222 | return | |
6edaee9c JM |
1223 | ev = self.wait_event(["WPS-SUCCESS"], timeout=15) |
1224 | else: | |
1225 | self.request("WPS_REG " + bssid + " " + pin) | |
6645ff50 JM |
1226 | if no_wait: |
1227 | return | |
6edaee9c JM |
1228 | ev = self.wait_event(["WPS-CRED-RECEIVED"], timeout=15) |
1229 | if ev is None: | |
1230 | raise Exception("WPS cred timed out") | |
1231 | ev = self.wait_event(["WPS-FAIL"], timeout=15) | |
1232 | if ev is None: | |
1233 | raise Exception("WPS timed out") | |
5f35a5e2 | 1234 | self.wait_connected(timeout=15) |
57661377 JM |
1235 | |
1236 | def relog(self): | |
5f797376 | 1237 | self.global_request("RELOG") |
41af1305 JM |
1238 | |
1239 | def wait_completed(self, timeout=10): | |
1240 | for i in range(0, timeout * 2): | |
1241 | if self.get_status_field("wpa_state") == "COMPLETED": | |
1242 | return | |
1243 | time.sleep(0.5) | |
1244 | raise Exception("Timeout while waiting for COMPLETED state") | |
0eff1ab3 JM |
1245 | |
1246 | def get_capability(self, field): | |
1247 | res = self.request("GET_CAPABILITY " + field) | |
1248 | if "FAIL" in res: | |
1249 | return None | |
1250 | return res.split(' ') | |
2cdd91d8 | 1251 | |
fb9cf82e | 1252 | def get_bss(self, bssid, ifname=None): |
0663ae22 | 1253 | if not ifname or ifname == self.ifname: |
fb9cf82e IP |
1254 | res = self.request("BSS " + bssid) |
1255 | elif ifname == self.group_ifname: | |
1256 | res = self.group_request("BSS " + bssid) | |
1257 | else: | |
1258 | return None | |
1259 | ||
c126cb4d JM |
1260 | if "FAIL" in res: |
1261 | return None | |
2cdd91d8 JM |
1262 | lines = res.splitlines() |
1263 | vals = dict() | |
1264 | for l in lines: | |
fab49f61 | 1265 | [name, value] = l.split('=', 1) |
2cdd91d8 | 1266 | vals[name] = value |
c126cb4d JM |
1267 | if len(vals) == 0: |
1268 | return None | |
2cdd91d8 | 1269 | return vals |
0fab9ce6 JM |
1270 | |
1271 | def get_pmksa(self, bssid): | |
1272 | res = self.request("PMKSA") | |
1273 | lines = res.splitlines() | |
1274 | for l in lines: | |
1275 | if bssid not in l: | |
1276 | continue | |
1277 | vals = dict() | |
6bb32582 | 1278 | try: |
fab49f61 | 1279 | [index, aa, pmkid, expiration, opportunistic] = l.split(' ') |
6bb32582 JM |
1280 | cache_id = None |
1281 | except ValueError: | |
fab49f61 | 1282 | [index, aa, pmkid, expiration, opportunistic, cache_id] = l.split(' ') |
0fab9ce6 JM |
1283 | vals['index'] = index |
1284 | vals['pmkid'] = pmkid | |
1285 | vals['expiration'] = expiration | |
1286 | vals['opportunistic'] = opportunistic | |
6bb32582 JM |
1287 | if cache_id != None: |
1288 | vals['cache_id'] = cache_id | |
0fab9ce6 JM |
1289 | return vals |
1290 | return None | |
e1a5e09a JM |
1291 | |
1292 | def get_sta(self, addr, info=None, next=False): | |
1293 | cmd = "STA-NEXT " if next else "STA " | |
1294 | if addr is None: | |
1295 | res = self.request("STA-FIRST") | |
1296 | elif info: | |
1297 | res = self.request(cmd + addr + " " + info) | |
1298 | else: | |
1299 | res = self.request(cmd + addr) | |
1300 | lines = res.splitlines() | |
1301 | vals = dict() | |
1302 | first = True | |
1303 | for l in lines: | |
1304 | if first: | |
1305 | vals['addr'] = l | |
1306 | first = False | |
1307 | else: | |
fab49f61 | 1308 | [name, value] = l.split('=', 1) |
e1a5e09a JM |
1309 | vals[name] = value |
1310 | return vals | |
daf3806d JM |
1311 | |
1312 | def mgmt_rx(self, timeout=5): | |
1313 | ev = self.wait_event(["MGMT-RX"], timeout=timeout) | |
1314 | if ev is None: | |
1315 | return None | |
1316 | msg = {} | |
1317 | items = ev.split(' ') | |
fab49f61 | 1318 | field, val = items[1].split('=') |
daf3806d JM |
1319 | if field != "freq": |
1320 | raise Exception("Unexpected MGMT-RX event format: " + ev) | |
1321 | msg['freq'] = val | |
df49b90e | 1322 | |
fab49f61 | 1323 | field, val = items[2].split('=') |
df49b90e JM |
1324 | if field != "datarate": |
1325 | raise Exception("Unexpected MGMT-RX event format: " + ev) | |
1326 | msg['datarate'] = val | |
1327 | ||
fab49f61 | 1328 | field, val = items[3].split('=') |
df49b90e JM |
1329 | if field != "ssi_signal": |
1330 | raise Exception("Unexpected MGMT-RX event format: " + ev) | |
1331 | msg['ssi_signal'] = val | |
1332 | ||
daf3806d JM |
1333 | frame = binascii.unhexlify(items[4]) |
1334 | msg['frame'] = frame | |
1335 | ||
1336 | hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) | |
1337 | msg['fc'] = hdr[0] | |
1338 | msg['subtype'] = (hdr[0] >> 4) & 0xf | |
1339 | hdr = hdr[1:] | |
1340 | msg['duration'] = hdr[0] | |
1341 | hdr = hdr[1:] | |
1342 | msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1343 | hdr = hdr[6:] | |
1344 | msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1345 | hdr = hdr[6:] | |
1346 | msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
1347 | hdr = hdr[6:] | |
1348 | msg['seq_ctrl'] = hdr[0] | |
1349 | msg['payload'] = frame[24:] | |
1350 | ||
1351 | return msg | |
5f35a5e2 JM |
1352 | |
1353 | def wait_connected(self, timeout=10, error="Connection timed out"): | |
1354 | ev = self.wait_event(["CTRL-EVENT-CONNECTED"], timeout=timeout) | |
1355 | if ev is None: | |
1356 | raise Exception(error) | |
1357 | return ev | |
1358 | ||
67a0d4f9 JA |
1359 | def wait_disconnected(self, timeout=None, error="Disconnection timed out"): |
1360 | if timeout is None: | |
1361 | timeout = 10 if self.hostname is None else 30 | |
5f35a5e2 JM |
1362 | ev = self.wait_event(["CTRL-EVENT-DISCONNECTED"], timeout=timeout) |
1363 | if ev is None: | |
1364 | raise Exception(error) | |
1365 | return ev | |
bbcbbd37 DS |
1366 | |
1367 | def get_group_ifname(self): | |
1368 | return self.group_ifname if self.group_ifname else self.ifname | |
7ebc7e8f JM |
1369 | |
1370 | def get_config(self): | |
1371 | res = self.request("DUMP") | |
1372 | if res.startswith("FAIL"): | |
1373 | raise Exception("DUMP failed") | |
1374 | lines = res.splitlines() | |
1375 | vals = dict() | |
1376 | for l in lines: | |
fab49f61 | 1377 | [name, value] = l.split('=', 1) |
7ebc7e8f JM |
1378 | vals[name] = value |
1379 | return vals | |
1a1b0156 AO |
1380 | |
1381 | def asp_provision(self, peer, adv_id, adv_mac, session_id, session_mac, | |
a5378870 | 1382 | method="1000", info="", status=None, cpt=None, role=None): |
1a1b0156 AO |
1383 | if status is None: |
1384 | cmd = "P2P_ASP_PROVISION" | |
1385 | params = "info='%s' method=%s" % (info, method) | |
1386 | else: | |
1387 | cmd = "P2P_ASP_PROVISION_RESP" | |
1388 | params = "status=%d" % status | |
1389 | ||
0663ae22 JM |
1390 | if role is not None: |
1391 | params += " role=" + role | |
c1e31bdb MS |
1392 | if cpt is not None: |
1393 | params += " cpt=" + cpt | |
1394 | ||
1a1b0156 AO |
1395 | if "OK" not in self.global_request("%s %s adv_id=%s adv_mac=%s session=%d session_mac=%s %s" % |
1396 | (cmd, peer, adv_id, adv_mac, session_id, session_mac, params)): | |
1397 | raise Exception("%s request failed" % cmd) | |
0a374295 JM |
1398 | |
1399 | def note(self, txt): | |
1400 | self.request("NOTE " + txt) | |
99bc57d0 JM |
1401 | |
1402 | def wait_regdom(self, country_ie=False): | |
1403 | for i in range(5): | |
1404 | ev = self.wait_event(["CTRL-EVENT-REGDOM-CHANGE"], timeout=1) | |
1405 | if ev is None: | |
1406 | break | |
1407 | if country_ie: | |
1408 | if "init=COUNTRY_IE" in ev: | |
1409 | break | |
1410 | else: | |
1411 | break | |
0422d06b JM |
1412 | |
1413 | def dpp_qr_code(self, uri): | |
1414 | res = self.request("DPP_QR_CODE " + uri) | |
1415 | if "FAIL" in res: | |
1416 | raise Exception("Failed to parse QR Code URI") | |
1417 | return int(res) | |
a5387062 JM |
1418 | |
1419 | def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None, | |
1420 | curve=None, key=None): | |
1421 | cmd = "DPP_BOOTSTRAP_GEN type=" + type | |
1422 | if chan: | |
1423 | cmd += " chan=" + chan | |
1424 | if mac: | |
1425 | if mac is True: | |
1426 | mac = self.own_addr() | |
1427 | cmd += " mac=" + mac.replace(':', '') | |
1428 | if info: | |
1429 | cmd += " info=" + info | |
1430 | if curve: | |
1431 | cmd += " curve=" + curve | |
1432 | if key: | |
1433 | cmd += " key=" + key | |
1434 | res = self.request(cmd) | |
1435 | if "FAIL" in res: | |
1436 | raise Exception("Failed to generate bootstrapping info") | |
1437 | return int(res) | |
7e009100 JM |
1438 | |
1439 | def dpp_listen(self, freq, netrole=None, qr=None, role=None): | |
1440 | cmd = "DPP_LISTEN " + str(freq) | |
1441 | if netrole: | |
1442 | cmd += " netrole=" + netrole | |
1443 | if qr: | |
1444 | cmd += " qr=" + qr | |
1445 | if role: | |
1446 | cmd += " role=" + role | |
1447 | if "OK" not in self.request(cmd): | |
1448 | raise Exception("Failed to start listen operation") | |
6d196e59 | 1449 | |
5725b3e3 JM |
1450 | def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None, |
1451 | extra=None, own=None, role=None, neg_freq=None, | |
1452 | ssid=None, passphrase=None, expect_fail=False): | |
1453 | cmd = "DPP_AUTH_INIT" | |
1454 | if peer is None: | |
1455 | peer = self.dpp_qr_code(uri) | |
1456 | cmd += " peer=%d" % peer | |
1457 | if own is not None: | |
1458 | cmd += " own=%d" % own | |
1459 | if role: | |
1460 | cmd += " role=" + role | |
1461 | if extra: | |
1462 | cmd += " " + extra | |
1463 | if conf: | |
1464 | cmd += " conf=" + conf | |
1465 | if configurator is not None: | |
1466 | cmd += " configurator=%d" % configurator | |
1467 | if neg_freq: | |
1468 | cmd += " neg_freq=%d" % neg_freq | |
1469 | if ssid: | |
1470 | cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() | |
1471 | if passphrase: | |
1472 | cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode() | |
1473 | res = self.request(cmd) | |
1474 | if expect_fail: | |
1475 | if "FAIL" not in res: | |
1476 | raise Exception("DPP authentication started unexpectedly") | |
1477 | return | |
1478 | if "OK" not in res: | |
1479 | raise Exception("Failed to initiate DPP Authentication") | |
1480 | ||
6d196e59 JM |
1481 | def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None, |
1482 | extra=None, use_id=None, allow_fail=False): | |
1483 | if use_id is None: | |
1484 | id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) | |
1485 | else: | |
1486 | id1 = use_id | |
1487 | cmd = "own=%d " % id1 | |
1488 | if identifier: | |
1489 | cmd += "identifier=%s " % identifier | |
1490 | cmd += "init=1 " | |
1491 | if role: | |
1492 | cmd += "role=%s " % role | |
1493 | if extra: | |
1494 | cmd += extra + " " | |
1495 | cmd += "code=%s" % code | |
1496 | res = self.request("DPP_PKEX_ADD " + cmd) | |
1497 | if allow_fail: | |
1498 | return id1 | |
1499 | if "FAIL" in res: | |
1500 | raise Exception("Failed to set PKEX data (initiator)") | |
1501 | return id1 | |
1502 | ||
1503 | def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None, | |
1504 | listen_role=None, use_id=None): | |
1505 | if use_id is None: | |
1506 | id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) | |
1507 | else: | |
1508 | id0 = use_id | |
1509 | cmd = "own=%d " % id0 | |
1510 | if identifier: | |
1511 | cmd += "identifier=%s " % identifier | |
1512 | cmd += "code=%s" % code | |
1513 | res = self.request("DPP_PKEX_ADD " + cmd) | |
1514 | if "FAIL" in res: | |
1515 | raise Exception("Failed to set PKEX data (responder)") | |
1516 | self.dpp_listen(freq, role=listen_role) | |
1517 | return id0 | |
e105110f JM |
1518 | |
1519 | def dpp_configurator_add(self, curve=None, key=None): | |
1520 | cmd = "DPP_CONFIGURATOR_ADD" | |
1521 | if curve: | |
1522 | cmd += " curve=" + curve | |
1523 | if key: | |
1524 | cmd += " key=" + key | |
1525 | res = self.request(cmd) | |
1526 | if "FAIL" in res: | |
1527 | raise Exception("Failed to add configurator") | |
1528 | return int(res) | |
1529 | ||
1530 | def dpp_configurator_remove(self, conf_id): | |
1531 | res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id) | |
1532 | if "OK" not in res: | |
1533 | raise Exception("DPP_CONFIGURATOR_REMOVE failed") |