blob: 51464d40890a19f9746722a9ed74df30df21aa6d [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
Matthew Wang08e868d2018-04-19 12:04:54 -0700221 # Tear down hostapbr bridge interfaces
222 result = self.host.run('ls -d /sys/class/net/%s*' %
223 self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
224 ignore_status=True)
225 if result.exit_status == 0:
226 for path in result.stdout.splitlines():
227 self.delete_link(path.split('/')[-1])
228
Paul Stewart548cf452012-11-27 17:46:23 -0800229 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700230 self._kill_process_instance('hostapd', timeout_seconds=30)
231 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700232
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700233 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700234 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700235
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800236 self.enable_all_antennas()
Peter Qiu2f973252014-02-20 15:30:37 -0800237
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700238 # Some tests want this functionality, but otherwise, it's a distraction.
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700239 if self._enable_avahi:
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700240 self.host.run('start avahi', ignore_status=True)
241 else:
242 self.host.run('stop avahi', ignore_status=True)
243
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700244 # Some routers have bad (slow?) random number generators.
245 self.rng_configure()
246
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700247
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700248 def close(self):
249 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800250 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700251 # dnsmasq and hostapd cause interesting events to go to system logs.
252 # Retrieve only the suffix of the logs after the timestamp we stored on
253 # router creation.
254 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" %
255 self._log_start_timestamp, ignore_status=True)
256 self.host.get_file('/tmp/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
Christopher Wileyeea12362013-12-12 17:24:29 -0800286 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
287 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
Tien Chang097f7462014-09-03 17:30:29 -0700288 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800289 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800290 hostapd_conf_dict = configuration.generate_dict(
291 interface, control_interface,
Peter Qiubde37ac2014-12-18 23:40:57 -0800292 self.build_unique_ssid(suffix=configuration.ssid_suffix))
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700293 logging.debug('hostapd parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800294
295 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800296 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
297 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800298 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800299
300 # Run hostapd.
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700301 logging.info('Starting hostapd on %s(%s) channel=%s...',
Edward Hill7d411222017-10-02 17:26:56 -0600302 interface, phy_name, configuration.channel)
Christopher Wiley1defc242013-09-18 10:28:37 -0700303 self.router.run('rm %s' % log_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800304 self.router.run('stop wpasupplicant', ignore_status=True)
Tien Chang097f7462014-09-03 17:30:29 -0700305 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % (
306 self.cmd_hostapd, conf_file, log_file, stderr_log_file)
Christopher Wiley0f64b842014-04-30 15:43:50 -0700307 pid = int(self.router.run(start_command).stdout.strip())
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700308 self.hostapd_instances.append(HostapdInstance(
309 hostapd_conf_dict['ssid'],
310 conf_file,
311 log_file,
312 interface,
Tien Chang097f7462014-09-03 17:30:29 -0700313 hostapd_conf_dict.copy(),
mukesh agrawal768e7b32015-05-06 14:53:59 -0700314 stderr_log_file,
315 configuration.scenario_name))
Paul Stewart548cf452012-11-27 17:46:23 -0800316
Christopher Wileyeea12362013-12-12 17:24:29 -0800317 # Wait for confirmation that the router came up.
Christopher Wileyeea12362013-12-12 17:24:29 -0800318 logging.info('Waiting for hostapd to startup.')
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700319 utils.poll_for_condition(
320 condition=lambda: self._has_hostapd_started(log_file, pid),
321 exception=error.TestFail('Timed out while waiting for hostapd '
322 'to start.'),
323 timeout=self.STARTUP_TIMEOUT_SECONDS,
324 sleep_interval=self.POLLING_INTERVAL_SECONDS)
Christopher Wileyeea12362013-12-12 17:24:29 -0800325
Edward Hill7d411222017-10-02 17:26:56 -0600326 if configuration.frag_threshold:
327 threshold = self.iw_runner.get_fragmentation_threshold(phy_name)
328 if threshold != configuration.frag_threshold:
329 raise error.TestNAError('Router does not support setting '
330 'fragmentation threshold')
331
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700332
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700333 def _has_hostapd_started(self, log_file, pid):
334 """Determines if hostapd has started.
335
336 @return Whether or not hostapd has started.
337 @raise error.TestFail if there was a bad config or hostapd terminated.
338 """
339 success = self.router.run(
340 'grep "Setup of interface done" %s' % log_file,
341 ignore_status=True).exit_status == 0
342 if success:
343 return True
344
345 # A common failure is an invalid router configuration.
346 # Detect this and exit early if we see it.
347 bad_config = self.router.run(
348 'grep "Interface initialization failed" %s' % log_file,
349 ignore_status=True).exit_status == 0
350 if bad_config:
351 raise error.TestFail('hostapd failed to initialize AP '
352 'interface.')
353
354 if pid:
355 early_exit = self.router.run('kill -0 %d' % pid,
356 ignore_status=True).exit_status
357 if early_exit:
358 raise error.TestFail('hostapd process terminated.')
359
360 return False
361
362
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700363 def _kill_process_instance(self,
364 process,
365 instance=None,
366 timeout_seconds=10,
367 ignore_timeouts=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700368 """Kill a process on the router.
369
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700370 Kills remote program named |process| (optionally only a specific
371 |instance|). Wait |timeout_seconds| for |process| to die
372 before returning. If |ignore_timeouts| is False, raise
373 a TestError on timeouts.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700374
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700375 @param process: string name of process to kill.
376 @param instance: string fragment of the command line unique to
377 this instance of the remote process.
378 @param timeout_seconds: float timeout in seconds to wait.
379 @param ignore_timeouts: True iff we should ignore failures to
380 kill processes.
381 @return True iff the specified process has exited.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700382
Thieu Le7b23a542012-01-27 15:54:48 -0800383 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700384 if instance is not None:
Christopher Wileya4754c02014-06-30 16:37:52 -0700385 search_arg = '-f "^%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800386 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800387 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800388
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700389 self.host.run('pkill %s' % search_arg, ignore_status=True)
Paul Stewart326badb2012-12-18 14:18:54 -0800390
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700391 # Wait for process to die
392 time.sleep(self.POLLING_INTERVAL_SECONDS)
393 try:
394 utils.poll_for_condition(
395 condition=lambda: self.host.run(
396 'pgrep -l %s' % search_arg,
397 ignore_status=True).exit_status != 0,
398 timeout=timeout_seconds,
399 sleep_interval=self.POLLING_INTERVAL_SECONDS)
400 except utils.TimeoutError:
401 if ignore_timeouts:
402 return False
403
404 raise error.TestError(
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700405 'Timed out waiting for %s%s to die' %
406 (process,
407 '' if instance is None else ' (instance=%s)' % instance))
Alex Khouderchahb0c624c2018-06-12 11:00:14 -0700408 return True
Paul Stewart326badb2012-12-18 14:18:54 -0800409
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700410
Paul Stewart326badb2012-12-18 14:18:54 -0800411 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700412 """Kills a hostapd instance.
413
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700414 @param instance HostapdInstance object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700415
416 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700417 is_dead = self._kill_process_instance(
Christopher Wileya4754c02014-06-30 16:37:52 -0700418 self.cmd_hostapd,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700419 instance=instance.conf_file,
420 timeout_seconds=30,
421 ignore_timeouts=True)
mukesh agrawal768e7b32015-05-06 14:53:59 -0700422 if instance.scenario_name:
423 log_identifier = instance.scenario_name
424 else:
425 log_identifier = '%d_%s' % (
426 self._total_hostapd_instances, instance.interface)
427 files_to_copy = [(instance.log_file,
428 'debug/hostapd_router_%s.log' % log_identifier),
Tien Chang097f7462014-09-03 17:30:29 -0700429 (instance.stderr_log_file,
mukesh agrawal768e7b32015-05-06 14:53:59 -0700430 'debug/hostapd_router_%s.stderr.log' %
431 log_identifier)]
Tien Chang097f7462014-09-03 17:30:29 -0700432 for remote_file, local_file in files_to_copy:
433 if self.host.run('ls %s >/dev/null 2>&1' % remote_file,
434 ignore_status=True).exit_status:
435 logging.error('Did not collect hostapd log file because '
436 'it was missing.')
437 else:
438 self.router.get_file(remote_file, local_file)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700439 self._total_hostapd_instances += 1
440 if not is_dead:
441 raise error.TestError('Timed out killing hostapd.')
Paul Stewart21737812012-12-06 11:03:32 -0800442
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700443
Peter Qiubde37ac2014-12-18 23:40:57 -0800444 def build_unique_ssid(self, suffix=''):
Tien Chang097f7462014-09-03 17:30:29 -0700445 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
446 the number of APs we've constructed already.
447
448 @param suffix string to append to SSID
449
450 """
Christopher Wiley08aafb02014-04-22 17:38:21 -0700451 base = len(self.SUFFIX_LETTERS)
452 number = self._number_unique_ssids
453 self._number_unique_ssids += 1
454 unique = ''
455 while number or not unique:
456 unique = self.SUFFIX_LETTERS[number % base] + unique
457 number = number / base
458 # And salt the SSID so that tests running in adjacent cells are unlikely
459 # to pick the same SSID and we're resistent to beacons leaking out of
460 # cells.
461 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
462 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700463
464
Brian Norrisbd5f83d2018-08-03 14:45:02 -0700465 def rng_configure(self):
466 """Configure the random generator to our liking.
467
468 Some routers (particularly, Gale) seem to have bad Random Number
469 Generators, such that hostapd can't always generate keys fast enough.
470 The on-board TPM seems to serve as a better generator, so we try to
471 switch to that if available.
472
473 Symptoms of a slow RNG: hostapd complains with:
474
475 WPA: Not enough entropy in random pool to proceed - reject first
476 4-way handshake
477
478 Ref:
479 https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854
480
481 Linux devices may have RNG parameters at
482 /sys/class/misc/hw_random/rng_{available,current}. See:
483
484 https://www.kernel.org/doc/Documentation/hw_random.txt
485
486 """
487
488 available = self.host.run('cat %s' % self._RNG_AVAILABLE, \
489 ignore_status=True).stdout.strip().split(' ')
490 # System may not have HWRNG support. Just skip this.
491 if available == "":
492 return
493 current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \
494 strip()
495 want_rng = "tpm-rng"
496
497 logging.info("Available / current RNGs on router: %r / %s",
498 available, current)
499 if want_rng in available and want_rng != current:
500 logging.info("Switching RNGs: %s -> %s", current, want_rng)
501 self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT))
502
503
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700504 def hostap_configure(self, configuration, multi_interface=None):
505 """Build up a hostapd configuration file and start hostapd.
506
507 Also setup a local server if this router supports them.
508
509 @param configuration HosetapConfig object.
510 @param multi_interface bool True iff multiple interfaces allowed.
511
512 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800513 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800514 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700515 self.deconfig()
Kris Rambishb9b46852014-12-19 15:29:58 -0800516 if configuration.is_11ac:
517 router_caps = self.get_capabilities()
518 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps:
519 raise error.TestNAError('Router does not have AC support')
520
Matthew Wang08e868d2018-04-19 12:04:54 -0700521 if configuration.use_bridge:
522 configuration._bridge = self.get_brif()
523
Peter Qiuc89c9a22014-02-27 10:03:55 -0800524 self.start_hostapd(configuration)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700525 interface = self.hostapd_instances[-1].interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800526 self.iw_runner.set_tx_power(interface, 'auto')
Paul Stewart65fb9212014-12-01 19:54:20 -0800527 self.set_beacon_footer(interface, configuration.beacon_footer)
Matthew Wang1667e5c2018-02-27 17:32:59 -0800528 self.start_local_server(interface, bridge=configuration.bridge)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700529 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700530
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700531
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700532 def ibss_configure(self, config):
533 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700534
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700535 Extract relevant configuration objects from |config| despite not
536 actually being a hostap managed endpoint.
537
538 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700539
540 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800541 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700542 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800543 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800544 ssid = (config.ssid or
545 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800546 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700547 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800548 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700549 # Always start a local server.
550 self.start_local_server(interface)
551 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800552 self.station_instances.append(
553 StationInstance(ssid=ssid, interface=interface,
554 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800555
556
Paul Stewart2bd823b2012-11-21 15:03:37 -0800557 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700558 """Get the local server address for an interface.
559
560 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700561 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700562
563 @param index int describing which local server this is for.
564
565 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700566 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800567
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700568
Paul Stewart6ddeba72013-11-18 10:08:23 -0800569 def local_peer_ip_address(self, index):
570 """Get the IP address allocated for the peer associated to the AP.
571
572 This address is assigned to a locally associated peer device that
573 is created for the DUT to perform connectivity tests with.
574 When we have multiple local servers, we give them static IP addresses
575 like 192.168.*.253.
576
577 @param index int describing which local server this is for.
578
579 """
580 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
581
Matthew Wang1667e5c2018-02-27 17:32:59 -0800582 def local_bridge_address(self, index):
583 """Get the bridge address for an interface.
584
585 This address is assigned to a local bridge device.
586
587 @param index int describing which local server this is for.
588
589 """
590 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800591
592 def local_peer_mac_address(self):
593 """Get the MAC address of the peer interface.
594
595 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800596
Paul Stewart6ddeba72013-11-18 10:08:23 -0800597 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800598 iface = interface.Interface(self.station_instances[0].interface,
599 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800600 return iface.mac_address
601
602
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700603 def _get_unused_server_address_index(self):
604 """@return an unused server address index."""
605 for address_index in range(0, 256):
606 if address_index not in self.server_address_index:
607 return address_index
608 raise error.TestFail('No available server address index')
609
610
611 def change_server_address_index(self, ap_num=0, server_address_index=None):
612 """Restart the local server with a different server address index.
613
614 This will restart the local server with different gateway IP address
615 and DHCP address ranges.
616
617 @param ap_num: int hostapd instance number.
618 @param server_address_index: int server address index.
619
620 """
621 interface = self.local_servers[ap_num]['interface'];
622 # Get an unused server address index if one is not specified, which
623 # will be different from the one that's currently in used.
624 if server_address_index is None:
625 server_address_index = self._get_unused_server_address_index()
626
627 # Restart local server with the new server address index.
628 self.stop_local_server(self.local_servers[ap_num])
629 self.start_local_server(interface,
630 ap_num=ap_num,
631 server_address_index=server_address_index)
632
633
634 def start_local_server(self,
635 interface,
636 ap_num=None,
Matthew Wang1667e5c2018-02-27 17:32:59 -0800637 server_address_index=None,
638 bridge=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700639 """Start a local server on an interface.
640
641 @param interface string (e.g. wlan0)
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700642 @param ap_num int the ap instance to start the server for
643 @param server_address_index int server address index
Matthew Wang1667e5c2018-02-27 17:32:59 -0800644 @param bridge string (e.g. br0)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700645
646 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700647 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700648
649 if len(self.local_servers) >= 256:
650 raise error.TestFail('Exhausted available local servers')
651
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700652 # Get an unused server address index if one is not specified.
653 # Validate server address index if one is specified.
654 if server_address_index is None:
655 server_address_index = self._get_unused_server_address_index()
656 elif server_address_index in self.server_address_index:
657 raise error.TestFail('Server address index %d already in used' %
658 server_address_index)
659
Christopher Wiley684878e2014-12-18 14:32:48 -0800660 server_addr = netblock.from_addr(
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700661 self.local_server_address(server_address_index),
Christopher Wiley594570d2014-03-14 16:50:15 -0700662 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700663
664 params = {}
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700665 params['address_index'] = server_address_index
Christopher Wiley594570d2014-03-14 16:50:15 -0700666 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700667 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700668 (server_addr.get_addr_in_block(1),
669 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700670 params['interface'] = interface
Matthew Wang1667e5c2018-02-27 17:32:59 -0800671 params['bridge'] = bridge
Christopher Wiley594570d2014-03-14 16:50:15 -0700672 params['ip_params'] = ('%s broadcast %s dev %s' %
673 (server_addr.netblock,
674 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700675 interface))
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700676 if ap_num is None:
677 self.local_servers.append(params)
678 else:
679 self.local_servers.insert(ap_num, params)
680 self.server_address_index.append(server_address_index)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700681
Christopher Wiley594570d2014-03-14 16:50:15 -0700682 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700683 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700684 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700685 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700686 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700687 (self.cmd_ip, interface))
Matthew Wang1667e5c2018-02-27 17:32:59 -0800688 if params['bridge']:
689 bridge_addr = netblock.from_addr(
690 self.local_bridge_address(server_address_index),
691 prefix_len=24)
692 self.router.run("ifconfig %s %s" %
693 (params['bridge'], bridge_addr.netblock))
Paul Stewart548cf452012-11-27 17:46:23 -0800694 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700695
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700696
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700697 def stop_local_server(self, server):
698 """Stop a local server on the router
699
700 @param server object server configuration parameters.
701
702 """
703 self.stop_dhcp_server(server['interface'])
704 self.router.run("%s addr del %s" %
705 (self.cmd_ip, server['ip_params']),
706 ignore_status=True)
707 self.server_address_index.remove(server['address_index'])
708 self.local_servers.remove(server)
709
710
Paul Stewart548cf452012-11-27 17:46:23 -0800711 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700712 """Start a dhcp server on an interface.
713
714 @param interface string (e.g. wlan0)
715
716 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800717 for server in self.local_servers:
718 if server['interface'] == interface:
719 params = server
720 break
721 else:
722 raise error.TestFail('Could not find local server '
723 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700724 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800725 dhcpd_conf_file = self.dhcpd_conf % interface
726 dhcp_conf = '\n'.join([
727 'port=0', # disables DNS server
728 'bind-interfaces',
729 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700730 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
731 server_addr.get_addr_in_block(128))),
Matthew Wang1667e5c2018-02-27 17:32:59 -0800732 'interface=%s' % (params['bridge'] or params['interface']),
Christopher Wileyeea12362013-12-12 17:24:29 -0800733 'dhcp-leasefile=%s' % self.dhcpd_leases])
734 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
735 (dhcpd_conf_file, dhcp_conf))
736 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700737
738
Paul Stewart326badb2012-12-18 14:18:54 -0800739 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700740 """Stop a dhcp server on the router.
741
742 @param instance string instance to kill.
743
744 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700745 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800746
747
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800748 def get_wifi_channel(self, ap_num):
749 """Return channel of BSS corresponding to |ap_num|.
750
751 @param ap_num int which BSS to get the channel of.
752 @return int primary channel of BSS.
753
754 """
755 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700756 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800757
758
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700759 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700760 """Return IP address on the WiFi subnet of a local server on the router.
761
762 If no local servers are configured (e.g. for an RSPro), a TestFail will
763 be raised.
764
765 @param ap_num int which local server to get an address from.
766
767 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700768 if not self.local_servers:
769 raise error.TestError('No IP address assigned')
770
771 return self.local_servers[ap_num]['netblock'].addr
772
773
774 def get_wifi_ip_subnet(self, ap_num):
775 """Return subnet of WiFi AP instance.
776
777 If no APs are configured a TestError will be raised.
778
779 @param ap_num int which local server to get an address from.
780
781 """
782 if not self.local_servers:
783 raise error.TestError('No APs configured.')
784
785 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700786
787
Christopher Wileya3effac2014-02-05 11:16:11 -0800788 def get_hostapd_interface(self, ap_num):
789 """Get the name of the interface associated with a hostapd instance.
790
791 @param ap_num: int hostapd instance number.
792 @return string interface name (e.g. 'managed0').
793
794 """
795 if ap_num not in range(len(self.hostapd_instances)):
796 raise error.TestFail('Invalid instance number (%d) with %d '
797 'instances configured.' %
798 (ap_num, len(self.hostapd_instances)))
799
800 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700801 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800802
803
Christopher Wileycc770d92015-02-02 17:37:16 -0800804 def get_station_interface(self, instance):
805 """Get the name of the interface associated with a station.
806
807 @param instance: int station instance number.
808 @return string interface name (e.g. 'managed0').
809
810 """
811 if instance not in range(len(self.station_instances)):
812 raise error.TestFail('Invalid instance number (%d) with %d '
813 'instances configured.' %
814 (instance, len(self.station_instances)))
815
816 instance = self.station_instances[instance]
817 return instance.interface
818
819
Paul Stewart17350be2012-12-14 13:34:54 -0800820 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700821 """Return the MAC address of an AP in the test.
822
823 @param ap_num int index of local server to read the MAC address from.
824 @return string MAC address like 00:11:22:33:44:55.
825
826 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800827 interface_name = self.get_hostapd_interface(ap_num)
828 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800829 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800830
831
Christopher Wileya3effac2014-02-05 11:16:11 -0800832 def get_hostapd_phy(self, ap_num):
833 """Get name of phy for hostapd instance.
834
835 @param ap_num int index of hostapd instance.
836 @return string phy name of phy corresponding to hostapd's
837 managed interface.
838
839 """
840 interface = self.iw_runner.get_interface(
841 self.get_hostapd_interface(ap_num))
842 return interface.phy
843
844
Christopher Wileyeea12362013-12-12 17:24:29 -0800845 def deconfig(self):
846 """A legacy, deprecated alias for deconfig_aps."""
847 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800848
849
850 def deconfig_aps(self, instance=None, silent=False):
851 """De-configure an AP (will also bring wlan down).
852
853 @param instance: int or None. If instance is None, will bring down all
854 instances of hostapd.
855 @param silent: True if instances should be brought without de-authing
856 the DUT.
857
858 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800859 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700860 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700861
Christopher Wileyeea12362013-12-12 17:24:29 -0800862 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800863 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800864 if instance is not None:
865 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800866 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700867 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800868 local_servers = [server]
Paul Stewart326badb2012-12-18 14:18:54 -0800869 break
Paul Stewart21737812012-12-06 11:03:32 -0800870 else:
871 instances = self.hostapd_instances
872 self.hostapd_instances = []
Peter Qiu79784512015-04-14 22:59:55 -0700873 local_servers = copy.copy(self.local_servers)
Paul Stewart64cc4292011-06-01 10:59:36 -0700874
Paul Stewart21737812012-12-06 11:03:32 -0800875 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800876 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800877 # Deconfigure without notifying DUT. Remove the interface
878 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700879 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800880
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700881 self.kill_hostapd_instance(instance)
882 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800883 if self.station_instances:
Peter Qiu79784512015-04-14 22:59:55 -0700884 local_servers = copy.copy(self.local_servers)
Christopher Wiley408d1812014-01-13 15:27:43 -0800885 instance = self.station_instances.pop()
886 if instance.dev_type == 'ibss':
887 self.iw_runner.ibss_leave(instance.interface)
888 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700889 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700890 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800891 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800892 self.iw_runner.disconnect_station(instance.interface)
893 self.router.run('%s link set %s down' %
894 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700895
Paul Stewart326badb2012-12-18 14:18:54 -0800896 for server in local_servers:
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700897 self.stop_local_server(server)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700898
Matthew Wang08e868d2018-04-19 12:04:54 -0700899 for brif in range(self._brif_index):
900 self.delete_link('%s%d' %
901 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif))
902
903
904 def delete_link(self, name):
905 """Delete link using the `ip` command.
906
907 @param name string link name.
908
909 """
910 self.host.run('%s link del %s' % (self.cmd_ip, name),
911 ignore_status=True)
912
Paul Stewart7cb1f062010-06-10 15:46:20 -0700913
Peter Qiu4bfb1e92015-03-12 16:43:28 -0700914 def set_ap_interface_down(self, instance=0):
915 """Bring down the hostapd interface.
916
917 @param instance int router instance number.
918
919 """
920 self.host.run('%s link set %s down' %
921 (self.cmd_ip, self.get_hostapd_interface(instance)))
922
923
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800924 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700925 """Verify that the PMKSA auth was cached on a hostapd instance.
926
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800927 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700928
929 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700930 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800931 pmksa_match = 'PMK from PMKSA cache'
932 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
933 ignore_status=True)
934 if result.exit_status:
935 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800936
937
Christopher Wileye0afecb2013-11-11 10:54:23 -0800938 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700939 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800940 if instance is None:
941 instance = 0
942 if len(self.hostapd_instances) > 1:
943 raise error.TestFail('No instance of hostapd specified with '
944 'multiple instances present.')
945
Christopher Wiley3099be72013-11-06 16:49:02 -0800946 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700947 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700948
Christopher Wiley408d1812014-01-13 15:27:43 -0800949 if self.station_instances:
950 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700951
Christopher Wiley408d1812014-01-13 15:27:43 -0800952 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700953
954
Wade Guthriee4074dd2013-10-30 11:00:48 -0700955 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700956 """Deauthenticates a client described in params.
957
Wade Guthriee4074dd2013-10-30 11:00:48 -0700958 @param client_mac string containing the mac address of the client to be
959 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700960
961 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700962 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700963 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800964 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700965
Matthew Wangab74f8d2018-01-18 16:29:35 -0800966 def send_bss_tm_req(self, client_mac, neighbor_list):
967 """Send a BSS Transition Management Request to a client.
968
969 @param client_mac string containing the mac address of the client.
970 @param neighbor_list list of strings containing mac addresses of
971 candidate APs.
Matthew Wang6886f282018-06-05 14:42:20 -0700972 @return bool True if BSS_TM_REQ is sent successfully.
Matthew Wangab74f8d2018-01-18 16:29:35 -0800973
974 """
975 control_if = self.hostapd_instances[0].config_dict['ctrl_interface']
Matthew Wang6886f282018-06-05 14:42:20 -0700976 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' %
977 (self.cmd_hostapd_cli, control_if, client_mac,
978 ',0,0,0,0 neighbor='.join(neighbor_list)))
979 ret = self.router.run(command).stdout
980 if ret.splitlines()[-1] != 'OK':
981 return False
982 return True
Wade Guthriee4074dd2013-10-30 11:00:48 -0700983
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700984 def _prep_probe_response_footer(self, footer):
985 """Write probe response footer temporarily to a local file and copy
986 over to test router.
987
988 @param footer string containing bytes for the probe response footer.
989 @raises AutoservRunError: If footer file copy fails.
990
991 """
mukesh agrawale2102b82015-07-17 11:16:30 -0700992 with tempfile.NamedTemporaryFile() as fp:
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700993 fp.write(footer)
mukesh agrawal41a817c2015-07-22 10:00:43 -0700994 fp.flush()
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700995 try:
mukesh agrawale2102b82015-07-17 11:16:30 -0700996 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE)
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700997 except error.AutoservRunError:
998 logging.error('failed to copy footer file to AP')
999 raise
1000
mukesh agrawale2102b82015-07-17 11:16:30 -07001001
Peter Qiuc4beba02014-03-24 14:46:24 -07001002 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -07001003 """Injects a management frame into an active hostapd session.
1004
1005 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -07001006 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -07001007 @param instance int indicating which hostapd instance to inject into.
1008
1009 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001010 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001011 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001012 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -07001013 self.router.run('%s -i %s -t %s -c %d' %
1014 (self.cmd_send_management_frame, interface, frame_type,
1015 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -08001016 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -07001017
1018
Peter Qiuc4beba02014-03-24 14:46:24 -07001019 def send_management_frame(self, interface, frame_type, channel,
1020 ssid_prefix=None, num_bss=None,
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001021 frame_count=None, delay=None,
1022 dest_addr=None, probe_resp_footer=None):
Peter Qiuc4beba02014-03-24 14:46:24 -07001023 """
1024 Injects management frames on specify channel |frequency|.
1025
1026 This function will spawn off a new process to inject specified
1027 management frames |frame_type| at the specified interface |interface|.
1028
1029 @param interface string interface to inject frames.
1030 @param frame_type string message type.
1031 @param channel int targeted channel.
1032 @param ssid_prefix string SSID prefix.
1033 @param num_bss int number of BSS.
1034 @param frame_count int number of frames to send.
1035 @param delay int milliseconds delay between frames.
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001036 @param dest_addr string destination address (DA) MAC address.
1037 @param probe_resp_footer string footer for probe response.
Peter Qiuc4beba02014-03-24 14:46:24 -07001038
1039 @return int PID of the newly created process.
1040
1041 """
1042 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
1043 interface, frame_type, channel)
1044 if ssid_prefix is not None:
1045 command += ' -s %s' % (ssid_prefix)
1046 if num_bss is not None:
1047 command += ' -b %d' % (num_bss)
1048 if frame_count is not None:
1049 command += ' -n %d' % (frame_count)
1050 if delay is not None:
1051 command += ' -d %d' % (delay)
Rebecca Silberstein194b4582015-06-17 13:29:38 -07001052 if dest_addr is not None:
1053 command += ' -a %s' % (dest_addr)
1054 if probe_resp_footer is not None:
1055 self._prep_probe_response_footer(footer=probe_resp_footer)
1056 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
Peter Qiuc4beba02014-03-24 14:46:24 -07001057 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
1058 pid = int(self.router.run(command).stdout)
1059 return pid
1060
1061
Paul Stewart25536942013-08-15 17:33:42 -07001062 def detect_client_deauth(self, client_mac, instance=0):
1063 """Detects whether hostapd has logged a deauthentication from
1064 |client_mac|.
1065
1066 @param client_mac string the MAC address of the client to detect.
1067 @param instance int indicating which hostapd instance to query.
1068
1069 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001070 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -07001071 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001072 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -07001073 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
1074 ignore_status=True)
1075 return result.exit_status == 0
1076
1077
Paul Stewart4ae471e2013-09-04 15:42:35 -07001078 def detect_client_coexistence_report(self, client_mac, instance=0):
1079 """Detects whether hostapd has logged an action frame from
1080 |client_mac| indicating information about 20/40MHz BSS coexistence.
1081
1082 @param client_mac string the MAC address of the client to detect.
1083 @param instance int indicating which hostapd instance to query.
1084
1085 """
1086 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
1087 '.. .. .. .. .. .. .. .. .. .. %s '
1088 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
1089 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001090 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -07001091 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
1092 ignore_status=True)
1093 return result.exit_status == 0
1094
1095
Paul Stewart6ddeba72013-11-18 10:08:23 -08001096 def add_connected_peer(self, instance=0):
1097 """Configure a station connected to a running AP instance.
1098
1099 Extract relevant configuration objects from the hostap
1100 configuration for |instance| and generate a wpa_supplicant
1101 instance that connects to it. This allows the DUT to interact
1102 with a client entity that is also connected to the same AP. A
1103 full wpa_supplicant instance is necessary here (instead of just
1104 using the "iw" command to connect) since we want to enable
1105 advanced features such as TDLS.
1106
1107 @param instance int indicating which hostapd instance to connect to.
1108
1109 """
1110 if not self.hostapd_instances:
1111 raise error.TestFail('Hostapd is not configured.')
1112
Christopher Wiley408d1812014-01-13 15:27:43 -08001113 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -08001114 raise error.TestFail('Station is already configured.')
1115
Christopher Wiley408d1812014-01-13 15:27:43 -08001116 ssid = self.get_ssid(instance)
Christopher Wileyd6e503c2014-06-23 15:53:47 -07001117 hostap_conf = self.hostapd_instances[instance].config_dict
Paul Stewart6ddeba72013-11-18 10:08:23 -08001118 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
1119 hostap_conf['channel'])
Christopher Wileyab3964f2015-01-26 12:59:12 -08001120 self.configure_managed_station(
1121 ssid, frequency, self.local_peer_ip_address(instance))
1122 interface = self.station_instances[0].interface
1123 # Since we now have two network interfaces connected to the same
1124 # network, we need to disable the kernel's protection against
1125 # incoming packets to an "unexpected" interface.
1126 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
1127 interface)
1128
1129 # Similarly, we'd like to prevent the hostap interface from
1130 # replying to ARP requests for the peer IP address and vice
1131 # versa.
1132 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1133 interface)
1134 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1135 hostap_conf['interface'])
1136
1137
1138 def configure_managed_station(self, ssid, frequency, ip_addr):
1139 """Configure a router interface to connect as a client to a network.
1140
1141 @param ssid: string SSID of network to join.
1142 @param frequency: int frequency required to join the network.
1143 @param ip_addr: IP address to assign to this interface
1144 (e.g. '192.168.1.200').
1145
1146 """
Christopher Wiley408d1812014-01-13 15:27:43 -08001147 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -08001148
1149 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
1150 # require them...
1151 supplicant_config = (
1152 'network={\n'
1153 ' ssid="%(ssid)s"\n'
1154 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -08001155 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -08001156 )
1157
Christopher Wileyeea12362013-12-12 17:24:29 -08001158 conf_file = self.STATION_CONF_FILE_PATTERN % interface
1159 log_file = self.STATION_LOG_FILE_PATTERN % interface
1160 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -08001161
1162 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
1163 (conf_file, supplicant_config))
1164
1165 # Connect the station.
1166 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Ben Chan630d6b42015-02-13 18:14:45 -08001167 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s >%s 2>&1 &' %
Paul Stewart6ddeba72013-11-18 10:08:23 -08001168 (self.cmd_wpa_supplicant,
1169 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -08001170 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -08001171 self.router.run(start_command)
1172 self.iw_runner.wait_for_link(interface)
1173
1174 # Assign an IP address to this interface.
1175 self.router.run('%s addr add %s/24 dev %s' %
Christopher Wileyab3964f2015-01-26 12:59:12 -08001176 (self.cmd_ip, ip_addr, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -08001177 self.station_instances.append(
1178 StationInstance(ssid=ssid, interface=interface,
1179 dev_type='managed'))
Eric Carusoeddedd32014-10-13 14:41:49 -07001180
1181
1182 def send_magic_packet(self, dest_ip, dest_mac):
1183 """Sends a magic packet to the NIC with the given IP and MAC addresses.
1184
1185 @param dest_ip the IP address of the device to send the packet to
1186 @param dest_mac the hardware MAC address of the device
1187
1188 """
1189 # magic packet is 6 0xff bytes followed by the hardware address
1190 # 16 times
1191 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
1192 magic_packet = '\xff' * 6 + mac_bytes * 16
1193
1194 logging.info('Sending magic packet to %s...', dest_ip)
1195 self.host.run('python -uc "import socket, sys;'
1196 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
1197 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
1198 (dest_ip, UDP_DISCARD_PORT),
1199 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -08001200
1201
1202 def set_beacon_footer(self, interface, footer=''):
1203 """Sets the beacon footer (appended IE information) for this interface.
1204
1205 @param interface string interface to set the footer on.
1206 @param footer string footer to be set on the interface.
1207
1208 """
1209 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' %
1210 self.iw_runner.get_interface(interface).phy)
1211 if self.router.run('test -e %s' % footer_file,
1212 ignore_status=True).exit_status != 0:
1213 logging.info('Beacon footer file does not exist. Ignoring.')
1214 return
1215 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file))
Peter Qiu9a63a8b2015-02-03 09:08:16 -08001216
1217
1218 def setup_bridge_mode_dhcp_server(self):
1219 """Setup an DHCP server for bridge mode.
1220
1221 Setup an DHCP server on the master interface of the virtual ethernet
1222 pair, with peer interface connected to the bridge interface. This is
1223 used for testing APs in bridge mode.
1224
1225 """
1226 # Start a local server on master interface of virtual ethernet pair.
1227 self.start_local_server(
1228 self.get_virtual_ethernet_master_interface())
1229 # Add peer interface to the bridge.
1230 self.add_interface_to_bridge(
Ben Chan630d6b42015-02-13 18:14:45 -08001231 self.get_virtual_ethernet_peer_interface())