]> git.ipfire.org Git - thirdparty/hostap.git/blame - tests/hwsim/hostapd.py
WNM: Fetch scan results before checking transition candidates
[thirdparty/hostap.git] / tests / hwsim / hostapd.py
CommitLineData
b8cd4c54 1# Python class for controlling hostapd
36408936 2# Copyright (c) 2013-2014, 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
7import os
8import time
9import logging
bfe375ec
JM
10import binascii
11import struct
b8cd4c54 12import wpaspy
8ce4855b 13import remotehost
b8cd4c54 14
c9aa4308 15logger = logging.getLogger()
b8cd4c54 16hapd_ctrl = '/var/run/hostapd'
8c87f65f 17hapd_global = '/var/run/hostapd-global'
b8cd4c54 18
bfe375ec
JM
19def mac2tuple(mac):
20 return struct.unpack('6B', binascii.unhexlify(mac.replace(':','')))
21
b8cd4c54 22class HostapdGlobal:
cb73f7e8 23 def __init__(self, hostname=None, port=8878):
8ce4855b 24 self.host = remotehost.Host(hostname)
cb73f7e8
JD
25 self.hostname = hostname
26 self.port = port
27 if hostname is None:
28 self.ctrl = wpaspy.Ctrl(hapd_global)
29 self.mon = wpaspy.Ctrl(hapd_global)
d4944fad 30 self.dbg = ""
cb73f7e8
JD
31 else:
32 self.ctrl = wpaspy.Ctrl(hostname, port)
33 self.mon = wpaspy.Ctrl(hostname, port)
d4944fad 34 self.dbg = hostname + "/" + str(port)
41a256ec
AN
35 self.mon.attach()
36
d4944fad
JD
37 def request(self, cmd, timeout=10):
38 logger.debug(self.dbg + ": CTRL(global): " + cmd)
39 return self.ctrl.request(cmd, timeout)
41a256ec
AN
40
41 def wait_event(self, events, timeout):
42 start = os.times()[4]
43 while True:
44 while self.mon.pending():
45 ev = self.mon.recv()
d4944fad 46 logger.debug(self.dbg + "(global): " + ev)
41a256ec
AN
47 for event in events:
48 if event in ev:
49 return ev
50 now = os.times()[4]
51 remaining = start + timeout - now
52 if remaining <= 0:
53 break
54 if not self.mon.pending(timeout=remaining):
55 break
56 return None
b8cd4c54 57
08763216
JM
58 def add(self, ifname, driver=None):
59 cmd = "ADD " + ifname + " " + hapd_ctrl
60 if driver:
61 cmd += " " + driver
d4944fad 62 res = self.request(cmd)
b8cd4c54
JM
63 if not "OK" in res:
64 raise Exception("Could not add hostapd interface " + ifname)
65
77990cd7 66 def add_iface(self, ifname, confname):
d4944fad 67 res = self.request("ADD " + ifname + " config=" + confname)
77990cd7
JM
68 if not "OK" in res:
69 raise Exception("Could not add hostapd interface")
70
a6333977 71 def add_bss(self, phy, confname, ignore_error=False):
d4944fad 72 res = self.request("ADD bss_config=" + phy + ":" + confname)
a6333977
JM
73 if not "OK" in res:
74 if not ignore_error:
75 raise Exception("Could not add hostapd BSS")
76
b8cd4c54 77 def remove(self, ifname):
d4944fad 78 self.request("REMOVE " + ifname, timeout=30)
b8cd4c54 79
75428961 80 def relog(self):
d4944fad 81 self.request("RELOG")
75428961 82
f8949f5f 83 def flush(self):
d4944fad 84 self.request("FLUSH")
f8949f5f 85
4d48d44c
JD
86 def get_ctrl_iface_port(self, ifname):
87 if self.hostname is None:
88 return None
89
d4944fad 90 res = self.request("INTERFACES ctrl")
4d48d44c
JD
91 lines = res.splitlines()
92 found = False
93 for line in lines:
94 words = line.split()
95 if words[0] == ifname:
96 found = True
97 break
98 if not found:
99 raise Exception("Could not find UDP port for " + ifname)
100 res = line.find("ctrl_iface=udp:")
101 if res == -1:
102 raise Exception("Wrong ctrl_interface format")
103 words = line.split(":")
104 return int(words[1])
b8cd4c54 105
e3b36d42
JD
106 def terminate(self):
107 self.mon.detach()
108 self.mon.close()
109 self.mon = None
110 self.ctrl.terminate()
111 self.ctrl = None
112
b8cd4c54 113class Hostapd:
cb73f7e8 114 def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
8ce4855b 115 self.host = remotehost.Host(hostname, ifname)
b8cd4c54 116 self.ifname = ifname
cb73f7e8
JD
117 if hostname is None:
118 self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
119 self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
d4944fad 120 self.dbg = ifname
cb73f7e8
JD
121 else:
122 self.ctrl = wpaspy.Ctrl(hostname, port)
123 self.mon = wpaspy.Ctrl(hostname, port)
d4944fad 124 self.dbg = hostname + "/" + ifname
b47750be 125 self.mon.attach()
f6420942 126 self.bssid = None
54cf411f 127 self.bssidx = bssidx
f6420942 128
e3b36d42
JD
129 def close_ctrl(self):
130 if self.mon is not None:
131 self.mon.detach()
132 self.mon.close()
133 self.mon = None
134 self.ctrl.close()
135 self.ctrl = None
136
f6420942
JM
137 def own_addr(self):
138 if self.bssid is None:
54cf411f 139 self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
f6420942 140 return self.bssid
b8cd4c54
JM
141
142 def request(self, cmd):
d4944fad 143 logger.debug(self.dbg + ": CTRL: " + cmd)
b8cd4c54
JM
144 return self.ctrl.request(cmd)
145
146 def ping(self):
147 return "PONG" in self.request("PING")
148
149 def set(self, field, value):
b8cd4c54
JM
150 if not "OK" in self.request("SET " + field + " " + value):
151 raise Exception("Failed to set hostapd parameter " + field)
152
153 def set_defaults(self):
154 self.set("driver", "nl80211")
155 self.set("hw_mode", "g")
156 self.set("channel", "1")
157 self.set("ieee80211n", "1")
789b9f1d
JM
158 self.set("logger_stdout", "-1")
159 self.set("logger_stdout_level", "0")
b8cd4c54
JM
160
161 def set_open(self, ssid):
162 self.set_defaults()
163 self.set("ssid", ssid)
164
165 def set_wpa2_psk(self, ssid, passphrase):
166 self.set_defaults()
167 self.set("ssid", ssid)
168 self.set("wpa_passphrase", passphrase)
169 self.set("wpa", "2")
170 self.set("wpa_key_mgmt", "WPA-PSK")
171 self.set("rsn_pairwise", "CCMP")
172
e492837b
JM
173 def set_wpa_psk(self, ssid, passphrase):
174 self.set_defaults()
175 self.set("ssid", ssid)
176 self.set("wpa_passphrase", passphrase)
177 self.set("wpa", "1")
178 self.set("wpa_key_mgmt", "WPA-PSK")
179 self.set("wpa_pairwise", "TKIP")
180
181 def set_wpa_psk_mixed(self, ssid, passphrase):
182 self.set_defaults()
183 self.set("ssid", ssid)
184 self.set("wpa_passphrase", passphrase)
185 self.set("wpa", "3")
186 self.set("wpa_key_mgmt", "WPA-PSK")
187 self.set("wpa_pairwise", "TKIP")
188 self.set("rsn_pairwise", "CCMP")
189
0165c4be
JM
190 def set_wep(self, ssid, key):
191 self.set_defaults()
192 self.set("ssid", ssid)
193 self.set("wep_key0", key)
194
b8cd4c54 195 def enable(self):
d45e417f 196 if not "OK" in self.request("ENABLE"):
b8cd4c54
JM
197 raise Exception("Failed to enable hostapd interface " + self.ifname)
198
199 def disable(self):
00f74dbd 200 if not "OK" in self.request("DISABLE"):
b8cd4c54 201 raise Exception("Failed to disable hostapd interface " + self.ifname)
e259d186 202
b47750be
JM
203 def dump_monitor(self):
204 while self.mon.pending():
205 ev = self.mon.recv()
d4944fad 206 logger.debug(self.dbg + ": " + ev)
b47750be
JM
207
208 def wait_event(self, events, timeout):
36408936
JM
209 start = os.times()[4]
210 while True:
b47750be
JM
211 while self.mon.pending():
212 ev = self.mon.recv()
d4944fad 213 logger.debug(self.dbg + ": " + ev)
b47750be
JM
214 for event in events:
215 if event in ev:
216 return ev
36408936
JM
217 now = os.times()[4]
218 remaining = start + timeout - now
219 if remaining <= 0:
220 break
221 if not self.mon.pending(timeout=remaining):
222 break
b47750be
JM
223 return None
224
225 def get_status(self):
226 res = self.request("STATUS")
227 lines = res.splitlines()
228 vals = dict()
229 for l in lines:
230 [name,value] = l.split('=', 1)
231 vals[name] = value
232 return vals
233
234 def get_status_field(self, field):
235 vals = self.get_status()
236 if field in vals:
237 return vals[field]
238 return None
239
a36158be
JM
240 def get_driver_status(self):
241 res = self.request("STATUS-DRIVER")
242 lines = res.splitlines()
243 vals = dict()
244 for l in lines:
245 [name,value] = l.split('=', 1)
246 vals[name] = value
247 return vals
248
249 def get_driver_status_field(self, field):
250 vals = self.get_driver_status()
251 if field in vals:
252 return vals[field]
253 return None
254
65038313
JM
255 def get_config(self):
256 res = self.request("GET_CONFIG")
257 lines = res.splitlines()
258 vals = dict()
259 for l in lines:
260 [name,value] = l.split('=', 1)
261 vals[name] = value
262 return vals
263
bfe375ec
JM
264 def mgmt_rx(self, timeout=5):
265 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
266 if ev is None:
267 return None
268 msg = {}
269 frame = binascii.unhexlify(ev.split(' ')[1])
270 msg['frame'] = frame
271
272 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
273 msg['fc'] = hdr[0]
274 msg['subtype'] = (hdr[0] >> 4) & 0xf
275 hdr = hdr[1:]
276 msg['duration'] = hdr[0]
277 hdr = hdr[1:]
278 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
279 hdr = hdr[6:]
280 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
281 hdr = hdr[6:]
282 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
283 hdr = hdr[6:]
284 msg['seq_ctrl'] = hdr[0]
285 msg['payload'] = frame[24:]
286
287 return msg
288
289 def mgmt_tx(self, msg):
290 t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
291 hdr = struct.pack('<HH6B6B6BH', *t)
292 self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
293
cce26eb4
JM
294 def get_sta(self, addr, info=None, next=False):
295 cmd = "STA-NEXT " if next else "STA "
296 if addr is None:
297 res = self.request("STA-FIRST")
298 elif info:
299 res = self.request(cmd + addr + " " + info)
5dec879d 300 else:
cce26eb4 301 res = self.request(cmd + addr)
6435799b
JM
302 lines = res.splitlines()
303 vals = dict()
304 first = True
305 for l in lines:
2496adf0 306 if first and '=' not in l:
6435799b
JM
307 vals['addr'] = l
308 first = False
309 else:
310 [name,value] = l.split('=', 1)
311 vals[name] = value
312 return vals
313
4fcee244
JM
314 def get_mib(self, param=None):
315 if param:
316 res = self.request("MIB " + param)
317 else:
318 res = self.request("MIB")
7fd15145
JM
319 lines = res.splitlines()
320 vals = dict()
321 for l in lines:
4fcee244
JM
322 name_val = l.split('=', 1)
323 if len(name_val) > 1:
324 vals[name_val[0]] = name_val[1]
7fd15145
JM
325 return vals
326
865fa1e9
JM
327 def get_pmksa(self, addr):
328 res = self.request("PMKSA")
329 lines = res.splitlines()
330 for l in lines:
331 if addr not in l:
332 continue
333 vals = dict()
334 [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
335 vals['index'] = index
336 vals['pmkid'] = pmkid
337 vals['expiration'] = expiration
338 vals['opportunistic'] = opportunistic
339 return vals
340 return None
341
78b83193
JD
342def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30):
343 if isinstance(apdev, dict):
344 ifname = apdev['ifname']
345 try:
346 hostname = apdev['hostname']
347 port = apdev['port']
348 logger.info("Starting AP " + hostname + "/" + port + " " + ifname)
349 except:
350 logger.info("Starting AP " + ifname)
351 hostname = None
352 port = 8878
353 else:
354 ifname = apdev
355 logger.info("Starting AP " + ifname + " (old add_ap argument type)")
356 hostname = None
357 port = 8878
cb73f7e8 358 hapd_global = HostapdGlobal(hostname=hostname, port=port)
e259d186
JM
359 hapd_global.remove(ifname)
360 hapd_global.add(ifname)
4d48d44c
JD
361 port = hapd_global.get_ctrl_iface_port(ifname)
362 hapd = Hostapd(ifname, hostname=hostname, port=port)
e259d186
JM
363 if not hapd.ping():
364 raise Exception("Could not ping hostapd")
365 hapd.set_defaults()
cd7f1b9a
JM
366 fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
367 "wpa",
7fd15145 368 "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
97de642a 369 "acct_server_addr", "osu_server_uri" ]
e259d186
JM
370 for field in fields:
371 if field in params:
372 hapd.set(field, params[field])
93a06242
JM
373 for f,v in params.items():
374 if f in fields:
375 continue
376 if isinstance(v, list):
377 for val in v:
378 hapd.set(f, val)
379 else:
380 hapd.set(f, v)
138ec97e
JM
381 if no_enable:
382 return hapd
e259d186 383 hapd.enable()
629dbdd3 384 if wait_enabled:
57ff37d0 385 ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
629dbdd3
JM
386 if ev is None:
387 raise Exception("AP startup timed out")
f8ad9dc2
JM
388 if "AP-ENABLED" not in ev:
389 raise Exception("AP startup failed")
b47750be 390 return hapd
e259d186 391
cb73f7e8
JD
392def add_bss(phy, ifname, confname, ignore_error=False, hostname=None,
393 port=8878):
a6333977 394 logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
cb73f7e8 395 hapd_global = HostapdGlobal(hostname=hostname, port=port)
a6333977 396 hapd_global.add_bss(phy, confname, ignore_error)
4d48d44c
JD
397 port = hapd_global.get_ctrl_iface_port(ifname)
398 hapd = Hostapd(ifname, hostname=hostname, port=port)
a6333977
JM
399 if not hapd.ping():
400 raise Exception("Could not ping hostapd")
401
cb73f7e8 402def add_iface(ifname, confname, hostname=None, port=8878):
77990cd7 403 logger.info("Starting interface " + ifname)
cb73f7e8 404 hapd_global = HostapdGlobal(hostname=hostname, port=port)
77990cd7 405 hapd_global.add_iface(ifname, confname)
4d48d44c
JD
406 port = hapd_global.get_ctrl_iface_port(ifname)
407 hapd = Hostapd(ifname, hostname=hostname, port=port)
77990cd7
JM
408 if not hapd.ping():
409 raise Exception("Could not ping hostapd")
410
cb73f7e8 411def remove_bss(ifname, hostname=None, port=8878):
a6333977 412 logger.info("Removing BSS " + ifname)
cb73f7e8 413 hapd_global = HostapdGlobal(hostname=hostname, port=port)
a6333977
JM
414 hapd_global.remove(ifname)
415
e3b36d42
JD
416def terminate(hostname=None, port=8878):
417 logger.info("Terminating hostapd")
418 hapd_global = HostapdGlobal(hostname=hostname, port=port)
419 hapd_global.terminate()
420
e259d186
JM
421def wpa2_params(ssid=None, passphrase=None):
422 params = { "wpa": "2",
423 "wpa_key_mgmt": "WPA-PSK",
424 "rsn_pairwise": "CCMP" }
425 if ssid:
426 params["ssid"] = ssid
427 if passphrase:
428 params["wpa_passphrase"] = passphrase
429 return params
430
431def wpa_params(ssid=None, passphrase=None):
432 params = { "wpa": "1",
433 "wpa_key_mgmt": "WPA-PSK",
434 "wpa_pairwise": "TKIP" }
435 if ssid:
436 params["ssid"] = ssid
437 if passphrase:
438 params["wpa_passphrase"] = passphrase
439 return params
440
441def wpa_mixed_params(ssid=None, passphrase=None):
442 params = { "wpa": "3",
443 "wpa_key_mgmt": "WPA-PSK",
444 "wpa_pairwise": "TKIP",
445 "rsn_pairwise": "CCMP" }
446 if ssid:
447 params["ssid"] = ssid
448 if passphrase:
449 params["wpa_passphrase"] = passphrase
450 return params
9626962d
JM
451
452def radius_params():
453 params = { "auth_server_addr": "127.0.0.1",
454 "auth_server_port": "1812",
455 "auth_server_shared_secret": "radius",
456 "nas_identifier": "nas.w1.fi" }
457 return params
458
71390dc8
JM
459def wpa_eap_params(ssid=None):
460 params = radius_params()
461 params["wpa"] = "1"
462 params["wpa_key_mgmt"] = "WPA-EAP"
463 params["wpa_pairwise"] = "TKIP"
464 params["ieee8021x"] = "1"
465 if ssid:
466 params["ssid"] = ssid
467 return params
468
9626962d
JM
469def wpa2_eap_params(ssid=None):
470 params = radius_params()
471 params["wpa"] = "2"
472 params["wpa_key_mgmt"] = "WPA-EAP"
473 params["rsn_pairwise"] = "CCMP"
474 params["ieee8021x"] = "1"
475 if ssid:
476 params["ssid"] = ssid
477 return params
c0ca24fc
JD
478
479def b_only_params(channel="1", ssid=None, country=None):
480 params = { "hw_mode" : "b",
481 "channel" : channel }
482 if ssid:
483 params["ssid"] = ssid
484 if country:
485 params["country_code"] = country
486 return params
487
488def g_only_params(channel="1", ssid=None, country=None):
489 params = { "hw_mode" : "g",
490 "channel" : channel }
491 if ssid:
492 params["ssid"] = ssid
493 if country:
494 params["country_code"] = country
495 return params
496
497def a_only_params(channel="36", ssid=None, country=None):
498 params = { "hw_mode" : "a",
499 "channel" : channel }
500 if ssid:
501 params["ssid"] = ssid
502 if country:
503 params["country_code"] = country
504 return params
505
506def ht20_params(channel="1", ssid=None, country=None):
507 params = { "ieee80211n" : "1",
508 "channel" : channel,
509 "hw_mode" : "g" }
510 if int(channel) > 14:
511 params["hw_mode"] = "a"
512 if ssid:
513 params["ssid"] = ssid
514 if country:
515 params["country_code"] = country
516 return params
517
518def ht40_plus_params(channel="1", ssid=None, country=None):
519 params = ht20_params(channel, ssid, country)
520 params['ht_capab'] = "[HT40+]"
521 return params
522
523def ht40_minus_params(channel="1", ssid=None, country=None):
524 params = ht20_params(channel, ssid, country)
525 params['ht_capab'] = "[HT40-]"
526 return params