Add regulatory test for channel move
Inject an 802.11h Spectrum Management Channel Switch Action Frame
into an actively running 802.11 connection and ensure that the
client vacates the channel.
CQ-DEPEND=CL:*39532
BUG=chrome-os-partner:19953
TEST=This is a test; Passes with Lumpy. Fails with daisy ToT, and
passes with daisy with kernel changes to support 802.11h.
Change-Id: I25a5d35e5a7f0c429ea52eb38ab9a8fce6c27211
Reviewed-on: https://gerrit.chromium.org/gerrit/58431
Reviewed-by: Christopher Wiley <[email protected]>
Tested-by: Paul Stewart <[email protected]>
Commit-Queue: Paul Stewart <[email protected]>
diff --git a/server/cros/wlan/wifi_client.py b/server/cros/wlan/wifi_client.py
index c2f65a1..acca65c 100644
--- a/server/cros/wlan/wifi_client.py
+++ b/server/cros/wlan/wifi_client.py
@@ -213,13 +213,15 @@
self._host.close()
- def ping(self, ping_ip, ping_args, count=None):
+ def ping(self, ping_ip, ping_args, count=None, ignore_status=False):
"""Ping an address from the client and return the command output.
@param ping_ip string IPv4 address for the client to ping.
@param ping_args dict of parameters understood by
wifi_test_utils.ping_args().
@param count int number of times to ping the address.
+ @param ignore_status bool whether to consider an error exit status
+ from the ping command to be a fatal error.
@return string raw output of the ping command
"""
@@ -233,7 +235,7 @@
'%s %s %s' % (self.COMMAND_PING,
wifi_test_utils.ping_args(ping_args),
ping_ip),
- timeout=timeout)
+ timeout=timeout, ignore_status=ignore_status)
return result.stdout
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 3af1d05..c1e8466 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -33,6 +33,18 @@
"""
+ def get_capabilities(self):
+ """@return iterable object of AP capabilities for this system."""
+ caps = set()
+ try:
+ self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
+ self.router, '/usr/bin/send_management_frame')
+ caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
+ except error.TestFail:
+ pass
+ return super(LinuxRouter, self).get_capabilities().union(caps)
+
+
def __init__(self, host, params, defssid):
"""Build a LinuxRouter.
@@ -737,6 +749,21 @@
params['client']))
+ def send_management_frame(self, frame_type, instance=0):
+ """Injects a management frame into an active hostapd session.
+
+ @param frame_type string the type of frame to send.
+ @param instance int indicating which hostapd instance to inject into.
+
+ """
+ hostap_interface = self.hostapd_instances[instance]['interface']
+ interface = self._get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
+ self.router.run("%s link set %s up" % (self.cmd_ip, interface))
+ self.router.run('%s %s %s' %
+ (self.cmd_send_management_frame, interface, frame_type))
+ self._release_wlanif(interface)
+
+
def _pre_config_hook(self, config):
"""Hook for subclasses.
diff --git a/server/site_linux_system.py b/server/site_linux_system.py
index 7b3b2f4..d4c8dff 100644
--- a/server/site_linux_system.py
+++ b/server/site_linux_system.py
@@ -23,6 +23,7 @@
CAPABILITY_MULTI_AP = 'multi_ap'
CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
CAPABILITY_IBSS = 'ibss_supported'
+ CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
@property
@@ -250,7 +251,7 @@
return phys[0]
- def _get_wlanif(self, frequency, phytype, mode = None):
+ def _get_wlanif(self, frequency, phytype, mode = None, same_phy_as = None):
"""Get a WiFi device that supports the given frequency, mode, and type.
This function is used by inherited classes, so we use the single '_'
@@ -263,10 +264,18 @@
@param frequency int WiFi frequency to support.
@param phytype string type of phy (e.g. 'monitor').
@param mode string 'a' 'b' or 'g'.
+ @param same_phy_as string create the interface on the same phy as this.
@return string WiFi device.
"""
- if mode in ('b', 'g') and self.phydev2 is not None:
+ if same_phy_as:
+ for phy, wlanif_i, phytype_i in self.wlanifs_in_use:
+ if wlanif_i == same_phy_as:
+ break
+ else:
+ raise error.TestFail('Unable to find phy for interface %s' %
+ same_phy_as)
+ elif mode in ('b', 'g') and self.phydev2 is not None:
phy = self.phydev2
elif mode == 'a' and self.phydev5 is not None:
phy = self.phydev5
diff --git a/server/site_tests/network_WiFi_Regulatory/control b/server/site_tests/network_WiFi_Regulatory/control
new file mode 100644
index 0000000..4e4687e
--- /dev/null
+++ b/server/site_tests/network_WiFi_Regulatory/control
@@ -0,0 +1,33 @@
+# Copyright (c) 2013 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.
+
+AUTHOR = '[email protected]'
+NAME = 'network_WiFi_Regulatory'
+TIME = 'SHORT'
+TEST_TYPE = 'Server'
+SUITE = 'wifi_matfunc'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test verifies that DUT will move off-channel if it is sent a
+Spectrum Management action frame that contains a Channel Move
+element. Such frames are sent on a DFS network to vacate the
+channel if radar is detected.
+"""
+
+
+from autotest_lib.server.cros.wlan import hostap_config
+
+
+def run(machine):
+ a_mode = hostap_config.HostapConfig.MODE_11A
+ configurations = [(hostap_config.HostapConfig(channel=64, mode=a_mode), 36)]
+ host = hosts.create_host(machine)
+ job.run_test('network_WiFi_Regulatory',
+ host=host,
+ raw_cmdline_args=args,
+ additional_params=configurations)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py b/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py
new file mode 100644
index 0000000..e643775
--- /dev/null
+++ b/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2013 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.
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.server import site_linux_system
+from autotest_lib.server.cros import wifi_test_utils
+from autotest_lib.server.cros.wlan import wifi_cell_test_base
+
+
+class network_WiFi_Regulatory(wifi_cell_test_base.WiFiCellTestBase):
+ """Test that the client vacates the channel and can no longer ping after
+ notification from the AP that it should switch channels."""
+ version = 1
+
+
+ def parse_additional_arguments(self, commandline_args, additional_params):
+ """Hook into super class to take control files parameters.
+
+ @param commandline_args dict of parsed parameters from the autotest.
+ @param additional_params list of dicts describing router configs.
+
+ """
+ self._configurations = additional_params
+
+
+ def run_once(self):
+ """Sets up a router, connects to it, then tests a channel switch."""
+ for router_conf, alternate_channel in self._configurations:
+ self.context.router.require_capabilities(
+ [site_linux_system.LinuxSystem.
+ CAPABILITY_SEND_MANAGEMENT_FRAME])
+ self.context.configure(router_conf)
+ assoc_params = xmlrpc_datatypes.AssociationParameters()
+ assoc_params.ssid = self.context.router.get_ssid()
+ self.context.assert_connect_wifi(assoc_params)
+ ping_ip = self.context.get_wifi_addr(ap_num=0)
+ result = self.context.client.ping(ping_ip, {}, ignore_status=True)
+ for attempt in range(10):
+ self.context.router.send_management_frame(
+ 'channel_switch:%d' % alternate_channel)
+ # This should fail at some point. Since the client
+ # might be in power-save, we are not guaranteed it will hear
+ # this message the first time around.
+ result = self.context.client.ping(ping_ip, {'count':3})
+ stats = wifi_test_utils.parse_ping_output(result)
+ if float(stats['loss']) > 60:
+ break
+ else:
+ raise error.TestFail('Client never lost connectivity')
+ self.context.client.shill.disconnect(assoc_params.ssid)
+ self.context.router.deconfig()