import os
import operator
import random
import re
import string
from .base import ConfigObject, ConfigOption
from .command import Command, KillCommand
CTRL_INTERFACE_DIR = "/tmp/hostapd"
IEEE80211_DIR = "/sys/class/ieee80211"
# Map hardware mode strings from UCI file to hostapd.conf format.
HOSTAPD_HWMODE = {
'11b': 'b',
'11g': 'g',
'11a': 'a'
}
# Map wifi-iface mode values to the type string we pass when running iw
# commands.
IW_IFACE_TYPE = {
'ap': '__ap',
'monitor': 'monitor',
'sta': 'managed'
}
HT40_LOWER_CHANNELS = set([36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157])
HT40_UPPER_CHANNELS = set([40, 48, 56, 64, 104, 112, 120, 128, 136, 144, 153, 161])
# Map 20 Mhz channel to index of 40 Mhz channel that contains it.
VHT40_CENTER_INDEX = {
36: 38,
40: 38,
44: 46,
48: 46,
52: 54,
56: 54,
60: 62,
64: 62,
100: 102,
104: 102,
108: 110,
112: 110,
116: 118,
120: 118,
124: 126,
128: 126,
132: 134,
136: 134,
140: 142,
144: 142,
149: 151,
153: 151,
157: 159,
161: 159
}
# Map 20 Mhz channel to index of 80 Mhz channel that contains it.
VHT80_CENTER_INDEX = {
36: 42,
40: 42,
44: 42,
48: 42,
52: 58,
56: 58,
60: 58,
64: 58,
100: 106,
104: 106,
108: 106,
112: 106,
116: 122,
120: 122,
124: 122,
128: 122,
132: 138,
136: 138,
140: 138,
144: 138,
149: 155,
153: 155,
157: 155,
161: 155
}
# Map 20 Mhz channel to index of 160 Mhz channel that contains it.
VHT160_CENTER_INDEX = {
36: 50,
40: 50,
44: 50,
48: 50,
52: 50,
56: 50,
60: 50,
64: 50,
100: 114,
104: 114,
108: 114,
112: 114,
116: 114,
120: 114,
124: 114,
128: 114
}
[docs]def getPhyMACAddress(phy):
path = "{}/{}/macaddress".format(IEEE80211_DIR, phy)
with open(path, 'r') as source:
return source.read().strip()
[docs]def getPhyFromMAC(mac):
for phy in os.listdir(IEEE80211_DIR):
if getPhyMACAddress(phy) == mac:
return phy
raise Exception("No wireless device with MAC address {}".format(mac))
[docs]def isHexString(data):
"""
Test if a string contains only hex digits.
"""
return all(c in string.hexdigits for c in data)
[docs]def get_cipher_list(encryption_mode):
"""
Get list of ciphers from encryption mode.
Example:
get_cipher_list("psk2+tkip+aes") -> ["TKIP", "CCMP"]
"""
parts = encryption_mode.lower().split('+')
ciphers = []
if "tkip" in parts:
ciphers.append("TKIP")
if "ccmp" in parts or "aes" in parts:
ciphers.append("CCMP")
if len(ciphers) == 0:
# We need to enable at least one cipher. Most modes default to CCMP
# except for wpa.
if parts[0] == "wpa":
ciphers.append("TKIP")
else:
ciphers.append("CCMP")
return ciphers
[docs]class ConfigWifiDevice(ConfigObject):
typename = "wifi-device"
options = [
ConfigOption(name="type", required=True),
ConfigOption(name="phy", type=str),
ConfigOption(name="macaddr", type=str),
ConfigOption(name="ifname", type=str),
ConfigOption(name="channel", type=int, required=True),
ConfigOption(name="hwmode"),
ConfigOption(name="txpower", type=int),
ConfigOption(name="country"),
ConfigOption(name="require_mode"),
ConfigOption(name="htmode"),
ConfigOption(name="beacon_int", type=int),
ConfigOption(name="frag", type=int),
ConfigOption(name="rts", type=int),
# 802.11n Capabilities
ConfigOption(name="ldpc", type=bool),
ConfigOption(name="short_gi_20", type=bool),
ConfigOption(name="short_gi_40", type=bool),
ConfigOption(name="tx_stbc", type=int),
ConfigOption(name="rx_stbc", type=int),
ConfigOption(name="max_amsdu", type=bool),
ConfigOption(name="dsss_cck_40", type=bool),
# 802.11ac Capabilities
ConfigOption(name="rxldpc", type=bool),
ConfigOption(name="short_gi_80", type=bool),
ConfigOption(name="short_gi_160", type=bool),
ConfigOption(name="tx_stbc_2by1", type=bool),
ConfigOption(name="rx_antenna_pattern", type=bool),
ConfigOption(name="tx_antenna_pattern", type=bool),
ConfigOption(name="vht_max_mpdu", type=int),
ConfigOption(name="rx_stbc", type=int)
]
[docs] def detectPrimaryInterface(self):
"""
Find the primary network interface associated with this Wi-Fi device.
By primary we mean the first interface (e.g. wlan0 or wlan1) that
exists at system startup before any `interface add` commands. We will
use the primary interface first, and create additional virtual
interfaces after that.
That seems overly complicated, but it is required in cases where the
Wi-Fi device does not support virtual interfaces.
Returns interface name or None.
"""
matches = []
# Search through all network interfaces. This includes non-wireless
# interfaces, so we will get some exceptions trying to read from
# the phy80211 subdirectory.
for ifname in os.listdir('/sys/class/net'):
try:
path = '/sys/class/net/{}/ifindex'.format(ifname)
with open(path, 'r') as source:
ifindex = int(source.read().rstrip())
info = {
'ifname': ifname,
'ifindex': ifindex
}
# Check if the current interface matches by phy name.
path = '/sys/class/net/{}/phy80211/name'.format(ifname)
with open(path, 'r') as source:
phy = source.read().rstrip()
if phy == self.phy:
matches.append(info)
continue
# Check if the current interface matches by macaddress.
path = '/sys/class/net/{}/phy80211/macaddress'.format(ifname)
with open(path, 'r') as source:
macaddr = source.read().rstrip()
if macaddr == self.macaddr:
matches.append(info)
continue
except:
pass
if len(matches) > 0:
# Sort by ifindex - lower ifindex means it was created earlier.
matches.sort(key=operator.itemgetter('ifindex'))
return matches[0]['ifname']
else:
return None
[docs] def nextInterfaceName(self):
"""
Get the next available interface name.
"""
if self._primary_available:
ifname = self._ifname
self._primary_available = False
else:
ifname = "v{}.{:04x}".format(self._ifname, self._ifname_counter)
self._ifname_counter += 1
return ifname
[docs] def releaseInterfaceName(self, ifname):
"""
Mark an interface name as no longer used.
"""
if ifname == self._ifname:
self._primary_available = True
[docs] def setup(self):
self._phy = self.phy
if self._phy is None and self.macaddr is not None:
self._phy = getPhyFromMAC(self.macaddr)
# Used to generate unique names for virtual interfaces.
self._ifname_counter = 0
self._primary_available = True
self._ifname = self.ifname
if self._ifname is None:
self._ifname = self.detectPrimaryInterface()
if self._ifname is None:
#TODO put a random hex string here.
self._ifname = "unknown"
self._primary_available = False
[docs] def apply(self, allConfigs):
commands = []
# On some systems the primary interface (e.g. wlan0) starts in the UP
# state and in managed mode. This would normally be fine, but for some
# devices, notably the WLE600VX, the driver/firmare throws a "Device or
# resource busy" error if both a managed mode and an __ap mode
# interface are UP.
#
# To make everyone happy, put the primary interface DOWN while we
# initialize the device, and if any wifi-iface sections are using it,
# they will bring it back UP.
primary = self.detectPrimaryInterface()
if primary is not None:
cmd = ["ip", "link", "set", "dev", primary, "down"]
commands.append((self.PRIO_CREATE_IFACE, Command(cmd, self)))
if self.txpower is not None:
# txpower is in dBm, iw takes mBm.
power = self.txpower * 100
cmd = ["iw", "phy", self._phy, "set", "txpower",
"fixed", str(power)]
else:
cmd = ["iw", "phy", self._phy, "set", "txpower", "auto"]
commands.append((self.PRIO_CONFIG_IFACE, Command(cmd, self)))
return commands
[docs] def revert(self, allConfigs):
commands = []
# We only need to revert txpower if it was actually set in the apply
# step.
if self.txpower is not None:
cmd = ["iw", "phy", self._phy, "set", "txpower", "auto"]
commands.append((-self.PRIO_CONFIG_IFACE, Command(cmd, self)))
# Bring primary interface back UP - see comment in apply function.
primary = self.detectPrimaryInterface()
if primary is not None:
cmd = ["ip", "link", "set", "dev", primary, "up"]
commands.append((-self.PRIO_CREATE_IFACE, Command(cmd, self)))
return commands
[docs]class ConfigWifiIface(ConfigObject):
typename = "wifi-iface"
options = [
ConfigOption(name="device", required=True),
ConfigOption(name="mode", required=True),
ConfigOption(name="ssid", default="Paradrop"),
ConfigOption(name="hidden", type=bool, default=False),
ConfigOption(name="isolate", type=bool, default=False),
ConfigOption(name="wmm", type=bool, default=True),
ConfigOption(name="network", default="lan"),
ConfigOption(name="encryption", default="none"),
ConfigOption(name="key"),
ConfigOption(name="maxassoc", type=int),
ConfigOption(name="doth", type=bool, default=True),
ConfigOption(name="short_preamble", type=bool, default=True),
# WPA Enterprise (RADIUS) options
ConfigOption(name="auth_server"),
ConfigOption(name="auth_port", type=int, default=1812),
ConfigOption(name="auth_secret"),
ConfigOption(name="acct_server"),
ConfigOption(name="acct_port", type=int, default=1813),
ConfigOption(name="acct_secret"),
ConfigOption(name="nasid"),
ConfigOption(name="ownip"),
ConfigOption(name="dynamic_vlan", type=int, default=0),
# WPA Enterprise (Client)
ConfigOption(name="identity"),
ConfigOption(name="password"),
# 802.11r - Fast BSS Transition
ConfigOption(name="ieee80211r", type=bool, default=False),
ConfigOption(name="mobility_domain", default="4f57"),
ConfigOption(name="r0_key_lifetime", type=int, default=10000),
ConfigOption(name="r1_key_holder", default="00004f577274"),
ConfigOption(name="reassociation_deadline", type=int, default=1000),
ConfigOption(name="r0kh", type=list, default=[]),
ConfigOption(name="r1kh", type=list, default=[]),
ConfigOption(name="pmk_r1_push", type=bool, default=False),
# Nonstandard: interval (seconds) for sending interim updates.
ConfigOption(name="acct_interval", type=int, default=300),
# NOTE: ifname is not defined in the UCI specs. We use it to declare a
# desired name for the virtual wireless interface that should be
# created.
#
# DEPRECATED: Remove in 1.0 release.
ConfigOption(name="ifname")
]
[docs] def getIfname(self, device, interface):
"""
Returns the name to be used by this WiFi interface, e.g. as seen by
ifconfig.
This comes from the "ifname" option if it is set. Otherwise, we use
the interface name of the associated network.
"""
if self.ifname is not None:
# We were given a specific ifname to use (deprecated).
return self.ifname
elif interface.type == "bridge":
# It's a bridge (e.g. br-lan), so generate a name based on the
# device properties.
return device.nextInterfaceName()
else:
# It's not a bridge (e.g. a chute's network), so use the the ifname
# from the network interface section.
return interface.config_ifname
[docs] def getName(self):
"""
Return a unique and consistent identifier for the section.
If ifname is set, then that is a good choice for the name because
interface names need to be unique on the system.
If ifname is not set, then we use the combined string device:network.
The assumption is that no one will put multiple APs on the same device
and same network, or if they do, (e.g. multiple APs on the br-lan
bridge), then they will configure the ifname to be unique.
"""
if self.ifname is not None:
return self.ifname
else:
return "{}:{}".format(self.device, self.network)
[docs] def apply(self, allConfigs):
commands = list()
# Look up the wifi-device section.
wifiDevice = self.lookup(allConfigs, "wireless", "wifi-device", self.device)
self._device = wifiDevice
# Look up the interface section.
interface = self.lookup(allConfigs, "network", "interface", self.network)
# Type to pass to iw command, e.g. '__ap'.
iw_type = IW_IFACE_TYPE[self.mode]
# Set _ifname while the section is being applied so that it is still
# available when the section is being reverted.
self._ifname = self.getIfname(wifiDevice, interface)
# If this is a primary interface, it should always exist. We just need
# to change its mode. Otherwise, it is a virtual interface, and we need
# to create it.
if self._ifname == wifiDevice._ifname:
# It needs to be "down" before changing type.
cmd = ["ip", "link", "set", "dev", self._ifname, "down"]
commands.append((self.PRIO_CREATE_IFACE, Command(cmd, self)))
cmd = ["iw", "dev", self._ifname, "set", "type", iw_type]
commands.append((self.PRIO_CREATE_IFACE, Command(cmd, self)))
else:
# Command to create the virtual interface.
cmd = ["iw", "phy", wifiDevice._phy, "interface", "add",
self._ifname, "type", iw_type]
commands.append((self.PRIO_CREATE_IFACE, Command(cmd, self)))
# Assign a random MAC address to avoid conflict with other
# interfaces using the same device.
cmd = ["ip", "link", "set", "dev", self._ifname,
"address", self.getRandomMAC()]
commands.append((self.PRIO_CREATE_IFACE, Command(cmd, self)))
if self.mode == "ap":
confFile = self.makeHostapdConf(wifiDevice, interface)
self.pidFile = "{}/hostapd-{}.pid".format(
self.manager.writeDir, self.internalName)
cmd = ["hostapd", "-P", self.pidFile, "-B", confFile]
commands.append((self.PRIO_START_DAEMON, Command(cmd, self)))
elif self.mode == "sta":
confFile = self.makeWpaSupplicantConf(wifiDevice, interface)
self.pidFile = "{}/wpa_supplicant-{}.pid".format(
self.manager.writeDir, self.internalName)
cmd = ["wpa_supplicant", "-B", "-c", confFile, "-i", self._ifname,
"-D", "nl80211", "-P", self.pidFile]
commands.append((self.PRIO_START_DAEMON, Command(cmd, self)))
return commands
[docs] def makeHostapdConf(self, wifiDevice, interface):
outputPath = "{}/hostapd-{}.conf".format(
self.manager.writeDir, self.internalName)
conf = HostapdConfGenerator(self, wifiDevice, interface)
conf.generate(outputPath)
return outputPath
[docs] def makeWpaSupplicantConf(self, wifiDevice, interface):
outputPath = "{}/wpa_supplicant-{}.conf".format(
self.manager.writeDir, self.internalName)
conf = WpaSupplicantConfGenerator(self, wifiDevice, interface)
conf.generate(outputPath)
return outputPath
[docs] def revert(self, allConfigs):
commands = list()
if self.mode == "ap":
commands.append((-self.PRIO_START_DAEMON,
KillCommand(self.pidFile, self)))
# If this is a primary interface we leave it alone but mark it as
# available again. Otherwise, delete the interface.
if self._ifname != self._device._ifname:
# Delete our virtual interface.
cmd = ["iw", "dev", self._ifname, "del"]
commands.append((-self.PRIO_CREATE_IFACE, Command(cmd, self)))
self._device.releaseInterfaceName(self._ifname)
return commands
[docs] def updateApply(self, new, allConfigs):
if new.mode != self.mode or \
new.device != self.device or \
new.network != self.network:
# Major change requires unloading the old section and applying the
# new.
return self.apply(allConfigs)
commands = list()
if new.mode == "ap":
# Look up the wifi-device section.
wifiDevice = new.lookup(allConfigs, "wireless", "wifi-device", new.device)
# Look up the interface section.
interface = new.lookup(allConfigs, "network", "interface", new.network)
# Copy fields that would normally be set in apply method.
new._device = wifiDevice
new._ifname = self._ifname
confFile = new.makeHostapdConf(wifiDevice, interface)
new.pidFile = "{}/hostapd-{}.pid".format(
new.manager.writeDir, new.internalName)
cmd = ["hostapd", "-P", new.pidFile, "-B", confFile]
commands.append((self.PRIO_START_DAEMON, Command(cmd, new)))
return commands
[docs] def updateRevert(self, new, allConfigs):
if new.mode != self.mode or \
new.device != self.device or \
new.network != self.network:
# Major change requires unloading the old section and applying the
# new.
return self.revert(allConfigs)
commands = list()
if self.mode == "ap":
# Bring down hostapd
commands.append((-self.PRIO_START_DAEMON,
KillCommand(self.pidFile, self)))
return commands
[docs] def getRandomMAC(self):
"""
Generate a random MAC address.
Returns a string "02:xx:xx:xx:xx:xx". The first byte is 02, which
indicates a locally administered address.
"""
parts = ["02"]
for i in range(5):
parts.append("{:02x}".format(random.randrange(0, 255)))
return ":".join(parts)
[docs]class ConfGenerator(object):
[docs] def writeOptions(self, options, output, title=None):
output.write("\n")
if title is not None:
head = "##### {} ".format(title)
tail = "#" * (79 - len(head))
output.write(head + tail + "\n")
for name, value in options:
if isinstance(value, list):
output.write("{}={{\n".format(name))
for subname, subvalue, quote in value:
if quote:
output.write("\t{}=\"{}\"\n".format(subname, subvalue))
else:
output.write("\t{}={}\n".format(subname, subvalue))
output.write("}\n")
else:
output.write("{}={}\n".format(name, value))
[docs]class HostapdConfGenerator(ConfGenerator):
def __init__(self, wifiIface, wifiDevice, interface):
self.wifiIface = wifiIface
self.wifiDevice = wifiDevice
self.interface = interface
self.readMode(wifiDevice)
[docs] def readMode(self, device):
"""
Determine HT/VHT mode if applicable.
"""
self.enable11n = False
self.enable11ac = False
if device.htmode is None:
return
if device.htmode.startswith("HT"):
self.enable11n = True
elif device.htmode.startswith("VHT"):
self.enable11n = True
self.enable11ac = True
[docs] def generate(self, path):
with open(path, "w") as output:
self.writeHeader(output)
options = self.getMainOptions()
self.writeOptions(options, output)
if self.enable11n:
options = self.get11nOptions()
self.writeOptions(options, output, title="802.11n")
if self.enable11ac:
options = self.get11acOptions()
self.writeOptions(options, output, title="802.11ac")
options = self.getSecurityOptions()
self.writeOptions(options, output, title="Security")
options = self.getRadiusOptions()
if len(options) > 0:
self.writeOptions(options, output, title="RADIUS Client")
if self.wifiIface.ieee80211r:
options = self.get11rOptions()
self.writeOptions(options, output, title="802.11r")
[docs] def getMainOptions(self):
options = list()
options.append(("interface", self.wifiIface._ifname))
if self.interface.type == "bridge":
options.append(("bridge", self.interface.config_ifname))
control_dir = os.path.join(self.wifiIface.manager.writeDir, "hostapd")
options.append(("ctrl_interface", control_dir))
# Do not set ctrl_interface_group because it triggers behavior
# that breaks under snap confinement.
#options.append(("ctrl_interface_group", 0))
options.append(("ssid", self.wifiIface.ssid))
if self.wifiDevice.country is not None:
options.append(("country_code", self.wifiDevice.country))
options.append(("ieee80211d", 1))
if self.wifiIface.doth:
options.append(("ieee80211h", 1))
hwmode = self.wifiDevice.hwmode
if hwmode is not None:
if hwmode in HOSTAPD_HWMODE:
# Convert UCI hwmode to hostapd hwmode format.
options.append(("hw_mode", HOSTAPD_HWMODE[hwmode]))
else:
raise Exception("Unrecognized hardware mode: {}".format(hwmode))
options.append(("channel", self.wifiDevice.channel))
if self.wifiDevice.beacon_int is not None:
options.append(("beacon_int", self.wifiDevice.beacon_int))
if self.wifiIface.maxassoc is not None:
options.append(("max_num_sta", self.wifiIface.maxassoc))
options.append(("no_probe_resp_if_max_sta", 1))
# 1 = send empty (length=0) SSID in beacon and ignore probe request for
# broadcast SSID
# 2 = clear SSID (ASCII 0), but keep the original length (this may be required
# with some clients that do not support empty SSID) and ignore probe
# requests for broadcast SSID
if self.wifiIface.hidden:
options.append(("ignore_broadcast_ssid", 2))
if self.wifiIface.isolate:
options.append(("ap_isolate", 1))
if self.wifiDevice.rts is not None:
options.append(("rts_threshold", self.wifiDevice.rts))
if self.wifiDevice.frag is not None:
options.append(("fragm_threshold", self.wifiDevice.rts))
if self.wifiIface.short_preamble:
options.append(("preamble", 1))
options.append(("wmm_enabled", 1 * self.wifiIface.wmm))
return options
[docs] def get11nOptions(self):
options = list()
options.append(("ieee80211n", 1 * self.enable11n))
ht_capab = ""
if self.wifiDevice.htmode.startswith("HT40"):
ht_capab += "[{}]".format(self.wifiDevice.htmode)
elif self.wifiDevice.htmode in ["VHT40", "VHT80", "VHT160"]:
if self.wifiDevice.channel in HT40_LOWER_CHANNELS:
ht_capab += "[HT40+]"
elif self.wifiDevice.channel in HT40_UPPER_CHANNELS:
ht_capab += "[HT40-]"
if self.wifiDevice.ldpc:
ht_capab += "[LDPC]"
if self.wifiDevice.short_gi_20:
ht_capab += "[SHORT-GI-20]"
if self.wifiDevice.short_gi_40:
ht_capab += "[SHORT-GI-40]"
if self.wifiDevice.tx_stbc:
ht_capab += "[TX-STBC]"
if self.wifiDevice.rx_stbc == 1:
ht_capab += "[RX-STBC1]"
elif self.wifiDevice.rx_stbc == 2:
ht_capab += "[RX-STBC12]"
elif self.wifiDevice.rx_stbc >= 3:
ht_capab += "[RX-STBC123]"
if self.wifiDevice.max_amsdu:
ht_capab += "[MAX-AMSDU-7935]"
if self.wifiDevice.dsss_cck_40:
ht_capab += "[DSSS_CCK-40]"
if len(ht_capab) > 0:
options.append(("ht_capab", ht_capab))
if self.wifiDevice.require_mode == "n":
options.append(("require_ht", 1))
return options
[docs] def get11acOptions(self):
options = list()
options.append(("ieee80211ac", 1 * self.enable11ac))
if self.wifiDevice.require_mode == "ac":
options.append(("require_vht", 1))
# Default chwidth=0 means 20 or 40 Mhz.
chwidth = 0
seg0_idx = self.wifiDevice.channel
seg1_idx = None
if self.wifiDevice.htmode == "VHT40":
seg0_idx = VHT40_CENTER_INDEX[self.wifiDevice.channel]
elif self.wifiDevice.htmode == "VHT80":
chwidth = 1
seg0_idx = VHT80_CENTER_INDEX[self.wifiDevice.channel]
elif self.wifiDevice.htmode == "VHT160":
chwidth = 2
seg0_idx = VHT160_CENTER_INDEX[self.wifiDevice.channel]
# TODO: How does the admin request 80+80 mode (chwidth=3)?
# We need a second channel number to specify seg1_idx in that case.
vht_capab = ""
if self.wifiDevice.rxldpc:
vht_capab += "[RXLDPC]"
if self.wifiDevice.short_gi_80:
vht_capab += "[SHORT-GI-80]"
if self.wifiDevice.short_gi_160:
vht_capab += "[SHORT-GI-160]"
if self.wifiDevice.tx_stbc_2by1:
vht_capab += "[TX-STBC-2BY1]"
if self.wifiDevice.rx_antenna_pattern:
vht_capab += "[RX-ANTENNA-PATTERN]"
if self.wifiDevice.tx_antenna_pattern:
vht_capab += "[TX-ANTENNA-PATTERN]"
if self.wifiDevice.vht_max_mpdu == 7991:
vht_capab += "[MAX-MPDU-7991]"
elif self.wifiDevice.vht_max_mpdu == 11454:
vht_capab += "[MAX-MPDU-11454]"
if self.wifiDevice.rx_stbc == 1:
vht_capab += "[RX-STBC-1]"
elif self.wifiDevice.rx_stbc == 2:
vht_capab += "[RX-STBC-12]"
elif self.wifiDevice.rx_stbc == 3:
vht_capab += "[RX-STBC-123]"
elif self.wifiDevice.rx_stbc >= 4:
vht_capab += "[RX-STBC-1234]"
if len(vht_capab) > 0:
options.append(("vht_capab", vht_capab))
options.append(("vht_oper_chwidth", chwidth))
options.append(("vht_oper_centr_freq_seg0_idx", seg0_idx))
if seg1_idx is not None:
options.append(("vht_oper_centr_freq_seg1_idx", seg1_idx))
return options
[docs] def getSecurityOptions(self):
options = list()
if self.wifiIface.encryption is None or \
self.wifiIface.encryption == "none":
options.append(("wpa", 0))
return options
modes = self.wifiIface.encryption.split("+")
# Check for WPA, WPA2, or mixed mode.
if modes[0] in ["psk2", "wpa2"]:
options.append(("wpa", 2))
elif modes[0] in ["psk", "wpa"]:
options.append(("wpa", 1))
elif modes[0] in ["psk-mixed", "wpa-mixed"]:
options.append(("wpa", 3))
else:
raise Exception("Encryption mode ({}) not supported".format(modes[0]))
# Check for PSK vs Enterprise mode.
if modes[0] in ["psk2", "psk", "psk-mixed"]:
# If key is a 64 character hex string, then treat it as the PSK
# directly, else treat it as a passphrase.
if len(self.wifiIface.key) == 64 and isHexString(self.wifiIface.key):
options.append(("wpa_psk", self.wifiIface.key))
else:
options.append(("wpa_passphrase", self.wifiIface.key))
options.append(("wpa_key_mgmt", "WPA-PSK"))
elif modes[0] in ["wpa2", "wpa", "wpa-mixed"]:
if self.wifiIface.auth_server is None:
raise Exception("Must configure auth_server with wpa2")
options.append(("wpa_key_mgmt", "WPA-EAP"))
ciphers = get_cipher_list(self.wifiIface.encryption)
# Ciphers to use for WPA:
options.append(("wpa_pairwise", " ".join(ciphers)))
# Ciphers to use for WPA2:
# Technically, these can be different, e.g. wpa_pairwise=TKIP and
# rsn_pairwise=CCMP. I am not sure we need to expose those options.
# Defaulting to CCMP only is good because TKIP is no longer secure.
options.append(("rsn_pairwise", " ".join(ciphers)))
return options
[docs] def getRadiusOptions(self):
options = list()
if self.wifiIface.ownip:
options.append(("own_ip_addr", self.wifiIface.ownip))
if self.wifiIface.nasid:
options.append(("nas_identifier", self.wifiIface.nasid))
if self.wifiIface.auth_server:
options.append(("auth_server_addr", self.wifiIface.auth_server))
options.append(("auth_server_port", self.wifiIface.auth_port))
if self.wifiIface.auth_secret:
options.append(("auth_server_shared_secret",
self.wifiIface.auth_secret))
options.append(("dynamic_vlan", self.wifiIface.dynamic_vlan))
if self.wifiIface.acct_server:
options.append(("acct_server_addr", self.wifiIface.acct_server))
options.append(("acct_server_port", self.wifiIface.acct_port))
if self.wifiIface.acct_secret:
options.append(("acct_server_shared_secret",
self.wifiIface.acct_secret))
options.append(("radius_acct_interim_interval", self.wifiIface.acct_interval))
return options
[docs] def get11rOptions(self):
"""
Get options related to 802.11r (fast BSS transition).
"""
options = list()
if self.wifiIface.nasid is None:
raise Exception("nasid is not defined but is required for 802.11r")
options.append(("mobility_domain", self.wifiIface.mobility_domain))
options.append(("r0_key_lifetime", self.wifiIface.r0_key_lifetime))
options.append(("r1_key_holder", self.wifiIface.r1_key_holder))
options.append(("reassociation_deadline", self.wifiIface.reassociation_deadline))
for item in self.wifiIface.r0kh:
# May be specified as either comma- or space-separated, but hostapd
# expects spaces.
parts = re.split("[, ]+", item)
options.append(("r0kh", " ".join(parts)))
for item in self.wifiIface.r1kh:
# May be specified as either comma- or space-separated, but hostapd
# expects spaces.
parts = re.split("[, ]+", item)
options.append(("r1kh", " ".join(parts)))
return options
[docs]class WpaSupplicantConfGenerator(ConfGenerator):
def __init__(self, wifiIface, wifiDevice, interface):
self.wifiIface = wifiIface
self.wifiDevice = wifiDevice
self.interface = interface
[docs] def generate(self, path):
with open(path, "w") as output:
self.writeHeader(output)
options = self.getMainOptions()
self.writeOptions(options, output)
[docs] def getMainOptions(self):
options = list()
options.append(("ap_scan", 1))
if self.wifiDevice.country is not None:
options.append(("country", self.wifiDevice.country))
# (name, value, quote)
network = list()
network.append(('ssid', self.wifiIface.ssid, True))
network.append(('scan_ssid', 1, False))
modes = self.wifiIface.encryption.split("+")
if modes[0] == "psk2":
network.append(('key_mgmt', 'WPA-PSK', False))
network.append(('psk', self.wifiIface.key, True))
elif modes[0] == "wpa2":
network.append(('key_mgmt', 'WPA-EAP', False))
network.append(('identity', self.wifiIface.identity))
network.append(('password', self.wifiIface.password))
else:
network.append(('key_mgmt', 'NONE', False))
options.append(("network", network))
return options