Channel scan dwell time test
This test instructs a router to continually send beacon frames for a
predefined set of BSSes at a specific interval. The BSSes are named
with an index suffix in hex numerical order. The device under test is
instructed to perform a single channel scan while the beacon frames are
being sent by the router. Based on the SSIDs in the scan results, we
can estimate the device dwell time.
BUG=chromium:354685
TEST=Run this test
Change-Id: Ib8034a112b37ba6584a2b1d8009b2126f2dbd5e4
Reviewed-on: https://chromium-review.googlesource.com/191287
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
diff --git a/server/cros/network/frame_sender.py b/server/cros/network/frame_sender.py
new file mode 100644
index 0000000..f73e703
--- /dev/null
+++ b/server/cros/network/frame_sender.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+class FrameSender(object):
+ """Context manager for sending management frames."""
+
+ def __init__(self, router, frame_type, channel, ssid_prefix=None,
+ num_bss=None, frame_count=None, delay=None):
+ """
+ @param router: LinuxRouter object router to send frames from.
+ @param frame_type: int management frame type.
+ @param channel: int targeted channel.
+ @param ssid_prefix: string SSID prefix for BSSes in the frames.
+ @param num_bss: int number of BSSes configured for sending frames.
+ @param frame_count: int number of frames to send, frame_count of 0
+ implies infinite number of frames.
+ @param delay: int delay in between frames in milliseconds.
+ """
+ self._router = router
+ self._channel = channel
+ self._frame_type = frame_type
+ self._ssid_prefix = ssid_prefix
+ self._num_bss = num_bss
+ self._frame_count = frame_count
+ self._delay = delay
+ self._interface = None
+ self._pid = None
+
+ def __enter__(self):
+ self._interface = self._router.setup_management_frame_interface(
+ self._channel)
+ self._pid = self._router.send_management_frame(self._interface,
+ self._frame_type, self._channel, ssid_prefix=self._ssid_prefix,
+ num_bss=self._num_bss, frame_count=self._frame_count,
+ delay=self._delay)
+ return self
+
+
+ def __exit__(self, exception, value, traceback):
+ if self._interface:
+ self._router.release_interface(self._interface)
+ if self._pid:
+ self._router.host.run('kill %d' % self._pid, ignore_status=True)
+
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 2f382fd..73edc6e 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -47,6 +47,8 @@
STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
+ MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
+
def get_capabilities(self):
"""@return iterable object of AP capabilities for this system."""
caps = set([self.CAPABILITY_IBSS])
@@ -613,21 +615,75 @@
(self.cmd_hostapd_cli, control_if, client_mac))
- def send_management_frame(self, frame_type, instance=0):
+ def send_management_frame_on_ap(self, frame_type, channel, instance=0):
"""Injects a management frame into an active hostapd session.
@param frame_type string the type of frame to send.
+ @param channel int targeted channel
@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.router.run('%s -i %s -t %s -c %d' %
+ (self.cmd_send_management_frame, interface, frame_type,
+ channel))
self.release_interface(interface)
+ def setup_management_frame_interface(self, channel):
+ """
+ Setup interface for injecting management frames.
+
+ @param channel int channel to inject the frames.
+
+ @return string name of the interface.
+
+ """
+ frequency = hostap_config.HostapConfig.get_frequency_for_channel(
+ channel)
+ interface = self.get_wlanif(frequency, 'monitor')
+ self.iw_runner.set_freq(interface, frequency)
+ self.router.run('%s link set %s up' % (self.cmd_ip, interface))
+ return interface
+
+
+ def send_management_frame(self, interface, frame_type, channel,
+ ssid_prefix=None, num_bss=None,
+ frame_count=None, delay=None):
+ """
+ Injects management frames on specify channel |frequency|.
+
+ This function will spawn off a new process to inject specified
+ management frames |frame_type| at the specified interface |interface|.
+
+ @param interface string interface to inject frames.
+ @param frame_type string message type.
+ @param channel int targeted channel.
+ @param ssid_prefix string SSID prefix.
+ @param num_bss int number of BSS.
+ @param frame_count int number of frames to send.
+ @param delay int milliseconds delay between frames.
+
+ @return int PID of the newly created process.
+
+ """
+ command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
+ interface, frame_type, channel)
+ if ssid_prefix is not None:
+ command += ' -s %s' % (ssid_prefix)
+ if num_bss is not None:
+ command += ' -b %d' % (num_bss)
+ if frame_count is not None:
+ command += ' -n %d' % (frame_count)
+ if delay is not None:
+ command += ' -d %d' % (delay)
+ command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
+ pid = int(self.router.run(command).stdout)
+ return pid
+
+
def detect_client_deauth(self, client_mac, instance=0):
"""Detects whether hostapd has logged a deauthentication from
|client_mac|.
diff --git a/server/site_tests/network_WiFi_ChannelScanDwellTime/control b/server/site_tests/network_WiFi_ChannelScanDwellTime/control
new file mode 100644
index 0000000..a22ffd3
--- /dev/null
+++ b/server/site_tests/network_WiFi_ChannelScanDwellTime/control
@@ -0,0 +1,23 @@
+# Copyright (c) 2014 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 = 'zqiu, wiley, pstew, quiche'
+NAME = 'network_WiFi_ChannelScanDwellTime'
+TIME = 'SHORT'
+TEST_TYPE = 'Server'
+SUITE = 'wifi_matfunc'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test is designed to determine the channel scan dwell time.
+"""
+
+def run(machine):
+ host = hosts.create_host(machine)
+ job.run_test('network_WiFi_ChannelScanDwellTime',
+ host=host,
+ raw_cmdline_args=args)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_ChannelScanDwellTime/network_WiFi_ChannelScanDwellTime.py b/server/site_tests/network_WiFi_ChannelScanDwellTime/network_WiFi_ChannelScanDwellTime.py
new file mode 100644
index 0000000..1494d3b
--- /dev/null
+++ b/server/site_tests/network_WiFi_ChannelScanDwellTime/network_WiFi_ChannelScanDwellTime.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2014 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
+import random
+import string
+import time
+
+from autotest_lib.server.cros.network import frame_sender
+from autotest_lib.server import site_linux_system
+from autotest_lib.client.common_lib import error
+from autotest_lib.server.cros.network import wifi_cell_test_base
+
+
+class network_WiFi_ChannelScanDwellTime(wifi_cell_test_base.WiFiCellTestBase):
+ """Test for determine channel scan dwell time."""
+ version = 1
+
+ KNOWN_TEST_PREFIX = 'network_WiFi'
+ SUFFIX_LETTERS = string.ascii_lowercase + string.digits
+ DELAY_INTERVAL_MILLISECONDS = 1
+ SCAN_RETRY_TIMEOUT_SECONDS = 10
+ NUM_BSS = 1024
+ MISSING_BEACON_THRESHOLD = 2
+
+ def _build_ssid_prefix(self):
+ """Build ssid prefix."""
+ unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
+ for x in range(5)])
+ prefix = self.__class__.__name__[len(self.KNOWN_TEST_PREFIX):]
+ prefix = prefix.lstrip('_')
+ prefix += '_' + unique_salt + '_'
+ return prefix[-24:]
+
+
+ def _get_dwell_time(self, bss_list):
+ """Parse scan result to get dwell time.
+
+ Calculate dwell time based on the SSIDs in the scan result.
+
+ @param bss_list: List of BSSs
+
+ @return int dwell time in ms.
+ """
+ # Get ssid indices from the scan result.
+ # Expected SSID format: [testName]_[salt]_[index]
+ ssid_index = []
+ for bss in bss_list:
+ ssid = int(bss.ssid.split('_')[-1], 16)
+ ssid_index.append(ssid)
+ # Calculate dwell time based on the start ssid index and end ssid index.
+ ssid_index.sort()
+ index_diff = ssid_index[-1] - ssid_index[0]
+ dwell_time = index_diff * self.DELAY_INTERVAL_MILLISECONDS
+ # Check if number of missed beacon frames exceed the test threshold.
+ missed_beacons = index_diff - (len(ssid_index) - 1)
+ if missed_beacons > self.MISSING_BEACON_THRESHOLD:
+ logging.info('Missed %d beacon frames, SSID Index: %r',
+ missed_beacons, ssid_index)
+ raise error.TestFail('DUT missed more than %d beacon frames',
+ missed_beacons)
+ return dwell_time
+
+
+ def _channel_dwell_time_test(self, single_channel):
+ """Perform test to determine channel dwell time.
+
+ This function invoke FrameSender to continuously send beacon frames
+ for specific number of BSSs with specific delay, the SSIDs of the
+ BSS are in hex numerical order. And at the same time, perform wifi scan
+ on the DUT. The index in the SSIDs of the scan result will be used to
+ interpret the relative start time and end time of the channel scan.
+
+ @param single_channel: bool perform single channel scan if true.
+
+ @return int dwell time in ms.
+
+ """
+ dwell_time = 0
+ ssid_prefix = self._build_ssid_prefix()
+ with frame_sender.FrameSender(self.context.router, 'beacon', 1,
+ ssid_prefix=ssid_prefix,
+ num_bss = self.NUM_BSS,
+ frame_count=0,
+ delay=self.DELAY_INTERVAL_MILLISECONDS):
+ if single_channel:
+ frequencies = [2412]
+ else:
+ frequencies = []
+ # Perform scan
+ start_time = time.time()
+ while time.time() - start_time < self.SCAN_RETRY_TIMEOUT_SECONDS:
+ bss_list = self.context.client.iw_runner.scan(
+ self.context.client.wifi_if, frequencies=frequencies)
+
+ if bss_list is not None:
+ break
+
+ time.sleep(0.5)
+ else:
+ raise error.TestFail('Unable to trigger scan on client.')
+ if not bss_list:
+ raise error.TestFail('Failed to find any BSS')
+ # Filter scan result based on ssid prefix to remove any cached
+ # BSSs from previous run.
+ result_list = [bss for bss in bss_list if
+ bss.ssid.startswith(ssid_prefix)]
+ if result_list is None:
+ raise error.TestFail('Failed to find any BSS for this test')
+ dwell_time = self._get_dwell_time(result_list)
+ return dwell_time
+
+
+ def run_once(self):
+ self.context.router.require_capabilities(
+ [site_linux_system.LinuxSystem.
+ CAPABILITY_SEND_MANAGEMENT_FRAME])
+ # Get channel dwell time for single-channel scan
+ dwell_time = self._channel_dwell_time_test(True)
+ logging.info('Channel dwell time for single-channel scan: %d ms',
+ dwell_time)
+ self.write_perf_keyval({'dwell_time_single_channel_scan': dwell_time})
diff --git a/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py b/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py
index 981c670..6e7a378 100644
--- a/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py
+++ b/server/site_tests/network_WiFi_Regulatory/network_WiFi_Regulatory.py
@@ -41,8 +41,8 @@
for attempt in range(10):
# Since the client might be in power-save, we are not
# guaranteed it will hear this message the first time around.
- self.context.router.send_management_frame(
- 'channel_switch:%d' % alternate_channel)
+ self.context.router.send_management_frame_on_ap(
+ 'channel_switch', alternate_channel)
# Test to see if the router received a deauth message from
# the client.