blob: c3b37474dcda1e03893e8e4b5c9b3acc9fde3e15 [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
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700189 # Log the most recent message on the router so that we can rebuild the
190 # suffix relevant to us when debugging failures.
Brian Norrise53f1692018-09-28 10:46:56 -0700191 last_log_line = self.host.run('tail -1 /var/log/messages',
192 ignore_status=True).stdout
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700193 # We're trying to get the timestamp from:
194 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah
Brian Norris2d8f2972018-09-26 14:55:38 -0700195 self._log_start_timestamp = last_log_line.strip().partition(' ')[0]
196 if self._log_start_timestamp:
197 logging.debug('Will only retrieve logs after %s.',
198 self._log_start_timestamp)
199 else:
200 # If syslog is empty, we just use a wildcard pattern, to grab
201 # everything.
202 logging.debug('Empty or corrupt log; will retrieve whole log')
203 self._log_start_timestamp = '.'
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700204
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700205 # hostapd configuration persists throughout the test, subsequent
206 # 'config' commands only modify it.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700207 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700208 # Many of our tests start with an uninteresting prefix.
209 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700210 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
211 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700212
Christopher Wileyeea12362013-12-12 17:24:29 -0800213 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700214 self.local_servers = []
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700215 self.server_address_index = []
Paul Stewart548cf452012-11-27 17:46:23 -0800216 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800217 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700218 self.dhcp_low = 1
219 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700220
Brian Norris7b09bde2019-05-08 13:02:56 -0700221 # Tear down hostapbr bridge and intermediate functional block
222 # interfaces.
223 result = self.host.run('ls -d /sys/class/net/%s* /sys/class/net/%s*'
224 ' 2>/dev/null' %
225 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
226 self.IFB_INTERFACE_PREFIX),
Matthew Wang08e868d2018-04-19 12:04:54 -0700227 ignore_status=True)
Brian Norris7b09bde2019-05-08 13:02:56 -0700228 for path in result.stdout.splitlines():
229 self.delete_link(path.split('/')[-1])
Matthew Wang08e868d2018-04-19 12:04:54 -0700230
Paul Stewart548cf452012-11-27 17:46:23 -0800231 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700232 self._kill_process_instance('hostapd', timeout_seconds=30)
233 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700234
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700235 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700236 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700237
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800238 self.enable_all_antennas()
Peter Qiu2f973252014-02-20 15:30:37 -0800239
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700240 # Some tests want this functionality, but otherwise, it's a distraction.
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700241 if self._enable_avahi:
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700242 self.host.run('start avahi', ignore_status=True)
243 else:
244 self.host.run('stop avahi', ignore_status=True)
245
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700246 # Some routers have bad (slow?) random number generators.
247 self.rng_configure()
248
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700249
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700250 def close(self):
251 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800252 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700253 # dnsmasq and hostapd cause interesting events to go to system logs.
254 # Retrieve only the suffix of the logs after the timestamp we stored on
255 # router creation.
256 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" %
257 self._log_start_timestamp, ignore_status=True)
258 self.host.get_file('/tmp/router_log', 'debug/router_host_messages')
Christopher Wiley408d1812014-01-13 15:27:43 -0800259 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700260
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700261
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700262 def reboot(self, timeout):
263 """Reboot this router, and restore it to a known-good state.
264
265 @param timeout Maximum seconds to wait for router to return.
266
267 """
268 super(LinuxRouter, self).reboot(timeout)
269 self.__setup()
270
271
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700272 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700273 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700274 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700275
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700276
Peter Qiuc89c9a22014-02-27 10:03:55 -0800277 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700278 """Start a hostapd instance described by conf.
279
Christopher Wileyeea12362013-12-12 17:24:29 -0800280 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700281
282 """
Paul Stewart548cf452012-11-27 17:46:23 -0800283 # Figure out the correct interface.
Brian Norris062390f2018-08-03 18:09:43 -0700284 interface = self.get_wlanif(configuration.frequency, 'managed',
285 configuration.min_streams)
Edward Hill7d411222017-10-02 17:26:56 -0600286 phy_name = self.iw_runner.get_interface(interface).phy
Paul Stewart326badb2012-12-18 14:18:54 -0800287
Christopher Wileyeea12362013-12-12 17:24:29 -0800288 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
289 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
Tien Chang097f7462014-09-03 17:30:29 -0700290 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800291 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800292 hostapd_conf_dict = configuration.generate_dict(
293 interface, control_interface,
Peter Qiubde37ac2014-12-18 23:40:57 -0800294 self.build_unique_ssid(suffix=configuration.ssid_suffix))
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700295 logging.debug('hostapd parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800296
297 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800298 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
299 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800300 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800301
302 # Run hostapd.
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700303 logging.info('Starting hostapd on %s(%s) channel=%s...',
Edward Hill7d411222017-10-02 17:26:56 -0600304 interface, phy_name, configuration.channel)
Christopher Wiley1defc242013-09-18 10:28:37 -0700305 self.router.run('rm %s' % log_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800306 self.router.run('stop wpasupplicant', ignore_status=True)
Tien Chang097f7462014-09-03 17:30:29 -0700307 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % (
308 self.cmd_hostapd, conf_file, log_file, stderr_log_file)
Christopher Wiley0f64b842014-04-30 15:43:50 -0700309 pid = int(self.router.run(start_command).stdout.strip())
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700310 self.hostapd_instances.append(HostapdInstance(
311 hostapd_conf_dict['ssid'],
312 conf_file,
313 log_file,
314 interface,
Tien Chang097f7462014-09-03 17:30:29 -0700315 hostapd_conf_dict.copy(),
mukesh agrawal768e7b32015-05-06 14:53:59 -0700316 stderr_log_file,
317 configuration.scenario_name))
Paul Stewart548cf452012-11-27 17:46:23 -0800318
Christopher Wileyeea12362013-12-12 17:24:29 -0800319 # Wait for confirmation that the router came up.
Christopher Wileyeea12362013-12-12 17:24:29 -0800320 logging.info('Waiting for hostapd to startup.')
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700321 utils.poll_for_condition(
322 condition=lambda: self._has_hostapd_started(log_file, pid),
323 exception=error.TestFail('Timed out while waiting for hostapd '
324 'to start.'),
325 timeout=self.STARTUP_TIMEOUT_SECONDS,
326 sleep_interval=self.POLLING_INTERVAL_SECONDS)
Christopher Wileyeea12362013-12-12 17:24:29 -0800327
Edward Hill7d411222017-10-02 17:26:56 -0600328 if configuration.frag_threshold:
329 threshold = self.iw_runner.get_fragmentation_threshold(phy_name)
330 if threshold != configuration.frag_threshold:
331 raise error.TestNAError('Router does not support setting '
332 'fragmentation threshold')
333
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700334
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700335 def _has_hostapd_started(self, log_file, pid):
336 """Determines if hostapd has started.
337
338 @return Whether or not hostapd has started.
339 @raise error.TestFail if there was a bad config or hostapd terminated.
340 """
341 success = self.router.run(
342 'grep "Setup of interface done" %s' % log_file,
343 ignore_status=True).exit_status == 0
344 if success:
345 return True
346
347 # A common failure is an invalid router configuration.
348 # Detect this and exit early if we see it.
349 bad_config = self.router.run(
350 'grep "Interface initialization failed" %s' % log_file,
351 ignore_status=True).exit_status == 0
352 if bad_config:
353 raise error.TestFail('hostapd failed to initialize AP '
354 'interface.')
355
356 if pid:
357 early_exit = self.router.run('kill -0 %d' % pid,
358 ignore_status=True).exit_status
359 if early_exit:
360 raise error.TestFail('hostapd process terminated.')
361
362 return False
363
364
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700365 def _kill_process_instance(self,
366 process,
367 instance=None,
368 timeout_seconds=10,
369 ignore_timeouts=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700370 """Kill a process on the router.
371
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700372 Kills remote program named |process| (optionally only a specific
373 |instance|). Wait |timeout_seconds| for |process| to die
374 before returning. If |ignore_timeouts| is False, raise
375 a TestError on timeouts.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700376
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700377 @param process: string name of process to kill.
378 @param instance: string fragment of the command line unique to
379 this instance of the remote process.
380 @param timeout_seconds: float timeout in seconds to wait.
381 @param ignore_timeouts: True iff we should ignore failures to
382 kill processes.
383 @return True iff the specified process has exited.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700384
Thieu Le7b23a542012-01-27 15:54:48 -0800385 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700386 if instance is not None:
Christopher Wileya4754c02014-06-30 16:37:52 -0700387 search_arg = '-f "^%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800388 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800389 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800390
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700391 self.host.run('pkill %s' % search_arg, ignore_status=True)
Paul Stewart326badb2012-12-18 14:18:54 -0800392
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700393 # Wait for process to die
394 time.sleep(self.POLLING_INTERVAL_SECONDS)
395 try:
396 utils.poll_for_condition(
397 condition=lambda: self.host.run(
398 'pgrep -l %s' % search_arg,
399 ignore_status=True).exit_status != 0,
400 timeout=timeout_seconds,
401 sleep_interval=self.POLLING_INTERVAL_SECONDS)
402 except utils.TimeoutError:
403 if ignore_timeouts:
404 return False
405
406 raise error.TestError(
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700407 'Timed out waiting for %s%s to die' %
408 (process,
409 '' if instance is None else ' (instance=%s)' % instance))
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700410 return True
Paul Stewart326badb2012-12-18 14:18:54 -0800411
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700412
Paul Stewart326badb2012-12-18 14:18:54 -0800413 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700414 """Kills a hostapd instance.
415
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700416 @param instance HostapdInstance object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700417
418 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700419 is_dead = self._kill_process_instance(
Christopher Wileya4754c02014-06-30 16:37:52 -0700420 self.cmd_hostapd,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700421 instance=instance.conf_file,
422 timeout_seconds=30,
423 ignore_timeouts=True)
mukesh agrawal768e7b32015-05-06 14:53:59 -0700424 if instance.scenario_name:
425 log_identifier = instance.scenario_name
426 else:
427 log_identifier = '%d_%s' % (
428 self._total_hostapd_instances, instance.interface)
429 files_to_copy = [(instance.log_file,
430 'debug/hostapd_router_%s.log' % log_identifier),
Tien Chang097f7462014-09-03 17:30:29 -0700431 (instance.stderr_log_file,
mukesh agrawal768e7b32015-05-06 14:53:59 -0700432 'debug/hostapd_router_%s.stderr.log' %
433 log_identifier)]
Tien Chang097f7462014-09-03 17:30:29 -0700434 for remote_file, local_file in files_to_copy:
435 if self.host.run('ls %s >/dev/null 2>&1' % remote_file,
436 ignore_status=True).exit_status:
437 logging.error('Did not collect hostapd log file because '
438 'it was missing.')
439 else:
440 self.router.get_file(remote_file, local_file)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700441 self._total_hostapd_instances += 1
442 if not is_dead:
443 raise error.TestError('Timed out killing hostapd.')
Paul Stewart21737812012-12-06 11:03:32 -0800444
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700445
Peter Qiubde37ac2014-12-18 23:40:57 -0800446 def build_unique_ssid(self, suffix=''):
Tien Chang097f7462014-09-03 17:30:29 -0700447 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
448 the number of APs we've constructed already.
449
450 @param suffix string to append to SSID
451
452 """
Christopher Wiley08aafb02014-04-22 17:38:21 -0700453 base = len(self.SUFFIX_LETTERS)
454 number = self._number_unique_ssids
455 self._number_unique_ssids += 1
456 unique = ''
457 while number or not unique:
458 unique = self.SUFFIX_LETTERS[number % base] + unique
459 number = number / base
460 # And salt the SSID so that tests running in adjacent cells are unlikely
461 # to pick the same SSID and we're resistent to beacons leaking out of
462 # cells.
463 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
464 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700465
466
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700467 def rng_configure(self):
468 """Configure the random generator to our liking.
469
470 Some routers (particularly, Gale) seem to have bad Random Number
471 Generators, such that hostapd can't always generate keys fast enough.
472 The on-board TPM seems to serve as a better generator, so we try to
473 switch to that if available.
474
475 Symptoms of a slow RNG: hostapd complains with:
476
477 WPA: Not enough entropy in random pool to proceed - reject first
478 4-way handshake
479
480 Ref:
481 https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854
482
483 Linux devices may have RNG parameters at
484 /sys/class/misc/hw_random/rng_{available,current}. See:
485
486 https://www.kernel.org/doc/Documentation/hw_random.txt
487
488 """
489
490 available = self.host.run('cat %s' % self._RNG_AVAILABLE, \
491 ignore_status=True).stdout.strip().split(' ')
492 # System may not have HWRNG support. Just skip this.
493 if available == "":
494 return
495 current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \
496 strip()
497 want_rng = "tpm-rng"
498
Brian Norris4bb65822019-03-04 16:03:55 -0800499 logging.debug("Available / current RNGs on router: %r / %s",
500 available, current)
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700501 if want_rng in available and want_rng != current:
Brian Norris4bb65822019-03-04 16:03:55 -0800502 logging.debug("Switching RNGs: %s -> %s", current, want_rng)
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700503 self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT))
504
505
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700506 def hostap_configure(self, configuration, multi_interface=None):
507 """Build up a hostapd configuration file and start hostapd.
508
509 Also setup a local server if this router supports them.
510
511 @param configuration HosetapConfig object.
512 @param multi_interface bool True iff multiple interfaces allowed.
513
514 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800515 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800516 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700517 self.deconfig()
Kris Rambishb9b46852014-12-19 15:29:58 -0800518 if configuration.is_11ac:
519 router_caps = self.get_capabilities()
520 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps:
521 raise error.TestNAError('Router does not have AC support')
522
Matthew Wang08e868d2018-04-19 12:04:54 -0700523 if configuration.use_bridge:
524 configuration._bridge = self.get_brif()
525
Peter Qiuc89c9a22014-02-27 10:03:55 -0800526 self.start_hostapd(configuration)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700527 interface = self.hostapd_instances[-1].interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800528 self.iw_runner.set_tx_power(interface, 'auto')
Matthew Wang1667e5c2018-02-27 17:32:59 -0800529 self.start_local_server(interface, bridge=configuration.bridge)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700530 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700531
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700532
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700533 def ibss_configure(self, config):
534 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700535
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700536 Extract relevant configuration objects from |config| despite not
537 actually being a hostap managed endpoint.
538
539 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700540
541 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800542 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700543 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800544 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800545 ssid = (config.ssid or
546 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800547 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700548 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800549 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700550 # Always start a local server.
551 self.start_local_server(interface)
552 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800553 self.station_instances.append(
554 StationInstance(ssid=ssid, interface=interface,
555 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800556
557
Paul Stewart2bd823b2012-11-21 15:03:37 -0800558 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700559 """Get the local server address for an interface.
560
561 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700562 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700563
564 @param index int describing which local server this is for.
565
566 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700567 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800568
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700569
Paul Stewart6ddeba72013-11-18 10:08:23 -0800570 def local_peer_ip_address(self, index):
571 """Get the IP address allocated for the peer associated to the AP.
572
573 This address is assigned to a locally associated peer device that
574 is created for the DUT to perform connectivity tests with.
575 When we have multiple local servers, we give them static IP addresses
576 like 192.168.*.253.
577
578 @param index int describing which local server this is for.
579
580 """
581 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
582
Matthew Wang1667e5c2018-02-27 17:32:59 -0800583 def local_bridge_address(self, index):
584 """Get the bridge address for an interface.
585
586 This address is assigned to a local bridge device.
587
588 @param index int describing which local server this is for.
589
590 """
591 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800592
593 def local_peer_mac_address(self):
594 """Get the MAC address of the peer interface.
595
596 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800597
Paul Stewart6ddeba72013-11-18 10:08:23 -0800598 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800599 iface = interface.Interface(self.station_instances[0].interface,
600 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800601 return iface.mac_address
602
603
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700604 def _get_unused_server_address_index(self):
605 """@return an unused server address index."""
606 for address_index in range(0, 256):
607 if address_index not in self.server_address_index:
608 return address_index
609 raise error.TestFail('No available server address index')
610
611
612 def change_server_address_index(self, ap_num=0, server_address_index=None):
613 """Restart the local server with a different server address index.
614
615 This will restart the local server with different gateway IP address
616 and DHCP address ranges.
617
618 @param ap_num: int hostapd instance number.
619 @param server_address_index: int server address index.
620
621 """
622 interface = self.local_servers[ap_num]['interface'];
623 # Get an unused server address index if one is not specified, which
624 # will be different from the one that's currently in used.
625 if server_address_index is None:
626 server_address_index = self._get_unused_server_address_index()
627
628 # Restart local server with the new server address index.
629 self.stop_local_server(self.local_servers[ap_num])
630 self.start_local_server(interface,
631 ap_num=ap_num,
632 server_address_index=server_address_index)
633
634
635 def start_local_server(self,
636 interface,
637 ap_num=None,
Matthew Wang1667e5c2018-02-27 17:32:59 -0800638 server_address_index=None,
639 bridge=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700640 """Start a local server on an interface.
641
642 @param interface string (e.g. wlan0)
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700643 @param ap_num int the ap instance to start the server for
644 @param server_address_index int server address index
Matthew Wang1667e5c2018-02-27 17:32:59 -0800645 @param bridge string (e.g. br0)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700646
647 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700648 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700649
650 if len(self.local_servers) >= 256:
651 raise error.TestFail('Exhausted available local servers')
652
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700653 # Get an unused server address index if one is not specified.
654 # Validate server address index if one is specified.
655 if server_address_index is None:
656 server_address_index = self._get_unused_server_address_index()
657 elif server_address_index in self.server_address_index:
658 raise error.TestFail('Server address index %d already in used' %
659 server_address_index)
660
Christopher Wiley684878e2014-12-18 14:32:48 -0800661 server_addr = netblock.from_addr(
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700662 self.local_server_address(server_address_index),
Christopher Wiley594570d2014-03-14 16:50:15 -0700663 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700664
665 params = {}
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700666 params['address_index'] = server_address_index
Christopher Wiley594570d2014-03-14 16:50:15 -0700667 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700668 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700669 (server_addr.get_addr_in_block(1),
670 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700671 params['interface'] = interface
Matthew Wang1667e5c2018-02-27 17:32:59 -0800672 params['bridge'] = bridge
Christopher Wiley594570d2014-03-14 16:50:15 -0700673 params['ip_params'] = ('%s broadcast %s dev %s' %
674 (server_addr.netblock,
675 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700676 interface))
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700677 if ap_num is None:
678 self.local_servers.append(params)
679 else:
680 self.local_servers.insert(ap_num, params)
681 self.server_address_index.append(server_address_index)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700682
Christopher Wiley594570d2014-03-14 16:50:15 -0700683 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700684 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700685 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700686 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700687 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700688 (self.cmd_ip, interface))
Matthew Wang1667e5c2018-02-27 17:32:59 -0800689 if params['bridge']:
690 bridge_addr = netblock.from_addr(
691 self.local_bridge_address(server_address_index),
692 prefix_len=24)
693 self.router.run("ifconfig %s %s" %
694 (params['bridge'], bridge_addr.netblock))
Paul Stewart548cf452012-11-27 17:46:23 -0800695 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700696
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700697
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700698 def stop_local_server(self, server):
699 """Stop a local server on the router
700
701 @param server object server configuration parameters.
702
703 """
704 self.stop_dhcp_server(server['interface'])
705 self.router.run("%s addr del %s" %
706 (self.cmd_ip, server['ip_params']),
707 ignore_status=True)
708 self.server_address_index.remove(server['address_index'])
709 self.local_servers.remove(server)
710
711
Paul Stewart548cf452012-11-27 17:46:23 -0800712 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700713 """Start a dhcp server on an interface.
714
715 @param interface string (e.g. wlan0)
716
717 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800718 for server in self.local_servers:
719 if server['interface'] == interface:
720 params = server
721 break
722 else:
723 raise error.TestFail('Could not find local server '
724 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700725 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800726 dhcpd_conf_file = self.dhcpd_conf % interface
727 dhcp_conf = '\n'.join([
728 'port=0', # disables DNS server
729 'bind-interfaces',
730 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700731 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
732 server_addr.get_addr_in_block(128))),
Matthew Wang1667e5c2018-02-27 17:32:59 -0800733 'interface=%s' % (params['bridge'] or params['interface']),
Christopher Wileyeea12362013-12-12 17:24:29 -0800734 'dhcp-leasefile=%s' % self.dhcpd_leases])
735 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
736 (dhcpd_conf_file, dhcp_conf))
737 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700738
739
Paul Stewart326badb2012-12-18 14:18:54 -0800740 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700741 """Stop a dhcp server on the router.
742
743 @param instance string instance to kill.
744
745 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700746 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800747
748
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800749 def get_wifi_channel(self, ap_num):
750 """Return channel of BSS corresponding to |ap_num|.
751
752 @param ap_num int which BSS to get the channel of.
753 @return int primary channel of BSS.
754
755 """
756 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700757 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800758
759
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700760 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700761 """Return IP address on the WiFi subnet of a local server on the router.
762
763 If no local servers are configured (e.g. for an RSPro), a TestFail will
764 be raised.
765
766 @param ap_num int which local server to get an address from.
767
768 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700769 if not self.local_servers:
770 raise error.TestError('No IP address assigned')
771
772 return self.local_servers[ap_num]['netblock'].addr
773
774
775 def get_wifi_ip_subnet(self, ap_num):
776 """Return subnet of WiFi AP instance.
777
778 If no APs are configured a TestError will be raised.
779
780 @param ap_num int which local server to get an address from.
781
782 """
783 if not self.local_servers:
784 raise error.TestError('No APs configured.')
785
786 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700787
788
Christopher Wileya3effac2014-02-05 11:16:11 -0800789 def get_hostapd_interface(self, ap_num):
790 """Get the name of the interface associated with a hostapd instance.
791
792 @param ap_num: int hostapd instance number.
793 @return string interface name (e.g. 'managed0').
794
795 """
796 if ap_num not in range(len(self.hostapd_instances)):
797 raise error.TestFail('Invalid instance number (%d) with %d '
798 'instances configured.' %
799 (ap_num, len(self.hostapd_instances)))
800
801 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700802 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800803
804
Christopher Wileycc770d92015-02-02 17:37:16 -0800805 def get_station_interface(self, instance):
806 """Get the name of the interface associated with a station.
807
808 @param instance: int station instance number.
809 @return string interface name (e.g. 'managed0').
810
811 """
812 if instance not in range(len(self.station_instances)):
813 raise error.TestFail('Invalid instance number (%d) with %d '
814 'instances configured.' %
815 (instance, len(self.station_instances)))
816
817 instance = self.station_instances[instance]
818 return instance.interface
819
820
Paul Stewart17350be2012-12-14 13:34:54 -0800821 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700822 """Return the MAC address of an AP in the test.
823
824 @param ap_num int index of local server to read the MAC address from.
825 @return string MAC address like 00:11:22:33:44:55.
826
827 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800828 interface_name = self.get_hostapd_interface(ap_num)
829 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800830 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800831
832
Christopher Wileya3effac2014-02-05 11:16:11 -0800833 def get_hostapd_phy(self, ap_num):
834 """Get name of phy for hostapd instance.
835
836 @param ap_num int index of hostapd instance.
837 @return string phy name of phy corresponding to hostapd's
838 managed interface.
839
840 """
841 interface = self.iw_runner.get_interface(
842 self.get_hostapd_interface(ap_num))
843 return interface.phy
844
845
Christopher Wileyeea12362013-12-12 17:24:29 -0800846 def deconfig(self):
847 """A legacy, deprecated alias for deconfig_aps."""
848 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800849
850
851 def deconfig_aps(self, instance=None, silent=False):
852 """De-configure an AP (will also bring wlan down).
853
854 @param instance: int or None. If instance is None, will bring down all
855 instances of hostapd.
856 @param silent: True if instances should be brought without de-authing
857 the DUT.
858
859 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800860 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700861 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700862
Christopher Wileyeea12362013-12-12 17:24:29 -0800863 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800864 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800865 if instance is not None:
866 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800867 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700868 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800869 local_servers = [server]
Paul Stewart326badb2012-12-18 14:18:54 -0800870 break
Paul Stewart21737812012-12-06 11:03:32 -0800871 else:
872 instances = self.hostapd_instances
873 self.hostapd_instances = []
Peter Qiu79784512015-04-14 22:59:55 -0700874 local_servers = copy.copy(self.local_servers)
Paul Stewart64cc4292011-06-01 10:59:36 -0700875
Paul Stewart21737812012-12-06 11:03:32 -0800876 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800877 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800878 # Deconfigure without notifying DUT. Remove the interface
879 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700880 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800881
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700882 self.kill_hostapd_instance(instance)
883 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800884 if self.station_instances:
Peter Qiu79784512015-04-14 22:59:55 -0700885 local_servers = copy.copy(self.local_servers)
Christopher Wiley408d1812014-01-13 15:27:43 -0800886 instance = self.station_instances.pop()
887 if instance.dev_type == 'ibss':
888 self.iw_runner.ibss_leave(instance.interface)
889 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700890 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700891 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800892 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800893 self.iw_runner.disconnect_station(instance.interface)
894 self.router.run('%s link set %s down' %
895 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700896
Paul Stewart326badb2012-12-18 14:18:54 -0800897 for server in local_servers:
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700898 self.stop_local_server(server)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700899
Matthew Wang08e868d2018-04-19 12:04:54 -0700900 for brif in range(self._brif_index):
901 self.delete_link('%s%d' %
902 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif))
903
904
905 def delete_link(self, name):
906 """Delete link using the `ip` command.
907
908 @param name string link name.
909
910 """
911 self.host.run('%s link del %s' % (self.cmd_ip, name),
912 ignore_status=True)
913
Paul Stewart7cb1f062010-06-10 15:46:20 -0700914
Peter Qiu4bfb1e92015-03-12 16:43:28 -0700915 def set_ap_interface_down(self, instance=0):
916 """Bring down the hostapd interface.
917
918 @param instance int router instance number.
919
920 """
921 self.host.run('%s link set %s down' %
922 (self.cmd_ip, self.get_hostapd_interface(instance)))
923
924
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800925 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700926 """Verify that the PMKSA auth was cached on a hostapd instance.
927
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800928 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700929
930 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700931 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800932 pmksa_match = 'PMK from PMKSA cache'
933 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
934 ignore_status=True)
935 if result.exit_status:
936 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800937
938
Christopher Wileye0afecb2013-11-11 10:54:23 -0800939 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700940 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800941 if instance is None:
942 instance = 0
943 if len(self.hostapd_instances) > 1:
944 raise error.TestFail('No instance of hostapd specified with '
945 'multiple instances present.')
946
Christopher Wiley3099be72013-11-06 16:49:02 -0800947 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700948 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700949
Christopher Wiley408d1812014-01-13 15:27:43 -0800950 if self.station_instances:
951 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700952
Christopher Wiley408d1812014-01-13 15:27:43 -0800953 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700954
955
Wade Guthriee4074dd2013-10-30 11:00:48 -0700956 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700957 """Deauthenticates a client described in params.
958
Wade Guthriee4074dd2013-10-30 11:00:48 -0700959 @param client_mac string containing the mac address of the client to be
960 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700961
962 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700963 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700964 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800965 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700966
Matthew Wangab74f8d2018-01-18 16:29:35 -0800967 def send_bss_tm_req(self, client_mac, neighbor_list):
968 """Send a BSS Transition Management Request to a client.
969
970 @param client_mac string containing the mac address of the client.
971 @param neighbor_list list of strings containing mac addresses of
972 candidate APs.
Matthew Wang91660ed2019-04-03 15:38:01 -0700973 @return string reply received from command
Matthew Wangab74f8d2018-01-18 16:29:35 -0800974
975 """
976 control_if = self.hostapd_instances[0].config_dict['ctrl_interface']
Matthew Wang6886f282018-06-05 14:42:20 -0700977 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' %
978 (self.cmd_hostapd_cli, control_if, client_mac,
979 ',0,0,0,0 neighbor='.join(neighbor_list)))
980 ret = self.router.run(command).stdout
Matthew Wang91660ed2019-04-03 15:38:01 -0700981 return ret.splitlines()[-1]
Wade Guthriee4074dd2013-10-30 11:00:48 -0700982
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700983 def _prep_probe_response_footer(self, footer):
984 """Write probe response footer temporarily to a local file and copy
985 over to test router.
986
987 @param footer string containing bytes for the probe response footer.
988 @raises AutoservRunError: If footer file copy fails.
989
990 """
mukesh agrawale2102b82015-07-17 11:16:30 -0700991 with tempfile.NamedTemporaryFile() as fp:
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700992 fp.write(footer)
mukesh agrawal41a817c2015-07-22 10:00:43 -0700993 fp.flush()
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700994 try:
mukesh agrawale2102b82015-07-17 11:16:30 -0700995 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE)
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700996 except error.AutoservRunError:
997 logging.error('failed to copy footer file to AP')
998 raise
999
mukesh agrawale2102b82015-07-17 11:16:30 -07001000
Peter Qiuc4beba02014-03-24 14:46:24 -07001001 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -07001002 """Injects a management frame into an active hostapd session.
1003
1004 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -07001005 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -07001006 @param instance int indicating which hostapd instance to inject into.
1007
1008 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001009 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001010 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001011 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -07001012 self.router.run('%s -i %s -t %s -c %d' %
1013 (self.cmd_send_management_frame, interface, frame_type,
1014 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001015 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001016
1017
Peter Qiuc4beba02014-03-24 14:46:24 -07001018 def send_management_frame(self, interface, frame_type, channel,
1019 ssid_prefix=None, num_bss=None,
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001020 frame_count=None, delay=None,
1021 dest_addr=None, probe_resp_footer=None):
Peter Qiuc4beba02014-03-24 14:46:24 -07001022 """
1023 Injects management frames on specify channel |frequency|.
1024
1025 This function will spawn off a new process to inject specified
1026 management frames |frame_type| at the specified interface |interface|.
1027
1028 @param interface string interface to inject frames.
1029 @param frame_type string message type.
1030 @param channel int targeted channel.
1031 @param ssid_prefix string SSID prefix.
1032 @param num_bss int number of BSS.
1033 @param frame_count int number of frames to send.
1034 @param delay int milliseconds delay between frames.
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001035 @param dest_addr string destination address (DA) MAC address.
1036 @param probe_resp_footer string footer for probe response.
Peter Qiuc4beba02014-03-24 14:46:24 -07001037
1038 @return int PID of the newly created process.
1039
1040 """
1041 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
1042 interface, frame_type, channel)
1043 if ssid_prefix is not None:
1044 command += ' -s %s' % (ssid_prefix)
1045 if num_bss is not None:
1046 command += ' -b %d' % (num_bss)
1047 if frame_count is not None:
1048 command += ' -n %d' % (frame_count)
1049 if delay is not None:
1050 command += ' -d %d' % (delay)
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001051 if dest_addr is not None:
1052 command += ' -a %s' % (dest_addr)
1053 if probe_resp_footer is not None:
1054 self._prep_probe_response_footer(footer=probe_resp_footer)
1055 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
Peter Qiuc4beba02014-03-24 14:46:24 -07001056 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
1057 pid = int(self.router.run(command).stdout)
1058 return pid
1059
1060
Paul Stewart25536942013-08-15 17:33:42 -07001061 def detect_client_deauth(self, client_mac, instance=0):
1062 """Detects whether hostapd has logged a deauthentication from
1063 |client_mac|.
1064
1065 @param client_mac string the MAC address of the client to detect.
1066 @param instance int indicating which hostapd instance to query.
1067
1068 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001069 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -07001070 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001071 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -07001072 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
1073 ignore_status=True)
1074 return result.exit_status == 0
1075
1076
Paul Stewart4ae471e2013-09-04 15:42:35 -07001077 def detect_client_coexistence_report(self, client_mac, instance=0):
1078 """Detects whether hostapd has logged an action frame from
1079 |client_mac| indicating information about 20/40MHz BSS coexistence.
1080
1081 @param client_mac string the MAC address of the client to detect.
1082 @param instance int indicating which hostapd instance to query.
1083
1084 """
1085 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
1086 '.. .. .. .. .. .. .. .. .. .. %s '
1087 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
1088 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001089 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -07001090 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
1091 ignore_status=True)
1092 return result.exit_status == 0
1093
1094
Paul Stewart6ddeba72013-11-18 10:08:23 -08001095 def add_connected_peer(self, instance=0):
1096 """Configure a station connected to a running AP instance.
1097
1098 Extract relevant configuration objects from the hostap
1099 configuration for |instance| and generate a wpa_supplicant
1100 instance that connects to it. This allows the DUT to interact
1101 with a client entity that is also connected to the same AP. A
1102 full wpa_supplicant instance is necessary here (instead of just
1103 using the "iw" command to connect) since we want to enable
1104 advanced features such as TDLS.
1105
1106 @param instance int indicating which hostapd instance to connect to.
1107
1108 """
1109 if not self.hostapd_instances:
1110 raise error.TestFail('Hostapd is not configured.')
1111
Christopher Wiley408d1812014-01-13 15:27:43 -08001112 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -08001113 raise error.TestFail('Station is already configured.')
1114
Christopher Wiley408d1812014-01-13 15:27:43 -08001115 ssid = self.get_ssid(instance)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001116 hostap_conf = self.hostapd_instances[instance].config_dict
Paul Stewart6ddeba72013-11-18 10:08:23 -08001117 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
1118 hostap_conf['channel'])
Christopher Wileyab3964f2015-01-26 12:59:12 -08001119 self.configure_managed_station(
1120 ssid, frequency, self.local_peer_ip_address(instance))
1121 interface = self.station_instances[0].interface
1122 # Since we now have two network interfaces connected to the same
1123 # network, we need to disable the kernel's protection against
1124 # incoming packets to an "unexpected" interface.
1125 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
1126 interface)
1127
1128 # Similarly, we'd like to prevent the hostap interface from
1129 # replying to ARP requests for the peer IP address and vice
1130 # versa.
1131 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1132 interface)
1133 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1134 hostap_conf['interface'])
1135
1136
1137 def configure_managed_station(self, ssid, frequency, ip_addr):
1138 """Configure a router interface to connect as a client to a network.
1139
1140 @param ssid: string SSID of network to join.
1141 @param frequency: int frequency required to join the network.
1142 @param ip_addr: IP address to assign to this interface
1143 (e.g. '192.168.1.200').
1144
1145 """
Christopher Wiley408d1812014-01-13 15:27:43 -08001146 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -08001147
1148 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
1149 # require them...
1150 supplicant_config = (
1151 'network={\n'
1152 ' ssid="%(ssid)s"\n'
1153 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -08001154 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -08001155 )
1156
Christopher Wileyeea12362013-12-12 17:24:29 -08001157 conf_file = self.STATION_CONF_FILE_PATTERN % interface
1158 log_file = self.STATION_LOG_FILE_PATTERN % interface
1159 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -08001160
1161 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
1162 (conf_file, supplicant_config))
1163
1164 # Connect the station.
1165 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Ben Chan630d6b42015-02-13 18:14:45 -08001166 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s >%s 2>&1 &' %
Paul Stewart6ddeba72013-11-18 10:08:23 -08001167 (self.cmd_wpa_supplicant,
1168 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -08001169 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -08001170 self.router.run(start_command)
1171 self.iw_runner.wait_for_link(interface)
1172
1173 # Assign an IP address to this interface.
1174 self.router.run('%s addr add %s/24 dev %s' %
Christopher Wileyab3964f2015-01-26 12:59:12 -08001175 (self.cmd_ip, ip_addr, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -08001176 self.station_instances.append(
1177 StationInstance(ssid=ssid, interface=interface,
1178 dev_type='managed'))
Eric Carusoeddedd32014-10-13 14:41:49 -07001179
1180
1181 def send_magic_packet(self, dest_ip, dest_mac):
1182 """Sends a magic packet to the NIC with the given IP and MAC addresses.
1183
1184 @param dest_ip the IP address of the device to send the packet to
1185 @param dest_mac the hardware MAC address of the device
1186
1187 """
1188 # magic packet is 6 0xff bytes followed by the hardware address
1189 # 16 times
1190 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
1191 magic_packet = '\xff' * 6 + mac_bytes * 16
1192
1193 logging.info('Sending magic packet to %s...', dest_ip)
1194 self.host.run('python -uc "import socket, sys;'
1195 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
1196 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
1197 (dest_ip, UDP_DISCARD_PORT),
1198 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -08001199
1200
Peter Qiu9a63a8b2015-02-03 09:08:16 -08001201 def setup_bridge_mode_dhcp_server(self):
1202 """Setup an DHCP server for bridge mode.
1203
1204 Setup an DHCP server on the master interface of the virtual ethernet
1205 pair, with peer interface connected to the bridge interface. This is
1206 used for testing APs in bridge mode.
1207
1208 """
1209 # Start a local server on master interface of virtual ethernet pair.
1210 self.start_local_server(
1211 self.get_virtual_ethernet_master_interface())
1212 # Add peer interface to the bridge.
1213 self.add_interface_to_bridge(
Ben Chan630d6b42015-02-13 18:14:45 -08001214 self.get_virtual_ethernet_peer_interface())