]>
Commit | Line | Data |
---|---|---|
b8cd4c54 | 1 | # Python class for controlling hostapd |
02a4ac0f | 2 | # Copyright (c) 2013-2019, Jouni Malinen <j@w1.fi> |
b8cd4c54 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 | |
ba1ff57a | 8 | import re |
b8cd4c54 JM |
9 | import time |
10 | import logging | |
bfe375ec JM |
11 | import binascii |
12 | import struct | |
b8cd4c54 | 13 | import wpaspy |
8ce4855b | 14 | import remotehost |
9cd6f4c0 | 15 | import utils |
7fd9fbc2 | 16 | import subprocess |
b8cd4c54 | 17 | |
c9aa4308 | 18 | logger = logging.getLogger() |
b8cd4c54 | 19 | hapd_ctrl = '/var/run/hostapd' |
8c87f65f | 20 | hapd_global = '/var/run/hostapd-global' |
b8cd4c54 | 21 | |
bfe375ec | 22 | def mac2tuple(mac): |
fab49f61 | 23 | return struct.unpack('6B', binascii.unhexlify(mac.replace(':', ''))) |
bfe375ec | 24 | |
b8cd4c54 | 25 | class HostapdGlobal: |
02a4ac0f | 26 | def __init__(self, apdev=None, global_ctrl_override=None): |
625bf744 JD |
27 | try: |
28 | hostname = apdev['hostname'] | |
29 | port = apdev['port'] | |
30 | except: | |
31 | hostname = None | |
32 | port = 8878 | |
8ce4855b | 33 | self.host = remotehost.Host(hostname) |
cb73f7e8 JD |
34 | self.hostname = hostname |
35 | self.port = port | |
36 | if hostname is None: | |
02a4ac0f JM |
37 | global_ctrl = hapd_global |
38 | if global_ctrl_override: | |
39 | global_ctrl = global_ctrl_override | |
40 | self.ctrl = wpaspy.Ctrl(global_ctrl) | |
41 | self.mon = wpaspy.Ctrl(global_ctrl) | |
d4944fad | 42 | self.dbg = "" |
cb73f7e8 JD |
43 | else: |
44 | self.ctrl = wpaspy.Ctrl(hostname, port) | |
45 | self.mon = wpaspy.Ctrl(hostname, port) | |
d4944fad | 46 | self.dbg = hostname + "/" + str(port) |
41a256ec AN |
47 | self.mon.attach() |
48 | ||
e9f2d54f | 49 | def cmd_execute(self, cmd_array, shell=False): |
7fd9fbc2 | 50 | if self.hostname is None: |
e9f2d54f JM |
51 | if shell: |
52 | cmd = ' '.join(cmd_array) | |
53 | else: | |
54 | cmd = cmd_array | |
7fd9fbc2 | 55 | proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, |
e9f2d54f | 56 | stdout=subprocess.PIPE, shell=shell) |
7fd9fbc2 JA |
57 | out = proc.communicate()[0] |
58 | ret = proc.returncode | |
45b0b88f | 59 | return ret, out.decode() |
7fd9fbc2 JA |
60 | else: |
61 | return self.host.execute(cmd_array) | |
62 | ||
d4944fad JD |
63 | def request(self, cmd, timeout=10): |
64 | logger.debug(self.dbg + ": CTRL(global): " + cmd) | |
65 | return self.ctrl.request(cmd, timeout) | |
41a256ec AN |
66 | |
67 | def wait_event(self, events, timeout): | |
68 | start = os.times()[4] | |
69 | while True: | |
70 | while self.mon.pending(): | |
71 | ev = self.mon.recv() | |
d4944fad | 72 | logger.debug(self.dbg + "(global): " + ev) |
41a256ec AN |
73 | for event in events: |
74 | if event in ev: | |
75 | return ev | |
76 | now = os.times()[4] | |
77 | remaining = start + timeout - now | |
78 | if remaining <= 0: | |
79 | break | |
80 | if not self.mon.pending(timeout=remaining): | |
81 | break | |
82 | return None | |
b8cd4c54 | 83 | |
08763216 JM |
84 | def add(self, ifname, driver=None): |
85 | cmd = "ADD " + ifname + " " + hapd_ctrl | |
86 | if driver: | |
87 | cmd += " " + driver | |
d4944fad | 88 | res = self.request(cmd) |
a8b8da11 | 89 | if "OK" not in res: |
b8cd4c54 JM |
90 | raise Exception("Could not add hostapd interface " + ifname) |
91 | ||
77990cd7 | 92 | def add_iface(self, ifname, confname): |
d4944fad | 93 | res = self.request("ADD " + ifname + " config=" + confname) |
a8b8da11 | 94 | if "OK" not in res: |
77990cd7 JM |
95 | raise Exception("Could not add hostapd interface") |
96 | ||
a6333977 | 97 | def add_bss(self, phy, confname, ignore_error=False): |
d4944fad | 98 | res = self.request("ADD bss_config=" + phy + ":" + confname) |
a8b8da11 | 99 | if "OK" not in res: |
a6333977 JM |
100 | if not ignore_error: |
101 | raise Exception("Could not add hostapd BSS") | |
102 | ||
b8cd4c54 | 103 | def remove(self, ifname): |
d4944fad | 104 | self.request("REMOVE " + ifname, timeout=30) |
b8cd4c54 | 105 | |
75428961 | 106 | def relog(self): |
d4944fad | 107 | self.request("RELOG") |
75428961 | 108 | |
f8949f5f | 109 | def flush(self): |
d4944fad | 110 | self.request("FLUSH") |
f8949f5f | 111 | |
4d48d44c JD |
112 | def get_ctrl_iface_port(self, ifname): |
113 | if self.hostname is None: | |
114 | return None | |
115 | ||
d4944fad | 116 | res = self.request("INTERFACES ctrl") |
4d48d44c JD |
117 | lines = res.splitlines() |
118 | found = False | |
119 | for line in lines: | |
120 | words = line.split() | |
121 | if words[0] == ifname: | |
122 | found = True | |
123 | break | |
124 | if not found: | |
125 | raise Exception("Could not find UDP port for " + ifname) | |
126 | res = line.find("ctrl_iface=udp:") | |
127 | if res == -1: | |
128 | raise Exception("Wrong ctrl_interface format") | |
129 | words = line.split(":") | |
130 | return int(words[1]) | |
b8cd4c54 | 131 | |
e3b36d42 JD |
132 | def terminate(self): |
133 | self.mon.detach() | |
134 | self.mon.close() | |
135 | self.mon = None | |
136 | self.ctrl.terminate() | |
137 | self.ctrl = None | |
138 | ||
16f18b2c JD |
139 | def send_file(self, src, dst): |
140 | self.host.send_file(src, dst) | |
141 | ||
b8cd4c54 | 142 | class Hostapd: |
cb73f7e8 | 143 | def __init__(self, ifname, bssidx=0, hostname=None, port=8877): |
e2f3f023 | 144 | self.hostname = hostname |
8ce4855b | 145 | self.host = remotehost.Host(hostname, ifname) |
b8cd4c54 | 146 | self.ifname = ifname |
cb73f7e8 JD |
147 | if hostname is None: |
148 | self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname)) | |
149 | self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname)) | |
d4944fad | 150 | self.dbg = ifname |
cb73f7e8 JD |
151 | else: |
152 | self.ctrl = wpaspy.Ctrl(hostname, port) | |
153 | self.mon = wpaspy.Ctrl(hostname, port) | |
d4944fad | 154 | self.dbg = hostname + "/" + ifname |
b47750be | 155 | self.mon.attach() |
f6420942 | 156 | self.bssid = None |
54cf411f | 157 | self.bssidx = bssidx |
f6420942 | 158 | |
e9f2d54f | 159 | def cmd_execute(self, cmd_array, shell=False): |
7fd9fbc2 | 160 | if self.hostname is None: |
e9f2d54f JM |
161 | if shell: |
162 | cmd = ' '.join(cmd_array) | |
163 | else: | |
164 | cmd = cmd_array | |
7fd9fbc2 | 165 | proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, |
e9f2d54f | 166 | stdout=subprocess.PIPE, shell=shell) |
7fd9fbc2 JA |
167 | out = proc.communicate()[0] |
168 | ret = proc.returncode | |
45b0b88f | 169 | return ret, out.decode() |
7fd9fbc2 JA |
170 | else: |
171 | return self.host.execute(cmd_array) | |
172 | ||
e3b36d42 JD |
173 | def close_ctrl(self): |
174 | if self.mon is not None: | |
175 | self.mon.detach() | |
176 | self.mon.close() | |
177 | self.mon = None | |
178 | self.ctrl.close() | |
179 | self.ctrl = None | |
180 | ||
f6420942 JM |
181 | def own_addr(self): |
182 | if self.bssid is None: | |
54cf411f | 183 | self.bssid = self.get_status_field('bssid[%d]' % self.bssidx) |
f6420942 | 184 | return self.bssid |
b8cd4c54 | 185 | |
1d9d6c24 TP |
186 | def get_addr(self, group=False): |
187 | return self.own_addr() | |
188 | ||
b8cd4c54 | 189 | def request(self, cmd): |
d4944fad | 190 | logger.debug(self.dbg + ": CTRL: " + cmd) |
b8cd4c54 JM |
191 | return self.ctrl.request(cmd) |
192 | ||
193 | def ping(self): | |
194 | return "PONG" in self.request("PING") | |
195 | ||
196 | def set(self, field, value): | |
a8b8da11 | 197 | if "OK" not in self.request("SET " + field + " " + value): |
b8cd4c54 JM |
198 | raise Exception("Failed to set hostapd parameter " + field) |
199 | ||
200 | def set_defaults(self): | |
201 | self.set("driver", "nl80211") | |
202 | self.set("hw_mode", "g") | |
203 | self.set("channel", "1") | |
204 | self.set("ieee80211n", "1") | |
789b9f1d JM |
205 | self.set("logger_stdout", "-1") |
206 | self.set("logger_stdout_level", "0") | |
b8cd4c54 JM |
207 | |
208 | def set_open(self, ssid): | |
209 | self.set_defaults() | |
210 | self.set("ssid", ssid) | |
211 | ||
212 | def set_wpa2_psk(self, ssid, passphrase): | |
213 | self.set_defaults() | |
214 | self.set("ssid", ssid) | |
215 | self.set("wpa_passphrase", passphrase) | |
216 | self.set("wpa", "2") | |
217 | self.set("wpa_key_mgmt", "WPA-PSK") | |
218 | self.set("rsn_pairwise", "CCMP") | |
219 | ||
e492837b JM |
220 | def set_wpa_psk(self, ssid, passphrase): |
221 | self.set_defaults() | |
222 | self.set("ssid", ssid) | |
223 | self.set("wpa_passphrase", passphrase) | |
224 | self.set("wpa", "1") | |
225 | self.set("wpa_key_mgmt", "WPA-PSK") | |
226 | self.set("wpa_pairwise", "TKIP") | |
227 | ||
228 | def set_wpa_psk_mixed(self, ssid, passphrase): | |
229 | self.set_defaults() | |
230 | self.set("ssid", ssid) | |
231 | self.set("wpa_passphrase", passphrase) | |
232 | self.set("wpa", "3") | |
233 | self.set("wpa_key_mgmt", "WPA-PSK") | |
234 | self.set("wpa_pairwise", "TKIP") | |
235 | self.set("rsn_pairwise", "CCMP") | |
236 | ||
0165c4be JM |
237 | def set_wep(self, ssid, key): |
238 | self.set_defaults() | |
239 | self.set("ssid", ssid) | |
240 | self.set("wep_key0", key) | |
241 | ||
b8cd4c54 | 242 | def enable(self): |
a8b8da11 | 243 | if "OK" not in self.request("ENABLE"): |
b8cd4c54 JM |
244 | raise Exception("Failed to enable hostapd interface " + self.ifname) |
245 | ||
246 | def disable(self): | |
a8b8da11 | 247 | if "OK" not in self.request("DISABLE"): |
b8cd4c54 | 248 | raise Exception("Failed to disable hostapd interface " + self.ifname) |
e259d186 | 249 | |
b47750be JM |
250 | def dump_monitor(self): |
251 | while self.mon.pending(): | |
252 | ev = self.mon.recv() | |
d4944fad | 253 | logger.debug(self.dbg + ": " + ev) |
b47750be JM |
254 | |
255 | def wait_event(self, events, timeout): | |
d1fb06c9 JM |
256 | if not isinstance(events, list): |
257 | raise Exception("Hostapd.wait_event() called with incorrect events argument type") | |
36408936 JM |
258 | start = os.times()[4] |
259 | while True: | |
b47750be JM |
260 | while self.mon.pending(): |
261 | ev = self.mon.recv() | |
d4944fad | 262 | logger.debug(self.dbg + ": " + ev) |
b47750be JM |
263 | for event in events: |
264 | if event in ev: | |
265 | return ev | |
36408936 JM |
266 | now = os.times()[4] |
267 | remaining = start + timeout - now | |
268 | if remaining <= 0: | |
269 | break | |
270 | if not self.mon.pending(timeout=remaining): | |
271 | break | |
b47750be JM |
272 | return None |
273 | ||
938c6e7b | 274 | def wait_sta(self, addr=None, timeout=2): |
c4a9610e | 275 | ev = self.wait_event(["AP-STA-CONNECT"], timeout=timeout) |
938c6e7b JM |
276 | if ev is None: |
277 | raise Exception("AP did not report STA connection") | |
278 | if addr and addr not in ev: | |
279 | raise Exception("Unexpected STA address in connection event: " + ev) | |
280 | ||
0075df74 JM |
281 | def wait_ptkinitdone(self, addr, timeout=2): |
282 | while timeout > 0: | |
283 | sta = self.get_sta(addr) | |
284 | if 'hostapdWPAPTKState' not in sta: | |
285 | raise Exception("GET_STA did not return hostapdWPAPTKState") | |
286 | state = sta['hostapdWPAPTKState'] | |
287 | if state == "11": | |
288 | return | |
7a423163 | 289 | time.sleep(0.1) |
0075df74 JM |
290 | timeout -= 0.1 |
291 | raise Exception("Timeout while waiting for PTKINITDONE") | |
292 | ||
b47750be JM |
293 | def get_status(self): |
294 | res = self.request("STATUS") | |
295 | lines = res.splitlines() | |
296 | vals = dict() | |
297 | for l in lines: | |
fab49f61 | 298 | [name, value] = l.split('=', 1) |
b47750be JM |
299 | vals[name] = value |
300 | return vals | |
301 | ||
302 | def get_status_field(self, field): | |
303 | vals = self.get_status() | |
304 | if field in vals: | |
305 | return vals[field] | |
306 | return None | |
307 | ||
a36158be JM |
308 | def get_driver_status(self): |
309 | res = self.request("STATUS-DRIVER") | |
310 | lines = res.splitlines() | |
311 | vals = dict() | |
312 | for l in lines: | |
fab49f61 | 313 | [name, value] = l.split('=', 1) |
a36158be JM |
314 | vals[name] = value |
315 | return vals | |
316 | ||
317 | def get_driver_status_field(self, field): | |
318 | vals = self.get_driver_status() | |
319 | if field in vals: | |
320 | return vals[field] | |
321 | return None | |
322 | ||
65038313 JM |
323 | def get_config(self): |
324 | res = self.request("GET_CONFIG") | |
325 | lines = res.splitlines() | |
326 | vals = dict() | |
327 | for l in lines: | |
fab49f61 | 328 | [name, value] = l.split('=', 1) |
65038313 JM |
329 | vals[name] = value |
330 | return vals | |
331 | ||
bfe375ec JM |
332 | def mgmt_rx(self, timeout=5): |
333 | ev = self.wait_event(["MGMT-RX"], timeout=timeout) | |
334 | if ev is None: | |
335 | return None | |
336 | msg = {} | |
337 | frame = binascii.unhexlify(ev.split(' ')[1]) | |
338 | msg['frame'] = frame | |
339 | ||
340 | hdr = struct.unpack('<HH6B6B6BH', frame[0:24]) | |
341 | msg['fc'] = hdr[0] | |
342 | msg['subtype'] = (hdr[0] >> 4) & 0xf | |
343 | hdr = hdr[1:] | |
344 | msg['duration'] = hdr[0] | |
345 | hdr = hdr[1:] | |
346 | msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
347 | hdr = hdr[6:] | |
348 | msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
349 | hdr = hdr[6:] | |
350 | msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6] | |
351 | hdr = hdr[6:] | |
352 | msg['seq_ctrl'] = hdr[0] | |
353 | msg['payload'] = frame[24:] | |
354 | ||
355 | return msg | |
356 | ||
357 | def mgmt_tx(self, msg): | |
358 | t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,) | |
359 | hdr = struct.pack('<HH6B6B6BH', *t) | |
7ab74770 MH |
360 | res = self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']).decode()) |
361 | if "OK" not in res: | |
0851a180 | 362 | raise Exception("MGMT_TX command to hostapd failed") |
bfe375ec | 363 | |
cce26eb4 JM |
364 | def get_sta(self, addr, info=None, next=False): |
365 | cmd = "STA-NEXT " if next else "STA " | |
366 | if addr is None: | |
367 | res = self.request("STA-FIRST") | |
368 | elif info: | |
369 | res = self.request(cmd + addr + " " + info) | |
5dec879d | 370 | else: |
cce26eb4 | 371 | res = self.request(cmd + addr) |
6435799b JM |
372 | lines = res.splitlines() |
373 | vals = dict() | |
374 | first = True | |
375 | for l in lines: | |
2496adf0 | 376 | if first and '=' not in l: |
6435799b JM |
377 | vals['addr'] = l |
378 | first = False | |
379 | else: | |
fab49f61 | 380 | [name, value] = l.split('=', 1) |
6435799b JM |
381 | vals[name] = value |
382 | return vals | |
383 | ||
4fcee244 JM |
384 | def get_mib(self, param=None): |
385 | if param: | |
386 | res = self.request("MIB " + param) | |
387 | else: | |
388 | res = self.request("MIB") | |
7fd15145 JM |
389 | lines = res.splitlines() |
390 | vals = dict() | |
391 | for l in lines: | |
4fcee244 JM |
392 | name_val = l.split('=', 1) |
393 | if len(name_val) > 1: | |
394 | vals[name_val[0]] = name_val[1] | |
7fd15145 JM |
395 | return vals |
396 | ||
865fa1e9 JM |
397 | def get_pmksa(self, addr): |
398 | res = self.request("PMKSA") | |
399 | lines = res.splitlines() | |
400 | for l in lines: | |
401 | if addr not in l: | |
402 | continue | |
403 | vals = dict() | |
fab49f61 | 404 | [index, aa, pmkid, expiration, opportunistic] = l.split(' ') |
865fa1e9 JM |
405 | vals['index'] = index |
406 | vals['pmkid'] = pmkid | |
407 | vals['expiration'] = expiration | |
408 | vals['opportunistic'] = opportunistic | |
409 | return vals | |
410 | return None | |
411 | ||
0422d06b JM |
412 | def dpp_qr_code(self, uri): |
413 | res = self.request("DPP_QR_CODE " + uri) | |
414 | if "FAIL" in res: | |
415 | raise Exception("Failed to parse QR Code URI") | |
416 | return int(res) | |
a5387062 JM |
417 | |
418 | def dpp_bootstrap_gen(self, type="qrcode", chan=None, mac=None, info=None, | |
419 | curve=None, key=None): | |
420 | cmd = "DPP_BOOTSTRAP_GEN type=" + type | |
421 | if chan: | |
422 | cmd += " chan=" + chan | |
423 | if mac: | |
424 | if mac is True: | |
425 | mac = self.own_addr() | |
426 | cmd += " mac=" + mac.replace(':', '') | |
427 | if info: | |
428 | cmd += " info=" + info | |
429 | if curve: | |
430 | cmd += " curve=" + curve | |
431 | if key: | |
432 | cmd += " key=" + key | |
433 | res = self.request(cmd) | |
434 | if "FAIL" in res: | |
435 | raise Exception("Failed to generate bootstrapping info") | |
436 | return int(res) | |
0422d06b | 437 | |
7e009100 JM |
438 | def dpp_listen(self, freq, netrole=None, qr=None, role=None): |
439 | cmd = "DPP_LISTEN " + str(freq) | |
440 | if netrole: | |
441 | cmd += " netrole=" + netrole | |
442 | if qr: | |
443 | cmd += " qr=" + qr | |
444 | if role: | |
445 | cmd += " role=" + role | |
446 | if "OK" not in self.request(cmd): | |
447 | raise Exception("Failed to start listen operation") | |
448 | ||
5725b3e3 JM |
449 | def dpp_auth_init(self, peer=None, uri=None, conf=None, configurator=None, |
450 | extra=None, own=None, role=None, neg_freq=None, | |
451 | ssid=None, passphrase=None, expect_fail=False): | |
452 | cmd = "DPP_AUTH_INIT" | |
453 | if peer is None: | |
454 | peer = self.dpp_qr_code(uri) | |
455 | cmd += " peer=%d" % peer | |
456 | if own is not None: | |
457 | cmd += " own=%d" % own | |
458 | if role: | |
459 | cmd += " role=" + role | |
460 | if extra: | |
461 | cmd += " " + extra | |
462 | if conf: | |
463 | cmd += " conf=" + conf | |
464 | if configurator is not None: | |
465 | cmd += " configurator=%d" % configurator | |
466 | if neg_freq: | |
467 | cmd += " neg_freq=%d" % neg_freq | |
468 | if ssid: | |
469 | cmd += " ssid=" + binascii.hexlify(ssid.encode()).decode() | |
470 | if passphrase: | |
471 | cmd += " pass=" + binascii.hexlify(passphrase.encode()).decode() | |
472 | res = self.request(cmd) | |
473 | if expect_fail: | |
474 | if "FAIL" not in res: | |
475 | raise Exception("DPP authentication started unexpectedly") | |
476 | return | |
477 | if "OK" not in res: | |
478 | raise Exception("Failed to initiate DPP Authentication") | |
479 | ||
6d196e59 JM |
480 | def dpp_pkex_init(self, identifier, code, role=None, key=None, curve=None, |
481 | extra=None, use_id=None): | |
482 | if use_id is None: | |
483 | id1 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) | |
484 | else: | |
485 | id1 = use_id | |
486 | cmd = "own=%d " % id1 | |
487 | if identifier: | |
488 | cmd += "identifier=%s " % identifier | |
489 | cmd += "init=1 " | |
490 | if role: | |
491 | cmd += "role=%s " % role | |
492 | if extra: | |
493 | cmd += extra + " " | |
494 | cmd += "code=%s" % code | |
495 | res = self.request("DPP_PKEX_ADD " + cmd) | |
496 | if "FAIL" in res: | |
497 | raise Exception("Failed to set PKEX data (initiator)") | |
498 | return id1 | |
499 | ||
500 | def dpp_pkex_resp(self, freq, identifier, code, key=None, curve=None, | |
501 | listen_role=None): | |
502 | id0 = self.dpp_bootstrap_gen(type="pkex", key=key, curve=curve) | |
503 | cmd = "own=%d " % id0 | |
504 | if identifier: | |
505 | cmd += "identifier=%s " % identifier | |
506 | cmd += "code=%s" % code | |
507 | res = self.request("DPP_PKEX_ADD " + cmd) | |
508 | if "FAIL" in res: | |
509 | raise Exception("Failed to set PKEX data (responder)") | |
510 | self.dpp_listen(freq, role=listen_role) | |
511 | ||
e105110f JM |
512 | def dpp_configurator_add(self, curve=None, key=None): |
513 | cmd = "DPP_CONFIGURATOR_ADD" | |
514 | if curve: | |
515 | cmd += " curve=" + curve | |
516 | if key: | |
517 | cmd += " key=" + key | |
518 | res = self.request(cmd) | |
519 | if "FAIL" in res: | |
520 | raise Exception("Failed to add configurator") | |
521 | return int(res) | |
522 | ||
523 | def dpp_configurator_remove(self, conf_id): | |
524 | res = self.request("DPP_CONFIGURATOR_REMOVE %d" % conf_id) | |
525 | if "OK" not in res: | |
526 | raise Exception("DPP_CONFIGURATOR_REMOVE failed") | |
527 | ||
431802df JM |
528 | def note(self, txt): |
529 | self.request("NOTE " + txt) | |
530 | ||
16f18b2c JD |
531 | def send_file(self, src, dst): |
532 | self.host.send_file(src, dst) | |
533 | ||
02a4ac0f | 534 | def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30, |
e374def2 | 535 | global_ctrl_override=None, driver=False): |
78b83193 JD |
536 | if isinstance(apdev, dict): |
537 | ifname = apdev['ifname'] | |
538 | try: | |
539 | hostname = apdev['hostname'] | |
540 | port = apdev['port'] | |
541 | logger.info("Starting AP " + hostname + "/" + port + " " + ifname) | |
542 | except: | |
543 | logger.info("Starting AP " + ifname) | |
544 | hostname = None | |
545 | port = 8878 | |
546 | else: | |
547 | ifname = apdev | |
548 | logger.info("Starting AP " + ifname + " (old add_ap argument type)") | |
549 | hostname = None | |
550 | port = 8878 | |
02a4ac0f JM |
551 | hapd_global = HostapdGlobal(apdev, |
552 | global_ctrl_override=global_ctrl_override) | |
e259d186 | 553 | hapd_global.remove(ifname) |
e374def2 | 554 | hapd_global.add(ifname, driver=driver) |
4d48d44c JD |
555 | port = hapd_global.get_ctrl_iface_port(ifname) |
556 | hapd = Hostapd(ifname, hostname=hostname, port=port) | |
e259d186 JM |
557 | if not hapd.ping(): |
558 | raise Exception("Could not ping hostapd") | |
559 | hapd.set_defaults() | |
fab49f61 | 560 | fields = ["ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt", |
5742d12d | 561 | "wpa", "wpa_deny_ptk0_rekey", |
fab49f61 JM |
562 | "wpa_pairwise", "rsn_pairwise", "auth_server_addr", |
563 | "acct_server_addr", "osu_server_uri"] | |
e259d186 JM |
564 | for field in fields: |
565 | if field in params: | |
566 | hapd.set(field, params[field]) | |
fab49f61 | 567 | for f, v in list(params.items()): |
93a06242 JM |
568 | if f in fields: |
569 | continue | |
570 | if isinstance(v, list): | |
571 | for val in v: | |
572 | hapd.set(f, val) | |
573 | else: | |
574 | hapd.set(f, v) | |
138ec97e JM |
575 | if no_enable: |
576 | return hapd | |
e259d186 | 577 | hapd.enable() |
629dbdd3 | 578 | if wait_enabled: |
57ff37d0 | 579 | ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout) |
629dbdd3 JM |
580 | if ev is None: |
581 | raise Exception("AP startup timed out") | |
f8ad9dc2 JM |
582 | if "AP-ENABLED" not in ev: |
583 | raise Exception("AP startup failed") | |
b47750be | 584 | return hapd |
e259d186 | 585 | |
9cd6f4c0 JD |
586 | def add_bss(apdev, ifname, confname, ignore_error=False): |
587 | phy = utils.get_phy(apdev) | |
588 | try: | |
589 | hostname = apdev['hostname'] | |
590 | port = apdev['port'] | |
591 | logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname) | |
592 | except: | |
593 | logger.info("Starting BSS phy=" + phy + " ifname=" + ifname) | |
594 | hostname = None | |
595 | port = 8878 | |
625bf744 | 596 | hapd_global = HostapdGlobal(apdev) |
ba1ff57a JD |
597 | confname = cfg_file(apdev, confname, ifname) |
598 | hapd_global.send_file(confname, confname) | |
a6333977 | 599 | hapd_global.add_bss(phy, confname, ignore_error) |
4d48d44c JD |
600 | port = hapd_global.get_ctrl_iface_port(ifname) |
601 | hapd = Hostapd(ifname, hostname=hostname, port=port) | |
a6333977 JM |
602 | if not hapd.ping(): |
603 | raise Exception("Could not ping hostapd") | |
fb1a7dcc | 604 | return hapd |
a6333977 | 605 | |
29444a08 JD |
606 | def add_iface(apdev, confname): |
607 | ifname = apdev['ifname'] | |
608 | try: | |
609 | hostname = apdev['hostname'] | |
610 | port = apdev['port'] | |
611 | logger.info("Starting interface " + hostname + "/" + port + " " + ifname) | |
612 | except: | |
613 | logger.info("Starting interface " + ifname) | |
614 | hostname = None | |
615 | port = 8878 | |
625bf744 | 616 | hapd_global = HostapdGlobal(apdev) |
ba1ff57a JD |
617 | confname = cfg_file(apdev, confname, ifname) |
618 | hapd_global.send_file(confname, confname) | |
77990cd7 | 619 | hapd_global.add_iface(ifname, confname) |
4d48d44c JD |
620 | port = hapd_global.get_ctrl_iface_port(ifname) |
621 | hapd = Hostapd(ifname, hostname=hostname, port=port) | |
77990cd7 JM |
622 | if not hapd.ping(): |
623 | raise Exception("Could not ping hostapd") | |
fb1a7dcc | 624 | return hapd |
77990cd7 | 625 | |
c92ee957 JD |
626 | def remove_bss(apdev, ifname=None): |
627 | if ifname == None: | |
628 | ifname = apdev['ifname'] | |
629 | try: | |
630 | hostname = apdev['hostname'] | |
631 | port = apdev['port'] | |
632 | logger.info("Removing BSS " + hostname + "/" + port + " " + ifname) | |
633 | except: | |
634 | logger.info("Removing BSS " + ifname) | |
625bf744 | 635 | hapd_global = HostapdGlobal(apdev) |
a6333977 JM |
636 | hapd_global.remove(ifname) |
637 | ||
369f712a JD |
638 | def terminate(apdev): |
639 | try: | |
640 | hostname = apdev['hostname'] | |
641 | port = apdev['port'] | |
625bf744 | 642 | logger.info("Terminating hostapd " + hostname + "/" + port) |
369f712a | 643 | except: |
369f712a | 644 | logger.info("Terminating hostapd") |
625bf744 | 645 | hapd_global = HostapdGlobal(apdev) |
e3b36d42 JD |
646 | hapd_global.terminate() |
647 | ||
e9aa3995 JM |
648 | def wpa2_params(ssid=None, passphrase=None, wpa_key_mgmt="WPA-PSK", |
649 | ieee80211w=None): | |
fab49f61 | 650 | params = {"wpa": "2", |
e9aa3995 | 651 | "wpa_key_mgmt": wpa_key_mgmt, |
fab49f61 | 652 | "rsn_pairwise": "CCMP"} |
e259d186 JM |
653 | if ssid: |
654 | params["ssid"] = ssid | |
655 | if passphrase: | |
656 | params["wpa_passphrase"] = passphrase | |
e9aa3995 JM |
657 | if ieee80211w is not None: |
658 | params["ieee80211w"] = ieee80211w | |
e259d186 JM |
659 | return params |
660 | ||
661 | def wpa_params(ssid=None, passphrase=None): | |
fab49f61 JM |
662 | params = {"wpa": "1", |
663 | "wpa_key_mgmt": "WPA-PSK", | |
664 | "wpa_pairwise": "TKIP"} | |
e259d186 JM |
665 | if ssid: |
666 | params["ssid"] = ssid | |
667 | if passphrase: | |
668 | params["wpa_passphrase"] = passphrase | |
669 | return params | |
670 | ||
671 | def wpa_mixed_params(ssid=None, passphrase=None): | |
fab49f61 JM |
672 | params = {"wpa": "3", |
673 | "wpa_key_mgmt": "WPA-PSK", | |
674 | "wpa_pairwise": "TKIP", | |
675 | "rsn_pairwise": "CCMP"} | |
e259d186 JM |
676 | if ssid: |
677 | params["ssid"] = ssid | |
678 | if passphrase: | |
679 | params["wpa_passphrase"] = passphrase | |
680 | return params | |
9626962d JM |
681 | |
682 | def radius_params(): | |
fab49f61 JM |
683 | params = {"auth_server_addr": "127.0.0.1", |
684 | "auth_server_port": "1812", | |
685 | "auth_server_shared_secret": "radius", | |
686 | "nas_identifier": "nas.w1.fi"} | |
9626962d JM |
687 | return params |
688 | ||
71390dc8 JM |
689 | def wpa_eap_params(ssid=None): |
690 | params = radius_params() | |
691 | params["wpa"] = "1" | |
692 | params["wpa_key_mgmt"] = "WPA-EAP" | |
693 | params["wpa_pairwise"] = "TKIP" | |
694 | params["ieee8021x"] = "1" | |
695 | if ssid: | |
696 | params["ssid"] = ssid | |
697 | return params | |
698 | ||
9626962d JM |
699 | def wpa2_eap_params(ssid=None): |
700 | params = radius_params() | |
701 | params["wpa"] = "2" | |
702 | params["wpa_key_mgmt"] = "WPA-EAP" | |
703 | params["rsn_pairwise"] = "CCMP" | |
704 | params["ieee8021x"] = "1" | |
705 | if ssid: | |
706 | params["ssid"] = ssid | |
707 | return params | |
c0ca24fc JD |
708 | |
709 | def b_only_params(channel="1", ssid=None, country=None): | |
fab49f61 JM |
710 | params = {"hw_mode": "b", |
711 | "channel": channel} | |
c0ca24fc JD |
712 | if ssid: |
713 | params["ssid"] = ssid | |
714 | if country: | |
715 | params["country_code"] = country | |
716 | return params | |
717 | ||
718 | def g_only_params(channel="1", ssid=None, country=None): | |
fab49f61 JM |
719 | params = {"hw_mode": "g", |
720 | "channel": channel} | |
c0ca24fc JD |
721 | if ssid: |
722 | params["ssid"] = ssid | |
723 | if country: | |
724 | params["country_code"] = country | |
725 | return params | |
726 | ||
727 | def a_only_params(channel="36", ssid=None, country=None): | |
fab49f61 JM |
728 | params = {"hw_mode": "a", |
729 | "channel": channel} | |
c0ca24fc JD |
730 | if ssid: |
731 | params["ssid"] = ssid | |
732 | if country: | |
733 | params["country_code"] = country | |
734 | return params | |
735 | ||
736 | def ht20_params(channel="1", ssid=None, country=None): | |
fab49f61 JM |
737 | params = {"ieee80211n": "1", |
738 | "channel": channel, | |
739 | "hw_mode": "g"} | |
c0ca24fc JD |
740 | if int(channel) > 14: |
741 | params["hw_mode"] = "a" | |
742 | if ssid: | |
743 | params["ssid"] = ssid | |
744 | if country: | |
745 | params["country_code"] = country | |
746 | return params | |
747 | ||
748 | def ht40_plus_params(channel="1", ssid=None, country=None): | |
749 | params = ht20_params(channel, ssid, country) | |
750 | params['ht_capab'] = "[HT40+]" | |
751 | return params | |
752 | ||
753 | def ht40_minus_params(channel="1", ssid=None, country=None): | |
754 | params = ht20_params(channel, ssid, country) | |
755 | params['ht_capab'] = "[HT40-]" | |
756 | return params | |
7fd9fbc2 | 757 | |
e9f2d54f | 758 | def cmd_execute(apdev, cmd, shell=False): |
7fd9fbc2 | 759 | hapd_global = HostapdGlobal(apdev) |
e9f2d54f | 760 | return hapd_global.cmd_execute(cmd, shell=shell) |
16f18b2c JD |
761 | |
762 | def send_file(apdev, src, dst): | |
763 | hapd_global = HostapdGlobal(apdev) | |
764 | return hapd_global.send_file(src, dst) | |
4d148384 JD |
765 | |
766 | def acl_file(dev, apdev, conf): | |
767 | filename = os.path.join("/tmp", conf) | |
768 | ||
769 | if conf == 'hostapd.macaddr': | |
770 | with open(filename, 'w') as f: | |
771 | mac0 = dev[0].get_status_field("address") | |
772 | f.write(mac0 + '\n') | |
773 | f.write("02:00:00:00:00:12\n") | |
774 | f.write("02:00:00:00:00:34\n") | |
775 | f.write("-02:00:00:00:00:12\n") | |
776 | f.write("-02:00:00:00:00:34\n") | |
777 | f.write("01:01:01:01:01:01\n") | |
778 | f.write("03:01:01:01:01:03\n") | |
779 | elif conf == 'hostapd.accept': | |
780 | with open(filename, 'w') as f: | |
781 | mac0 = dev[0].get_status_field("address") | |
782 | mac1 = dev[1].get_status_field("address") | |
783 | f.write(mac0 + " 1\n") | |
784 | f.write(mac1 + " 2\n") | |
785 | elif conf == 'hostapd.accept2': | |
786 | with open(filename, 'w') as f: | |
787 | mac0 = dev[0].get_status_field("address") | |
788 | mac1 = dev[1].get_status_field("address") | |
789 | mac2 = dev[2].get_status_field("address") | |
790 | f.write(mac0 + " 1\n") | |
791 | f.write(mac1 + " 2\n") | |
792 | f.write(mac2 + " 3\n") | |
793 | else: | |
794 | return conf | |
795 | ||
796 | return filename | |
ba1ff57a JD |
797 | |
798 | def bssid_inc(apdev, inc=1): | |
799 | parts = apdev['bssid'].split(':') | |
800 | parts[5] = '%02x' % (int(parts[5], 16) + int(inc)) | |
801 | bssid = '%s:%s:%s:%s:%s:%s' % (parts[0], parts[1], parts[2], | |
802 | parts[3], parts[4], parts[5]) | |
803 | return bssid | |
804 | ||
805 | def cfg_file(apdev, conf, ifname=None): | |
806 | # put cfg file in /tmp directory | |
807 | fname = os.path.join("/tmp", conf) | |
808 | ||
809 | match = re.search(r'^bss-\d+', conf) | |
810 | if match: | |
811 | with open(fname, 'w') as f: | |
812 | idx = ''.join(filter(str.isdigit, conf)) | |
813 | if ifname is None: | |
814 | ifname = apdev['ifname'] | |
815 | if idx != '1': | |
816 | ifname = ifname + '-' + idx | |
817 | ||
818 | f.write("driver=nl80211\n") | |
819 | f.write("ctrl_interface=/var/run/hostapd\n") | |
820 | f.write("hw_mode=g\n") | |
821 | f.write("channel=1\n") | |
822 | f.write("ieee80211n=1\n") | |
823 | f.write("interface=%s\n" % ifname) | |
824 | ||
825 | f.write("ssid=bss-%s\n" % idx) | |
826 | if conf == 'bss-2-dup.conf': | |
827 | bssid = apdev['bssid'] | |
828 | else: | |
829 | bssid = bssid_inc(apdev, int(idx) - 1) | |
830 | f.write("bssid=%s\n" % bssid) | |
831 | else: | |
832 | return conf | |
833 | ||
834 | return fname |