blob: dd9c9f357f265bc4ca1e442a87114153f36d781c [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
Christopher Wiley36039222014-12-13 18:27:52 -080014from autotest_lib.client.common_lib.cros import path_utils
Paul Stewart6ddeba72013-11-18 10:08:23 -080015from autotest_lib.client.common_lib.cros.network import interface
Christopher Wiley594570d2014-03-14 16:50:15 -070016from autotest_lib.client.common_lib.cros.network import netblock
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070017from autotest_lib.client.common_lib.cros.network import ping_runner
18from autotest_lib.server import hosts
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070019from autotest_lib.server import site_linux_system
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080020from autotest_lib.server.cros import dnsname_mangler
Christopher Wiley99d42c92013-07-09 16:40:16 -070021from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070022
Christopher Wiley408d1812014-01-13 15:27:43 -080023
24StationInstance = collections.namedtuple('StationInstance',
25 ['ssid', 'interface', 'dev_type'])
Christopher Wileyd6e503c2014-06-23 15:53:47 -070026HostapdInstance = collections.namedtuple('HostapdInstance',
27 ['ssid', 'conf_file', 'log_file',
Tien Chang097f7462014-09-03 17:30:29 -070028 'interface', 'config_dict',
mukesh agrawal768e7b32015-05-06 14:53:59 -070029 'stderr_log_file',
30 'scenario_name'])
Tien Chang097f7462014-09-03 17:30:29 -070031
Eric Carusoeddedd32014-10-13 14:41:49 -070032# Send magic packets here, so they can wake up the system but are otherwise
33# dropped.
34UDP_DISCARD_PORT = 9
Christopher Wiley408d1812014-01-13 15:27:43 -080035
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070036def build_router_hostname(client_hostname=None, router_hostname=None):
37 """Build a router hostname from a client hostname.
38
39 @param client_hostname: string hostname of DUT connected to a router.
40 @param router_hostname: string hostname of router.
41 @return string hostname of connected router or None if the hostname
42 cannot be inferred from the client hostname.
43
44 """
45 if not router_hostname and not client_hostname:
46 raise error.TestError('Either client_hostname or router_hostname must '
47 'be specified to build_router_hostname.')
48
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080049 return dnsname_mangler.get_router_addr(client_hostname,
50 cmdline_override=router_hostname)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070051
52
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070053def build_router_proxy(test_name='', client_hostname=None, router_addr=None,
54 enable_avahi=False):
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070055 """Build up a LinuxRouter object.
56
57 Verifies that the remote host responds to ping.
58 Either client_hostname or router_addr must be specified.
59
60 @param test_name: string name of this test (e.g. 'network_WiFi_TestName').
61 @param client_hostname: string hostname of DUT if we're in the lab.
62 @param router_addr: string DNS/IPv4 address to use for router host object.
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070063 @param enable_avahi: boolean True iff avahi should be started on the router.
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070064
65 @return LinuxRouter or raise error.TestError on failure.
66
67 """
Christopher Wiley297910b2014-07-01 13:53:19 -070068 router_hostname = build_router_hostname(client_hostname=client_hostname,
69 router_hostname=router_addr)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070070 logging.info('Connecting to router at %s', router_hostname)
71 ping_helper = ping_runner.PingRunner()
72 if not ping_helper.simple_ping(router_hostname):
73 raise error.TestError('Router at %s is not pingable.' %
74 router_hostname)
75
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -070076 return LinuxRouter(hosts.create_host(router_hostname), test_name,
77 enable_avahi=enable_avahi)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070078
Christopher Wiley408d1812014-01-13 15:27:43 -080079
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070080class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070081 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070082
83 This class implements test methods/steps that communicate with a
84 router implemented with Linux/mac80211. The router must
85 be pre-configured to enable ssh access and have a mac80211-based
86 wireless device. We also assume hostapd 0.7.x and iw are present
87 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070088
Sam Leffler6969d1d2010-03-15 16:07:11 -070089 """
90
Christopher Wiley08aafb02014-04-22 17:38:21 -070091 KNOWN_TEST_PREFIX = 'network_WiFi_'
Christopher Wileyd6e503c2014-06-23 15:53:47 -070092 POLLING_INTERVAL_SECONDS = 0.5
Christopher Wiley1defc242013-09-18 10:28:37 -070093 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070094 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070095 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070096
Christopher Wileyeea12362013-12-12 17:24:29 -080097 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
98 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
Tien Chang097f7462014-09-03 17:30:29 -070099 HOSTAPD_STDERR_LOG_FILE_PATTERN = '/tmp/hostapd-stderr-test-%s.log'
Paul Stewart80bb3372014-01-22 15:06:08 -0800100 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -0800101 HOSTAPD_DRIVER_NAME = 'nl80211'
102
103 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
104 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
105 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
106
Peter Qiuc4beba02014-03-24 14:46:24 -0700107 MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
108
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700109 PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer'
110
Paul Stewart51b0f382013-06-12 09:03:02 -0700111 def get_capabilities(self):
112 """@return iterable object of AP capabilities for this system."""
mukesh agrawal064ea972015-06-26 10:00:53 -0700113 caps = set()
Paul Stewart51b0f382013-06-12 09:03:02 -0700114 try:
Christopher Wiley36039222014-12-13 18:27:52 -0800115 self.cmd_send_management_frame = path_utils.must_be_installed(
116 '/usr/bin/send_management_frame', host=self.host)
Paul Stewart51b0f382013-06-12 09:03:02 -0700117 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
118 except error.TestFail:
119 pass
120 return super(LinuxRouter, self).get_capabilities().union(caps)
121
122
Christopher Wiley408d1812014-01-13 15:27:43 -0800123 @property
124 def router(self):
125 """Deprecated. Use self.host instead.
126
127 @return Host object representing the remote router.
128
129 """
130 return self.host
131
132
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700133 @property
134 def wifi_ip(self):
135 """Simple accessor for the WiFi IP when there is only one AP.
136
137 @return string IP of WiFi interface.
138
139 """
140 if len(self.local_servers) != 1:
141 raise error.TestError('Could not pick a WiFi IP to return.')
142
143 return self.get_wifi_ip(0)
144
145
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700146 def __init__(self, host, test_name, enable_avahi=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700147 """Build a LinuxRouter.
148
149 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -0700150 @param test_name string name of this test. Used in SSID creation.
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700151 @param enable_avahi: boolean True iff avahi should be started on the
152 router.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700153
154 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800155 super(LinuxRouter, self).__init__(host, 'router')
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700156 self._ssid_prefix = test_name
157 self._enable_avahi = enable_avahi
158 self.__setup()
Wade Guthrie24d1e312012-04-24 16:53:40 -0700159
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700160
161 def __setup(self):
162 """Set up this system.
163
164 Can be used either to complete initialization of a LinuxRouter
165 object, or to re-establish a good state after a reboot.
166
167 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800168 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wiley36039222014-12-13 18:27:52 -0800169 self.cmd_hostapd = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700170 '/usr/sbin/hostapd', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800171 self.cmd_hostapd_cli = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700172 '/usr/sbin/hostapd_cli', host=self.host)
Christopher Wiley36039222014-12-13 18:27:52 -0800173 self.cmd_wpa_supplicant = path_utils.must_be_installed(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700174 '/usr/sbin/wpa_supplicant', host=self.host)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700175 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
176 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700177
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700178 # Log the most recent message on the router so that we can rebuild the
179 # suffix relevant to us when debugging failures.
180 last_log_line = self.host.run('tail -1 /var/log/messages').stdout
181 # We're trying to get the timestamp from:
182 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah
183 self._log_start_timestamp = last_log_line.strip().split(None, 2)[0]
184 logging.debug('Will only retrieve logs after %s.',
185 self._log_start_timestamp)
186
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700187 # hostapd configuration persists throughout the test, subsequent
188 # 'config' commands only modify it.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700189 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700190 # Many of our tests start with an uninteresting prefix.
191 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700192 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
193 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700194
Christopher Wileyeea12362013-12-12 17:24:29 -0800195 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700196 self.local_servers = []
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700197 self.server_address_index = []
Paul Stewart548cf452012-11-27 17:46:23 -0800198 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800199 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700200 self.dhcp_low = 1
201 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700202
Paul Stewart548cf452012-11-27 17:46:23 -0800203 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700204 self._kill_process_instance('hostapd', timeout_seconds=30)
205 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700206
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700207 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700208 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700209
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800210 self.enable_all_antennas()
Peter Qiu2f973252014-02-20 15:30:37 -0800211
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700212 # Some tests want this functionality, but otherwise, it's a distraction.
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700213 if self._enable_avahi:
Christopher Wiley4d7bd3e2015-03-18 09:05:04 -0700214 self.host.run('start avahi', ignore_status=True)
215 else:
216 self.host.run('stop avahi', ignore_status=True)
217
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700218
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700219 def close(self):
220 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800221 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700222 # dnsmasq and hostapd cause interesting events to go to system logs.
223 # Retrieve only the suffix of the logs after the timestamp we stored on
224 # router creation.
225 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" %
226 self._log_start_timestamp, ignore_status=True)
227 self.host.get_file('/tmp/router_log', 'debug/router_host_messages')
Christopher Wiley408d1812014-01-13 15:27:43 -0800228 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700229
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700230
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700231 def reboot(self, timeout):
232 """Reboot this router, and restore it to a known-good state.
233
234 @param timeout Maximum seconds to wait for router to return.
235
236 """
237 super(LinuxRouter, self).reboot(timeout)
238 self.__setup()
239
240
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700241 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700242 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700243 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700244
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700245
Peter Qiuc89c9a22014-02-27 10:03:55 -0800246 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700247 """Start a hostapd instance described by conf.
248
Christopher Wileyeea12362013-12-12 17:24:29 -0800249 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700250
251 """
Paul Stewart548cf452012-11-27 17:46:23 -0800252 # Figure out the correct interface.
mukesh agrawal43644aa2015-07-06 14:40:22 -0700253 if configuration.min_streams is None:
254 interface = self.get_wlanif(configuration.frequency, 'managed')
255 else:
256 interface = self.get_wlanif(
257 configuration.frequency, 'managed', configuration.min_streams)
Paul Stewart326badb2012-12-18 14:18:54 -0800258
Christopher Wileyeea12362013-12-12 17:24:29 -0800259 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
260 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
Tien Chang097f7462014-09-03 17:30:29 -0700261 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800262 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800263 hostapd_conf_dict = configuration.generate_dict(
264 interface, control_interface,
Peter Qiubde37ac2014-12-18 23:40:57 -0800265 self.build_unique_ssid(suffix=configuration.ssid_suffix))
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700266 logging.debug('hostapd parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800267
268 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800269 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
270 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800271 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800272
273 # Run hostapd.
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700274 logging.info('Starting hostapd on %s(%s) channel=%s...',
275 interface, self.iw_runner.get_interface(interface).phy,
276 configuration.channel)
Christopher Wiley1defc242013-09-18 10:28:37 -0700277 self.router.run('rm %s' % log_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800278 self.router.run('stop wpasupplicant', ignore_status=True)
Tien Chang097f7462014-09-03 17:30:29 -0700279 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % (
280 self.cmd_hostapd, conf_file, log_file, stderr_log_file)
Christopher Wiley0f64b842014-04-30 15:43:50 -0700281 pid = int(self.router.run(start_command).stdout.strip())
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700282 self.hostapd_instances.append(HostapdInstance(
283 hostapd_conf_dict['ssid'],
284 conf_file,
285 log_file,
286 interface,
Tien Chang097f7462014-09-03 17:30:29 -0700287 hostapd_conf_dict.copy(),
mukesh agrawal768e7b32015-05-06 14:53:59 -0700288 stderr_log_file,
289 configuration.scenario_name))
Paul Stewart548cf452012-11-27 17:46:23 -0800290
Christopher Wileyeea12362013-12-12 17:24:29 -0800291 # Wait for confirmation that the router came up.
Christopher Wileyeea12362013-12-12 17:24:29 -0800292 logging.info('Waiting for hostapd to startup.')
293 start_time = time.time()
294 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
295 success = self.router.run(
mukesh agrawal09319912015-06-30 15:54:16 -0700296 'grep "Setup of interface done" %s' % log_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800297 ignore_status=True).exit_status == 0
298 if success:
299 break
300
301 # A common failure is an invalid router configuration.
302 # Detect this and exit early if we see it.
303 bad_config = self.router.run(
304 'grep "Interface initialization failed" %s' % log_file,
305 ignore_status=True).exit_status == 0
306 if bad_config:
307 raise error.TestFail('hostapd failed to initialize AP '
308 'interface.')
309
310 if pid:
311 early_exit = self.router.run('kill -0 %d' % pid,
312 ignore_status=True).exit_status
313 if early_exit:
314 raise error.TestFail('hostapd process terminated.')
315
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700316 time.sleep(self.POLLING_INTERVAL_SECONDS)
Christopher Wileyeea12362013-12-12 17:24:29 -0800317 else:
318 raise error.TestFail('Timed out while waiting for hostapd '
319 'to start.')
320
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700321
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700322 def _kill_process_instance(self,
323 process,
324 instance=None,
325 timeout_seconds=10,
326 ignore_timeouts=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700327 """Kill a process on the router.
328
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700329 Kills remote program named |process| (optionally only a specific
330 |instance|). Wait |timeout_seconds| for |process| to die
331 before returning. If |ignore_timeouts| is False, raise
332 a TestError on timeouts.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700333
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700334 @param process: string name of process to kill.
335 @param instance: string fragment of the command line unique to
336 this instance of the remote process.
337 @param timeout_seconds: float timeout in seconds to wait.
338 @param ignore_timeouts: True iff we should ignore failures to
339 kill processes.
340 @return True iff the specified process has exited.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700341
Thieu Le7b23a542012-01-27 15:54:48 -0800342 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700343 if instance is not None:
Christopher Wileya4754c02014-06-30 16:37:52 -0700344 search_arg = '-f "^%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800345 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800346 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800347
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700348 self.host.run('pkill %s' % search_arg, ignore_status=True)
349 is_dead = False
350 start_time = time.time()
351 while not is_dead and time.time() - start_time < timeout_seconds:
352 time.sleep(self.POLLING_INTERVAL_SECONDS)
353 is_dead = self.host.run(
Christopher Wileya4754c02014-06-30 16:37:52 -0700354 'pgrep -l %s' % search_arg,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700355 ignore_status=True).exit_status != 0
356 if is_dead or ignore_timeouts:
357 return is_dead
Paul Stewart326badb2012-12-18 14:18:54 -0800358
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700359 raise error.TestError(
360 'Timed out waiting for %s%s to die' %
361 (process,
362 '' if instance is None else ' (instance=%s)' % instance))
Paul Stewart326badb2012-12-18 14:18:54 -0800363
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700364
Paul Stewart326badb2012-12-18 14:18:54 -0800365 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700366 """Kills a hostapd instance.
367
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700368 @param instance HostapdInstance object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700369
370 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700371 is_dead = self._kill_process_instance(
Christopher Wileya4754c02014-06-30 16:37:52 -0700372 self.cmd_hostapd,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700373 instance=instance.conf_file,
374 timeout_seconds=30,
375 ignore_timeouts=True)
mukesh agrawal768e7b32015-05-06 14:53:59 -0700376 if instance.scenario_name:
377 log_identifier = instance.scenario_name
378 else:
379 log_identifier = '%d_%s' % (
380 self._total_hostapd_instances, instance.interface)
381 files_to_copy = [(instance.log_file,
382 'debug/hostapd_router_%s.log' % log_identifier),
Tien Chang097f7462014-09-03 17:30:29 -0700383 (instance.stderr_log_file,
mukesh agrawal768e7b32015-05-06 14:53:59 -0700384 'debug/hostapd_router_%s.stderr.log' %
385 log_identifier)]
Tien Chang097f7462014-09-03 17:30:29 -0700386 for remote_file, local_file in files_to_copy:
387 if self.host.run('ls %s >/dev/null 2>&1' % remote_file,
388 ignore_status=True).exit_status:
389 logging.error('Did not collect hostapd log file because '
390 'it was missing.')
391 else:
392 self.router.get_file(remote_file, local_file)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700393 self._total_hostapd_instances += 1
394 if not is_dead:
395 raise error.TestError('Timed out killing hostapd.')
Paul Stewart21737812012-12-06 11:03:32 -0800396
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700397
Peter Qiubde37ac2014-12-18 23:40:57 -0800398 def build_unique_ssid(self, suffix=''):
Tien Chang097f7462014-09-03 17:30:29 -0700399 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
400 the number of APs we've constructed already.
401
402 @param suffix string to append to SSID
403
404 """
Christopher Wiley08aafb02014-04-22 17:38:21 -0700405 base = len(self.SUFFIX_LETTERS)
406 number = self._number_unique_ssids
407 self._number_unique_ssids += 1
408 unique = ''
409 while number or not unique:
410 unique = self.SUFFIX_LETTERS[number % base] + unique
411 number = number / base
412 # And salt the SSID so that tests running in adjacent cells are unlikely
413 # to pick the same SSID and we're resistent to beacons leaking out of
414 # cells.
415 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
416 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700417
418
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700419 def hostap_configure(self, configuration, multi_interface=None):
420 """Build up a hostapd configuration file and start hostapd.
421
422 Also setup a local server if this router supports them.
423
424 @param configuration HosetapConfig object.
425 @param multi_interface bool True iff multiple interfaces allowed.
426
427 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800428 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800429 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700430 self.deconfig()
Kris Rambishb9b46852014-12-19 15:29:58 -0800431 if configuration.is_11ac:
432 router_caps = self.get_capabilities()
433 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps:
434 raise error.TestNAError('Router does not have AC support')
435
Peter Qiuc89c9a22014-02-27 10:03:55 -0800436 self.start_hostapd(configuration)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700437 interface = self.hostapd_instances[-1].interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800438 self.iw_runner.set_tx_power(interface, 'auto')
Paul Stewart65fb9212014-12-01 19:54:20 -0800439 self.set_beacon_footer(interface, configuration.beacon_footer)
Christopher Wileyeea12362013-12-12 17:24:29 -0800440 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700441 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700442
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700443
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700444 def ibss_configure(self, config):
445 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700446
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700447 Extract relevant configuration objects from |config| despite not
448 actually being a hostap managed endpoint.
449
450 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700451
452 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800453 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700454 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800455 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800456 ssid = (config.ssid or
457 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800458 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700459 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800460 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700461 # Always start a local server.
462 self.start_local_server(interface)
463 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800464 self.station_instances.append(
465 StationInstance(ssid=ssid, interface=interface,
466 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800467
468
Paul Stewart2bd823b2012-11-21 15:03:37 -0800469 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700470 """Get the local server address for an interface.
471
472 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700473 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700474
475 @param index int describing which local server this is for.
476
477 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700478 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800479
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700480
Paul Stewart6ddeba72013-11-18 10:08:23 -0800481 def local_peer_ip_address(self, index):
482 """Get the IP address allocated for the peer associated to the AP.
483
484 This address is assigned to a locally associated peer device that
485 is created for the DUT to perform connectivity tests with.
486 When we have multiple local servers, we give them static IP addresses
487 like 192.168.*.253.
488
489 @param index int describing which local server this is for.
490
491 """
492 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
493
494
495 def local_peer_mac_address(self):
496 """Get the MAC address of the peer interface.
497
498 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800499
Paul Stewart6ddeba72013-11-18 10:08:23 -0800500 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800501 iface = interface.Interface(self.station_instances[0].interface,
502 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800503 return iface.mac_address
504
505
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700506 def _get_unused_server_address_index(self):
507 """@return an unused server address index."""
508 for address_index in range(0, 256):
509 if address_index not in self.server_address_index:
510 return address_index
511 raise error.TestFail('No available server address index')
512
513
514 def change_server_address_index(self, ap_num=0, server_address_index=None):
515 """Restart the local server with a different server address index.
516
517 This will restart the local server with different gateway IP address
518 and DHCP address ranges.
519
520 @param ap_num: int hostapd instance number.
521 @param server_address_index: int server address index.
522
523 """
524 interface = self.local_servers[ap_num]['interface'];
525 # Get an unused server address index if one is not specified, which
526 # will be different from the one that's currently in used.
527 if server_address_index is None:
528 server_address_index = self._get_unused_server_address_index()
529
530 # Restart local server with the new server address index.
531 self.stop_local_server(self.local_servers[ap_num])
532 self.start_local_server(interface,
533 ap_num=ap_num,
534 server_address_index=server_address_index)
535
536
537 def start_local_server(self,
538 interface,
539 ap_num=None,
540 server_address_index=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700541 """Start a local server on an interface.
542
543 @param interface string (e.g. wlan0)
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700544 @param ap_num int the ap instance to start the server for
545 @param server_address_index int server address index
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700546
547 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700548 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700549
550 if len(self.local_servers) >= 256:
551 raise error.TestFail('Exhausted available local servers')
552
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700553 # Get an unused server address index if one is not specified.
554 # Validate server address index if one is specified.
555 if server_address_index is None:
556 server_address_index = self._get_unused_server_address_index()
557 elif server_address_index in self.server_address_index:
558 raise error.TestFail('Server address index %d already in used' %
559 server_address_index)
560
Christopher Wiley684878e2014-12-18 14:32:48 -0800561 server_addr = netblock.from_addr(
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700562 self.local_server_address(server_address_index),
Christopher Wiley594570d2014-03-14 16:50:15 -0700563 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700564
565 params = {}
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700566 params['address_index'] = server_address_index
Christopher Wiley594570d2014-03-14 16:50:15 -0700567 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700568 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700569 (server_addr.get_addr_in_block(1),
570 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700571 params['interface'] = interface
Christopher Wiley594570d2014-03-14 16:50:15 -0700572 params['ip_params'] = ('%s broadcast %s dev %s' %
573 (server_addr.netblock,
574 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700575 interface))
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700576 if ap_num is None:
577 self.local_servers.append(params)
578 else:
579 self.local_servers.insert(ap_num, params)
580 self.server_address_index.append(server_address_index)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700581
Christopher Wiley594570d2014-03-14 16:50:15 -0700582 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700583 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700584 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700585 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700586 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700587 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800588 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700589
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700590
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700591 def stop_local_server(self, server):
592 """Stop a local server on the router
593
594 @param server object server configuration parameters.
595
596 """
597 self.stop_dhcp_server(server['interface'])
598 self.router.run("%s addr del %s" %
599 (self.cmd_ip, server['ip_params']),
600 ignore_status=True)
601 self.server_address_index.remove(server['address_index'])
602 self.local_servers.remove(server)
603
604
Paul Stewart548cf452012-11-27 17:46:23 -0800605 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700606 """Start a dhcp server on an interface.
607
608 @param interface string (e.g. wlan0)
609
610 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800611 for server in self.local_servers:
612 if server['interface'] == interface:
613 params = server
614 break
615 else:
616 raise error.TestFail('Could not find local server '
617 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700618 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800619 dhcpd_conf_file = self.dhcpd_conf % interface
620 dhcp_conf = '\n'.join([
621 'port=0', # disables DNS server
622 'bind-interfaces',
623 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700624 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
625 server_addr.get_addr_in_block(128))),
Christopher Wileyeea12362013-12-12 17:24:29 -0800626 'interface=%s' % params['interface'],
627 'dhcp-leasefile=%s' % self.dhcpd_leases])
628 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
629 (dhcpd_conf_file, dhcp_conf))
630 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700631
632
Paul Stewart326badb2012-12-18 14:18:54 -0800633 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700634 """Stop a dhcp server on the router.
635
636 @param instance string instance to kill.
637
638 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700639 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800640
641
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800642 def get_wifi_channel(self, ap_num):
643 """Return channel of BSS corresponding to |ap_num|.
644
645 @param ap_num int which BSS to get the channel of.
646 @return int primary channel of BSS.
647
648 """
649 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700650 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800651
652
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700653 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700654 """Return IP address on the WiFi subnet of a local server on the router.
655
656 If no local servers are configured (e.g. for an RSPro), a TestFail will
657 be raised.
658
659 @param ap_num int which local server to get an address from.
660
661 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700662 if not self.local_servers:
663 raise error.TestError('No IP address assigned')
664
665 return self.local_servers[ap_num]['netblock'].addr
666
667
668 def get_wifi_ip_subnet(self, ap_num):
669 """Return subnet of WiFi AP instance.
670
671 If no APs are configured a TestError will be raised.
672
673 @param ap_num int which local server to get an address from.
674
675 """
676 if not self.local_servers:
677 raise error.TestError('No APs configured.')
678
679 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700680
681
Christopher Wileya3effac2014-02-05 11:16:11 -0800682 def get_hostapd_interface(self, ap_num):
683 """Get the name of the interface associated with a hostapd instance.
684
685 @param ap_num: int hostapd instance number.
686 @return string interface name (e.g. 'managed0').
687
688 """
689 if ap_num not in range(len(self.hostapd_instances)):
690 raise error.TestFail('Invalid instance number (%d) with %d '
691 'instances configured.' %
692 (ap_num, len(self.hostapd_instances)))
693
694 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700695 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800696
697
Christopher Wileycc770d92015-02-02 17:37:16 -0800698 def get_station_interface(self, instance):
699 """Get the name of the interface associated with a station.
700
701 @param instance: int station instance number.
702 @return string interface name (e.g. 'managed0').
703
704 """
705 if instance not in range(len(self.station_instances)):
706 raise error.TestFail('Invalid instance number (%d) with %d '
707 'instances configured.' %
708 (instance, len(self.station_instances)))
709
710 instance = self.station_instances[instance]
711 return instance.interface
712
713
Paul Stewart17350be2012-12-14 13:34:54 -0800714 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700715 """Return the MAC address of an AP in the test.
716
717 @param ap_num int index of local server to read the MAC address from.
718 @return string MAC address like 00:11:22:33:44:55.
719
720 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800721 interface_name = self.get_hostapd_interface(ap_num)
722 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800723 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800724
725
Christopher Wileya3effac2014-02-05 11:16:11 -0800726 def get_hostapd_phy(self, ap_num):
727 """Get name of phy for hostapd instance.
728
729 @param ap_num int index of hostapd instance.
730 @return string phy name of phy corresponding to hostapd's
731 managed interface.
732
733 """
734 interface = self.iw_runner.get_interface(
735 self.get_hostapd_interface(ap_num))
736 return interface.phy
737
738
Christopher Wileyeea12362013-12-12 17:24:29 -0800739 def deconfig(self):
740 """A legacy, deprecated alias for deconfig_aps."""
741 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800742
743
744 def deconfig_aps(self, instance=None, silent=False):
745 """De-configure an AP (will also bring wlan down).
746
747 @param instance: int or None. If instance is None, will bring down all
748 instances of hostapd.
749 @param silent: True if instances should be brought without de-authing
750 the DUT.
751
752 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800753 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700754 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700755
Christopher Wileyeea12362013-12-12 17:24:29 -0800756 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800757 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800758 if instance is not None:
759 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800760 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700761 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800762 local_servers = [server]
Paul Stewart326badb2012-12-18 14:18:54 -0800763 break
Paul Stewart21737812012-12-06 11:03:32 -0800764 else:
765 instances = self.hostapd_instances
766 self.hostapd_instances = []
Peter Qiu79784512015-04-14 22:59:55 -0700767 local_servers = copy.copy(self.local_servers)
Paul Stewart64cc4292011-06-01 10:59:36 -0700768
Paul Stewart21737812012-12-06 11:03:32 -0800769 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800770 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800771 # Deconfigure without notifying DUT. Remove the interface
772 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700773 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800774
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700775 self.kill_hostapd_instance(instance)
776 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800777 if self.station_instances:
Peter Qiu79784512015-04-14 22:59:55 -0700778 local_servers = copy.copy(self.local_servers)
Christopher Wiley408d1812014-01-13 15:27:43 -0800779 instance = self.station_instances.pop()
780 if instance.dev_type == 'ibss':
781 self.iw_runner.ibss_leave(instance.interface)
782 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700783 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700784 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800785 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800786 self.iw_runner.disconnect_station(instance.interface)
787 self.router.run('%s link set %s down' %
788 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700789
Paul Stewart326badb2012-12-18 14:18:54 -0800790 for server in local_servers:
Peter Qiucc4bd9e2015-04-07 14:22:40 -0700791 self.stop_local_server(server)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700792
Paul Stewart7cb1f062010-06-10 15:46:20 -0700793
Peter Qiu4bfb1e92015-03-12 16:43:28 -0700794 def set_ap_interface_down(self, instance=0):
795 """Bring down the hostapd interface.
796
797 @param instance int router instance number.
798
799 """
800 self.host.run('%s link set %s down' %
801 (self.cmd_ip, self.get_hostapd_interface(instance)))
802
803
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800804 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700805 """Verify that the PMKSA auth was cached on a hostapd instance.
806
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800807 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700808
809 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700810 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800811 pmksa_match = 'PMK from PMKSA cache'
812 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
813 ignore_status=True)
814 if result.exit_status:
815 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800816
817
Christopher Wileye0afecb2013-11-11 10:54:23 -0800818 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700819 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800820 if instance is None:
821 instance = 0
822 if len(self.hostapd_instances) > 1:
823 raise error.TestFail('No instance of hostapd specified with '
824 'multiple instances present.')
825
Christopher Wiley3099be72013-11-06 16:49:02 -0800826 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700827 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700828
Christopher Wiley408d1812014-01-13 15:27:43 -0800829 if self.station_instances:
830 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700831
Christopher Wiley408d1812014-01-13 15:27:43 -0800832 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700833
834
Wade Guthriee4074dd2013-10-30 11:00:48 -0700835 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700836 """Deauthenticates a client described in params.
837
Wade Guthriee4074dd2013-10-30 11:00:48 -0700838 @param client_mac string containing the mac address of the client to be
839 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700840
841 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700842 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700843 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800844 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700845
846
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700847 def _prep_probe_response_footer(self, footer):
848 """Write probe response footer temporarily to a local file and copy
849 over to test router.
850
851 @param footer string containing bytes for the probe response footer.
852 @raises AutoservRunError: If footer file copy fails.
853
854 """
mukesh agrawale2102b82015-07-17 11:16:30 -0700855 with tempfile.NamedTemporaryFile() as fp:
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700856 fp.write(footer)
mukesh agrawal41a817c2015-07-22 10:00:43 -0700857 fp.flush()
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700858 try:
mukesh agrawale2102b82015-07-17 11:16:30 -0700859 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE)
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700860 except error.AutoservRunError:
861 logging.error('failed to copy footer file to AP')
862 raise
863
mukesh agrawale2102b82015-07-17 11:16:30 -0700864
Peter Qiuc4beba02014-03-24 14:46:24 -0700865 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -0700866 """Injects a management frame into an active hostapd session.
867
868 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -0700869 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -0700870 @param instance int indicating which hostapd instance to inject into.
871
872 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700873 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800874 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700875 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -0700876 self.router.run('%s -i %s -t %s -c %d' %
877 (self.cmd_send_management_frame, interface, frame_type,
878 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800879 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700880
881
Peter Qiuc4beba02014-03-24 14:46:24 -0700882 def setup_management_frame_interface(self, channel):
883 """
884 Setup interface for injecting management frames.
885
886 @param channel int channel to inject the frames.
887
888 @return string name of the interface.
889
890 """
891 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
892 channel)
893 interface = self.get_wlanif(frequency, 'monitor')
Peter Qiuc4beba02014-03-24 14:46:24 -0700894 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Peter Qiu39e38f62014-09-30 15:33:10 -0700895 self.iw_runner.set_freq(interface, frequency)
Peter Qiuc4beba02014-03-24 14:46:24 -0700896 return interface
897
898
899 def send_management_frame(self, interface, frame_type, channel,
900 ssid_prefix=None, num_bss=None,
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700901 frame_count=None, delay=None,
902 dest_addr=None, probe_resp_footer=None):
Peter Qiuc4beba02014-03-24 14:46:24 -0700903 """
904 Injects management frames on specify channel |frequency|.
905
906 This function will spawn off a new process to inject specified
907 management frames |frame_type| at the specified interface |interface|.
908
909 @param interface string interface to inject frames.
910 @param frame_type string message type.
911 @param channel int targeted channel.
912 @param ssid_prefix string SSID prefix.
913 @param num_bss int number of BSS.
914 @param frame_count int number of frames to send.
915 @param delay int milliseconds delay between frames.
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700916 @param dest_addr string destination address (DA) MAC address.
917 @param probe_resp_footer string footer for probe response.
Peter Qiuc4beba02014-03-24 14:46:24 -0700918
919 @return int PID of the newly created process.
920
921 """
922 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
923 interface, frame_type, channel)
924 if ssid_prefix is not None:
925 command += ' -s %s' % (ssid_prefix)
926 if num_bss is not None:
927 command += ' -b %d' % (num_bss)
928 if frame_count is not None:
929 command += ' -n %d' % (frame_count)
930 if delay is not None:
931 command += ' -d %d' % (delay)
Rebecca Silberstein194b4582015-06-17 13:29:38 -0700932 if dest_addr is not None:
933 command += ' -a %s' % (dest_addr)
934 if probe_resp_footer is not None:
935 self._prep_probe_response_footer(footer=probe_resp_footer)
936 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE)
Peter Qiuc4beba02014-03-24 14:46:24 -0700937 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
938 pid = int(self.router.run(command).stdout)
939 return pid
940
941
Paul Stewart25536942013-08-15 17:33:42 -0700942 def detect_client_deauth(self, client_mac, instance=0):
943 """Detects whether hostapd has logged a deauthentication from
944 |client_mac|.
945
946 @param client_mac string the MAC address of the client to detect.
947 @param instance int indicating which hostapd instance to query.
948
949 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700950 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -0700951 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700952 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -0700953 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
954 ignore_status=True)
955 return result.exit_status == 0
956
957
Paul Stewart4ae471e2013-09-04 15:42:35 -0700958 def detect_client_coexistence_report(self, client_mac, instance=0):
959 """Detects whether hostapd has logged an action frame from
960 |client_mac| indicating information about 20/40MHz BSS coexistence.
961
962 @param client_mac string the MAC address of the client to detect.
963 @param instance int indicating which hostapd instance to query.
964
965 """
966 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
967 '.. .. .. .. .. .. .. .. .. .. %s '
968 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
969 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700970 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -0700971 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
972 ignore_status=True)
973 return result.exit_status == 0
974
975
Paul Stewart6ddeba72013-11-18 10:08:23 -0800976 def add_connected_peer(self, instance=0):
977 """Configure a station connected to a running AP instance.
978
979 Extract relevant configuration objects from the hostap
980 configuration for |instance| and generate a wpa_supplicant
981 instance that connects to it. This allows the DUT to interact
982 with a client entity that is also connected to the same AP. A
983 full wpa_supplicant instance is necessary here (instead of just
984 using the "iw" command to connect) since we want to enable
985 advanced features such as TDLS.
986
987 @param instance int indicating which hostapd instance to connect to.
988
989 """
990 if not self.hostapd_instances:
991 raise error.TestFail('Hostapd is not configured.')
992
Christopher Wiley408d1812014-01-13 15:27:43 -0800993 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800994 raise error.TestFail('Station is already configured.')
995
Christopher Wiley408d1812014-01-13 15:27:43 -0800996 ssid = self.get_ssid(instance)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700997 hostap_conf = self.hostapd_instances[instance].config_dict
Paul Stewart6ddeba72013-11-18 10:08:23 -0800998 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
999 hostap_conf['channel'])
Christopher Wileyab3964f2015-01-26 12:59:12 -08001000 self.configure_managed_station(
1001 ssid, frequency, self.local_peer_ip_address(instance))
1002 interface = self.station_instances[0].interface
1003 # Since we now have two network interfaces connected to the same
1004 # network, we need to disable the kernel's protection against
1005 # incoming packets to an "unexpected" interface.
1006 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
1007 interface)
1008
1009 # Similarly, we'd like to prevent the hostap interface from
1010 # replying to ARP requests for the peer IP address and vice
1011 # versa.
1012 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1013 interface)
1014 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
1015 hostap_conf['interface'])
1016
1017
1018 def configure_managed_station(self, ssid, frequency, ip_addr):
1019 """Configure a router interface to connect as a client to a network.
1020
1021 @param ssid: string SSID of network to join.
1022 @param frequency: int frequency required to join the network.
1023 @param ip_addr: IP address to assign to this interface
1024 (e.g. '192.168.1.200').
1025
1026 """
Christopher Wiley408d1812014-01-13 15:27:43 -08001027 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -08001028
1029 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
1030 # require them...
1031 supplicant_config = (
1032 'network={\n'
1033 ' ssid="%(ssid)s"\n'
1034 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -08001035 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -08001036 )
1037
Christopher Wileyeea12362013-12-12 17:24:29 -08001038 conf_file = self.STATION_CONF_FILE_PATTERN % interface
1039 log_file = self.STATION_LOG_FILE_PATTERN % interface
1040 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -08001041
1042 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
1043 (conf_file, supplicant_config))
1044
1045 # Connect the station.
1046 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Ben Chan630d6b42015-02-13 18:14:45 -08001047 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s >%s 2>&1 &' %
Paul Stewart6ddeba72013-11-18 10:08:23 -08001048 (self.cmd_wpa_supplicant,
1049 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -08001050 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -08001051 self.router.run(start_command)
1052 self.iw_runner.wait_for_link(interface)
1053
1054 # Assign an IP address to this interface.
1055 self.router.run('%s addr add %s/24 dev %s' %
Christopher Wileyab3964f2015-01-26 12:59:12 -08001056 (self.cmd_ip, ip_addr, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -08001057 self.station_instances.append(
1058 StationInstance(ssid=ssid, interface=interface,
1059 dev_type='managed'))
Eric Carusoeddedd32014-10-13 14:41:49 -07001060
1061
1062 def send_magic_packet(self, dest_ip, dest_mac):
1063 """Sends a magic packet to the NIC with the given IP and MAC addresses.
1064
1065 @param dest_ip the IP address of the device to send the packet to
1066 @param dest_mac the hardware MAC address of the device
1067
1068 """
1069 # magic packet is 6 0xff bytes followed by the hardware address
1070 # 16 times
1071 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
1072 magic_packet = '\xff' * 6 + mac_bytes * 16
1073
1074 logging.info('Sending magic packet to %s...', dest_ip)
1075 self.host.run('python -uc "import socket, sys;'
1076 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
1077 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
1078 (dest_ip, UDP_DISCARD_PORT),
1079 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -08001080
1081
1082 def set_beacon_footer(self, interface, footer=''):
1083 """Sets the beacon footer (appended IE information) for this interface.
1084
1085 @param interface string interface to set the footer on.
1086 @param footer string footer to be set on the interface.
1087
1088 """
1089 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' %
1090 self.iw_runner.get_interface(interface).phy)
1091 if self.router.run('test -e %s' % footer_file,
1092 ignore_status=True).exit_status != 0:
1093 logging.info('Beacon footer file does not exist. Ignoring.')
1094 return
1095 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file))
Peter Qiu9a63a8b2015-02-03 09:08:16 -08001096
1097
1098 def setup_bridge_mode_dhcp_server(self):
1099 """Setup an DHCP server for bridge mode.
1100
1101 Setup an DHCP server on the master interface of the virtual ethernet
1102 pair, with peer interface connected to the bridge interface. This is
1103 used for testing APs in bridge mode.
1104
1105 """
1106 # Start a local server on master interface of virtual ethernet pair.
1107 self.start_local_server(
1108 self.get_virtual_ethernet_master_interface())
1109 # Add peer interface to the bridge.
1110 self.add_interface_to_bridge(
Ben Chan630d6b42015-02-13 18:14:45 -08001111 self.get_virtual_ethernet_peer_interface())