blob: 8fc708a2de93d9137ce3908fde95ff9138d8d0dd [file] [log] [blame]
Sam Leffler6969d1d2010-03-15 16:07:11 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Christopher Wiley408d1812014-01-13 15:27:43 -08005import collections
Peter Qiu79784512015-04-14 22:59:55 -07006import copy
Christopher Wiley14796b32013-04-03 14:53:33 -07007import logging
Christopher Wiley3166e432013-08-06 09:53:12 -07008import random
Christopher Wiley3166e432013-08-06 09:53:12 -07009import string
Rebecca Silberstein194b4582015-06-17 13:29:38 -070010import tempfile
Christopher Wileyeea12362013-12-12 17:24:29 -080011import time
Christopher Wiley14796b32013-04-03 14:53:33 -070012
Paul Stewartc9628b32010-08-11 13:03:51 -070013from autotest_lib.client.common_lib import error
Alex Khouderchahb0c624c2018-06-12 11:00:14 -070014from autotest_lib.client.common_lib import utils
Christopher Wiley36039222014-12-13 18:27:52 -080015from autotest_lib.client.common_lib.cros import path_utils
Paul Stewart6ddeba72013-11-18 10:08:23 -080016from autotest_lib.client.common_lib.cros.network import interface
Christopher Wiley594570d2014-03-14 16:50:15 -070017from autotest_lib.client.common_lib.cros.network import netblock
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070018from autotest_lib.client.common_lib.cros.network import ping_runner
19from autotest_lib.server import hosts
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070020from autotest_lib.server import site_linux_system
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080021from autotest_lib.server.cros import dnsname_mangler
Christopher Wiley99d42c92013-07-09 16:40:16 -070022from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070023
Christopher Wiley408d1812014-01-13 15:27:43 -080024
25StationInstance = collections.namedtuple('StationInstance',
26 ['ssid', 'interface', 'dev_type'])
Christopher Wileyd6e503c2014-06-23 15:53:47 -070027HostapdInstance = collections.namedtuple('HostapdInstance',
28 ['ssid', 'conf_file', 'log_file',
Tien Chang097f7462014-09-03 17:30:29 -070029 'interface', 'config_dict',
mukesh agrawal768e7b32015-05-06 14:53:59 -070030 'stderr_log_file',
31 'scenario_name'])
Tien Chang097f7462014-09-03 17:30:29 -070032
Eric Carusoeddedd32014-10-13 14:41:49 -070033# Send magic packets here, so they can wake up the system but are otherwise
34# dropped.
35UDP_DISCARD_PORT = 9
Christopher Wiley408d1812014-01-13 15:27:43 -080036
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070037def build_router_hostname(client_hostname=None, router_hostname=None):
38 """Build a router hostname from a client hostname.
39
40 @param client_hostname: string hostname of DUT connected to a router.
41 @param router_hostname: string hostname of router.
42 @return string hostname of connected router or None if the hostname
43 cannot be inferred from the client hostname.
44
45 """
46 if not router_hostname and not client_hostname:
47 raise error.TestError('Either client_hostname or router_hostname must '
48 'be specified to build_router_hostname.')
49
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080050 return dnsname_mangler.get_router_addr(client_hostname,
51 cmdline_override=router_hostname)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070052
53
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070054def build_router_proxy(test_name='', client_hostname=None, router_addr=None,
55 enable_avahi=False):
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070056 """Build up a LinuxRouter object.
57
58 Verifies that the remote host responds to ping.
59 Either client_hostname or router_addr must be specified.
60
61 @param test_name: string name of this test (e.g. 'network_WiFi_TestName').
62 @param client_hostname: string hostname of DUT if we're in the lab.
63 @param router_addr: string DNS/IPv4 address to use for router host object.
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070064 @param enable_avahi: boolean True iff avahi should be started on the router.
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070065
66 @return LinuxRouter or raise error.TestError on failure.
67
68 """
Christopher Wiley297910b2014-07-01 13:53:19 -070069 router_hostname = build_router_hostname(client_hostname=client_hostname,
70 router_hostname=router_addr)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070071 logging.info('Connecting to router at %s', router_hostname)
72 ping_helper = ping_runner.PingRunner()
73 if not ping_helper.simple_ping(router_hostname):
74 raise error.TestError('Router at %s is not pingable.' %
75 router_hostname)
76
Edward Hill1f7ae3a2017-07-25 10:31:27 -060077 # Use CrosHost for all router hosts and avoid host detection.
78 # Host detection would use JetstreamHost for Whirlwind routers.
79 # JetstreamHost assumes ap-daemons are running.
80 # Testbed routers run the testbed-ap profile with no ap-daemons.
81 # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection.
82 return LinuxRouter(hosts.create_host(router_hostname,
83 host_class=hosts.CrosHost),
84 test_name,
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070085 enable_avahi=enable_avahi)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070086
Christopher Wiley408d1812014-01-13 15:27:43 -080087
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070088class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070089 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070090
91 This class implements test methods/steps that communicate with a
92 router implemented with Linux/mac80211. The router must
93 be pre-configured to enable ssh access and have a mac80211-based
94 wireless device. We also assume hostapd 0.7.x and iw are present
95 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070096
Sam Leffler6969d1d2010-03-15 16:07:11 -070097 """
98
Christopher Wiley08aafb02014-04-22 17:38:21 -070099 KNOWN_TEST_PREFIX = 'network_WiFi_'
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700100 POLLING_INTERVAL_SECONDS = 0.5
Brian Norris73456902018-12-06 10:05:33 -0800101 STARTUP_TIMEOUT_SECONDS = 30
Christopher Wiley3166e432013-08-06 09:53:12 -0700102 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700103 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700104
Christopher Wileyeea12362013-12-12 17:24:29 -0800105 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
106 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
Tien Chang097f7462014-09-03 17:30:29 -0700107 HOSTAPD_STDERR_LOG_FILE_PATTERN = '/tmp/hostapd-stderr-test-%s.log'
Paul Stewart80bb3372014-01-22 15:06:08 -0800108 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -0800109 HOSTAPD_DRIVER_NAME = 'nl80211'
110
111 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
112 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
113 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
114
Peter Qiuc4beba02014-03-24 14:46:24 -0700115 MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
116
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700117 PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer'
118
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700119 _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available'
120 _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current'
121
Paul Stewart51b0f382013-06-12 09:03:02 -0700122 def get_capabilities(self):
123 """@return iterable object of AP capabilities for this system."""
mukesh agrawal064ea972015-06-26 10:00:53 -0700124 caps = set()
Paul Stewart51b0f382013-06-12 09:03:02 -0700125 try:
Christopher Wiley36039222014-12-13 18:27:52 -0800126 self.cmd_send_management_frame = path_utils.must_be_installed(
127 '/usr/bin/send_management_frame', host=self.host)
Paul Stewart51b0f382013-06-12 09:03:02 -0700128 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
129 except error.TestFail:
130 pass
131 return super(LinuxRouter, self).get_capabilities().union(caps)
132
133
Christopher Wiley408d1812014-01-13 15:27:43 -0800134 @property
135 def router(self):
136 """Deprecated. Use self.host instead.
137
138 @return Host object representing the remote router.
139
140 """
141 return self.host
142
143
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700144 @property
145 def wifi_ip(self):
146 """Simple accessor for the WiFi IP when there is only one AP.
147
148 @return string IP of WiFi interface.
149
150 """
151 if len(self.local_servers) != 1:
152 raise error.TestError('Could not pick a WiFi IP to return.')
153
154 return self.get_wifi_ip(0)
155
156
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700157 def __init__(self, host, test_name, enable_avahi=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700158 """Build a LinuxRouter.
159
160 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -0700161 @param test_name string name of this test. Used in SSID creation.
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700162 @param enable_avahi: boolean True iff avahi should be started on the
163 router.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700164
165 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800166 super(LinuxRouter, self).__init__(host, 'router')
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700167 self._ssid_prefix = test_name
168 self._enable_avahi = enable_avahi
169 self.__setup()
Wade Guthrie24d1e312012-04-24 16:53:40 -0700170
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700171
172 def __setup(self):
173 """Set up this system.
174
175 Can be used either to complete initialization of a LinuxRouter
176 object, or to re-establish a good state after a reboot.
177
178 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800179 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wiley36039222014-12-13 18:27:52 -0800180 self.cmd_hostapd = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700181 '/usr/sbin/hostapd', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800182 self.cmd_hostapd_cli = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700183 '/usr/sbin/hostapd_cli', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800184 self.cmd_wpa_supplicant = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700185 '/usr/sbin/wpa_supplicant', host=self.host)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700186 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
187 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700188
Brian Norrisfacbe712019-02-11 15:40:27 -0800189 # TODO(crbug.com/839164): some routers fill their stateful partition
190 # with uncollected metrics.
191 self.host.run('rm -f /var/lib/metrics/uma-events', ignore_status=True)
192
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700193 # Log the most recent message on the router so that we can rebuild the
194 # suffix relevant to us when debugging failures.
Brian Norrise53f1692018-09-28 10:46:56 -0700195 last_log_line = self.host.run('tail -1 /var/log/messages',
196 ignore_status=True).stdout
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700197 # We're trying to get the timestamp from:
198 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah
Brian Norris2d8f2972018-09-26 14:55:38 -0700199 self._log_start_timestamp = last_log_line.strip().partition(' ')[0]
200 if self._log_start_timestamp:
201 logging.debug('Will only retrieve logs after %s.',
202 self._log_start_timestamp)
203 else:
204 # If syslog is empty, we just use a wildcard pattern, to grab
205 # everything.
206 logging.debug('Empty or corrupt log; will retrieve whole log')
207 self._log_start_timestamp = '.'
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700208
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700209 # hostapd configuration persists throughout the test, subsequent
210 # 'config' commands only modify it.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700211 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700212 # Many of our tests start with an uninteresting prefix.
213 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700214 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
215 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700216
Christopher Wileyeea12362013-12-12 17:24:29 -0800217 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700218 self.local_servers = []
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700219 self.server_address_index = []
Paul Stewart548cf452012-11-27 17:46:23 -0800220 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800221 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700222 self.dhcp_low = 1
223 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700224
Matthew Wang08e868d2018-04-19 12:04:54 -0700225 # Tear down hostapbr bridge interfaces
226 result = self.host.run('ls -d /sys/class/net/%s*' %
227 self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
228 ignore_status=True)
229 if result.exit_status == 0:
230 for path in result.stdout.splitlines():
231 self.delete_link(path.split('/')[-1])
232
Paul Stewart548cf452012-11-27 17:46:23 -0800233 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700234 self._kill_process_instance('hostapd', timeout_seconds=30)
235 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700236
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700237 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700238 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700239
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800240 self.enable_all_antennas()
Peter Qiu2f973252014-02-20 15:30:37 -0800241
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700242 # Some tests want this functionality, but otherwise, it's a distraction.
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700243 if self._enable_avahi:
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700244 self.host.run('start avahi', ignore_status=True)
245 else:
246 self.host.run('stop avahi', ignore_status=True)
247
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700248 # Some routers have bad (slow?) random number generators.
249 self.rng_configure()
250
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700251
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700252 def close(self):
253 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800254 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700255 # dnsmasq and hostapd cause interesting events to go to system logs.
256 # Retrieve only the suffix of the logs after the timestamp we stored on
257 # router creation.
258 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" %
259 self._log_start_timestamp, ignore_status=True)
260 self.host.get_file('/tmp/router_log', 'debug/router_host_messages')
Christopher Wiley408d1812014-01-13 15:27:43 -0800261 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700262
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700263
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700264 def reboot(self, timeout):
265 """Reboot this router, and restore it to a known-good state.
266
267 @param timeout Maximum seconds to wait for router to return.
268
269 """
270 super(LinuxRouter, self).reboot(timeout)
271 self.__setup()
272
273
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700274 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700275 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700276 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700277
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700278
Peter Qiuc89c9a22014-02-27 10:03:55 -0800279 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700280 """Start a hostapd instance described by conf.
281
Christopher Wileyeea12362013-12-12 17:24:29 -0800282 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700283
284 """
Paul Stewart548cf452012-11-27 17:46:23 -0800285 # Figure out the correct interface.
Brian Norris062390f2018-08-03 18:09:43 -0700286 interface = self.get_wlanif(configuration.frequency, 'managed',
287 configuration.min_streams)
Edward Hill7d411222017-10-02 17:26:56 -0600288 phy_name = self.iw_runner.get_interface(interface).phy
Paul Stewart326badb2012-12-18 14:18:54 -0800289
Christopher Wileyeea12362013-12-12 17:24:29 -0800290 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
291 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
Tien Chang097f7462014-09-03 17:30:29 -0700292 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800293 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800294 hostapd_conf_dict = configuration.generate_dict(
295 interface, control_interface,
Peter Qiubde37ac2014-12-18 23:40:57 -0800296 self.build_unique_ssid(suffix=configuration.ssid_suffix))
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700297 logging.debug('hostapd parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800298
299 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800300 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
301 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800302 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800303
304 # Run hostapd.
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700305 logging.info('Starting hostapd on %s(%s) channel=%s...',
Edward Hill7d411222017-10-02 17:26:56 -0600306 interface, phy_name, configuration.channel)
Christopher Wiley1defc242013-09-18 10:28:37 -0700307 self.router.run('rm %s' % log_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800308 self.router.run('stop wpasupplicant', ignore_status=True)
Tien Chang097f7462014-09-03 17:30:29 -0700309 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % (
310 self.cmd_hostapd, conf_file, log_file, stderr_log_file)
Christopher Wiley0f64b842014-04-30 15:43:50 -0700311 pid = int(self.router.run(start_command).stdout.strip())
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700312 self.hostapd_instances.append(HostapdInstance(
313 hostapd_conf_dict['ssid'],
314 conf_file,
315 log_file,
316 interface,
Tien Chang097f7462014-09-03 17:30:29 -0700317 hostapd_conf_dict.copy(),
mukesh agrawal768e7b32015-05-06 14:53:59 -0700318 stderr_log_file,
319 configuration.scenario_name))
Paul Stewart548cf452012-11-27 17:46:23 -0800320
Christopher Wileyeea12362013-12-12 17:24:29 -0800321 # Wait for confirmation that the router came up.
Christopher Wileyeea12362013-12-12 17:24:29 -0800322 logging.info('Waiting for hostapd to startup.')
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700323 utils.poll_for_condition(
324 condition=lambda: self._has_hostapd_started(log_file, pid),
325 exception=error.TestFail('Timed out while waiting for hostapd '
326 'to start.'),
327 timeout=self.STARTUP_TIMEOUT_SECONDS,
328 sleep_interval=self.POLLING_INTERVAL_SECONDS)
Christopher Wileyeea12362013-12-12 17:24:29 -0800329
Edward Hill7d411222017-10-02 17:26:56 -0600330 if configuration.frag_threshold:
331 threshold = self.iw_runner.get_fragmentation_threshold(phy_name)
332 if threshold != configuration.frag_threshold:
333 raise error.TestNAError('Router does not support setting '
334 'fragmentation threshold')
335
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700336
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700337 def _has_hostapd_started(self, log_file, pid):
338 """Determines if hostapd has started.
339
340 @return Whether or not hostapd has started.
341 @raise error.TestFail if there was a bad config or hostapd terminated.
342 """
343 success = self.router.run(
344 'grep "Setup of interface done" %s' % log_file,
345 ignore_status=True).exit_status == 0
346 if success:
347 return True
348
349 # A common failure is an invalid router configuration.
350 # Detect this and exit early if we see it.
351 bad_config = self.router.run(
352 'grep "Interface initialization failed" %s' % log_file,
353 ignore_status=True).exit_status == 0
354 if bad_config:
355 raise error.TestFail('hostapd failed to initialize AP '
356 'interface.')
357
358 if pid:
359 early_exit = self.router.run('kill -0 %d' % pid,
360 ignore_status=True).exit_status
361 if early_exit:
362 raise error.TestFail('hostapd process terminated.')
363
364 return False
365
366
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700367 def _kill_process_instance(self,
368 process,
369 instance=None,
370 timeout_seconds=10,
371 ignore_timeouts=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700372 """Kill a process on the router.
373
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700374 Kills remote program named |process| (optionally only a specific
375 |instance|). Wait |timeout_seconds| for |process| to die
376 before returning. If |ignore_timeouts| is False, raise
377 a TestError on timeouts.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700378
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700379 @param process: string name of process to kill.
380 @param instance: string fragment of the command line unique to
381 this instance of the remote process.
382 @param timeout_seconds: float timeout in seconds to wait.
383 @param ignore_timeouts: True iff we should ignore failures to
384 kill processes.
385 @return True iff the specified process has exited.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700386
Thieu Le7b23a542012-01-27 15:54:48 -0800387 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700388 if instance is not None:
Christopher Wileya4754c02014-06-30 16:37:52 -0700389 search_arg = '-f "^%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800390 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800391 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800392
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700393 self.host.run('pkill %s' % search_arg, ignore_status=True)
Paul Stewart326badb2012-12-18 14:18:54 -0800394
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700395 # Wait for process to die
396 time.sleep(self.POLLING_INTERVAL_SECONDS)
397 try:
398 utils.poll_for_condition(
399 condition=lambda: self.host.run(
400 'pgrep -l %s' % search_arg,
401 ignore_status=True).exit_status != 0,
402 timeout=timeout_seconds,
403 sleep_interval=self.POLLING_INTERVAL_SECONDS)
404 except utils.TimeoutError:
405 if ignore_timeouts:
406 return False
407
408 raise error.TestError(
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700409 'Timed out waiting for %s%s to die' %
410 (process,
411 '' if instance is None else ' (instance=%s)' % instance))
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700412 return True
Paul Stewart326badb2012-12-18 14:18:54 -0800413
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700414
Paul Stewart326badb2012-12-18 14:18:54 -0800415 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700416 """Kills a hostapd instance.
417
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700418 @param instance HostapdInstance object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700419
420 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700421 is_dead = self._kill_process_instance(
Christopher Wileya4754c02014-06-30 16:37:52 -0700422 self.cmd_hostapd,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700423 instance=instance.conf_file,
424 timeout_seconds=30,
425 ignore_timeouts=True)
mukesh agrawal768e7b32015-05-06 14:53:59 -0700426 if instance.scenario_name:
427 log_identifier = instance.scenario_name
428 else:
429 log_identifier = '%d_%s' % (
430 self._total_hostapd_instances, instance.interface)
431 files_to_copy = [(instance.log_file,
432 'debug/hostapd_router_%s.log' % log_identifier),
Tien Chang097f7462014-09-03 17:30:29 -0700433 (instance.stderr_log_file,
mukesh agrawal768e7b32015-05-06 14:53:59 -0700434 'debug/hostapd_router_%s.stderr.log' %
435 log_identifier)]
Tien Chang097f7462014-09-03 17:30:29 -0700436 for remote_file, local_file in files_to_copy:
437 if self.host.run('ls %s >/dev/null 2>&1' % remote_file,
438 ignore_status=True).exit_status:
439 logging.error('Did not collect hostapd log file because '
440 'it was missing.')
441 else:
442 self.router.get_file(remote_file, local_file)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700443 self._total_hostapd_instances += 1
444 if not is_dead:
445 raise error.TestError('Timed out killing hostapd.')
Paul Stewart21737812012-12-06 11:03:32 -0800446
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700447
Peter Qiubde37ac2014-12-18 23:40:57 -0800448 def build_unique_ssid(self, suffix=''):
Tien Chang097f7462014-09-03 17:30:29 -0700449 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
450 the number of APs we've constructed already.
451
452 @param suffix string to append to SSID
453
454 """
Christopher Wiley08aafb02014-04-22 17:38:21 -0700455 base = len(self.SUFFIX_LETTERS)
456 number = self._number_unique_ssids
457 self._number_unique_ssids += 1
458 unique = ''
459 while number or not unique:
460 unique = self.SUFFIX_LETTERS[number % base] + unique
461 number = number / base
462 # And salt the SSID so that tests running in adjacent cells are unlikely
463 # to pick the same SSID and we're resistent to beacons leaking out of
464 # cells.
465 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
466 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700467
468
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700469 def rng_configure(self):
470 """Configure the random generator to our liking.
471
472 Some routers (particularly, Gale) seem to have bad Random Number
473 Generators, such that hostapd can't always generate keys fast enough.
474 The on-board TPM seems to serve as a better generator, so we try to
475 switch to that if available.
476
477 Symptoms of a slow RNG: hostapd complains with:
478
479 WPA: Not enough entropy in random pool to proceed - reject first
480 4-way handshake
481
482 Ref:
483 https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854
484
485 Linux devices may have RNG parameters at
486 /sys/class/misc/hw_random/rng_{available,current}. See:
487
488 https://www.kernel.org/doc/Documentation/hw_random.txt
489
490 """
491
492 available = self.host.run('cat %s' % self._RNG_AVAILABLE, \
493 ignore_status=True).stdout.strip().split(' ')
494 # System may not have HWRNG support. Just skip this.
495 if available == "":
496 return
497 current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \
498 strip()
499 want_rng = "tpm-rng"
500
Brian Norris4bb65822019-03-04 16:03:55 -0800501 logging.debug("Available / current RNGs on router: %r / %s",
502 available, current)
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700503 if want_rng in available and want_rng != current:
Brian Norris4bb65822019-03-04 16:03:55 -0800504 logging.debug("Switching RNGs: %s -> %s", current, want_rng)
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700505 self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT))
506
507
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700508 def hostap_configure(self, configuration, multi_interface=None):
509 """Build up a hostapd configuration file and start hostapd.
510
511 Also setup a local server if this router supports them.
512
513 @param configuration HosetapConfig object.
514 @param multi_interface bool True iff multiple interfaces allowed.
515
516 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800517 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800518 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700519 self.deconfig()
Kris Rambishb9b46852014-12-19 15:29:58 -0800520 if configuration.is_11ac:
521 router_caps = self.get_capabilities()
522 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps:
523 raise error.TestNAError('Router does not have AC support')
524
Matthew Wang08e868d2018-04-19 12:04:54 -0700525 if configuration.use_bridge:
526 configuration._bridge = self.get_brif()
527
Peter Qiuc89c9a22014-02-27 10:03:55 -0800528 self.start_hostapd(configuration)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700529 interface = self.hostapd_instances[-1].interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800530 self.iw_runner.set_tx_power(interface, 'auto')
Paul Stewart65fb9212014-12-01 19:54:20 -0800531 self.set_beacon_footer(interface, configuration.beacon_footer)
Matthew Wang1667e5c2018-02-27 17:32:59 -0800532 self.start_local_server(interface, bridge=configuration.bridge)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700533 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700534
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700535
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700536 def ibss_configure(self, config):
537 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700538
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700539 Extract relevant configuration objects from |config| despite not
540 actually being a hostap managed endpoint.
541
542 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700543
544 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800545 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700546 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800547 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800548 ssid = (config.ssid or
549 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800550 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700551 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800552 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700553 # Always start a local server.
554 self.start_local_server(interface)
555 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800556 self.station_instances.append(
557 StationInstance(ssid=ssid, interface=interface,
558 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800559
560
Paul Stewart2bd823b2012-11-21 15:03:37 -0800561 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700562 """Get the local server address for an interface.
563
564 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700565 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700566
567 @param index int describing which local server this is for.
568
569 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700570 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800571
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700572
Paul Stewart6ddeba72013-11-18 10:08:23 -0800573 def local_peer_ip_address(self, index):
574 """Get the IP address allocated for the peer associated to the AP.
575
576 This address is assigned to a locally associated peer device that
577 is created for the DUT to perform connectivity tests with.
578 When we have multiple local servers, we give them static IP addresses
579 like 192.168.*.253.
580
581 @param index int describing which local server this is for.
582
583 """
584 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
585
Matthew Wang1667e5c2018-02-27 17:32:59 -0800586 def local_bridge_address(self, index):
587 """Get the bridge address for an interface.
588
589 This address is assigned to a local bridge device.
590
591 @param index int describing which local server this is for.
592
593 """
594 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800595
596 def local_peer_mac_address(self):
597 """Get the MAC address of the peer interface.
598
599 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800600
Paul Stewart6ddeba72013-11-18 10:08:23 -0800601 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800602 iface = interface.Interface(self.station_instances[0].interface,
603 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800604 return iface.mac_address
605
606
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700607 def _get_unused_server_address_index(self):
608 """@return an unused server address index."""
609 for address_index in range(0, 256):
610 if address_index not in self.server_address_index:
611 return address_index
612 raise error.TestFail('No available server address index')
613
614
615 def change_server_address_index(self, ap_num=0, server_address_index=None):
616 """Restart the local server with a different server address index.
617
618 This will restart the local server with different gateway IP address
619 and DHCP address ranges.
620
621 @param ap_num: int hostapd instance number.
622 @param server_address_index: int server address index.
623
624 """
625 interface = self.local_servers[ap_num]['interface'];
626 # Get an unused server address index if one is not specified, which
627 # will be different from the one that's currently in used.
628 if server_address_index is None:
629 server_address_index = self._get_unused_server_address_index()
630
631 # Restart local server with the new server address index.
632 self.stop_local_server(self.local_servers[ap_num])
633 self.start_local_server(interface,
634 ap_num=ap_num,
635 server_address_index=server_address_index)
636
637
638 def start_local_server(self,
639 interface,
640 ap_num=None,
Matthew Wang1667e5c2018-02-27 17:32:59 -0800641 server_address_index=None,
642 bridge=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700643 """Start a local server on an interface.
644
645 @param interface string (e.g. wlan0)
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700646 @param ap_num int the ap instance to start the server for
647 @param server_address_index int server address index
Matthew Wang1667e5c2018-02-27 17:32:59 -0800648 @param bridge string (e.g. br0)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700649
650 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700651 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700652
653 if len(self.local_servers) >= 256:
654 raise error.TestFail('Exhausted available local servers')
655
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700656 # Get an unused server address index if one is not specified.
657 # Validate server address index if one is specified.
658 if server_address_index is None:
659 server_address_index = self._get_unused_server_address_index()
660 elif server_address_index in self.server_address_index:
661 raise error.TestFail('Server address index %d already in used' %
662 server_address_index)
663
Christopher Wiley684878e2014-12-18 14:32:48 -0800664 server_addr = netblock.from_addr(
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700665 self.local_server_address(server_address_index),
Christopher Wiley594570d2014-03-14 16:50:15 -0700666 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700667
668 params = {}
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700669 params['address_index'] = server_address_index
Christopher Wiley594570d2014-03-14 16:50:15 -0700670 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700671 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700672 (server_addr.get_addr_in_block(1),
673 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700674 params['interface'] = interface
Matthew Wang1667e5c2018-02-27 17:32:59 -0800675 params['bridge'] = bridge
Christopher Wiley594570d2014-03-14 16:50:15 -0700676 params['ip_params'] = ('%s broadcast %s dev %s' %
677 (server_addr.netblock,
678 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700679 interface))
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700680 if ap_num is None:
681 self.local_servers.append(params)
682 else:
683 self.local_servers.insert(ap_num, params)
684 self.server_address_index.append(server_address_index)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700685
Christopher Wiley594570d2014-03-14 16:50:15 -0700686 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700687 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700688 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700689 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700690 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700691 (self.cmd_ip, interface))
Matthew Wang1667e5c2018-02-27 17:32:59 -0800692 if params['bridge']:
693 bridge_addr = netblock.from_addr(
694 self.local_bridge_address(server_address_index),
695 prefix_len=24)
696 self.router.run("ifconfig %s %s" %
697 (params['bridge'], bridge_addr.netblock))
Paul Stewart548cf452012-11-27 17:46:23 -0800698 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700699
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700700
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700701 def stop_local_server(self, server):
702 """Stop a local server on the router
703
704 @param server object server configuration parameters.
705
706 """
707 self.stop_dhcp_server(server['interface'])
708 self.router.run("%s addr del %s" %
709 (self.cmd_ip, server['ip_params']),
710 ignore_status=True)
711 self.server_address_index.remove(server['address_index'])
712 self.local_servers.remove(server)
713
714
Paul Stewart548cf452012-11-27 17:46:23 -0800715 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700716 """Start a dhcp server on an interface.
717
718 @param interface string (e.g. wlan0)
719
720 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800721 for server in self.local_servers:
722 if server['interface'] == interface:
723 params = server
724 break
725 else:
726 raise error.TestFail('Could not find local server '
727 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700728 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800729 dhcpd_conf_file = self.dhcpd_conf % interface
730 dhcp_conf = '\n'.join([
731 'port=0', # disables DNS server
732 'bind-interfaces',
733 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700734 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
735 server_addr.get_addr_in_block(128))),
Matthew Wang1667e5c2018-02-27 17:32:59 -0800736 'interface=%s' % (params['bridge'] or params['interface']),
Christopher Wileyeea12362013-12-12 17:24:29 -0800737 'dhcp-leasefile=%s' % self.dhcpd_leases])
738 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
739 (dhcpd_conf_file, dhcp_conf))
740 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700741
742
Paul Stewart326badb2012-12-18 14:18:54 -0800743 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700744 """Stop a dhcp server on the router.
745
746 @param instance string instance to kill.
747
748 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700749 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800750
751
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800752 def get_wifi_channel(self, ap_num):
753 """Return channel of BSS corresponding to |ap_num|.
754
755 @param ap_num int which BSS to get the channel of.
756 @return int primary channel of BSS.
757
758 """
759 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700760 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800761
762
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700763 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700764 """Return IP address on the WiFi subnet of a local server on the router.
765
766 If no local servers are configured (e.g. for an RSPro), a TestFail will
767 be raised.
768
769 @param ap_num int which local server to get an address from.
770
771 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700772 if not self.local_servers:
773 raise error.TestError('No IP address assigned')
774
775 return self.local_servers[ap_num]['netblock'].addr
776
777
778 def get_wifi_ip_subnet(self, ap_num):
779 """Return subnet of WiFi AP instance.
780
781 If no APs are configured a TestError will be raised.
782
783 @param ap_num int which local server to get an address from.
784
785 """
786 if not self.local_servers:
787 raise error.TestError('No APs configured.')
788
789 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700790
791
Christopher Wileya3effac2014-02-05 11:16:11 -0800792 def get_hostapd_interface(self, ap_num):
793 """Get the name of the interface associated with a hostapd instance.
794
795 @param ap_num: int hostapd instance number.
796 @return string interface name (e.g. 'managed0').
797
798 """
799 if ap_num not in range(len(self.hostapd_instances)):
800 raise error.TestFail('Invalid instance number (%d) with %d '
801 'instances configured.' %
802 (ap_num, len(self.hostapd_instances)))
803
804 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700805 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800806
807
Christopher Wileycc770d92015-02-02 17:37:16 -0800808 def get_station_interface(self, instance):
809 """Get the name of the interface associated with a station.
810
811 @param instance: int station instance number.
812 @return string interface name (e.g. 'managed0').
813
814 """
815 if instance not in range(len(self.station_instances)):
816 raise error.TestFail('Invalid instance number (%d) with %d '
817 'instances configured.' %
818 (instance, len(self.station_instances)))
819
820 instance = self.station_instances[instance]
821 return instance.interface
822
823
Paul Stewart17350be2012-12-14 13:34:54 -0800824 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700825 """Return the MAC address of an AP in the test.
826
827 @param ap_num int index of local server to read the MAC address from.
828 @return string MAC address like 00:11:22:33:44:55.
829
830 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800831 interface_name = self.get_hostapd_interface(ap_num)
832 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800833 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800834
835
Christopher Wileya3effac2014-02-05 11:16:11 -0800836 def get_hostapd_phy(self, ap_num):
837 """Get name of phy for hostapd instance.
838
839 @param ap_num int index of hostapd instance.
840 @return string phy name of phy corresponding to hostapd's
841 managed interface.
842
843 """
844 interface = self.iw_runner.get_interface(
845 self.get_hostapd_interface(ap_num))
846 return interface.phy
847
848
Christopher Wileyeea12362013-12-12 17:24:29 -0800849 def deconfig(self):
850 """A legacy, deprecated alias for deconfig_aps."""
851 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800852
853
854 def deconfig_aps(self, instance=None, silent=False):
855 """De-configure an AP (will also bring wlan down).
856
857 @param instance: int or None. If instance is None, will bring down all
858 instances of hostapd.
859 @param silent: True if instances should be brought without de-authing
860 the DUT.
861
862 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800863 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700864 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700865
Christopher Wileyeea12362013-12-12 17:24:29 -0800866 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800867 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800868 if instance is not None:
869 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800870 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700871 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800872 local_servers = [server]
Paul Stewart326badb2012-12-18 14:18:54 -0800873 break
Paul Stewart21737812012-12-06 11:03:32 -0800874 else:
875 instances = self.hostapd_instances
876 self.hostapd_instances = []
Peter Qiu79784512015-04-14 22:59:55 -0700877 local_servers = copy.copy(self.local_servers)
Paul Stewart64cc4292011-06-01 10:59:36 -0700878
Paul Stewart21737812012-12-06 11:03:32 -0800879 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800880 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800881 # Deconfigure without notifying DUT. Remove the interface
882 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700883 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800884
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700885 self.kill_hostapd_instance(instance)
886 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800887 if self.station_instances:
Peter Qiu79784512015-04-14 22:59:55 -0700888 local_servers = copy.copy(self.local_servers)
Christopher Wiley408d1812014-01-13 15:27:43 -0800889 instance = self.station_instances.pop()
890 if instance.dev_type == 'ibss':
891 self.iw_runner.ibss_leave(instance.interface)
892 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700893 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700894 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800895 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800896 self.iw_runner.disconnect_station(instance.interface)
897 self.router.run('%s link set %s down' %
898 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700899
Paul Stewart326badb2012-12-18 14:18:54 -0800900 for server in local_servers:
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700901 self.stop_local_server(server)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700902
Matthew Wang08e868d2018-04-19 12:04:54 -0700903 for brif in range(self._brif_index):
904 self.delete_link('%s%d' %
905 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif))
906
907
908 def delete_link(self, name):
909 """Delete link using the `ip` command.
910
911 @param name string link name.
912
913 """
914 self.host.run('%s link del %s' % (self.cmd_ip, name),
915 ignore_status=True)
916
Paul Stewart7cb1f062010-06-10 15:46:20 -0700917
Peter Qiu4bfb1e92015-03-12 16:43:28 -0700918 def set_ap_interface_down(self, instance=0):
919 """Bring down the hostapd interface.
920
921 @param instance int router instance number.
922
923 """
924 self.host.run('%s link set %s down' %
925 (self.cmd_ip, self.get_hostapd_interface(instance)))
926
927
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800928 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700929 """Verify that the PMKSA auth was cached on a hostapd instance.
930
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800931 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700932
933 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700934 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800935 pmksa_match = 'PMK from PMKSA cache'
936 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
937 ignore_status=True)
938 if result.exit_status:
939 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800940
941
Christopher Wileye0afecb2013-11-11 10:54:23 -0800942 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700943 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800944 if instance is None:
945 instance = 0
946 if len(self.hostapd_instances) > 1:
947 raise error.TestFail('No instance of hostapd specified with '
948 'multiple instances present.')
949
Christopher Wiley3099be72013-11-06 16:49:02 -0800950 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700951 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700952
Christopher Wiley408d1812014-01-13 15:27:43 -0800953 if self.station_instances:
954 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700955
Christopher Wiley408d1812014-01-13 15:27:43 -0800956 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700957
958
Wade Guthriee4074dd2013-10-30 11:00:48 -0700959 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700960 """Deauthenticates a client described in params.
961
Wade Guthriee4074dd2013-10-30 11:00:48 -0700962 @param client_mac string containing the mac address of the client to be
963 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700964
965 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700966 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700967 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800968 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700969
Matthew Wangab74f8d2018-01-18 16:29:35 -0800970 def send_bss_tm_req(self, client_mac, neighbor_list):
971 """Send a BSS Transition Management Request to a client.
972
973 @param client_mac string containing the mac address of the client.
974 @param neighbor_list list of strings containing mac addresses of
975 candidate APs.
Matthew Wang6886f282018-06-05 14:42:20 -0700976 @return bool True if BSS_TM_REQ is sent successfully.
Matthew Wangab74f8d2018-01-18 16:29:35 -0800977
978 """
979 control_if = self.hostapd_instances[0].config_dict['ctrl_interface']
Matthew Wang6886f282018-06-05 14:42:20 -0700980 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' %
981 (self.cmd_hostapd_cli, control_if, client_mac,
982 ',0,0,0,0 neighbor='.join(neighbor_list)))
983 ret = self.router.run(command).stdout
984 if ret.splitlines()[-1] != 'OK':
985 return False
986 return True
Wade Guthriee4074dd2013-10-30 11:00:48 -0700987
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700988 def _prep_probe_response_footer(self, footer):
989 """Write probe response footer temporarily to a local file and copy
990 over to test router.
991
992 @param footer string containing bytes for the probe response footer.
993 @raises AutoservRunError: If footer file copy fails.
994
995 """
mukesh agrawale2102b82015-07-17 11:16:30 -0700996 with tempfile.NamedTemporaryFile() as fp:
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700997 fp.write(footer)
mukesh agrawal41a817c2015-07-22 10:00:43 -0700998 fp.flush()
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700999 try:
mukesh agrawale2102b82015-07-17 11:16:30 -07001000 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE)
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001001 except error.AutoservRunError:
1002 logging.error('failed to copy footer file to AP')
1003 raise
1004
mukesh agrawale2102b82015-07-17 11:16:30 -07001005
Peter Qiuc4beba02014-03-24 14:46:24 -07001006 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -07001007 """Injects a management frame into an active hostapd session.
1008
1009 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -07001010 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -07001011 @param instance int indicating which hostapd instance to inject into.
1012
1013 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001014 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001015 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001016 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -07001017 self.router.run('%s -i %s -t %s -c %d' %
1018 (self.cmd_send_management_frame, interface, frame_type,
1019 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001020 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001021
1022
Peter Qiuc4beba02014-03-24 14:46:24 -07001023 def send_management_frame(self, interface, frame_type, channel,
1024 ssid_prefix=None, num_bss=None,
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001025 frame_count=None, delay=None,
1026 dest_addr=None, probe_resp_footer=None):
Peter Qiuc4beba02014-03-24 14:46:24 -07001027 """
1028 Injects management frames on specify channel |frequency|.
1029
1030 This function will spawn off a new process to inject specified
1031 management frames |frame_type| at the specified interface |interface|.
1032
1033 @param interface string interface to inject frames.
1034 @param frame_type string message type.
1035 @param channel int targeted channel.
1036 @param ssid_prefix string SSID prefix.
1037 @param num_bss int number of BSS.
1038 @param frame_count int number of frames to send.
1039 @param delay int milliseconds delay between frames.
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001040 @param dest_addr string destination address (DA) MAC address.
1041 @param probe_resp_footer string footer for probe response.
Peter Qiuc4beba02014-03-24 14:46:24 -07001042
1043 @return int PID of the newly created process.
1044
1045 """
1046 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
1047 interface, frame_type, channel)
1048 if ssid_prefix is not None:
1049 command += ' -s %s' % (ssid_prefix)
1050 if num_bss is not None:
1051 command += ' -b %d' % (num_bss)
1052 if frame_count is not None:
1053 command += ' -n %d' % (frame_count)
1054 if delay is not None:
1055 command += ' -d %d' % (delay)
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001056 if dest_addr is not None:
1057 command += ' -a %s' % (dest_addr)
1058 if probe_resp_footer is not None:
1059 self._prep_probe_response_footer(footer=probe_resp_footer)
1060 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
Peter Qiuc4beba02014-03-24 14:46:24 -07001061 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
1062 pid = int(self.router.run(command).stdout)
1063 return pid
1064
1065
Paul Stewart25536942013-08-15 17:33:42 -07001066 def detect_client_deauth(self, client_mac, instance=0):
1067 """Detects whether hostapd has logged a deauthentication from
1068 |client_mac|.
1069
1070 @param client_mac string the MAC address of the client to detect.
1071 @param instance int indicating which hostapd instance to query.
1072
1073 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001074 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -07001075 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001076 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -07001077 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
1078 ignore_status=True)
1079 return result.exit_status == 0
1080
1081
Paul Stewart4ae471e2013-09-04 15:42:35 -07001082 def detect_client_coexistence_report(self, client_mac, instance=0):
1083 """Detects whether hostapd has logged an action frame from
1084 |client_mac| indicating information about 20/40MHz BSS coexistence.
1085
1086 @param client_mac string the MAC address of the client to detect.
1087 @param instance int indicating which hostapd instance to query.
1088
1089 """
1090 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
1091 '.. .. .. .. .. .. .. .. .. .. %s '
1092 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
1093 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001094 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -07001095 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
1096 ignore_status=True)
1097 return result.exit_status == 0
1098
1099
Paul Stewart6ddeba72013-11-18 10:08:23 -08001100 def add_connected_peer(self, instance=0):
1101 """Configure a station connected to a running AP instance.
1102
1103 Extract relevant configuration objects from the hostap
1104 configuration for |instance| and generate a wpa_supplicant
1105 instance that connects to it. This allows the DUT to interact
1106 with a client entity that is also connected to the same AP. A
1107 full wpa_supplicant instance is necessary here (instead of just
1108 using the "iw" command to connect) since we want to enable
1109 advanced features such as TDLS.
1110
1111 @param instance int indicating which hostapd instance to connect to.
1112
1113 """
1114 if not self.hostapd_instances:
1115 raise error.TestFail('Hostapd is not configured.')
1116
Christopher Wiley408d1812014-01-13 15:27:43 -08001117 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -08001118 raise error.TestFail('Station is already configured.')
1119
Christopher Wiley408d1812014-01-13 15:27:43 -08001120 ssid = self.get_ssid(instance)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001121 hostap_conf = self.hostapd_instances[instance].config_dict
Paul Stewart6ddeba72013-11-18 10:08:23 -08001122 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
1123 hostap_conf['channel'])
Christopher Wileyab3964f2015-01-26 12:59:12 -08001124 self.configure_managed_station(
1125 ssid, frequency, self.local_peer_ip_address(instance))
1126 interface = self.station_instances[0].interface
1127 # Since we now have two network interfaces connected to the same
1128 # network, we need to disable the kernel's protection against
1129 # incoming packets to an "unexpected" interface.
1130 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
1131 interface)
1132
1133 # Similarly, we'd like to prevent the hostap interface from
1134 # replying to ARP requests for the peer IP address and vice
1135 # versa.
1136 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1137 interface)
1138 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1139 hostap_conf['interface'])
1140
1141
1142 def configure_managed_station(self, ssid, frequency, ip_addr):
1143 """Configure a router interface to connect as a client to a network.
1144
1145 @param ssid: string SSID of network to join.
1146 @param frequency: int frequency required to join the network.
1147 @param ip_addr: IP address to assign to this interface
1148 (e.g. '192.168.1.200').
1149
1150 """
Christopher Wiley408d1812014-01-13 15:27:43 -08001151 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -08001152
1153 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
1154 # require them...
1155 supplicant_config = (
1156 'network={\n'
1157 ' ssid="%(ssid)s"\n'
1158 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -08001159 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -08001160 )
1161
Christopher Wileyeea12362013-12-12 17:24:29 -08001162 conf_file = self.STATION_CONF_FILE_PATTERN % interface
1163 log_file = self.STATION_LOG_FILE_PATTERN % interface
1164 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -08001165
1166 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
1167 (conf_file, supplicant_config))
1168
1169 # Connect the station.
1170 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Ben Chan630d6b42015-02-13 18:14:45 -08001171 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s >%s 2>&1 &' %
Paul Stewart6ddeba72013-11-18 10:08:23 -08001172 (self.cmd_wpa_supplicant,
1173 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -08001174 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -08001175 self.router.run(start_command)
1176 self.iw_runner.wait_for_link(interface)
1177
1178 # Assign an IP address to this interface.
1179 self.router.run('%s addr add %s/24 dev %s' %
Christopher Wileyab3964f2015-01-26 12:59:12 -08001180 (self.cmd_ip, ip_addr, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -08001181 self.station_instances.append(
1182 StationInstance(ssid=ssid, interface=interface,
1183 dev_type='managed'))
Eric Carusoeddedd32014-10-13 14:41:49 -07001184
1185
1186 def send_magic_packet(self, dest_ip, dest_mac):
1187 """Sends a magic packet to the NIC with the given IP and MAC addresses.
1188
1189 @param dest_ip the IP address of the device to send the packet to
1190 @param dest_mac the hardware MAC address of the device
1191
1192 """
1193 # magic packet is 6 0xff bytes followed by the hardware address
1194 # 16 times
1195 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
1196 magic_packet = '\xff' * 6 + mac_bytes * 16
1197
1198 logging.info('Sending magic packet to %s...', dest_ip)
1199 self.host.run('python -uc "import socket, sys;'
1200 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
1201 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
1202 (dest_ip, UDP_DISCARD_PORT),
1203 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -08001204
1205
1206 def set_beacon_footer(self, interface, footer=''):
1207 """Sets the beacon footer (appended IE information) for this interface.
1208
1209 @param interface string interface to set the footer on.
1210 @param footer string footer to be set on the interface.
1211
1212 """
1213 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' %
1214 self.iw_runner.get_interface(interface).phy)
1215 if self.router.run('test -e %s' % footer_file,
1216 ignore_status=True).exit_status != 0:
1217 logging.info('Beacon footer file does not exist. Ignoring.')
1218 return
1219 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file))
Peter Qiu9a63a8b2015-02-03 09:08:16 -08001220
1221
1222 def setup_bridge_mode_dhcp_server(self):
1223 """Setup an DHCP server for bridge mode.
1224
1225 Setup an DHCP server on the master interface of the virtual ethernet
1226 pair, with peer interface connected to the bridge interface. This is
1227 used for testing APs in bridge mode.
1228
1229 """
1230 # Start a local server on master interface of virtual ethernet pair.
1231 self.start_local_server(
1232 self.get_virtual_ethernet_master_interface())
1233 # Add peer interface to the bridge.
1234 self.add_interface_to_bridge(
Ben Chan630d6b42015-02-13 18:14:45 -08001235 self.get_virtual_ethernet_peer_interface())