blob: 8e1b66499d000a540676b4aeb4f9350852c8b861 [file] [log] [blame]
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging, re, time
from autotest_lib.client.common_lib import error
def isLinuxRouter(router):
router_uname = router.run('uname').stdout
return re.search('Linux', router_uname)
class LinuxRouter(object):
"""
Linux/mac80211-style WiFi Router support for WiFiTest class.
This class implements test methods/steps that communicate with a
router implemented with Linux/mac80211. The router must
be pre-configured to enable ssh access and have a mac80211-based
wireless device. We also assume hostapd 0.7.x and iw are present
and any necessary modules are pre-loaded.
"""
def __init__(self, host, params, defssid):
# Command locations.
self.cmd_iw = "/usr/sbin/iw"
self.cmd_ip = "/usr/sbin/ip"
self.cmd_brctl = "/usr/sbin/brctl"
self.cmd_hostapd = "/usr/sbin/hostapd"
self.cmd_dhcpd = "/usr/sbin/dhcpd"
# Router host.
self.router = host
# Network interfaces.
self.bridgeif = params.get('bridgedev', "br-lan")
self.wiredif = params.get('wiredev', "eth0")
self.wlanif2 = "wlan2"
self.wlanif5 = "wlan5"
# Parse the output of 'iw phy' and find a device for each frequency
if "phydev2" not in params:
output = self.router.run("%s list" % self.cmd_iw).stdout
re_wiphy = re.compile("Wiphy (.*)")
re_mhz = re.compile("(\d+) MHz")
in_phy = False
self.phydev2 = None
self.phydev5 = None
for line in output.splitlines():
match_wiphy = re_wiphy.match(line)
if match_wiphy:
in_phy = True
widevname = match_wiphy.group(1)
elif in_phy:
if line[0] == '\t':
match_mhz = re_mhz.search(line)
if match_mhz:
mhz = int(match_mhz.group(1))
if self.phydev2 is None and \
mhz in range(2402,2472,5):
self.phydev2 = widevname
elif self.phydev5 is None and \
mhz in range(5100,6000,20):
self.phydev5 = widevname
if None not in (self.phydev2, self.phydev5):
break
else:
in_phy = False
else:
raise error.TestFail("No Wireless NIC detected on the device")
else:
self.phydev2 = params['phydev2']
self.phydev5 = params.get('phydev5', self.phydev2)
# hostapd configuration persists throughout the test, subsequent
# 'config' commands only modify it.
self.defssid = defssid
self.hostapd = {
'configured': False,
'file': "/tmp/hostapd-test.conf",
'driver': "nl80211",
'conf': {
'ssid': defssid,
'bridge': self.bridgeif,
'hw_mode': 'g'
}
}
self.station = {
'configured': False,
'conf': {
'ssid': defssid,
},
'local_server_state': None,
'local_server': {
'address': '192.168.3.254/24',
'dhcp_range': (1, 128),
'dhcpd_conf': '/tmp/dhcpd.conf',
'lease_file': '/tmp/dhcpd.leases'
}
}
self.station['local_server'].update(params.get('local_server', {}))
# Kill hostapd if already running.
self.router.run("pkill hostapd >/dev/null 2>&1", ignore_status=True)
# Remove all bridges.
output = self.router.run("%s show" % self.cmd_brctl).stdout
test = re.compile("^(\S+).*")
for line in output.splitlines()[1:]:
m = test.match(line)
if m:
device = m.group(1)
self.router.run("%s link set %s down" % (self.cmd_ip, device))
self.router.run("%s delbr %s" % (self.cmd_brctl, device))
# Remove all wifi devices.
output = self.router.run("%s dev" % self.cmd_iw).stdout
test = re.compile("[\s]*Interface (.*)")
for line in output.splitlines():
m = test.match(line)
if m:
device = m.group(1)
self.router.run("%s link set %s down" % (self.cmd_ip, device))
self.router.run("%s dev %s del" % (self.cmd_iw, device))
# Place us in the US by default
self.router.run("%s reg set US" % self.cmd_iw)
def create(self, params):
""" Create a wifi device of the specified type """
#
# AP mode is handled entirely by hostapd so we only
# have to setup others (mapping the bsd type to what
# iw wants)
#
# map from bsd types to iw types
self.apmode = params['type'] in ("ap", "hostap")
if not self.apmode:
self.station['type'] = params['type']
phytype = {
"sta" : "managed",
"monitor" : "monitor",
"adhoc" : "adhoc",
"ibss" : "ibss",
"ap" : "managed", # NB: handled by hostapd
"hostap" : "managed", # NB: handled by hostapd
"mesh" : "mesh",
"wds" : "wds",
}[params['type']]
self.router.run("%s phy %s interface add %s type %s" %
(self.cmd_iw, self.phydev2, self.wlanif2, phytype))
self.router.run("%s phy %s interface add %s type %s" %
(self.cmd_iw, self.phydev5, self.wlanif5, phytype))
def destroy(self, params):
""" Destroy a previously created device """
# For linux, this is the same as deconfig.
self.deconfig(params)
def hostap_config(self, params):
""" Configure the AP per test requirements """
multi_interface = 'multi_interface' in params
if multi_interface:
params.pop('multi_interface')
elif self.hostapd['configured'] or self.station['configured']:
self.deconfig({})
# Construct the hostapd.conf file and start hostapd.
conf = self.hostapd['conf']
tx_power_params = {}
htcaps = set()
conf['driver'] = params.get('hostapd_driver',
self.hostapd['driver'])
for k, v in params.iteritems():
if k == 'ssid':
conf['ssid'] = v
elif k == 'ssid_suffix':
conf['ssid'] = self.defssid + v
elif k == 'channel':
freq = int(v)
# 2.4GHz
if freq <= 2484:
# Make sure hw_mode is set
if conf.get('hw_mode') == 'a':
conf['hw_mode'] = 'g'
# Freq = 5 * chan + 2407, except channel 14
if freq == 2484:
conf['channel'] = 14
else:
conf['channel'] = (freq - 2407) / 5
# 5GHz
else:
# Make sure hw_mode is set
conf['hw_mode'] = 'a'
# Freq = 5 * chan + 4000
if freq < 5000:
conf['channel'] = (freq - 4000) / 5
# Freq = 5 * chan + 5000
else:
conf['channel'] = (freq - 5000) / 5
elif k == 'country':
conf['country_code'] = v
elif k == 'dotd':
conf['ieee80211d'] = 1
elif k == '-dotd':
conf['ieee80211d'] = 0
elif k == 'mode':
if v == '11a':
conf['hw_mode'] = 'a'
elif v == '11g':
conf['hw_mode'] = 'g'
elif v == '11b':
conf['hw_mode'] = 'b'
elif v == '11n':
conf['ieee80211n'] = 1
elif k == 'bintval':
conf['beacon_int'] = v
elif k == 'dtimperiod':
conf['dtim_period'] = v
elif k == 'rtsthreshold':
conf['rts_threshold'] = v
elif k == 'fragthreshold':
conf['fragm_threshold'] = v
elif k == 'shortpreamble':
conf['preamble'] = 1
elif k == 'authmode':
if v == "open":
conf['auth_algs'] = 1
elif v == "shared":
conf['auth_algs'] = 2
elif k == 'hidessid':
conf['ignore_broadcast_ssid'] = 1
elif k == 'wme':
conf['wmm_enabled'] = 1
elif k == '-wme':
conf['wmm_enabled'] = 0
elif k == 'deftxkey':
conf['wep_default_key'] = v
elif k == 'ht20':
htcaps.add('') # NB: ensure 802.11n setup below
conf['wmm_enabled'] = 1
elif k == 'ht40':
htcaps.add('[HT40-]')
htcaps.add('[HT40+]')
conf['wmm_enabled'] = 1
elif k in ('ht40+', 'ht40-'):
htcaps.add('[%s]' % k.upper())
conf['wmm_enabled'] = 1
elif k == 'shortgi':
htcaps.add('[SHORT-GI-20]')
htcaps.add('[SHORT-GI-40]')
elif k == 'pureg':
pass # TODO(sleffler) need hostapd support
elif k == 'puren':
pass # TODO(sleffler) need hostapd support
elif k == 'protmode':
pass # TODO(sleffler) need hostapd support
elif k == 'ht':
htcaps.add('') # NB: ensure 802.11n setup below
elif k == 'htprotmode':
pass # TODO(sleffler) need hostapd support
elif k == 'rifs':
pass # TODO(sleffler) need hostapd support
elif k == 'wepmode':
pass # NB: meaningless for hostapd; ignore
elif k == '-ampdu':
pass # TODO(sleffler) need hostapd support
elif k == 'txpower':
tx_power_params['power'] = v
else:
conf[k] = v
# Aggregate ht_capab.
if htcaps:
conf['ieee80211n'] = 1
conf['ht_capab'] = ''.join(htcaps)
# Figure out the correct interface.
if conf.get('hw_mode', 'b') == 'a':
conf['interface'] = self.wlanif5
else:
conf['interface'] = self.wlanif2
# Generate hostapd.conf.
self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
(self.hostapd['file'], '\n'.join(
"%s=%s" % kv for kv in conf.iteritems())))
if not multi_interface:
logging.info("Initializing bridge...")
self.router.run("%s addbr %s" %
(self.cmd_brctl, self.bridgeif))
self.router.run("%s setfd %s %d" %
(self.cmd_brctl, self.bridgeif, 0))
self.router.run("%s stp %s %d" %
(self.cmd_brctl, self.bridgeif, 0))
# Run hostapd.
logging.info("Starting hostapd...")
self.router.run("%s -B %s" %
(self.cmd_hostapd, self.hostapd['file']))
# Set up the bridge.
if not multi_interface:
logging.info("Setting up the bridge...")
self.router.run("%s addif %s %s" %
(self.cmd_brctl, self.bridgeif, self.wiredif))
self.router.run("%s link set %s up" %
(self.cmd_ip, self.wiredif))
self.router.run("%s link set %s up" %
(self.cmd_ip, self.bridgeif))
self.hostapd['interface'] = conf['interface']
else:
tx_power_params['interface'] = conf['interface']
# Configure transmit power
self.set_txpower(tx_power_params)
logging.info("AP configured.")
self.hostapd['configured'] = True
def station_local_addr(self, idx):
"""
Simple IPv4 calculator. Takes host address in "IP/bits" notation
and returns netmask, broadcast address as well as integer offsets
into the address range.
"""
addr_str,bits = self.station['local_server_state']['address'].split('/')
addr = map(int, addr_str.split('.'))
mask_bits = (-1 << (32-int(bits))) & 0xffffffff
mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
if idx == 'netmask':
return '.'.join(map(str, mask))
elif idx == 'broadcast':
offset = [m ^ 0xff for m in mask]
else:
offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
return '.'.join(map(str, [(a & m) + o
for a, m, o in zip(addr, mask, offset)]))
def station_config(self, params):
multi_interface = 'multi_interface' in params
if multi_interface:
params.pop('multi_interface')
elif self.station['configured'] or self.hostapd['configured']:
self.deconfig({})
local_server = params.pop('local_server', False)
interface = self.wlanif2
conf = self.station['conf']
for k, v in params.iteritems():
if k == 'ssid_suffix':
conf['ssid'] = self.defssid + v
elif k == 'channel':
freq = int(v)
if freq > 2484:
interface = self.wlanif5
elif k == 'mode':
if v == '11a':
interface = self.wlanif5
else:
conf[k] = v
if not multi_interface:
logging.info("Initializing bridge...")
self.router.run("%s addbr %s" %
(self.cmd_brctl, self.bridgeif))
self.router.run("%s setfd %s %d" %
(self.cmd_brctl, self.bridgeif, 0))
self.router.run("%s stp %s %d" %
(self.cmd_brctl, self.bridgeif, 0))
# Run interface configuration commands
for k, v in conf.iteritems():
if k != 'ssid':
self.router.run("%s dev %s set %s %s" %
(self.cmd_iw, interface, k, v))
# Connect the station
self.router.run("%s link set %s up" % (self.cmd_ip, interface))
connect_cmd = ('ibss join' if self.station['type'] == 'ibss'
else 'connect')
self.router.run("%s dev %s %s %s %d" %
(self.cmd_iw, interface, connect_cmd,
conf['ssid'], freq))
if self.station['type'] != 'ibss':
# Add wireless interface to the bridge
self.router.run("%s addif %s %s" %
(self.cmd_brctl, self.bridgeif, interface))
# Add wired interface to the bridge, then bring up the bridge if
if not multi_interface:
logging.info("Setting up the bridge...")
self.router.run("%s addif %s %s" %
(self.cmd_brctl, self.bridgeif, self.wiredif))
self.router.run("%s link set %s up" %
(self.cmd_ip, self.wiredif))
self.router.run("%s link set %s up" %
(self.cmd_ip, self.bridgeif))
if local_server is not False:
logging.info("Starting up local server...")
params = self.station['local_server'].copy()
params.update(local_server or {})
self.station['local_server_state'] = params
params['subnet'] = self.station_local_addr(0)
params['netmask'] = self.station_local_addr('netmask')
params['dhcp_range'] = ' '.join(map(self.station_local_addr,
params['dhcp_range']))
params['ip_params'] = ("%s broadcast %s dev %s" %
(params['address'],
self.station_local_addr('broadcast'),
interface))
self.router.run("%s addr add %s" %
(self.cmd_ip, params['ip_params']))
self.router.run("%s link set %s up" %
(self.cmd_ip, interface))
self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
(params['dhcpd_conf'],
'\n'.join(('ddns-update-style none;',
'subnet %(subnet)s netmask %(netmask)s {',
' range %(dhcp_range)s;', '}')) % params))
self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True)
self.router.run("%s -q -cf %s -lf %s %s" %
(self.cmd_dhcpd, params['dhcpd_conf'],
params['lease_file'], interface))
self.station['configured'] = True
self.station['interface'] = interface
def config(self, params):
if self.apmode:
self.hostap_config(params)
else:
self.station_config(params)
def deconfig(self, params):
""" De-configure the AP (will also bring wlan and the bridge down) """
if not self.hostapd['configured'] and not self.station['configured']:
return
# Taking down hostapd takes wlan0 and mon.wlan0 down.
if self.hostapd['configured']:
self.router.run("pkill hostapd >/dev/null 2>&1", ignore_status=True)
# self.router.run("rm -f %s" % self.hostapd['file'])
if self.station['configured']:
if self.station['type'] == 'ibss':
self.router.run("%s dev %s ibss leave" %
(self.cmd_iw, self.station['interface']))
else:
self.router.run("%s dev %s disconnect" %
(self.cmd_iw, self.station['interface']))
self.router.run("%s link set %s down" % (self.cmd_ip,
self.station['interface']))
if self.station['local_server_state']:
self.router.run("pkill dhcpd >/dev/null 2>&1",
ignore_status=True)
self.router.run("%s addr del %s" %
(self.cmd_ip, self.station
['local_server_state']['ip_params']))
self.station['local_server_state'] = None
# Try a couple times to remove the bridge; hostapd may still be exiting
for attempt in range(3):
self.router.run("%s link set %s down" %
(self.cmd_ip, self.bridgeif), ignore_status=True)
result = self.router.run("%s delbr %s" %
(self.cmd_brctl, self.bridgeif),
ignore_status=True)
if not result.stderr or 'No such device' in result.stderr:
break
time.sleep(1)
else:
raise error.TestFail("Unable to delete bridge %s: %s" %
(self.bridgeif, result.stderr))
self.hostapd['configured'] = False
self.station['configured'] = False
def get_ssid(self):
return self.hostapd['conf']['ssid']
def set_txpower(self, params):
self.router.run("%s dev %s set txpower %s" %
(self.cmd_iw, params.get('interface',
self.hostapd['interface']),
params.get('power', 'auto')))