blob: ae3ed05333b2a86ef21b053d0e9061df71bbe629 [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
Brian Norris9494c0f2019-12-11 16:29:55 -08008import os
Christopher Wiley3166e432013-08-06 09:53:12 -07009import random
Christopher Wiley3166e432013-08-06 09:53:12 -070010import string
Rebecca Silberstein194b4582015-06-17 13:29:38 -070011import tempfile
Christopher Wileyeea12362013-12-12 17:24:29 -080012import time
Christopher Wiley14796b32013-04-03 14:53:33 -070013
Paul Stewartc9628b32010-08-11 13:03:51 -070014from autotest_lib.client.common_lib import error
Alex Khouderchahb0c624c2018-06-12 11:00:14 -070015from autotest_lib.client.common_lib import utils
Christopher Wiley36039222014-12-13 18:27:52 -080016from autotest_lib.client.common_lib.cros import path_utils
Paul Stewart6ddeba72013-11-18 10:08:23 -080017from autotest_lib.client.common_lib.cros.network import interface
Christopher Wiley594570d2014-03-14 16:50:15 -070018from autotest_lib.client.common_lib.cros.network import netblock
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070019from autotest_lib.client.common_lib.cros.network import ping_runner
20from autotest_lib.server import hosts
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070021from autotest_lib.server import site_linux_system
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080022from autotest_lib.server.cros import dnsname_mangler
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
Brian Norris9494c0f2019-12-11 16:29:55 -0800105 HOSTAPD_CONF_FILE_PATTERN = 'hostapd-test-%s.conf'
106 HOSTAPD_LOG_FILE_PATTERN = 'hostapd-test-%s.log'
107 HOSTAPD_STDERR_LOG_FILE_PATTERN = 'hostapd-stderr-test-%s.log'
108 HOSTAPD_CONTROL_INTERFACE_PATTERN = 'hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -0800109 HOSTAPD_DRIVER_NAME = 'nl80211'
110
Brian Norris9494c0f2019-12-11 16:29:55 -0800111 MGMT_FRAME_SENDER_LOG_FILE = 'send_management_frame-test.log'
Peter Qiuc4beba02014-03-24 14:46:24 -0700112
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700113 PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer'
114
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700115 _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available'
116 _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current'
117
Paul Stewart51b0f382013-06-12 09:03:02 -0700118 def get_capabilities(self):
119 """@return iterable object of AP capabilities for this system."""
mukesh agrawal064ea972015-06-26 10:00:53 -0700120 caps = set()
Paul Stewart51b0f382013-06-12 09:03:02 -0700121 try:
Christopher Wiley36039222014-12-13 18:27:52 -0800122 self.cmd_send_management_frame = path_utils.must_be_installed(
123 '/usr/bin/send_management_frame', host=self.host)
Paul Stewart51b0f382013-06-12 09:03:02 -0700124 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
125 except error.TestFail:
126 pass
127 return super(LinuxRouter, self).get_capabilities().union(caps)
128
129
Christopher Wiley408d1812014-01-13 15:27:43 -0800130 @property
131 def router(self):
132 """Deprecated. Use self.host instead.
133
134 @return Host object representing the remote router.
135
136 """
137 return self.host
138
139
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700140 @property
141 def wifi_ip(self):
142 """Simple accessor for the WiFi IP when there is only one AP.
143
144 @return string IP of WiFi interface.
145
146 """
147 if len(self.local_servers) != 1:
148 raise error.TestError('Could not pick a WiFi IP to return.')
149
150 return self.get_wifi_ip(0)
151
152
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700153 def __init__(self, host, test_name, enable_avahi=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700154 """Build a LinuxRouter.
155
156 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -0700157 @param test_name string name of this test. Used in SSID creation.
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700158 @param enable_avahi: boolean True iff avahi should be started on the
159 router.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700160
161 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800162 super(LinuxRouter, self).__init__(host, 'router')
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700163 self._ssid_prefix = test_name
164 self._enable_avahi = enable_avahi
165 self.__setup()
Wade Guthrie24d1e312012-04-24 16:53:40 -0700166
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700167
168 def __setup(self):
169 """Set up this system.
170
171 Can be used either to complete initialization of a LinuxRouter
172 object, or to re-establish a good state after a reboot.
173
174 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800175 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wiley36039222014-12-13 18:27:52 -0800176 self.cmd_hostapd = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700177 '/usr/sbin/hostapd', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800178 self.cmd_hostapd_cli = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700179 '/usr/sbin/hostapd_cli', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800180 self.cmd_wpa_supplicant = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700181 '/usr/sbin/wpa_supplicant', host=self.host)
Brian Norris9494c0f2019-12-11 16:29:55 -0800182 self.dhcpd_conf = os.path.join(self.logdir, 'dhcpd.%s.conf')
183 self.dhcpd_leases = os.path.join(self.logdir, 'dhcpd.leases')
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700184
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700185 # Log the most recent message on the router so that we can rebuild the
186 # suffix relevant to us when debugging failures.
Brian Norrise53f1692018-09-28 10:46:56 -0700187 last_log_line = self.host.run('tail -1 /var/log/messages',
188 ignore_status=True).stdout
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700189 # We're trying to get the timestamp from:
190 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah
Brian Norris2d8f2972018-09-26 14:55:38 -0700191 self._log_start_timestamp = last_log_line.strip().partition(' ')[0]
192 if self._log_start_timestamp:
193 logging.debug('Will only retrieve logs after %s.',
194 self._log_start_timestamp)
195 else:
196 # If syslog is empty, we just use a wildcard pattern, to grab
197 # everything.
198 logging.debug('Empty or corrupt log; will retrieve whole log')
199 self._log_start_timestamp = '.'
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700200
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700201 # hostapd configuration persists throughout the test, subsequent
202 # 'config' commands only modify it.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700203 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700204 # Many of our tests start with an uninteresting prefix.
205 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700206 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
207 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700208
Christopher Wileyeea12362013-12-12 17:24:29 -0800209 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700210 self.local_servers = []
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700211 self.server_address_index = []
Paul Stewart548cf452012-11-27 17:46:23 -0800212 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800213 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700214 self.dhcp_low = 1
215 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700216
Brian Norris7b09bde2019-05-08 13:02:56 -0700217 # Tear down hostapbr bridge and intermediate functional block
218 # interfaces.
219 result = self.host.run('ls -d /sys/class/net/%s* /sys/class/net/%s*'
220 ' 2>/dev/null' %
221 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
222 self.IFB_INTERFACE_PREFIX),
Matthew Wang08e868d2018-04-19 12:04:54 -0700223 ignore_status=True)
Brian Norris7b09bde2019-05-08 13:02:56 -0700224 for path in result.stdout.splitlines():
225 self.delete_link(path.split('/')[-1])
Matthew Wang08e868d2018-04-19 12:04:54 -0700226
Paul Stewart548cf452012-11-27 17:46:23 -0800227 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700228 self._kill_process_instance('hostapd', timeout_seconds=30)
229 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700230
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700231 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700232 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700233
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800234 self.enable_all_antennas()
Peter Qiu2f973252014-02-20 15:30:37 -0800235
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700236 # Some tests want this functionality, but otherwise, it's a distraction.
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700237 if self._enable_avahi:
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700238 self.host.run('start avahi', ignore_status=True)
239 else:
240 self.host.run('stop avahi', ignore_status=True)
241
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700242 # Some routers have bad (slow?) random number generators.
243 self.rng_configure()
244
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700245
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700246 def close(self):
247 """Close global resources held by this system."""
Brian Norris9494c0f2019-12-11 16:29:55 -0800248 router_log = os.path.join(self.logdir, 'router_log')
Christopher Wileyeea12362013-12-12 17:24:29 -0800249 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700250 # dnsmasq and hostapd cause interesting events to go to system logs.
251 # Retrieve only the suffix of the logs after the timestamp we stored on
252 # router creation.
Brian Norris9494c0f2019-12-11 16:29:55 -0800253 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >%s" %
254 (self._log_start_timestamp, router_log),
255 ignore_status=True)
256 self.host.get_file(router_log, 'debug/router_host_messages')
Christopher Wiley408d1812014-01-13 15:27:43 -0800257 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700258
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700259
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700260 def reboot(self, timeout):
261 """Reboot this router, and restore it to a known-good state.
262
263 @param timeout Maximum seconds to wait for router to return.
264
265 """
266 super(LinuxRouter, self).reboot(timeout)
267 self.__setup()
268
269
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700270 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700271 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700272 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700273
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700274
Peter Qiuc89c9a22014-02-27 10:03:55 -0800275 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700276 """Start a hostapd instance described by conf.
277
Christopher Wileyeea12362013-12-12 17:24:29 -0800278 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700279
280 """
Paul Stewart548cf452012-11-27 17:46:23 -0800281 # Figure out the correct interface.
Brian Norris062390f2018-08-03 18:09:43 -0700282 interface = self.get_wlanif(configuration.frequency, 'managed',
283 configuration.min_streams)
Edward Hill7d411222017-10-02 17:26:56 -0600284 phy_name = self.iw_runner.get_interface(interface).phy
Paul Stewart326badb2012-12-18 14:18:54 -0800285
Brian Norris9494c0f2019-12-11 16:29:55 -0800286 conf_file = os.path.join(self.logdir,
287 self.HOSTAPD_CONF_FILE_PATTERN % interface)
288 log_file = os.path.join(self.logdir,
289 self.HOSTAPD_LOG_FILE_PATTERN % interface)
290 stderr_log_file = os.path.join(self.logdir,
291 self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface)
292 control_interface = os.path.join(self.logdir,
293 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')
Matthew Wang1667e5c2018-02-27 17:32:59 -0800531 self.start_local_server(interface, bridge=configuration.bridge)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700532 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700533
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700534
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700535 def ibss_configure(self, config):
536 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700537
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700538 Extract relevant configuration objects from |config| despite not
539 actually being a hostap managed endpoint.
540
541 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700542
543 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800544 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700545 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800546 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800547 ssid = (config.ssid or
548 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800549 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700550 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800551 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700552 # Always start a local server.
553 self.start_local_server(interface)
554 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800555 self.station_instances.append(
556 StationInstance(ssid=ssid, interface=interface,
557 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800558
559
Paul Stewart2bd823b2012-11-21 15:03:37 -0800560 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700561 """Get the local server address for an interface.
562
563 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700564 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700565
566 @param index int describing which local server this is for.
567
568 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700569 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800570
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700571
Paul Stewart6ddeba72013-11-18 10:08:23 -0800572 def local_peer_ip_address(self, index):
573 """Get the IP address allocated for the peer associated to the AP.
574
575 This address is assigned to a locally associated peer device that
576 is created for the DUT to perform connectivity tests with.
577 When we have multiple local servers, we give them static IP addresses
578 like 192.168.*.253.
579
580 @param index int describing which local server this is for.
581
582 """
583 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
584
Matthew Wang1667e5c2018-02-27 17:32:59 -0800585 def local_bridge_address(self, index):
586 """Get the bridge address for an interface.
587
588 This address is assigned to a local bridge device.
589
590 @param index int describing which local server this is for.
591
592 """
593 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800594
595 def local_peer_mac_address(self):
596 """Get the MAC address of the peer interface.
597
598 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800599
Paul Stewart6ddeba72013-11-18 10:08:23 -0800600 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800601 iface = interface.Interface(self.station_instances[0].interface,
602 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800603 return iface.mac_address
604
605
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700606 def _get_unused_server_address_index(self):
607 """@return an unused server address index."""
608 for address_index in range(0, 256):
609 if address_index not in self.server_address_index:
610 return address_index
611 raise error.TestFail('No available server address index')
612
613
614 def change_server_address_index(self, ap_num=0, server_address_index=None):
615 """Restart the local server with a different server address index.
616
617 This will restart the local server with different gateway IP address
618 and DHCP address ranges.
619
620 @param ap_num: int hostapd instance number.
621 @param server_address_index: int server address index.
622
623 """
624 interface = self.local_servers[ap_num]['interface'];
625 # Get an unused server address index if one is not specified, which
626 # will be different from the one that's currently in used.
627 if server_address_index is None:
628 server_address_index = self._get_unused_server_address_index()
629
630 # Restart local server with the new server address index.
631 self.stop_local_server(self.local_servers[ap_num])
632 self.start_local_server(interface,
633 ap_num=ap_num,
634 server_address_index=server_address_index)
635
636
637 def start_local_server(self,
638 interface,
639 ap_num=None,
Matthew Wang1667e5c2018-02-27 17:32:59 -0800640 server_address_index=None,
641 bridge=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700642 """Start a local server on an interface.
643
644 @param interface string (e.g. wlan0)
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700645 @param ap_num int the ap instance to start the server for
646 @param server_address_index int server address index
Matthew Wang1667e5c2018-02-27 17:32:59 -0800647 @param bridge string (e.g. br0)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700648
649 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700650 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700651
652 if len(self.local_servers) >= 256:
653 raise error.TestFail('Exhausted available local servers')
654
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700655 # Get an unused server address index if one is not specified.
656 # Validate server address index if one is specified.
657 if server_address_index is None:
658 server_address_index = self._get_unused_server_address_index()
659 elif server_address_index in self.server_address_index:
660 raise error.TestFail('Server address index %d already in used' %
661 server_address_index)
662
Christopher Wiley684878e2014-12-18 14:32:48 -0800663 server_addr = netblock.from_addr(
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700664 self.local_server_address(server_address_index),
Christopher Wiley594570d2014-03-14 16:50:15 -0700665 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700666
667 params = {}
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700668 params['address_index'] = server_address_index
Christopher Wiley594570d2014-03-14 16:50:15 -0700669 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700670 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700671 (server_addr.get_addr_in_block(1),
672 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700673 params['interface'] = interface
Matthew Wang1667e5c2018-02-27 17:32:59 -0800674 params['bridge'] = bridge
Christopher Wiley594570d2014-03-14 16:50:15 -0700675 params['ip_params'] = ('%s broadcast %s dev %s' %
676 (server_addr.netblock,
677 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700678 interface))
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700679 if ap_num is None:
680 self.local_servers.append(params)
681 else:
682 self.local_servers.insert(ap_num, params)
683 self.server_address_index.append(server_address_index)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700684
Christopher Wiley594570d2014-03-14 16:50:15 -0700685 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700686 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700687 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700688 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700689 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700690 (self.cmd_ip, interface))
Matthew Wang1667e5c2018-02-27 17:32:59 -0800691 if params['bridge']:
692 bridge_addr = netblock.from_addr(
693 self.local_bridge_address(server_address_index),
694 prefix_len=24)
695 self.router.run("ifconfig %s %s" %
696 (params['bridge'], bridge_addr.netblock))
Paul Stewart548cf452012-11-27 17:46:23 -0800697 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700698
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700699
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700700 def stop_local_server(self, server):
701 """Stop a local server on the router
702
703 @param server object server configuration parameters.
704
705 """
706 self.stop_dhcp_server(server['interface'])
707 self.router.run("%s addr del %s" %
708 (self.cmd_ip, server['ip_params']),
709 ignore_status=True)
710 self.server_address_index.remove(server['address_index'])
711 self.local_servers.remove(server)
712
713
Paul Stewart548cf452012-11-27 17:46:23 -0800714 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700715 """Start a dhcp server on an interface.
716
717 @param interface string (e.g. wlan0)
718
719 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800720 for server in self.local_servers:
721 if server['interface'] == interface:
722 params = server
723 break
724 else:
725 raise error.TestFail('Could not find local server '
726 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700727 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800728 dhcpd_conf_file = self.dhcpd_conf % interface
729 dhcp_conf = '\n'.join([
730 'port=0', # disables DNS server
731 'bind-interfaces',
732 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700733 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
734 server_addr.get_addr_in_block(128))),
Matthew Wang1667e5c2018-02-27 17:32:59 -0800735 'interface=%s' % (params['bridge'] or params['interface']),
Christopher Wileyeea12362013-12-12 17:24:29 -0800736 'dhcp-leasefile=%s' % self.dhcpd_leases])
737 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
738 (dhcpd_conf_file, dhcp_conf))
739 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700740
741
Paul Stewart326badb2012-12-18 14:18:54 -0800742 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700743 """Stop a dhcp server on the router.
744
745 @param instance string instance to kill.
746
747 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700748 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800749
750
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800751 def get_wifi_channel(self, ap_num):
752 """Return channel of BSS corresponding to |ap_num|.
753
754 @param ap_num int which BSS to get the channel of.
755 @return int primary channel of BSS.
756
757 """
758 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700759 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800760
761
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700762 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700763 """Return IP address on the WiFi subnet of a local server on the router.
764
765 If no local servers are configured (e.g. for an RSPro), a TestFail will
766 be raised.
767
768 @param ap_num int which local server to get an address from.
769
770 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700771 if not self.local_servers:
772 raise error.TestError('No IP address assigned')
773
774 return self.local_servers[ap_num]['netblock'].addr
775
776
777 def get_wifi_ip_subnet(self, ap_num):
778 """Return subnet of WiFi AP instance.
779
780 If no APs are configured a TestError will be raised.
781
782 @param ap_num int which local server to get an address from.
783
784 """
785 if not self.local_servers:
786 raise error.TestError('No APs configured.')
787
788 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700789
790
Christopher Wileya3effac2014-02-05 11:16:11 -0800791 def get_hostapd_interface(self, ap_num):
792 """Get the name of the interface associated with a hostapd instance.
793
794 @param ap_num: int hostapd instance number.
795 @return string interface name (e.g. 'managed0').
796
797 """
798 if ap_num not in range(len(self.hostapd_instances)):
799 raise error.TestFail('Invalid instance number (%d) with %d '
800 'instances configured.' %
801 (ap_num, len(self.hostapd_instances)))
802
803 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700804 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800805
806
Christopher Wileycc770d92015-02-02 17:37:16 -0800807 def get_station_interface(self, instance):
808 """Get the name of the interface associated with a station.
809
810 @param instance: int station instance number.
811 @return string interface name (e.g. 'managed0').
812
813 """
814 if instance not in range(len(self.station_instances)):
815 raise error.TestFail('Invalid instance number (%d) with %d '
816 'instances configured.' %
817 (instance, len(self.station_instances)))
818
819 instance = self.station_instances[instance]
820 return instance.interface
821
822
Paul Stewart17350be2012-12-14 13:34:54 -0800823 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700824 """Return the MAC address of an AP in the test.
825
826 @param ap_num int index of local server to read the MAC address from.
827 @return string MAC address like 00:11:22:33:44:55.
828
829 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800830 interface_name = self.get_hostapd_interface(ap_num)
831 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800832 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800833
834
Christopher Wileya3effac2014-02-05 11:16:11 -0800835 def get_hostapd_phy(self, ap_num):
836 """Get name of phy for hostapd instance.
837
838 @param ap_num int index of hostapd instance.
839 @return string phy name of phy corresponding to hostapd's
840 managed interface.
841
842 """
843 interface = self.iw_runner.get_interface(
844 self.get_hostapd_interface(ap_num))
845 return interface.phy
846
847
Christopher Wileyeea12362013-12-12 17:24:29 -0800848 def deconfig(self):
849 """A legacy, deprecated alias for deconfig_aps."""
850 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800851
852
853 def deconfig_aps(self, instance=None, silent=False):
854 """De-configure an AP (will also bring wlan down).
855
856 @param instance: int or None. If instance is None, will bring down all
857 instances of hostapd.
858 @param silent: True if instances should be brought without de-authing
859 the DUT.
860
861 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800862 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700863 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700864
Christopher Wileyeea12362013-12-12 17:24:29 -0800865 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800866 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800867 if instance is not None:
868 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800869 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700870 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800871 local_servers = [server]
Paul Stewart326badb2012-12-18 14:18:54 -0800872 break
Paul Stewart21737812012-12-06 11:03:32 -0800873 else:
874 instances = self.hostapd_instances
875 self.hostapd_instances = []
Peter Qiu79784512015-04-14 22:59:55 -0700876 local_servers = copy.copy(self.local_servers)
Paul Stewart64cc4292011-06-01 10:59:36 -0700877
Paul Stewart21737812012-12-06 11:03:32 -0800878 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800879 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800880 # Deconfigure without notifying DUT. Remove the interface
881 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700882 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800883
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700884 self.kill_hostapd_instance(instance)
885 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800886 if self.station_instances:
Peter Qiu79784512015-04-14 22:59:55 -0700887 local_servers = copy.copy(self.local_servers)
Christopher Wiley408d1812014-01-13 15:27:43 -0800888 instance = self.station_instances.pop()
889 if instance.dev_type == 'ibss':
890 self.iw_runner.ibss_leave(instance.interface)
891 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700892 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700893 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800894 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800895 self.iw_runner.disconnect_station(instance.interface)
896 self.router.run('%s link set %s down' %
897 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700898
Paul Stewart326badb2012-12-18 14:18:54 -0800899 for server in local_servers:
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700900 self.stop_local_server(server)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700901
Matthew Wang08e868d2018-04-19 12:04:54 -0700902 for brif in range(self._brif_index):
903 self.delete_link('%s%d' %
904 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif))
905
906
907 def delete_link(self, name):
908 """Delete link using the `ip` command.
909
910 @param name string link name.
911
912 """
913 self.host.run('%s link del %s' % (self.cmd_ip, name),
914 ignore_status=True)
915
Paul Stewart7cb1f062010-06-10 15:46:20 -0700916
Peter Qiu4bfb1e92015-03-12 16:43:28 -0700917 def set_ap_interface_down(self, instance=0):
918 """Bring down the hostapd interface.
919
920 @param instance int router instance number.
921
922 """
923 self.host.run('%s link set %s down' %
924 (self.cmd_ip, self.get_hostapd_interface(instance)))
925
926
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800927 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700928 """Verify that the PMKSA auth was cached on a hostapd instance.
929
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800930 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700931
932 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700933 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800934 pmksa_match = 'PMK from PMKSA cache'
935 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
936 ignore_status=True)
937 if result.exit_status:
938 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800939
940
Christopher Wileye0afecb2013-11-11 10:54:23 -0800941 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700942 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800943 if instance is None:
944 instance = 0
945 if len(self.hostapd_instances) > 1:
946 raise error.TestFail('No instance of hostapd specified with '
947 'multiple instances present.')
948
Christopher Wiley3099be72013-11-06 16:49:02 -0800949 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700950 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700951
Christopher Wiley408d1812014-01-13 15:27:43 -0800952 if self.station_instances:
953 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700954
Christopher Wiley408d1812014-01-13 15:27:43 -0800955 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700956
957
Wade Guthriee4074dd2013-10-30 11:00:48 -0700958 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700959 """Deauthenticates a client described in params.
960
Wade Guthriee4074dd2013-10-30 11:00:48 -0700961 @param client_mac string containing the mac address of the client to be
962 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700963
964 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700965 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700966 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800967 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700968
Matthew Wangab74f8d2018-01-18 16:29:35 -0800969 def send_bss_tm_req(self, client_mac, neighbor_list):
970 """Send a BSS Transition Management Request to a client.
971
972 @param client_mac string containing the mac address of the client.
973 @param neighbor_list list of strings containing mac addresses of
974 candidate APs.
Matthew Wang91660ed2019-04-03 15:38:01 -0700975 @return string reply received from command
Matthew Wangab74f8d2018-01-18 16:29:35 -0800976
977 """
978 control_if = self.hostapd_instances[0].config_dict['ctrl_interface']
Matthew Wang6886f282018-06-05 14:42:20 -0700979 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' %
980 (self.cmd_hostapd_cli, control_if, client_mac,
981 ',0,0,0,0 neighbor='.join(neighbor_list)))
982 ret = self.router.run(command).stdout
Matthew Wang91660ed2019-04-03 15:38:01 -0700983 return ret.splitlines()[-1]
Wade Guthriee4074dd2013-10-30 11:00:48 -0700984
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700985 def _prep_probe_response_footer(self, footer):
986 """Write probe response footer temporarily to a local file and copy
987 over to test router.
988
989 @param footer string containing bytes for the probe response footer.
990 @raises AutoservRunError: If footer file copy fails.
991
992 """
mukesh agrawale2102b82015-07-17 11:16:30 -0700993 with tempfile.NamedTemporaryFile() as fp:
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700994 fp.write(footer)
mukesh agrawal41a817c2015-07-22 10:00:43 -0700995 fp.flush()
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700996 try:
mukesh agrawale2102b82015-07-17 11:16:30 -0700997 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE)
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700998 except error.AutoservRunError:
999 logging.error('failed to copy footer file to AP')
1000 raise
1001
mukesh agrawale2102b82015-07-17 11:16:30 -07001002
Peter Qiuc4beba02014-03-24 14:46:24 -07001003 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -07001004 """Injects a management frame into an active hostapd session.
1005
1006 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -07001007 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -07001008 @param instance int indicating which hostapd instance to inject into.
1009
1010 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001011 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001012 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001013 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -07001014 self.router.run('%s -i %s -t %s -c %d' %
1015 (self.cmd_send_management_frame, interface, frame_type,
1016 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001017 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001018
1019
Peter Qiuc4beba02014-03-24 14:46:24 -07001020 def send_management_frame(self, interface, frame_type, channel,
1021 ssid_prefix=None, num_bss=None,
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001022 frame_count=None, delay=None,
1023 dest_addr=None, probe_resp_footer=None):
Peter Qiuc4beba02014-03-24 14:46:24 -07001024 """
1025 Injects management frames on specify channel |frequency|.
1026
1027 This function will spawn off a new process to inject specified
1028 management frames |frame_type| at the specified interface |interface|.
1029
1030 @param interface string interface to inject frames.
1031 @param frame_type string message type.
1032 @param channel int targeted channel.
1033 @param ssid_prefix string SSID prefix.
1034 @param num_bss int number of BSS.
1035 @param frame_count int number of frames to send.
1036 @param delay int milliseconds delay between frames.
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001037 @param dest_addr string destination address (DA) MAC address.
1038 @param probe_resp_footer string footer for probe response.
Peter Qiuc4beba02014-03-24 14:46:24 -07001039
1040 @return int PID of the newly created process.
1041
1042 """
1043 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
1044 interface, frame_type, channel)
1045 if ssid_prefix is not None:
1046 command += ' -s %s' % (ssid_prefix)
1047 if num_bss is not None:
1048 command += ' -b %d' % (num_bss)
1049 if frame_count is not None:
1050 command += ' -n %d' % (frame_count)
1051 if delay is not None:
1052 command += ' -d %d' % (delay)
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001053 if dest_addr is not None:
1054 command += ' -a %s' % (dest_addr)
1055 if probe_resp_footer is not None:
1056 self._prep_probe_response_footer(footer=probe_resp_footer)
1057 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
Brian Norris9494c0f2019-12-11 16:29:55 -08001058 command += ' > %s 2>&1 & echo $!' % (os.path.join(self.logdir,
1059 self.MGMT_FRAME_SENDER_LOG_FILE))
Peter Qiuc4beba02014-03-24 14:46:24 -07001060 pid = int(self.router.run(command).stdout)
1061 return pid
1062
1063
Paul Stewart25536942013-08-15 17:33:42 -07001064 def detect_client_deauth(self, client_mac, instance=0):
1065 """Detects whether hostapd has logged a deauthentication from
1066 |client_mac|.
1067
1068 @param client_mac string the MAC address of the client to detect.
1069 @param instance int indicating which hostapd instance to query.
1070
1071 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001072 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -07001073 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001074 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -07001075 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
1076 ignore_status=True)
1077 return result.exit_status == 0
1078
1079
Paul Stewart4ae471e2013-09-04 15:42:35 -07001080 def detect_client_coexistence_report(self, client_mac, instance=0):
1081 """Detects whether hostapd has logged an action frame from
1082 |client_mac| indicating information about 20/40MHz BSS coexistence.
1083
1084 @param client_mac string the MAC address of the client to detect.
1085 @param instance int indicating which hostapd instance to query.
1086
1087 """
1088 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
1089 '.. .. .. .. .. .. .. .. .. .. %s '
1090 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
1091 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001092 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -07001093 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
1094 ignore_status=True)
1095 return result.exit_status == 0
1096
1097
Eric Carusoeddedd32014-10-13 14:41:49 -07001098 def send_magic_packet(self, dest_ip, dest_mac):
1099 """Sends a magic packet to the NIC with the given IP and MAC addresses.
1100
1101 @param dest_ip the IP address of the device to send the packet to
1102 @param dest_mac the hardware MAC address of the device
1103
1104 """
1105 # magic packet is 6 0xff bytes followed by the hardware address
1106 # 16 times
1107 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
1108 magic_packet = '\xff' * 6 + mac_bytes * 16
1109
1110 logging.info('Sending magic packet to %s...', dest_ip)
1111 self.host.run('python -uc "import socket, sys;'
1112 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
1113 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
1114 (dest_ip, UDP_DISCARD_PORT),
1115 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -08001116
1117
Peter Qiu9a63a8b2015-02-03 09:08:16 -08001118 def setup_bridge_mode_dhcp_server(self):
1119 """Setup an DHCP server for bridge mode.
1120
1121 Setup an DHCP server on the master interface of the virtual ethernet
1122 pair, with peer interface connected to the bridge interface. This is
1123 used for testing APs in bridge mode.
1124
1125 """
1126 # Start a local server on master interface of virtual ethernet pair.
1127 self.start_local_server(
1128 self.get_virtual_ethernet_master_interface())
1129 # Add peer interface to the bridge.
1130 self.add_interface_to_bridge(
Ben Chan630d6b42015-02-13 18:14:45 -08001131 self.get_virtual_ethernet_peer_interface())