autotest (wifi): test malformed probe responses
Add autotest for malformed probe response packets. This test covers
probe responses that include an element with an incorrect tag length.
This test covers a case where a malformed probe response may have been
the cause of a lucas device wifi disconnect. The probe response had
an incorrect length field. This test reproduces the scenario by
triggering a valid probe request. The AP in the test is configured to
repeatedly send out unsolicited (malformed) probe responses. To reduce
test flake, multiple attempts are made to receive a probe response.
The test fails if no probe responses are received, the device
disconnects or if the wifi card resets.
BUG=chromium:498018
TEST=tested on pit and lucas in wifi dev cell
Change-Id: Ib1902ab21503c65ce5028b3395ddf6f37de6888c
Reviewed-on: https://chromium-review.googlesource.com/278251
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: Rebecca Silberstein <silberst@chromium.org>
Commit-Queue: Rebecca Silberstein <silberst@chromium.org>
diff --git a/server/cros/network/frame_sender.py b/server/cros/network/frame_sender.py
index f73e703..d266847 100644
--- a/server/cros/network/frame_sender.py
+++ b/server/cros/network/frame_sender.py
@@ -6,7 +6,8 @@
"""Context manager for sending management frames."""
def __init__(self, router, frame_type, channel, ssid_prefix=None,
- num_bss=None, frame_count=None, delay=None):
+ num_bss=None, frame_count=None, delay=None, dest_addr=None,
+ probe_resp_footer=None):
"""
@param router: LinuxRouter object router to send frames from.
@param frame_type: int management frame type.
@@ -16,6 +17,8 @@
@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.
+ @param dest_addr: MAC address of the destination address (DA).
+ @param probe_resp_footer: footer bytes for probe responses.
"""
self._router = router
self._channel = channel
@@ -24,6 +27,8 @@
self._num_bss = num_bss
self._frame_count = frame_count
self._delay = delay
+ self._dest_addr = dest_addr
+ self._probe_resp_footer = probe_resp_footer
self._interface = None
self._pid = None
@@ -33,7 +38,8 @@
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)
+ delay=self._delay, dest_addr=self._dest_addr,
+ probe_resp_footer=self._probe_resp_footer)
return self
diff --git a/server/cros/network/wifi_client.py b/server/cros/network/wifi_client.py
index d18a19b..6ea4a51 100644
--- a/server/cros/network/wifi_client.py
+++ b/server/cros/network/wifi_client.py
@@ -992,6 +992,17 @@
return self.assert_disconnect_count(0)
+ def get_num_card_resets(self):
+ """Get card reset count."""
+ reset_msg = 'mwifiex_sdio_card_reset'
+ result = self.host.run('grep -c %s /var/log/messages' % reset_msg,
+ ignore_status=True)
+ if result.exit_status == 1:
+ return 0
+ count = int(result.stdout.strip())
+ return count
+
+
def release_wifi_if(self):
"""Release the control over the wifi interface back to normal operation.
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 3626c08..43999b3 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -7,6 +7,7 @@
import logging
import random
import string
+import tempfile
import time
from autotest_lib.client.common_lib import error
@@ -105,6 +106,8 @@
MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
+ PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer'
+
def get_capabilities(self):
"""@return iterable object of AP capabilities for this system."""
caps = set([self.CAPABILITY_IBSS])
@@ -818,6 +821,22 @@
(self.cmd_hostapd_cli, control_if, client_mac))
+ def _prep_probe_response_footer(self, footer):
+ """Write probe response footer temporarily to a local file and copy
+ over to test router.
+
+ @param footer string containing bytes for the probe response footer.
+ @raises AutoservRunError: If footer file copy fails.
+
+ """
+ with tempfile.TemporaryFile() as fp:
+ fp.write(footer)
+ try:
+ self.host.send_file(fp, self.PROBE_RESPONSE_FOOTER_FILE)
+ except error.AutoservRunError:
+ logging.error('failed to copy footer file to AP')
+ raise
+
def send_management_frame_on_ap(self, frame_type, channel, instance=0):
"""Injects a management frame into an active hostapd session.
@@ -854,7 +873,8 @@
def send_management_frame(self, interface, frame_type, channel,
ssid_prefix=None, num_bss=None,
- frame_count=None, delay=None):
+ frame_count=None, delay=None,
+ dest_addr=None, probe_resp_footer=None):
"""
Injects management frames on specify channel |frequency|.
@@ -868,6 +888,8 @@
@param num_bss int number of BSS.
@param frame_count int number of frames to send.
@param delay int milliseconds delay between frames.
+ @param dest_addr string destination address (DA) MAC address.
+ @param probe_resp_footer string footer for probe response.
@return int PID of the newly created process.
@@ -882,6 +904,11 @@
command += ' -n %d' % (frame_count)
if delay is not None:
command += ' -d %d' % (delay)
+ if dest_addr is not None:
+ command += ' -a %s' % (dest_addr)
+ if probe_resp_footer is not None:
+ self._prep_probe_response_footer(footer=probe_resp_footer)
+ command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
pid = int(self.router.run(command).stdout)
return pid
diff --git a/server/site_tests/network_WiFi_MalformedProbeResp/control b/server/site_tests/network_WiFi_MalformedProbeResp/control
new file mode 100644
index 0000000..fe092c1
--- /dev/null
+++ b/server/site_tests/network_WiFi_MalformedProbeResp/control
@@ -0,0 +1,29 @@
+# Copyright 2015 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 = 'silberst, pstew, quiche'
+NAME = 'network_WiFi_MalformedProbeResp'
+TIME = 'MEDIUM'
+TEST_TYPE = 'Server'
+# TODO(silberst): add to wifi test suites crbug.com/505662
+# ATTRIBUTES = "suite:wifi_correctness_cros_core, suite:wifi_matfunc, suite:wifi_matfunc_bcm4356, suite:wifi_matfunc_marvell8897, suite:wifi_release"
+# SUITE = ('wifi_matfunc, wifi_matfunc_bcm4356, wifi_matfunc_marvell8897,'
+# 'wifi_correctness_cros_core, wifi_release')
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test attempts to verify that we can stay connected to a router even
+if we receive malformed probe responses. In this particular case, the
+probe response data has a tag with an incorrect length.
+"""
+
+
+def run(machine):
+ host = hosts.create_host(machine)
+ job.run_test('network_WiFi_MalformedProbeResp',
+ host=host,
+ raw_cmdline_args=args)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_MalformedProbeResp/network_WiFi_MalformedProbeResp.py b/server/site_tests/network_WiFi_MalformedProbeResp/network_WiFi_MalformedProbeResp.py
new file mode 100644
index 0000000..35ae51c
--- /dev/null
+++ b/server/site_tests/network_WiFi_MalformedProbeResp/network_WiFi_MalformedProbeResp.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2015 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 time
+
+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.network import frame_sender
+from autotest_lib.server.cros.network import hostap_config
+from autotest_lib.server.cros.network import wifi_cell_test_base
+
+class network_WiFi_MalformedProbeResp(wifi_cell_test_base.WiFiCellTestBase):
+ """Test that we can stay connected to the configured AP when receiving
+ malformed probe responses from an AP that we are not connected to."""
+ version = 1
+
+ PROBE_RESPONSE_DELAY_MSEC = 50
+ SCAN_LOOP_SEC = 60
+ SCAN_LOOP_SLEEP_SEC = 10
+ PROBE_RESPONSE_TEST_CHANNEL = 1
+
+ def run_once(self):
+ """Sets up a router, connects to it, pings it, and repeats."""
+ configuration = hostap_config.HostapConfig(
+ channel=self.PROBE_RESPONSE_TEST_CHANNEL,
+ mode=hostap_config.HostapConfig.MODE_11B)
+ self.context.router.require_capabilities(
+ [site_linux_system.LinuxSystem.CAPABILITY_SEND_MANAGEMENT_FRAME])
+
+ self.context.configure(configuration)
+ client_mac = self.context.client.wifi_mac
+
+ pretest_reset_count = self.context.client.get_num_card_resets()
+ logging.debug('pretest_reset_count=%d', pretest_reset_count)
+ self.context.router.start_capture(
+ configuration.frequency,
+ ht_type=configuration.ht_packet_capture_mode)
+ assoc_params = xmlrpc_datatypes.AssociationParameters()
+ assoc_params.ssid = self.context.router.get_ssid()
+ assoc_result = self.context.assert_connect_wifi(assoc_params)
+ start_time = time.time()
+ count = 1
+ scan = 0
+ rx_probe_resp_count = 0
+ with self.context.client.assert_no_disconnects():
+ with frame_sender.FrameSender(
+ self.context.router,
+ 'probe_response',
+ self.PROBE_RESPONSE_TEST_CHANNEL,
+ ssid_prefix='TestingProbes',
+ num_bss=1,
+ frame_count=0,
+ delay=self.PROBE_RESPONSE_DELAY_MSEC,
+ dest_addr=client_mac,
+ probe_resp_footer='\xdd\xb7\x00\x1a\x11\x01\x01\x02\x03'):
+ while time.time() - start_time < self.SCAN_LOOP_SEC:
+ bss_list = self.context.client.iw_runner.scan(
+ self.context.client.wifi_if, [2412])
+ for bss in bss_list:
+ logging.debug('found bss: %s', bss.ssid)
+ if bss.ssid == 'TestingProbes00000000':
+ rx_probe_resp_count += 1
+ time.sleep(self.SCAN_LOOP_SLEEP_SEC)
+ else:
+ logging.debug('done scanning for networks')
+
+ logging.debug('received %s probe_responses', rx_probe_resp_count)
+ if rx_probe_resp_count == 0:
+ raise error.TestFail('Client failed to receive probe responses')
+
+ reset_count = self.context.client.get_num_card_resets()
+ logging.debug('reset count = %s', reset_count)
+ test_resets = reset_count - pretest_reset_count
+ if test_resets < 0:
+ logging.debug('logs rotated during test')
+ if reset_count > 0:
+ test_resets = reset_count
+
+ if test_resets > 0:
+ raise error.TestFail('Client reset card')
+ self.context.client.shill.disconnect(assoc_params.ssid)
+ self.context.router.deconfig()
+ self.context.router.stop_capture()