/bin/wifi: WDS/4-address mode support for APs.
Adds a --wds flag that turns on 4-address mode for the AP interface,
and enables hostapd support.
Change-Id: I9406a5c5cf6f8d09cada72876d602e88605cf8da
diff --git a/wifi/configs.py b/wifi/configs.py
index e83ee68..23eac7f 100644
--- a/wifi/configs.py
+++ b/wifi/configs.py
@@ -94,6 +94,7 @@
{require_vht}
{hidden}
{ap_isolate}
+{wds}
{vendor_elements}
ht_capab={ht20}{ht40}{guard_interval}{ht_rxstbc}
@@ -288,6 +289,7 @@
hidden = 'ignore_broadcast_ssid=1' if opt.hidden_mode else ''
bridge = 'bridge=%s' % opt.bridge if opt.bridge else ''
ap_isolate = 'ap_isolate=1' if opt.client_isolation else ''
+ wds = 'wds_sta=1' if opt.wds else ''
hostapd_conf_parts = [_HOSTCONF_TPL.format(
interface=interface, band=band, channel=channel, width=width,
protocols=protocols, hostapd_band=hostapd_band,
@@ -296,7 +298,7 @@
ht_rxstbc=ht_rxstbc, vht_settings=vht_settings,
guard_interval=guard_interval, enable_wmm=enable_wmm, hidden=hidden,
ap_isolate=ap_isolate, auth_algs=auth_algs, bridge=bridge,
- ssid=utils.sanitize_ssid(opt.ssid),
+ ssid=utils.sanitize_ssid(opt.ssid), wds=wds,
vendor_elements=get_vendor_elements(opt))]
if opt.encryption != 'NONE':
diff --git a/wifi/configs_test.py b/wifi/configs_test.py
index 68e7b2a..e088298 100755
--- a/wifi/configs_test.py
+++ b/wifi/configs_test.py
@@ -352,6 +352,32 @@
+
+ht_capab=[HT20][RX-STBC1]
+
+"""
+
+_HOSTAPD_CONFIG_WDS = """ctrl_interface=/var/run/hostapd
+interface=wlan0
+
+ssid=TEST_SSID
+utf8_ssid=1
+auth_algs=1
+hw_mode=g
+channel=1
+country_code=US
+ieee80211d=1
+ieee80211h=1
+ieee80211n=1
+
+
+
+
+
+
+wds_sta=1
+
+
ht_capab=[HT20][RX-STBC1]
"""
@@ -376,6 +402,7 @@
+
ht_capab=[HT20][RX-STBC1]
"""
@@ -398,6 +425,7 @@
ignore_broadcast_ssid=1
+
vendor_elements=dd04f4f5e801dd0df4f5e803544553545f53534944
ht_capab=[HT20][RX-STBC1]
@@ -435,6 +463,7 @@
self.client_isolation = False
self.supports_provisioning = False
self.no_band_restriction = False
+ self.wds = False
def wpa_passphrase(ssid, passphrase):
@@ -505,6 +534,17 @@
config)
opt.bridge = default_bridge
+ # Test WDS.
+ default_wds, opt.wds = opt.wds, True
+ config = configs.generate_hostapd_config(
+ _PHY_INFO, 'wlan0', '2.4', '1', '20', set(('a', 'b', 'g', 'n', 'ac')),
+ 'asdfqwer', opt)
+ wvtest.WVPASSEQ('\n'.join((_HOSTAPD_CONFIG_WDS,
+ _HOSTAPD_CONFIG_WPA,
+ '# Experiments: ()\n')),
+ config)
+ opt.wds = default_wds
+
# Test provisioning IEs.
default_hidden_mode, opt.hidden_mode = opt.hidden_mode, True
default_supports_provisioning, opt.supports_provisioning = (
diff --git a/wifi/iw.py b/wifi/iw.py
index f16c50b..0110267 100644
--- a/wifi/iw.py
+++ b/wifi/iw.py
@@ -421,3 +421,11 @@
def scan(interface, scan_args):
"""Return 'iw scan' output for printing."""
return _scan(interface, scan_args)
+
+
+def set_4address_mode(interface, on):
+ try:
+ setting = 'on' if on else 'off'
+ subprocess.check_output(['iw', 'dev', interface, 'set', '4addr', setting])
+ except subprocess.CalledProcessError as e:
+ raise utils.BinWifiException('Failed to set 4addr mode %s: %s', setting, e)
diff --git a/wifi/utils.py b/wifi/utils.py
index 3b2db84..33f3e53 100644
--- a/wifi/utils.py
+++ b/wifi/utils.py
@@ -196,19 +196,20 @@
return ''
-def validate_set_wifi_options(band, width, autotype, protocols, encryption):
+def validate_set_wifi_options(opt):
"""Validates options to set_wifi.
Args:
- band: The specified band, as a string; '2.4' or '5'.
- width: The specified channel width.
- autotype: The specified autotype.
- protocols: The specified 802.11 levels, as a collection of strings.
- encryption: The specified encryption type.
+ opt: The options to validate.
Raises:
BinWifiException: if anything is not valid.
"""
+ band = opt.band
+ width = opt.width
+ autotype = opt.autotype
+ protocols = set(opt.protocols.split('/'))
+
if band not in ('2.4', '5'):
raise BinWifiException('You must specify band with -b2.4 or -b5')
@@ -240,11 +241,14 @@
elif width == '80' and 'ac' not in protocols:
raise BinWifiException('-p ac is needed for 40 MHz channels')
- if encryption == 'WEP' or '_PSK_' in encryption:
+ if opt.encryption == 'WEP' or '_PSK_' in opt.encryption:
if 'WIFI_PSK' not in os.environ:
raise BinWifiException(
'Encryption enabled; use WIFI_PSK=whatever wifi set ...')
+ if opt.wds and not opt.bridge:
+ raise BinWifiException('WDS mode enabled; must specify a bridge.')
+
def sanitize_ssid(ssid):
"""Remove control and non-UTF8 characters from an SSID.
diff --git a/wifi/utils_test.py b/wifi/utils_test.py
index 8febea3..24e8716 100755
--- a/wifi/utils_test.py
+++ b/wifi/utils_test.py
@@ -2,13 +2,13 @@
"""Tests for utils.py."""
-import collections
import multiprocessing
import os
import shutil
import sys
import tempfile
+import configs_test
import utils
from wvtest import wvtest
@@ -18,8 +18,9 @@
{'band': '5'},
{'width': '40'},
{'autotype': 'ANY'},
- {'protocols': ('a', 'b', 'ac')},
+ {'protocols': 'a/b/ac'},
{'encryption': 'NONE'},
+ {'wds': True, 'bridge': 'br0'},
)
_VALIDATION_FAIL = (
@@ -32,25 +33,23 @@
{'band': '2.4', 'autotype': 'DFS'},
{'band': '5', 'autotype': 'OVERLAP'},
# Invalid protocols
- {'protocols': set('abc')},
- {'protocols': set()},
+ {'protocols': 'a/b/c'},
+ {'protocols': ''},
# Invalid width
{'width': '25'},
# Invalid width/protocols
- {'width': '40', 'protocols': set('abg')},
- {'width': '80', 'protocols': set('abgn')},
+ {'width': '40', 'protocols': 'a/b/g'},
+ {'width': '80', 'protocols': 'a/b/g/n'},
+ {'wds': True, 'bridge': ''},
)
-_DEFAULTS = collections.OrderedDict((('band', '2.4'), ('width', '20'),
- ('autotype', 'NONDFS'),
- ('protocols', ('a', 'b', 'g', 'n', 'ac')),
- ('encryption', 'WPA2_PSK_AES')))
-
-
-def modify_defaults(**kwargs):
- result = collections.OrderedDict(_DEFAULTS)
- result.update(kwargs)
+def make_optdict(**kwargs):
+ result = configs_test.FakeOptDict()
+ # This is the default band for 'wifi set'.
+ result.band = '2.4'
+ for k, v in kwargs.iteritems():
+ setattr(result, k, v)
return result
@@ -62,23 +61,24 @@
for case in _VALIDATION_PASS:
try:
- utils.validate_set_wifi_options(*modify_defaults(**case).values())
+ utils.validate_set_wifi_options(make_optdict(**case))
+ wvtest.WVPASS(True) # Make WvTest count this as a test.
except utils.BinWifiException:
wvtest.WVFAIL('Test failed.')
for case in _VALIDATION_FAIL:
wvtest.WVEXCEPT(
utils.BinWifiException, utils.validate_set_wifi_options,
- *modify_defaults(**case).values())
+ make_optdict(**case))
# Test failure when WIFI_PSK is missing
del os.environ['WIFI_PSK']
wvtest.WVEXCEPT(
utils.BinWifiException, utils.validate_set_wifi_options,
- *_DEFAULTS.values())
+ make_optdict(**_VALIDATION_PASS[0]))
wvtest.WVEXCEPT(
utils.BinWifiException, utils.validate_set_wifi_options,
- *modify_defaults(encryption='WEP').values())
+ make_optdict(encryption='WEP'))
@wvtest.wvtest
diff --git a/wifi/wifi.py b/wifi/wifi.py
index 42ed53c..e4142c1 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -27,7 +27,7 @@
_OPTSPEC_FORMAT = """
{bin} set Enable or modify access points. Takes all options unless otherwise specified.
-{bin} setclient Enable or modify wifi clients. Takes -b, -P, -s, --bssid, -S.
+{bin} setclient Enable or modify wifi clients. Takes -b, -P, -s, --bssid, -S, --wds.
{bin} stop|off Disable access points and clients. Takes -b, -P, -S.
{bin} stopap Disable access points. Takes -b, -P, -S.
{bin} stopclient Disable wifi clients. Takes -b, -P, -S.
@@ -53,6 +53,7 @@
Y,yottasecond-timeouts Don't rotate any keys: PTK, GTK, or GMK
P,persist For set commands, persist options so we can restore them with 'wifi restore'. For stop commands, remove persisted options.
S,interface-suffix= Interface suffix (defaults to ALL for stop commands; use NONE to specify no suffix) []
+W,wds Enable WDS mode (nl80211 only)
lock-timeout= How long, in seconds, to wait for another /bin/wifi process to finish before giving up. [60]
scan-ap-force (Scan only) scan when in AP mode
scan-passive (Scan only) do not probe, scan passively
@@ -228,8 +229,7 @@
autotype = opt.autotype
protocols = set(opt.protocols.split('/'))
- utils.validate_set_wifi_options(
- band, width, autotype, protocols, opt.encryption)
+ utils.validate_set_wifi_options(opt)
psk = None
if opt.encryption == 'WEP' or '_PSK_' in opt.encryption:
@@ -840,6 +840,10 @@
if not _stop_hostapd(interface):
raise utils.BinWifiException("Couldn't stop hostapd")
+ # Set or unset 4-address mode. This has to be done while hostapd is down.
+ utils.log('%s 4-address mode', 'Enabling' if opt.wds else 'Disabling')
+ iw.set_4address_mode(interface, opt.wds)
+
# We don't want to try to rewrite this file if this is just a forced restart.
if not forced:
utils.atomic_write(tmp_config_filename, config)