2013-03-29 17:37:03 +01:00
|
|
|
# Python class for controlling hostapd
|
2014-01-05 07:02:06 +01:00
|
|
|
# Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi>
|
2013-03-29 17:37:03 +01:00
|
|
|
#
|
|
|
|
# This software may be distributed under the terms of the BSD license.
|
|
|
|
# See README for more details.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
import logging
|
2013-12-26 09:52:11 +01:00
|
|
|
import binascii
|
|
|
|
import struct
|
2013-03-29 17:37:03 +01:00
|
|
|
import wpaspy
|
|
|
|
|
2013-10-31 11:46:42 +01:00
|
|
|
logger = logging.getLogger()
|
2013-03-29 17:37:03 +01:00
|
|
|
hapd_ctrl = '/var/run/hostapd'
|
2013-05-10 16:09:55 +02:00
|
|
|
hapd_global = '/var/run/hostapd-global'
|
2013-03-29 17:37:03 +01:00
|
|
|
|
2013-12-26 09:52:11 +01:00
|
|
|
def mac2tuple(mac):
|
|
|
|
return struct.unpack('6B', binascii.unhexlify(mac.replace(':','')))
|
|
|
|
|
2013-03-29 17:37:03 +01:00
|
|
|
class HostapdGlobal:
|
|
|
|
def __init__(self):
|
|
|
|
self.ctrl = wpaspy.Ctrl(hapd_global)
|
|
|
|
|
|
|
|
def add(self, ifname):
|
|
|
|
res = self.ctrl.request("ADD " + ifname + " " + hapd_ctrl)
|
|
|
|
if not "OK" in res:
|
|
|
|
raise Exception("Could not add hostapd interface " + ifname)
|
|
|
|
|
2013-11-03 19:20:50 +01:00
|
|
|
def add_iface(self, ifname, confname):
|
|
|
|
res = self.ctrl.request("ADD " + ifname + " config=" + confname)
|
|
|
|
if not "OK" in res:
|
|
|
|
raise Exception("Could not add hostapd interface")
|
|
|
|
|
2013-10-31 16:28:43 +01:00
|
|
|
def add_bss(self, phy, confname, ignore_error=False):
|
|
|
|
res = self.ctrl.request("ADD bss_config=" + phy + ":" + confname)
|
|
|
|
if not "OK" in res:
|
|
|
|
if not ignore_error:
|
|
|
|
raise Exception("Could not add hostapd BSS")
|
|
|
|
|
2013-03-29 17:37:03 +01:00
|
|
|
def remove(self, ifname):
|
2014-03-12 10:42:59 +01:00
|
|
|
self.ctrl.request("REMOVE " + ifname, timeout=30)
|
2013-03-29 17:37:03 +01:00
|
|
|
|
2013-11-02 10:53:38 +01:00
|
|
|
def relog(self):
|
|
|
|
self.ctrl.request("RELOG")
|
|
|
|
|
2013-11-28 14:50:47 +01:00
|
|
|
def flush(self):
|
|
|
|
self.ctrl.request("FLUSH")
|
|
|
|
|
2013-03-29 17:37:03 +01:00
|
|
|
|
|
|
|
class Hostapd:
|
|
|
|
def __init__(self, ifname):
|
|
|
|
self.ifname = ifname
|
|
|
|
self.ctrl = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
|
2013-11-03 19:50:39 +01:00
|
|
|
self.mon = wpaspy.Ctrl(os.path.join(hapd_ctrl, ifname))
|
|
|
|
self.mon.attach()
|
2014-10-19 19:55:02 +02:00
|
|
|
self.bssid = None
|
|
|
|
|
|
|
|
def own_addr(self):
|
|
|
|
if self.bssid is None:
|
|
|
|
self.bssid = self.get_status_field('bssid[0]')
|
|
|
|
return self.bssid
|
2013-03-29 17:37:03 +01:00
|
|
|
|
|
|
|
def request(self, cmd):
|
|
|
|
logger.debug(self.ifname + ": CTRL: " + cmd)
|
|
|
|
return self.ctrl.request(cmd)
|
|
|
|
|
|
|
|
def ping(self):
|
|
|
|
return "PONG" in self.request("PING")
|
|
|
|
|
|
|
|
def set(self, field, value):
|
|
|
|
if not "OK" in self.request("SET " + field + " " + value):
|
|
|
|
raise Exception("Failed to set hostapd parameter " + field)
|
|
|
|
|
|
|
|
def set_defaults(self):
|
|
|
|
self.set("driver", "nl80211")
|
|
|
|
self.set("hw_mode", "g")
|
|
|
|
self.set("channel", "1")
|
|
|
|
self.set("ieee80211n", "1")
|
2013-10-29 23:31:14 +01:00
|
|
|
self.set("logger_stdout", "-1")
|
|
|
|
self.set("logger_stdout_level", "0")
|
2013-03-29 17:37:03 +01:00
|
|
|
|
|
|
|
def set_open(self, ssid):
|
|
|
|
self.set_defaults()
|
|
|
|
self.set("ssid", ssid)
|
|
|
|
|
|
|
|
def set_wpa2_psk(self, ssid, passphrase):
|
|
|
|
self.set_defaults()
|
|
|
|
self.set("ssid", ssid)
|
|
|
|
self.set("wpa_passphrase", passphrase)
|
|
|
|
self.set("wpa", "2")
|
|
|
|
self.set("wpa_key_mgmt", "WPA-PSK")
|
|
|
|
self.set("rsn_pairwise", "CCMP")
|
|
|
|
|
2013-03-29 17:51:48 +01:00
|
|
|
def set_wpa_psk(self, ssid, passphrase):
|
|
|
|
self.set_defaults()
|
|
|
|
self.set("ssid", ssid)
|
|
|
|
self.set("wpa_passphrase", passphrase)
|
|
|
|
self.set("wpa", "1")
|
|
|
|
self.set("wpa_key_mgmt", "WPA-PSK")
|
|
|
|
self.set("wpa_pairwise", "TKIP")
|
|
|
|
|
|
|
|
def set_wpa_psk_mixed(self, ssid, passphrase):
|
|
|
|
self.set_defaults()
|
|
|
|
self.set("ssid", ssid)
|
|
|
|
self.set("wpa_passphrase", passphrase)
|
|
|
|
self.set("wpa", "3")
|
|
|
|
self.set("wpa_key_mgmt", "WPA-PSK")
|
|
|
|
self.set("wpa_pairwise", "TKIP")
|
|
|
|
self.set("rsn_pairwise", "CCMP")
|
|
|
|
|
2013-03-29 18:15:43 +01:00
|
|
|
def set_wep(self, ssid, key):
|
|
|
|
self.set_defaults()
|
|
|
|
self.set("ssid", ssid)
|
|
|
|
self.set("wep_key0", key)
|
|
|
|
|
2013-03-29 17:37:03 +01:00
|
|
|
def enable(self):
|
2014-01-05 06:47:42 +01:00
|
|
|
if not "OK" in self.request("ENABLE"):
|
2013-03-29 17:37:03 +01:00
|
|
|
raise Exception("Failed to enable hostapd interface " + self.ifname)
|
|
|
|
|
|
|
|
def disable(self):
|
2014-03-13 19:41:54 +01:00
|
|
|
if not "OK" in self.request("DISABLE"):
|
2013-03-29 17:37:03 +01:00
|
|
|
raise Exception("Failed to disable hostapd interface " + self.ifname)
|
2013-03-29 19:33:25 +01:00
|
|
|
|
2013-11-03 19:50:39 +01:00
|
|
|
def dump_monitor(self):
|
|
|
|
while self.mon.pending():
|
|
|
|
ev = self.mon.recv()
|
|
|
|
logger.debug(self.ifname + ": " + ev)
|
|
|
|
|
|
|
|
def wait_event(self, events, timeout):
|
2014-01-05 07:02:06 +01:00
|
|
|
start = os.times()[4]
|
|
|
|
while True:
|
2013-11-03 19:50:39 +01:00
|
|
|
while self.mon.pending():
|
|
|
|
ev = self.mon.recv()
|
|
|
|
logger.debug(self.ifname + ": " + ev)
|
|
|
|
for event in events:
|
|
|
|
if event in ev:
|
|
|
|
return ev
|
2014-01-05 07:02:06 +01:00
|
|
|
now = os.times()[4]
|
|
|
|
remaining = start + timeout - now
|
|
|
|
if remaining <= 0:
|
|
|
|
break
|
|
|
|
if not self.mon.pending(timeout=remaining):
|
|
|
|
break
|
2013-11-03 19:50:39 +01:00
|
|
|
return None
|
|
|
|
|
|
|
|
def get_status(self):
|
|
|
|
res = self.request("STATUS")
|
|
|
|
lines = res.splitlines()
|
|
|
|
vals = dict()
|
|
|
|
for l in lines:
|
|
|
|
[name,value] = l.split('=', 1)
|
|
|
|
vals[name] = value
|
|
|
|
return vals
|
|
|
|
|
|
|
|
def get_status_field(self, field):
|
|
|
|
vals = self.get_status()
|
|
|
|
if field in vals:
|
|
|
|
return vals[field]
|
|
|
|
return None
|
|
|
|
|
2014-01-05 20:54:46 +01:00
|
|
|
def get_driver_status(self):
|
|
|
|
res = self.request("STATUS-DRIVER")
|
|
|
|
lines = res.splitlines()
|
|
|
|
vals = dict()
|
|
|
|
for l in lines:
|
|
|
|
[name,value] = l.split('=', 1)
|
|
|
|
vals[name] = value
|
|
|
|
return vals
|
|
|
|
|
|
|
|
def get_driver_status_field(self, field):
|
|
|
|
vals = self.get_driver_status()
|
|
|
|
if field in vals:
|
|
|
|
return vals[field]
|
|
|
|
return None
|
|
|
|
|
2014-03-22 17:57:44 +01:00
|
|
|
def get_config(self):
|
|
|
|
res = self.request("GET_CONFIG")
|
|
|
|
lines = res.splitlines()
|
|
|
|
vals = dict()
|
|
|
|
for l in lines:
|
|
|
|
[name,value] = l.split('=', 1)
|
|
|
|
vals[name] = value
|
|
|
|
return vals
|
|
|
|
|
2013-12-26 09:52:11 +01:00
|
|
|
def mgmt_rx(self, timeout=5):
|
|
|
|
ev = self.wait_event(["MGMT-RX"], timeout=timeout)
|
|
|
|
if ev is None:
|
|
|
|
return None
|
|
|
|
msg = {}
|
|
|
|
frame = binascii.unhexlify(ev.split(' ')[1])
|
|
|
|
msg['frame'] = frame
|
|
|
|
|
|
|
|
hdr = struct.unpack('<HH6B6B6BH', frame[0:24])
|
|
|
|
msg['fc'] = hdr[0]
|
|
|
|
msg['subtype'] = (hdr[0] >> 4) & 0xf
|
|
|
|
hdr = hdr[1:]
|
|
|
|
msg['duration'] = hdr[0]
|
|
|
|
hdr = hdr[1:]
|
|
|
|
msg['da'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
|
|
|
|
hdr = hdr[6:]
|
|
|
|
msg['sa'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
|
|
|
|
hdr = hdr[6:]
|
|
|
|
msg['bssid'] = "%02x:%02x:%02x:%02x:%02x:%02x" % hdr[0:6]
|
|
|
|
hdr = hdr[6:]
|
|
|
|
msg['seq_ctrl'] = hdr[0]
|
|
|
|
msg['payload'] = frame[24:]
|
|
|
|
|
|
|
|
return msg
|
|
|
|
|
|
|
|
def mgmt_tx(self, msg):
|
|
|
|
t = (msg['fc'], 0) + mac2tuple(msg['da']) + mac2tuple(msg['sa']) + mac2tuple(msg['bssid']) + (0,)
|
|
|
|
hdr = struct.pack('<HH6B6B6BH', *t)
|
|
|
|
self.request("MGMT_TX " + binascii.hexlify(hdr + msg['payload']))
|
|
|
|
|
2014-01-31 22:12:38 +01:00
|
|
|
def get_sta(self, addr, info=None, next=False):
|
|
|
|
cmd = "STA-NEXT " if next else "STA "
|
|
|
|
if addr is None:
|
|
|
|
res = self.request("STA-FIRST")
|
|
|
|
elif info:
|
|
|
|
res = self.request(cmd + addr + " " + info)
|
2014-01-02 17:10:30 +01:00
|
|
|
else:
|
2014-01-31 22:12:38 +01:00
|
|
|
res = self.request(cmd + addr)
|
2013-12-26 20:09:55 +01:00
|
|
|
lines = res.splitlines()
|
|
|
|
vals = dict()
|
|
|
|
first = True
|
|
|
|
for l in lines:
|
|
|
|
if first:
|
|
|
|
vals['addr'] = l
|
|
|
|
first = False
|
|
|
|
else:
|
|
|
|
[name,value] = l.split('=', 1)
|
|
|
|
vals[name] = value
|
|
|
|
return vals
|
|
|
|
|
2014-02-15 14:57:21 +01:00
|
|
|
def get_mib(self, param=None):
|
|
|
|
if param:
|
|
|
|
res = self.request("MIB " + param)
|
|
|
|
else:
|
|
|
|
res = self.request("MIB")
|
2013-12-31 12:57:49 +01:00
|
|
|
lines = res.splitlines()
|
|
|
|
vals = dict()
|
|
|
|
for l in lines:
|
2014-02-15 14:57:21 +01:00
|
|
|
name_val = l.split('=', 1)
|
|
|
|
if len(name_val) > 1:
|
|
|
|
vals[name_val[0]] = name_val[1]
|
2013-12-31 12:57:49 +01:00
|
|
|
return vals
|
|
|
|
|
2014-03-29 08:51:54 +01:00
|
|
|
def add_ap(ifname, params, wait_enabled=True, no_enable=False):
|
2013-03-29 19:33:25 +01:00
|
|
|
logger.info("Starting AP " + ifname)
|
|
|
|
hapd_global = HostapdGlobal()
|
|
|
|
hapd_global.remove(ifname)
|
|
|
|
hapd_global.add(ifname)
|
|
|
|
hapd = Hostapd(ifname)
|
|
|
|
if not hapd.ping():
|
|
|
|
raise Exception("Could not ping hostapd")
|
|
|
|
hapd.set_defaults()
|
2013-05-24 15:05:40 +02:00
|
|
|
fields = [ "ssid", "wpa_passphrase", "nas_identifier", "wpa_key_mgmt",
|
|
|
|
"wpa",
|
2013-12-31 12:57:49 +01:00
|
|
|
"wpa_pairwise", "rsn_pairwise", "auth_server_addr",
|
2014-03-01 16:45:39 +01:00
|
|
|
"acct_server_addr", "osu_server_uri" ]
|
2013-03-29 19:33:25 +01:00
|
|
|
for field in fields:
|
|
|
|
if field in params:
|
|
|
|
hapd.set(field, params[field])
|
2013-04-01 00:01:24 +02:00
|
|
|
for f,v in params.items():
|
|
|
|
if f in fields:
|
|
|
|
continue
|
|
|
|
if isinstance(v, list):
|
|
|
|
for val in v:
|
|
|
|
hapd.set(f, val)
|
|
|
|
else:
|
|
|
|
hapd.set(f, v)
|
2014-03-29 08:51:54 +01:00
|
|
|
if no_enable:
|
|
|
|
return hapd
|
2013-03-29 19:33:25 +01:00
|
|
|
hapd.enable()
|
2013-12-27 07:27:44 +01:00
|
|
|
if wait_enabled:
|
2014-03-12 12:35:10 +01:00
|
|
|
ev = hapd.wait_event(["AP-ENABLED", "AP-DISABLED"], timeout=30)
|
2013-12-27 07:27:44 +01:00
|
|
|
if ev is None:
|
|
|
|
raise Exception("AP startup timed out")
|
2014-03-12 12:35:10 +01:00
|
|
|
if "AP-ENABLED" not in ev:
|
|
|
|
raise Exception("AP startup failed")
|
2013-11-03 19:50:39 +01:00
|
|
|
return hapd
|
2013-03-29 19:33:25 +01:00
|
|
|
|
2013-10-31 16:28:43 +01:00
|
|
|
def add_bss(phy, ifname, confname, ignore_error=False):
|
|
|
|
logger.info("Starting BSS phy=" + phy + " ifname=" + ifname)
|
|
|
|
hapd_global = HostapdGlobal()
|
|
|
|
hapd_global.add_bss(phy, confname, ignore_error)
|
|
|
|
hapd = Hostapd(ifname)
|
|
|
|
if not hapd.ping():
|
|
|
|
raise Exception("Could not ping hostapd")
|
|
|
|
|
2013-11-03 19:20:50 +01:00
|
|
|
def add_iface(ifname, confname):
|
|
|
|
logger.info("Starting interface " + ifname)
|
|
|
|
hapd_global = HostapdGlobal()
|
|
|
|
hapd_global.add_iface(ifname, confname)
|
|
|
|
hapd = Hostapd(ifname)
|
|
|
|
if not hapd.ping():
|
|
|
|
raise Exception("Could not ping hostapd")
|
|
|
|
|
2013-10-31 16:28:43 +01:00
|
|
|
def remove_bss(ifname):
|
|
|
|
logger.info("Removing BSS " + ifname)
|
|
|
|
hapd_global = HostapdGlobal()
|
|
|
|
hapd_global.remove(ifname)
|
|
|
|
|
2013-03-29 19:33:25 +01:00
|
|
|
def wpa2_params(ssid=None, passphrase=None):
|
|
|
|
params = { "wpa": "2",
|
|
|
|
"wpa_key_mgmt": "WPA-PSK",
|
|
|
|
"rsn_pairwise": "CCMP" }
|
|
|
|
if ssid:
|
|
|
|
params["ssid"] = ssid
|
|
|
|
if passphrase:
|
|
|
|
params["wpa_passphrase"] = passphrase
|
|
|
|
return params
|
|
|
|
|
|
|
|
def wpa_params(ssid=None, passphrase=None):
|
|
|
|
params = { "wpa": "1",
|
|
|
|
"wpa_key_mgmt": "WPA-PSK",
|
|
|
|
"wpa_pairwise": "TKIP" }
|
|
|
|
if ssid:
|
|
|
|
params["ssid"] = ssid
|
|
|
|
if passphrase:
|
|
|
|
params["wpa_passphrase"] = passphrase
|
|
|
|
return params
|
|
|
|
|
|
|
|
def wpa_mixed_params(ssid=None, passphrase=None):
|
|
|
|
params = { "wpa": "3",
|
|
|
|
"wpa_key_mgmt": "WPA-PSK",
|
|
|
|
"wpa_pairwise": "TKIP",
|
|
|
|
"rsn_pairwise": "CCMP" }
|
|
|
|
if ssid:
|
|
|
|
params["ssid"] = ssid
|
|
|
|
if passphrase:
|
|
|
|
params["wpa_passphrase"] = passphrase
|
|
|
|
return params
|
2013-09-29 19:35:26 +02:00
|
|
|
|
|
|
|
def radius_params():
|
|
|
|
params = { "auth_server_addr": "127.0.0.1",
|
|
|
|
"auth_server_port": "1812",
|
|
|
|
"auth_server_shared_secret": "radius",
|
|
|
|
"nas_identifier": "nas.w1.fi" }
|
|
|
|
return params
|
|
|
|
|
2013-12-28 11:30:28 +01:00
|
|
|
def wpa_eap_params(ssid=None):
|
|
|
|
params = radius_params()
|
|
|
|
params["wpa"] = "1"
|
|
|
|
params["wpa_key_mgmt"] = "WPA-EAP"
|
|
|
|
params["wpa_pairwise"] = "TKIP"
|
|
|
|
params["ieee8021x"] = "1"
|
|
|
|
if ssid:
|
|
|
|
params["ssid"] = ssid
|
|
|
|
return params
|
|
|
|
|
2013-09-29 19:35:26 +02:00
|
|
|
def wpa2_eap_params(ssid=None):
|
|
|
|
params = radius_params()
|
|
|
|
params["wpa"] = "2"
|
|
|
|
params["wpa_key_mgmt"] = "WPA-EAP"
|
|
|
|
params["rsn_pairwise"] = "CCMP"
|
|
|
|
params["ieee8021x"] = "1"
|
|
|
|
if ssid:
|
|
|
|
params["ssid"] = ssid
|
|
|
|
return params
|