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.host = remotehost.Host(hostname, ifname)
125 self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
126 self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
129 self.ctrl = wpaspy.Ctrl(hostname, port)
130 self.mon = wpaspy.Ctrl(hostname, port)
131 self.dbg = hostname + "/" + ifname
136 def close_ctrl(self):
137 if self.mon is not None:
145 if self.bssid is None:
146 self.bssid = self.get_status_field('bssid[%d]' % self.bssidx)
149 def request(self, cmd):
150 logger.debug(self.dbg + ": CTRL: " + cmd)
151 return self.ctrl.request(cmd)
154 return "PONG" in self.request("PING")
156 def set(self, field, value):
157 if not "OK" in self.request("SET " + field + " " + value):
158 raise Exception("Failed to set hostapd parameter " + field)
160 def set_defaults(self):
161 self.set("driver", "nl80211")
162 self.set("hw_mode", "g")
163 self.set("channel", "1")
164 self.set("ieee80211n", "1")
165 self.set("logger_stdout", "-1")
166 self.set("logger_stdout_level", "0")
168 def set_open(self, ssid):
170 self.set("ssid", ssid)
172 def set_wpa2_psk(self, ssid, passphrase):
174 self.set("ssid", ssid)
175 self.set("wpa_passphrase", passphrase)
177 self.set("wpa_key_mgmt", "WPA-PSK")
178 self.set("rsn_pairwise", "CCMP")
180 def set_wpa_psk(self, ssid, passphrase):
182 self.set("ssid", ssid)
183 self.set("wpa_passphrase", passphrase)
185 self.set("wpa_key_mgmt", "WPA-PSK")
186 self.set("wpa_pairwise", "TKIP")
188 def set_wpa_psk_mixed(self, ssid, passphrase):
190 self.set("ssid", ssid)
191 self.set("wpa_passphrase", passphrase)
193 self.set("wpa_key_mgmt", "WPA-PSK")
194 self.set("wpa_pairwise", "TKIP")
195 self.set("rsn_pairwise", "CCMP")
197 def set_wep(self, ssid, key):
199 self.set("ssid", ssid)
200 self.set("wep_key0", key)
203 if not "OK" in self.request("ENABLE"):
204 raise Exception("Failed to enable hostapd interface " + self.ifname)
207 if not "OK" in self.request("DISABLE"):
208 raise Exception("Failed to disable hostapd interface " + self.ifname)
210 def dump_monitor(self):
211 while self.mon.pending():
213 logger.debug(self.dbg + ": " + ev)
215 def wait_event(self, events, timeout):
216 start = os.times()[4]
218 while self.mon.pending():
220 logger.debug(self.dbg + ": " + ev)
225 remaining = start + timeout - now
228 if not self.mon.pending(timeout=remaining):
232 def get_status(self):
233 res = self.request("STATUS")
234 lines = res.splitlines()
237 [name,value] = l.split('=', 1)
241 def get_status_field(self, field):
242 vals = self.get_status()
247 def get_driver_status(self):
248 res = self.request("STATUS-DRIVER")
249 lines = res.splitlines()
252 [name,value] = l.split('=', 1)
256 def get_driver_status_field(self, field):
257 vals = self.get_driver_status()
262 def get_config(self):
263 res = self.request("GET_CONFIG")
264 lines = res.splitlines()
267 [name,value] = l.split('=', 1)
271 def mgmt_rx(self, timeout=5):
272 ev = self.wait_event(["MGMT-RX"], timeout=timeout)
276 frame = binascii.unhexlify(ev.split(' ')[1])
279 hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
281 msg['subtype'] = (hdr[0] >> 4) & 0xf
283 msg['duration'] = hdr[0]
285 msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
287 msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
289 msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
291 msg['seq_ctrl'] = hdr[0]
292 msg['payload'] = frame[24:]
296 def mgmt_tx(self, msg):
297 t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
298 hdr = struct.pack('<HH6B6B6BH', *t)
299 self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
301 def get_sta(self, addr, info=None, next=False):
302 cmd = "STA-NEXT " if next else "STA "
304 res = self.request("STA-FIRST")
306 res = self.request(cmd + addr + " " + info)
308 res = self.request(cmd + addr)
309 lines = res.splitlines()
313 if first and '=' not in l:
317 [name,value] = l.split('=', 1)
321 def get_mib(self, param=None):
323 res = self.request("MIB " + param)
325 res = self.request("MIB")
326 lines = res.splitlines()
329 name_val = l.split('=', 1)
330 if len(name_val) > 1:
331 vals[name_val[0]] = name_val[1]
334 def get_pmksa(self, addr):
335 res = self.request("PMKSA")
336 lines = res.splitlines()
341 [index,aa,pmkid,expiration,opportunistic] = l.split(' ')
342 vals['index'] = index
343 vals['pmkid'] = pmkid
344 vals['expiration'] = expiration
345 vals['opportunistic'] = opportunistic
349 def add_ap(apdev, params, wait_enabled=True, no_enable=False, timeout=30):
350 if isinstance(apdev, dict):
351 ifname = apdev['ifname']
353 hostname = apdev['hostname']
355 logger.info("Starting AP " + hostname + "/" + port + " " + ifname)
357 logger.info("Starting AP " + ifname)
362 logger.info("Starting AP " + ifname + " (old add_ap argument type)")
365 hapd_global = HostapdGlobal(apdev)
366 hapd_global.remove(ifname)
367 hapd_global.add(ifname)
368 port = hapd_global.get_ctrl_iface_port(ifname)
369 hapd = Hostapd(ifname, hostname=hostname, port=port)
371 raise Exception("Could not ping hostapd")
373 fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
375 "wpa_pairwise", "rsn_pairwise", "auth_server_addr",
376 "acct_server_addr", "osu_server_uri" ]
379 hapd.set(field, params[field])
380 for f,v in params.items():
383 if isinstance(v, list):
392 ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=timeout)
394 raise Exception("AP startup timed out")
395 if "AP-ENABLED" not in ev:
396 raise Exception("AP startup failed")
399 def add_bss(apdev, ifname, confname, ignore_error=False):
400 phy = utils.get_phy(apdev)
402 hostname = apdev['hostname']
404 logger.info("Starting BSS " + hostname + "/" + port + " phy=" + phy + " ifname=" + ifname)
406 logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
409 hapd_global = HostapdGlobal(apdev)
410 hapd_global.add_bss(phy, confname, ignore_error)
411 port = hapd_global.get_ctrl_iface_port(ifname)
412 hapd = Hostapd(ifname, hostname=hostname, port=port)
414 raise Exception("Could not ping hostapd")
417 def add_iface(apdev, confname):
418 ifname = apdev['ifname']
420 hostname = apdev['hostname']
422 logger.info("Starting interface " + hostname + "/" + port + " " + ifname)
424 logger.info("Starting interface " + ifname)
427 hapd_global = HostapdGlobal(apdev)
428 hapd_global.add_iface(ifname, confname)
429 port = hapd_global.get_ctrl_iface_port(ifname)
430 hapd = Hostapd(ifname, hostname=hostname, port=port)
432 raise Exception("Could not ping hostapd")
435 def remove_bss(apdev, ifname=None):
437 ifname = apdev['ifname']
439 hostname = apdev['hostname']
441 logger.info("Removing BSS " + hostname + "/" + port + " " + ifname)
443 logger.info("Removing BSS " + ifname)
444 hapd_global = HostapdGlobal(apdev)
445 hapd_global.remove(ifname)
447 def terminate(apdev):
449 hostname = apdev['hostname']
451 logger.info("Terminating hostapd " + hostname + "/" + port)
453 logger.info("Terminating hostapd")
454 hapd_global = HostapdGlobal(apdev)
455 hapd_global.terminate()
457 def wpa2_params(ssid=None, passphrase=None):
458 params = { "wpa": "2",
459 "wpa_key_mgmt": "WPA-PSK",
460 "rsn_pairwise": "CCMP" }
462 params["ssid"] = ssid
464 params["wpa_passphrase"] = passphrase
467 def wpa_params(ssid=None, passphrase=None):
468 params = { "wpa": "1",
469 "wpa_key_mgmt": "WPA-PSK",
470 "wpa_pairwise": "TKIP" }
472 params["ssid"] = ssid
474 params["wpa_passphrase"] = passphrase
477 def wpa_mixed_params(ssid=None, passphrase=None):
478 params = { "wpa": "3",
479 "wpa_key_mgmt": "WPA-PSK",
480 "wpa_pairwise": "TKIP",
481 "rsn_pairwise": "CCMP" }
483 params["ssid"] = ssid
485 params["wpa_passphrase"] = passphrase
489 params = { "auth_server_addr": "127.0.0.1",
490 "auth_server_port": "1812",
491 "auth_server_shared_secret": "radius",
492 "nas_identifier": "nas.w1.fi" }
495 def wpa_eap_params(ssid=None):
496 params = radius_params()
498 params["wpa_key_mgmt"] = "WPA-EAP"
499 params["wpa_pairwise"] = "TKIP"
500 params["ieee8021x"] = "1"
502 params["ssid"] = ssid
505 def wpa2_eap_params(ssid=None):
506 params = radius_params()
508 params["wpa_key_mgmt"] = "WPA-EAP"
509 params["rsn_pairwise"] = "CCMP"
510 params["ieee8021x"] = "1"
512 params["ssid"] = ssid
515 def b_only_params(channel="1", ssid=None, country=None):
516 params = { "hw_mode" : "b",
517 "channel" : channel }
519 params["ssid"] = ssid
521 params["country_code"] = country
524 def g_only_params(channel="1", ssid=None, country=None):
525 params = { "hw_mode" : "g",
526 "channel" : channel }
528 params["ssid"] = ssid
530 params["country_code"] = country
533 def a_only_params(channel="36", ssid=None, country=None):
534 params = { "hw_mode" : "a",
535 "channel" : channel }
537 params["ssid"] = ssid
539 params["country_code"] = country
542 def ht20_params(channel="1", ssid=None, country=None):
543 params = { "ieee80211n" : "1",
546 if int(channel) > 14:
547 params["hw_mode"] = "a"
549 params["ssid"] = ssid
551 params["country_code"] = country
554 def ht40_plus_params(channel="1", ssid=None, country=None):
555 params = ht20_params(channel, ssid, country)
556 params['ht_capab'] = "[HT40+]"
559 def ht40_minus_params(channel="1", ssid=None, country=None):
560 params = ht20_params(channel, ssid, country)
561 params['ht_capab'] = "[HT40-]"