1 # Python class for controlling hostapd
2 # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi>
4 # This software may be distributed under the terms of the BSD license.
5 # See README for more details.
16 logger = logging.getLogger()
17 hapd_ctrl = '/var/run/hostapd'
18 hapd_global = '/var/run/hostapd-global'
21 return struct.unpack('6B', binascii.unhexlify(mac.replace(':','')))
24 def __init__(self, apdev=None):
26 hostname = apdev['hostname']
31 self.host = remotehost.Host(hostname)
32 self.hostname = hostname
35 self.ctrl = wpaspy.Ctrl(hapd_global)
36 self.mon = wpaspy.Ctrl(hapd_global)
39 self.ctrl = wpaspy.Ctrl(hostname, port)
40 self.mon = wpaspy.Ctrl(hostname, port)
41 self.dbg = hostname + "/" + str(port)
44 def request(self, cmd, timeout=10):
45 logger.debug(self.dbg + ": CTRL(global): " + cmd)
46 return self.ctrl.request(cmd, timeout)
48 def wait_event(self, events, timeout):
51 while self.mon.pending():
53 logger.debug(self.dbg + "(global): " + ev)
58 remaining = start + timeout - now
61 if not self.mon.pending(timeout=remaining):
65 def add(self, ifname, driver=None):
66 cmd = "ADD " + ifname + " " + hapd_ctrl
69 res = self.request(cmd)
71 raise Exception("Could not add hostapd interface " + ifname)
73 def add_iface(self, ifname, confname):
74 res = self.request("ADD " + ifname + " config=" + confname)
76 raise Exception("Could not add hostapd interface")
78 def add_bss(self, phy, confname, ignore_error=False):
79 res = self.request("ADD bss_config=" + phy + ":" + confname)
82 raise Exception("Could not add hostapd BSS")
84 def remove(self, ifname):
85 self.request("REMOVE " + ifname, timeout=30)
93 def get_ctrl_iface_port(self, ifname):
94 if self.hostname is None:
97 res = self.request("INTERFACES ctrl")
98 lines = res.splitlines()
102 if words[0] == ifname:
106 raise Exception("Could not find UDP port for " + ifname)
107 res = line.find("ctrl_iface=udp:")
109 raise Exception("Wrong ctrl_interface format")
110 words = line.split(":")
117 self.ctrl.terminate()
121 def __init__(self, ifname, bssidx=0, hostname=None, port=8877):
122 self.hostname = hostname
123 self.host = remotehost.Host(hostname, ifname)
126 self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
127 self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
130 self.ctrl = wpaspy.Ctrl(hostname, port)
131 self.mon = wpaspy.Ctrl(hostname, port)
132 self.dbg = hostname + "/" + ifname
137 def close_ctrl(self):
138 if self.mon is not None:
146 if self.bssid is None:
147 self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
150 def request(self, cmd):
151 logger.debug(self.dbg + ": CTRL: " + cmd)
152 return self.ctrl.request(cmd)
155 return "PONG" in self.request("PING")
157 def set(self, field, value):
158 if not "OK" in self.request("SET " + field + " " + value):
159 raise Exception("Failed to set hostapd parameter " + field)
161 def set_defaults(self):
162 self.set("driver", "nl80211")
163 self.set("hw_mode", "g")
164 self.set("channel", "1")
165 self.set("ieee80211n", "1")
166 self.set("logger_stdout", "-1")
167 self.set("logger_stdout_level", "0")
169 def set_open(self, ssid):
171 self.set("ssid", ssid)
173 def set_wpa2_psk(self, ssid, passphrase):
175 self.set("ssid", ssid)
176 self.set("wpa_passphrase", passphrase)
178 self.set("wpa_key_mgmt", "WPA-PSK")
179 self.set("rsn_pairwise", "CCMP")
181 def set_wpa_psk(self, ssid, passphrase):
183 self.set("ssid", ssid)
184 self.set("wpa_passphrase", passphrase)
186 self.set("wpa_key_mgmt", "WPA-PSK")
187 self.set("wpa_pairwise", "TKIP")
189 def set_wpa_psk_mixed(self, ssid, passphrase):
191 self.set("ssid", ssid)
192 self.set("wpa_passphrase", passphrase)
194 self.set("wpa_key_mgmt", "WPA-PSK")
195 self.set("wpa_pairwise", "TKIP")
196 self.set("rsn_pairwise", "CCMP")
198 def set_wep(self, ssid, key):
200 self.set("ssid", ssid)
201 self.set("wep_key0", key)
204 if not "OK" in self.request("ENABLE"):
205 raise Exception("Failed to enable hostapd interface " + self.ifname)
208 if not "OK" in self.request("DISABLE"):
209 raise Exception("Failed to disable hostapd interface " + self.ifname)
211 def dump_monitor(self):
212 while self.mon.pending():
214 logger.debug(self.dbg + ": " + ev)
216 def wait_event(self, events, timeout):
217 start = os.times()[4]
219 while self.mon.pending():
221 logger.debug(self.dbg + ": " + ev)
226 remaining = start + timeout - now
229 if not self.mon.pending(timeout=remaining):
233 def get_status(self):
234 res = self.request("STATUS")
235 lines = res.splitlines()
238 [name,value] = l.split('=', 1)
242 def get_status_field(self, field):
243 vals = self.get_status()
248 def get_driver_status(self):
249 res = self.request("STATUS-DRIVER")
250 lines = res.splitlines()
253 [name,value] = l.split('=', 1)
257 def get_driver_status_field(self, field):
258 vals = self.get_driver_status()
263 def get_config(self):
264 res = self.request("GET_CONFIG")
265 lines = res.splitlines()
268 [name,value] = l.split('=', 1)
272 def mgmt_rx(self, timeout=5):
273 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
277 frame = binascii.unhexlify(ev.split(' ')[1])
280 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
282 msg['subtype'] = (hdr[0] >> 4) & 0xf
284 msg['duration'] = hdr[0]
286 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
288 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
290 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
292 msg['seq_ctrl'] = hdr[0]
293 msg['payload'] = frame[24:]
297 def mgmt_tx(self, msg):
298 t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
299 hdr = struct.pack('<HH6B6B6BH', *t)
300 self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
302 def get_sta(self, addr, info=None, next=False):
303 cmd = "STA-NEXT " if next else "STA "
305 res = self.request("STA-FIRST")
307 res = self.request(cmd + addr + " " + info)
309 res = self.request(cmd + addr)
310 lines = res.splitlines()
314 if first and '=' not in l:
318 [name,value] = l.split('=', 1)
322 def get_mib(self, param=None):
324 res = self.request("MIB " + param)
326 res = self.request("MIB")
327 lines = res.splitlines()
330 name_val = l.split('=', 1)
331 if len(name_val) > 1:
332 vals[name_val[0]] = name_val[1]
335 def get_pmksa(self, addr):
336 res = self.request("PMKSA")
337 lines = res.splitlines()
342 [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
343 vals['index'] = index
344 vals['pmkid'] = pmkid
345 vals['expiration'] = expiration
346 vals['opportunistic'] = opportunistic
350 def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30):
351 if isinstance(apdev, dict):
352 ifname = apdev['ifname']
354 hostname = apdev['hostname']
356 logger.info("Starting AP " + hostname + "/" + port + " " + ifname)
358 logger.info("Starting AP " + ifname)
363 logger.info("Starting AP " + ifname + " (old add_ap argument type)")
366 hapd_global = HostapdGlobal(apdev)
367 hapd_global.remove(ifname)
368 hapd_global.add(ifname)
369 port = hapd_global.get_ctrl_iface_port(ifname)
370 hapd = Hostapd(ifname, hostname=hostname, port=port)
372 raise Exception("Could not ping hostapd")
374 fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
376 "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
377 "acct_server_addr", "osu_server_uri" ]
380 hapd.set(field, params[field])
381 for f,v in params.items():
384 if isinstance(v, list):
393 ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
395 raise Exception("AP startup timed out")
396 if "AP-ENABLED" not in ev:
397 raise Exception("AP startup failed")
400 def add_bss(apdev, ifname, confname, ignore_error=False):
401 phy = utils.get_phy(apdev)
403 hostname = apdev['hostname']
405 logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname)
407 logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
410 hapd_global = HostapdGlobal(apdev)
411 hapd_global.add_bss(phy, confname, ignore_error)
412 port = hapd_global.get_ctrl_iface_port(ifname)
413 hapd = Hostapd(ifname, hostname=hostname, port=port)
415 raise Exception("Could not ping hostapd")
418 def add_iface(apdev, confname):
419 ifname = apdev['ifname']
421 hostname = apdev['hostname']
423 logger.info("Starting interface " + hostname + "/" + port + " " + ifname)
425 logger.info("Starting interface " + ifname)
428 hapd_global = HostapdGlobal(apdev)
429 hapd_global.add_iface(ifname, confname)
430 port = hapd_global.get_ctrl_iface_port(ifname)
431 hapd = Hostapd(ifname, hostname=hostname, port=port)
433 raise Exception("Could not ping hostapd")
436 def remove_bss(apdev, ifname=None):
438 ifname = apdev['ifname']
440 hostname = apdev['hostname']
442 logger.info("Removing BSS " + hostname + "/" + port + " " + ifname)
444 logger.info("Removing BSS " + ifname)
445 hapd_global = HostapdGlobal(apdev)
446 hapd_global.remove(ifname)
448 def terminate(apdev):
450 hostname = apdev['hostname']
452 logger.info("Terminating hostapd " + hostname + "/" + port)
454 logger.info("Terminating hostapd")
455 hapd_global = HostapdGlobal(apdev)
456 hapd_global.terminate()
458 def wpa2_params(ssid=None, passphrase=None):
459 params = { "wpa": "2",
460 "wpa_key_mgmt": "WPA-PSK",
461 "rsn_pairwise": "CCMP" }
463 params["ssid"] = ssid
465 params["wpa_passphrase"] = passphrase
468 def wpa_params(ssid=None, passphrase=None):
469 params = { "wpa": "1",
470 "wpa_key_mgmt": "WPA-PSK",
471 "wpa_pairwise": "TKIP" }
473 params["ssid"] = ssid
475 params["wpa_passphrase"] = passphrase
478 def wpa_mixed_params(ssid=None, passphrase=None):
479 params = { "wpa": "3",
480 "wpa_key_mgmt": "WPA-PSK",
481 "wpa_pairwise": "TKIP",
482 "rsn_pairwise": "CCMP" }
484 params["ssid"] = ssid
486 params["wpa_passphrase"] = passphrase
490 params = { "auth_server_addr": "127.0.0.1",
491 "auth_server_port": "1812",
492 "auth_server_shared_secret": "radius",
493 "nas_identifier": "nas.w1.fi" }
496 def wpa_eap_params(ssid=None):
497 params = radius_params()
499 params["wpa_key_mgmt"] = "WPA-EAP"
500 params["wpa_pairwise"] = "TKIP"
501 params["ieee8021x"] = "1"
503 params["ssid"] = ssid
506 def wpa2_eap_params(ssid=None):
507 params = radius_params()
509 params["wpa_key_mgmt"] = "WPA-EAP"
510 params["rsn_pairwise"] = "CCMP"
511 params["ieee8021x"] = "1"
513 params["ssid"] = ssid
516 def b_only_params(channel="1", ssid=None, country=None):
517 params = { "hw_mode" : "b",
518 "channel" : channel }
520 params["ssid"] = ssid
522 params["country_code"] = country
525 def g_only_params(channel="1", ssid=None, country=None):
526 params = { "hw_mode" : "g",
527 "channel" : channel }
529 params["ssid"] = ssid
531 params["country_code"] = country
534 def a_only_params(channel="36", ssid=None, country=None):
535 params = { "hw_mode" : "a",
536 "channel" : channel }
538 params["ssid"] = ssid
540 params["country_code"] = country
543 def ht20_params(channel="1", ssid=None, country=None):
544 params = { "ieee80211n" : "1",
547 if int(channel) > 14:
548 params["hw_mode"] = "a"
550 params["ssid"] = ssid
552 params["country_code"] = country
555 def ht40_plus_params(channel="1", ssid=None, country=None):
556 params = ht20_params(channel, ssid, country)
557 params['ht_capab'] = "[HT40+]"
560 def ht40_minus_params(channel="1", ssid=None, country=None):
561 params = ht20_params(channel, ssid, country)
562 params['ht_capab'] = "[HT40-]"