blob: 0806f1cc1571a09b5527d93abd3913b3950df9fc [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
Christopher Wiley14796b32013-04-03 14:53:33 -07006import logging
Christopher Wiley3166e432013-08-06 09:53:12 -07007import random
Christopher Wiley3166e432013-08-06 09:53:12 -07008import string
Christopher Wileyeea12362013-12-12 17:24:29 -08009import time
Christopher Wiley14796b32013-04-03 14:53:33 -070010
Paul Stewartc9628b32010-08-11 13:03:51 -070011from autotest_lib.client.common_lib import error
Christopher Wiley36039222014-12-13 18:27:52 -080012from autotest_lib.client.common_lib.cros import path_utils
Paul Stewart6ddeba72013-11-18 10:08:23 -080013from autotest_lib.client.common_lib.cros.network import interface
Christopher Wiley594570d2014-03-14 16:50:15 -070014from autotest_lib.client.common_lib.cros.network import netblock
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070015from autotest_lib.client.common_lib.cros.network import ping_runner
16from autotest_lib.server import hosts
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070017from autotest_lib.server import site_linux_system
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080018from autotest_lib.server.cros import dnsname_mangler
Christopher Wiley99d42c92013-07-09 16:40:16 -070019from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070020
Christopher Wiley408d1812014-01-13 15:27:43 -080021
22StationInstance = collections.namedtuple('StationInstance',
23 ['ssid', 'interface', 'dev_type'])
Christopher Wileyd6e503c2014-06-23 15:53:47 -070024HostapdInstance = collections.namedtuple('HostapdInstance',
25 ['ssid', 'conf_file', 'log_file',
Tien Chang097f7462014-09-03 17:30:29 -070026 'interface', 'config_dict',
27 'stderr_log_file'])
28
Eric Carusoeddedd32014-10-13 14:41:49 -070029# Send magic packets here, so they can wake up the system but are otherwise
30# dropped.
31UDP_DISCARD_PORT = 9
Christopher Wiley408d1812014-01-13 15:27:43 -080032
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070033def build_router_hostname(client_hostname=None, router_hostname=None):
34 """Build a router hostname from a client hostname.
35
36 @param client_hostname: string hostname of DUT connected to a router.
37 @param router_hostname: string hostname of router.
38 @return string hostname of connected router or None if the hostname
39 cannot be inferred from the client hostname.
40
41 """
42 if not router_hostname and not client_hostname:
43 raise error.TestError('Either client_hostname or router_hostname must '
44 'be specified to build_router_hostname.')
45
Christopher Wiley6b1d9e72014-12-13 18:07:41 -080046 return dnsname_mangler.get_router_addr(client_hostname,
47 cmdline_override=router_hostname)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070048
49
50def build_router_proxy(test_name='', client_hostname=None, router_addr=None):
51 """Build up a LinuxRouter object.
52
53 Verifies that the remote host responds to ping.
54 Either client_hostname or router_addr must be specified.
55
56 @param test_name: string name of this test (e.g. 'network_WiFi_TestName').
57 @param client_hostname: string hostname of DUT if we're in the lab.
58 @param router_addr: string DNS/IPv4 address to use for router host object.
59
60 @return LinuxRouter or raise error.TestError on failure.
61
62 """
Christopher Wiley297910b2014-07-01 13:53:19 -070063 router_hostname = build_router_hostname(client_hostname=client_hostname,
64 router_hostname=router_addr)
Christopher Wiley9adeb2a2014-06-19 14:36:13 -070065 logging.info('Connecting to router at %s', router_hostname)
66 ping_helper = ping_runner.PingRunner()
67 if not ping_helper.simple_ping(router_hostname):
68 raise error.TestError('Router at %s is not pingable.' %
69 router_hostname)
70
71 return LinuxRouter(hosts.create_host(router_hostname), test_name)
72
Christopher Wiley408d1812014-01-13 15:27:43 -080073
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070074class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070075 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070076
77 This class implements test methods/steps that communicate with a
78 router implemented with Linux/mac80211. The router must
79 be pre-configured to enable ssh access and have a mac80211-based
80 wireless device. We also assume hostapd 0.7.x and iw are present
81 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070082
Sam Leffler6969d1d2010-03-15 16:07:11 -070083 """
84
Christopher Wiley08aafb02014-04-22 17:38:21 -070085 KNOWN_TEST_PREFIX = 'network_WiFi_'
Christopher Wileyd6e503c2014-06-23 15:53:47 -070086 POLLING_INTERVAL_SECONDS = 0.5
Christopher Wiley1defc242013-09-18 10:28:37 -070087 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070088 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070089 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070090
Christopher Wileyeea12362013-12-12 17:24:29 -080091 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
92 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
Tien Chang097f7462014-09-03 17:30:29 -070093 HOSTAPD_STDERR_LOG_FILE_PATTERN = '/tmp/hostapd-stderr-test-%s.log'
Paul Stewart80bb3372014-01-22 15:06:08 -080094 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -080095 HOSTAPD_DRIVER_NAME = 'nl80211'
96
97 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
98 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
99 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
100
Peter Qiuc4beba02014-03-24 14:46:24 -0700101 MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
102
Paul Stewart51b0f382013-06-12 09:03:02 -0700103 def get_capabilities(self):
104 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800105 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -0700106 try:
Christopher Wiley36039222014-12-13 18:27:52 -0800107 self.cmd_send_management_frame = path_utils.must_be_installed(
108 '/usr/bin/send_management_frame', host=self.host)
Paul Stewart51b0f382013-06-12 09:03:02 -0700109 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
110 except error.TestFail:
111 pass
112 return super(LinuxRouter, self).get_capabilities().union(caps)
113
114
Christopher Wiley408d1812014-01-13 15:27:43 -0800115 @property
116 def router(self):
117 """Deprecated. Use self.host instead.
118
119 @return Host object representing the remote router.
120
121 """
122 return self.host
123
124
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700125 @property
126 def wifi_ip(self):
127 """Simple accessor for the WiFi IP when there is only one AP.
128
129 @return string IP of WiFi interface.
130
131 """
132 if len(self.local_servers) != 1:
133 raise error.TestError('Could not pick a WiFi IP to return.')
134
135 return self.get_wifi_ip(0)
136
137
Christopher Wiley408d1812014-01-13 15:27:43 -0800138 def __init__(self, host, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700139 """Build a LinuxRouter.
140
141 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -0700142 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700143
144 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800145 super(LinuxRouter, self).__init__(host, 'router')
Wade Guthrie24d1e312012-04-24 16:53:40 -0700146
Christopher Wileyeea12362013-12-12 17:24:29 -0800147 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wiley36039222014-12-13 18:27:52 -0800148 self.cmd_hostapd = path_utils.must_be_installed(
149 '/usr/sbin/hostapd', host=host)
150 self.cmd_hostapd_cli = path_utils.must_be_installed(
151 '/usr/sbin/hostapd_cli', host=host)
152 self.cmd_wpa_supplicant = path_utils.must_be_installed(
153 '/usr/sbin/wpa_supplicant', host=host)
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700154 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
155 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700156
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700157 # Log the most recent message on the router so that we can rebuild the
158 # suffix relevant to us when debugging failures.
159 last_log_line = self.host.run('tail -1 /var/log/messages').stdout
160 # We're trying to get the timestamp from:
161 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah
162 self._log_start_timestamp = last_log_line.strip().split(None, 2)[0]
163 logging.debug('Will only retrieve logs after %s.',
164 self._log_start_timestamp)
165
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700166 # hostapd configuration persists throughout the test, subsequent
167 # 'config' commands only modify it.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700168 self._ssid_prefix = test_name
169 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700170 # Many of our tests start with an uninteresting prefix.
171 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700172 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
173 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700174
Christopher Wileyeea12362013-12-12 17:24:29 -0800175 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700176 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800177 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800178 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700179 self.dhcp_low = 1
180 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700181
Paul Stewart548cf452012-11-27 17:46:23 -0800182 # Kill hostapd and dhcp server if already running.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700183 self._kill_process_instance('hostapd', timeout_seconds=30)
184 self.stop_dhcp_server(instance=None)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700185
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700186 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700187 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700188
Peter Qiu2f973252014-02-20 15:30:37 -0800189 # Reset all antennas to be active
190 self.set_default_antenna_bitmap()
191
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700192
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700193 def close(self):
194 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800195 self.deconfig()
Christopher Wileye5fdbdc2014-07-22 16:50:51 -0700196 # dnsmasq and hostapd cause interesting events to go to system logs.
197 # Retrieve only the suffix of the logs after the timestamp we stored on
198 # router creation.
199 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" %
200 self._log_start_timestamp, ignore_status=True)
201 self.host.get_file('/tmp/router_log', 'debug/router_host_messages')
Christopher Wiley408d1812014-01-13 15:27:43 -0800202 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700203
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700204
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700205 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700206 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700207 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700208
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700209
Peter Qiuc89c9a22014-02-27 10:03:55 -0800210 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700211 """Start a hostapd instance described by conf.
212
Christopher Wileyeea12362013-12-12 17:24:29 -0800213 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700214
215 """
Paul Stewart548cf452012-11-27 17:46:23 -0800216 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800217 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800218
Christopher Wileyeea12362013-12-12 17:24:29 -0800219 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
220 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
Tien Chang097f7462014-09-03 17:30:29 -0700221 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800222 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800223 hostapd_conf_dict = configuration.generate_dict(
224 interface, control_interface,
Peter Qiubde37ac2014-12-18 23:40:57 -0800225 self.build_unique_ssid(suffix=configuration.ssid_suffix))
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700226 logging.debug('hostapd parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800227
228 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800229 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
230 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800231 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800232
233 # Run hostapd.
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700234 logging.info('Starting hostapd on %s(%s) channel=%s...',
235 interface, self.iw_runner.get_interface(interface).phy,
236 configuration.channel)
Christopher Wiley1defc242013-09-18 10:28:37 -0700237 self.router.run('rm %s' % log_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800238 self.router.run('stop wpasupplicant', ignore_status=True)
Tien Chang097f7462014-09-03 17:30:29 -0700239 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % (
240 self.cmd_hostapd, conf_file, log_file, stderr_log_file)
Christopher Wiley0f64b842014-04-30 15:43:50 -0700241 pid = int(self.router.run(start_command).stdout.strip())
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700242 self.hostapd_instances.append(HostapdInstance(
243 hostapd_conf_dict['ssid'],
244 conf_file,
245 log_file,
246 interface,
Tien Chang097f7462014-09-03 17:30:29 -0700247 hostapd_conf_dict.copy(),
248 stderr_log_file))
Paul Stewart548cf452012-11-27 17:46:23 -0800249
Christopher Wileyeea12362013-12-12 17:24:29 -0800250 # Wait for confirmation that the router came up.
Christopher Wileyeea12362013-12-12 17:24:29 -0800251 logging.info('Waiting for hostapd to startup.')
252 start_time = time.time()
253 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
254 success = self.router.run(
255 'grep "Completing interface initialization" %s' % log_file,
256 ignore_status=True).exit_status == 0
257 if success:
258 break
259
260 # A common failure is an invalid router configuration.
261 # Detect this and exit early if we see it.
262 bad_config = self.router.run(
263 'grep "Interface initialization failed" %s' % log_file,
264 ignore_status=True).exit_status == 0
265 if bad_config:
266 raise error.TestFail('hostapd failed to initialize AP '
267 'interface.')
268
269 if pid:
270 early_exit = self.router.run('kill -0 %d' % pid,
271 ignore_status=True).exit_status
272 if early_exit:
273 raise error.TestFail('hostapd process terminated.')
274
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700275 time.sleep(self.POLLING_INTERVAL_SECONDS)
Christopher Wileyeea12362013-12-12 17:24:29 -0800276 else:
277 raise error.TestFail('Timed out while waiting for hostapd '
278 'to start.')
279
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700280
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700281 def _kill_process_instance(self,
282 process,
283 instance=None,
284 timeout_seconds=10,
285 ignore_timeouts=False):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700286 """Kill a process on the router.
287
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700288 Kills remote program named |process| (optionally only a specific
289 |instance|). Wait |timeout_seconds| for |process| to die
290 before returning. If |ignore_timeouts| is False, raise
291 a TestError on timeouts.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700292
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700293 @param process: string name of process to kill.
294 @param instance: string fragment of the command line unique to
295 this instance of the remote process.
296 @param timeout_seconds: float timeout in seconds to wait.
297 @param ignore_timeouts: True iff we should ignore failures to
298 kill processes.
299 @return True iff the specified process has exited.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700300
Thieu Le7b23a542012-01-27 15:54:48 -0800301 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700302 if instance is not None:
Christopher Wileya4754c02014-06-30 16:37:52 -0700303 search_arg = '-f "^%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800304 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800305 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800306
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700307 self.host.run('pkill %s' % search_arg, ignore_status=True)
308 is_dead = False
309 start_time = time.time()
310 while not is_dead and time.time() - start_time < timeout_seconds:
311 time.sleep(self.POLLING_INTERVAL_SECONDS)
312 is_dead = self.host.run(
Christopher Wileya4754c02014-06-30 16:37:52 -0700313 'pgrep -l %s' % search_arg,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700314 ignore_status=True).exit_status != 0
315 if is_dead or ignore_timeouts:
316 return is_dead
Paul Stewart326badb2012-12-18 14:18:54 -0800317
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700318 raise error.TestError(
319 'Timed out waiting for %s%s to die' %
320 (process,
321 '' if instance is None else ' (instance=%s)' % instance))
Paul Stewart326badb2012-12-18 14:18:54 -0800322
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700323
Paul Stewart326badb2012-12-18 14:18:54 -0800324 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700325 """Kills a hostapd instance.
326
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700327 @param instance HostapdInstance object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700328
329 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700330 is_dead = self._kill_process_instance(
Christopher Wileya4754c02014-06-30 16:37:52 -0700331 self.cmd_hostapd,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700332 instance=instance.conf_file,
333 timeout_seconds=30,
334 ignore_timeouts=True)
Tien Chang097f7462014-09-03 17:30:29 -0700335 files_to_copy = [(instance.log_file, 'debug/hostapd_router_%d_%s.log' %
336 (self._total_hostapd_instances, instance.interface)),
337 (instance.stderr_log_file,
338 'debug/hostapd_router_%d_%s.stderr.log' %
339 (self._total_hostapd_instances, instance.interface))
340 ]
341 for remote_file, local_file in files_to_copy:
342 if self.host.run('ls %s >/dev/null 2>&1' % remote_file,
343 ignore_status=True).exit_status:
344 logging.error('Did not collect hostapd log file because '
345 'it was missing.')
346 else:
347 self.router.get_file(remote_file, local_file)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700348 self._total_hostapd_instances += 1
349 if not is_dead:
350 raise error.TestError('Timed out killing hostapd.')
Paul Stewart21737812012-12-06 11:03:32 -0800351
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700352
Peter Qiubde37ac2014-12-18 23:40:57 -0800353 def build_unique_ssid(self, suffix=''):
Tien Chang097f7462014-09-03 17:30:29 -0700354 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
355 the number of APs we've constructed already.
356
357 @param suffix string to append to SSID
358
359 """
Christopher Wiley08aafb02014-04-22 17:38:21 -0700360 base = len(self.SUFFIX_LETTERS)
361 number = self._number_unique_ssids
362 self._number_unique_ssids += 1
363 unique = ''
364 while number or not unique:
365 unique = self.SUFFIX_LETTERS[number % base] + unique
366 number = number / base
367 # And salt the SSID so that tests running in adjacent cells are unlikely
368 # to pick the same SSID and we're resistent to beacons leaking out of
369 # cells.
370 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
371 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700372
373
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700374 def hostap_configure(self, configuration, multi_interface=None):
375 """Build up a hostapd configuration file and start hostapd.
376
377 Also setup a local server if this router supports them.
378
379 @param configuration HosetapConfig object.
380 @param multi_interface bool True iff multiple interfaces allowed.
381
382 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800383 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800384 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700385 self.deconfig()
Kris Rambishb9b46852014-12-19 15:29:58 -0800386 if configuration.is_11ac:
387 router_caps = self.get_capabilities()
388 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps:
389 raise error.TestNAError('Router does not have AC support')
390
Peter Qiuc89c9a22014-02-27 10:03:55 -0800391 self.start_hostapd(configuration)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700392 interface = self.hostapd_instances[-1].interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800393 self.iw_runner.set_tx_power(interface, 'auto')
Paul Stewart65fb9212014-12-01 19:54:20 -0800394 self.set_beacon_footer(interface, configuration.beacon_footer)
Christopher Wileyeea12362013-12-12 17:24:29 -0800395 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700396 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700397
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700398
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700399 def ibss_configure(self, config):
400 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700401
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700402 Extract relevant configuration objects from |config| despite not
403 actually being a hostap managed endpoint.
404
405 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700406
407 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800408 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700409 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800410 interface = self.get_wlanif(config.frequency, 'ibss')
Peter Qiubde37ac2014-12-18 23:40:57 -0800411 ssid = (config.ssid or
412 self.build_unique_ssid(suffix=config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800413 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700414 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800415 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700416 # Always start a local server.
417 self.start_local_server(interface)
418 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800419 self.station_instances.append(
420 StationInstance(ssid=ssid, interface=interface,
421 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800422
423
Paul Stewart2bd823b2012-11-21 15:03:37 -0800424 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700425 """Get the local server address for an interface.
426
427 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700428 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700429
430 @param index int describing which local server this is for.
431
432 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700433 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800434
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700435
Paul Stewart6ddeba72013-11-18 10:08:23 -0800436 def local_peer_ip_address(self, index):
437 """Get the IP address allocated for the peer associated to the AP.
438
439 This address is assigned to a locally associated peer device that
440 is created for the DUT to perform connectivity tests with.
441 When we have multiple local servers, we give them static IP addresses
442 like 192.168.*.253.
443
444 @param index int describing which local server this is for.
445
446 """
447 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
448
449
450 def local_peer_mac_address(self):
451 """Get the MAC address of the peer interface.
452
453 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800454
Paul Stewart6ddeba72013-11-18 10:08:23 -0800455 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800456 iface = interface.Interface(self.station_instances[0].interface,
457 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800458 return iface.mac_address
459
460
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700461 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700462 """Start a local server on an interface.
463
464 @param interface string (e.g. wlan0)
465
466 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700467 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700468
469 if len(self.local_servers) >= 256:
470 raise error.TestFail('Exhausted available local servers')
471
Christopher Wiley684878e2014-12-18 14:32:48 -0800472 server_addr = netblock.from_addr(
Christopher Wiley594570d2014-03-14 16:50:15 -0700473 self.local_server_address(len(self.local_servers)),
474 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700475
476 params = {}
Christopher Wiley594570d2014-03-14 16:50:15 -0700477 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700478 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700479 (server_addr.get_addr_in_block(1),
480 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700481 params['interface'] = interface
Christopher Wiley594570d2014-03-14 16:50:15 -0700482 params['ip_params'] = ('%s broadcast %s dev %s' %
483 (server_addr.netblock,
484 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700485 interface))
486 self.local_servers.append(params)
487
Christopher Wiley594570d2014-03-14 16:50:15 -0700488 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700489 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700490 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700491 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700492 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700493 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800494 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700495
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700496
Paul Stewart548cf452012-11-27 17:46:23 -0800497 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700498 """Start a dhcp server on an interface.
499
500 @param interface string (e.g. wlan0)
501
502 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800503 for server in self.local_servers:
504 if server['interface'] == interface:
505 params = server
506 break
507 else:
508 raise error.TestFail('Could not find local server '
509 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700510 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800511 dhcpd_conf_file = self.dhcpd_conf % interface
512 dhcp_conf = '\n'.join([
513 'port=0', # disables DNS server
514 'bind-interfaces',
515 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700516 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
517 server_addr.get_addr_in_block(128))),
Christopher Wileyeea12362013-12-12 17:24:29 -0800518 'interface=%s' % params['interface'],
519 'dhcp-leasefile=%s' % self.dhcpd_leases])
520 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
521 (dhcpd_conf_file, dhcp_conf))
522 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700523
524
Paul Stewart326badb2012-12-18 14:18:54 -0800525 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700526 """Stop a dhcp server on the router.
527
528 @param instance string instance to kill.
529
530 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700531 self._kill_process_instance('dnsmasq', instance=instance)
Paul Stewart548cf452012-11-27 17:46:23 -0800532
533
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800534 def get_wifi_channel(self, ap_num):
535 """Return channel of BSS corresponding to |ap_num|.
536
537 @param ap_num int which BSS to get the channel of.
538 @return int primary channel of BSS.
539
540 """
541 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700542 return instance.config_dict['channel']
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800543
544
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700545 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700546 """Return IP address on the WiFi subnet of a local server on the router.
547
548 If no local servers are configured (e.g. for an RSPro), a TestFail will
549 be raised.
550
551 @param ap_num int which local server to get an address from.
552
553 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700554 if not self.local_servers:
555 raise error.TestError('No IP address assigned')
556
557 return self.local_servers[ap_num]['netblock'].addr
558
559
560 def get_wifi_ip_subnet(self, ap_num):
561 """Return subnet of WiFi AP instance.
562
563 If no APs are configured a TestError will be raised.
564
565 @param ap_num int which local server to get an address from.
566
567 """
568 if not self.local_servers:
569 raise error.TestError('No APs configured.')
570
571 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700572
573
Christopher Wileya3effac2014-02-05 11:16:11 -0800574 def get_hostapd_interface(self, ap_num):
575 """Get the name of the interface associated with a hostapd instance.
576
577 @param ap_num: int hostapd instance number.
578 @return string interface name (e.g. 'managed0').
579
580 """
581 if ap_num not in range(len(self.hostapd_instances)):
582 raise error.TestFail('Invalid instance number (%d) with %d '
583 'instances configured.' %
584 (ap_num, len(self.hostapd_instances)))
585
586 instance = self.hostapd_instances[ap_num]
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700587 return instance.interface
Christopher Wileya3effac2014-02-05 11:16:11 -0800588
589
Paul Stewart17350be2012-12-14 13:34:54 -0800590 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700591 """Return the MAC address of an AP in the test.
592
593 @param ap_num int index of local server to read the MAC address from.
594 @return string MAC address like 00:11:22:33:44:55.
595
596 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800597 interface_name = self.get_hostapd_interface(ap_num)
598 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800599 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800600
601
Christopher Wileya3effac2014-02-05 11:16:11 -0800602 def get_hostapd_phy(self, ap_num):
603 """Get name of phy for hostapd instance.
604
605 @param ap_num int index of hostapd instance.
606 @return string phy name of phy corresponding to hostapd's
607 managed interface.
608
609 """
610 interface = self.iw_runner.get_interface(
611 self.get_hostapd_interface(ap_num))
612 return interface.phy
613
614
Christopher Wileyeea12362013-12-12 17:24:29 -0800615 def deconfig(self):
616 """A legacy, deprecated alias for deconfig_aps."""
617 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800618
619
620 def deconfig_aps(self, instance=None, silent=False):
621 """De-configure an AP (will also bring wlan down).
622
623 @param instance: int or None. If instance is None, will bring down all
624 instances of hostapd.
625 @param silent: True if instances should be brought without de-authing
626 the DUT.
627
628 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800629 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700630 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700631
Christopher Wileyeea12362013-12-12 17:24:29 -0800632 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800633 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800634 if instance is not None:
635 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800636 for server in self.local_servers:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700637 if server['interface'] == instances[0].interface:
Paul Stewart326badb2012-12-18 14:18:54 -0800638 local_servers = [server]
639 self.local_servers.remove(server)
640 break
Paul Stewart21737812012-12-06 11:03:32 -0800641 else:
642 instances = self.hostapd_instances
643 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800644 local_servers = self.local_servers
645 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700646
Paul Stewart21737812012-12-06 11:03:32 -0800647 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800648 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800649 # Deconfigure without notifying DUT. Remove the interface
650 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700651 self.remove_interface(instance.interface)
Paul Stewart21737812012-12-06 11:03:32 -0800652
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700653 self.kill_hostapd_instance(instance)
654 self.release_interface(instance.interface)
Christopher Wiley408d1812014-01-13 15:27:43 -0800655 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700656 local_servers = self.local_servers
657 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800658 instance = self.station_instances.pop()
659 if instance.dev_type == 'ibss':
660 self.iw_runner.ibss_leave(instance.interface)
661 elif instance.dev_type == 'managed':
Christopher Wileya4754c02014-06-30 16:37:52 -0700662 self._kill_process_instance(self.cmd_wpa_supplicant,
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700663 instance=instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800664 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800665 self.iw_runner.disconnect_station(instance.interface)
666 self.router.run('%s link set %s down' %
667 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700668
Paul Stewart326badb2012-12-18 14:18:54 -0800669 for server in local_servers:
670 self.stop_dhcp_server(server['interface'])
671 self.router.run("%s addr del %s" %
672 (self.cmd_ip, server['ip_params']),
673 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700674
Paul Stewart7cb1f062010-06-10 15:46:20 -0700675
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800676 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700677 """Verify that the PMKSA auth was cached on a hostapd instance.
678
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800679 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700680
681 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700682 log_file = self.hostapd_instances[instance].log_file
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800683 pmksa_match = 'PMK from PMKSA cache'
684 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
685 ignore_status=True)
686 if result.exit_status:
687 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800688
689
Christopher Wileye0afecb2013-11-11 10:54:23 -0800690 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700691 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800692 if instance is None:
693 instance = 0
694 if len(self.hostapd_instances) > 1:
695 raise error.TestFail('No instance of hostapd specified with '
696 'multiple instances present.')
697
Christopher Wiley3099be72013-11-06 16:49:02 -0800698 if self.hostapd_instances:
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700699 return self.hostapd_instances[instance].ssid
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700700
Christopher Wiley408d1812014-01-13 15:27:43 -0800701 if self.station_instances:
702 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700703
Christopher Wiley408d1812014-01-13 15:27:43 -0800704 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700705
706
Wade Guthriee4074dd2013-10-30 11:00:48 -0700707 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700708 """Deauthenticates a client described in params.
709
Wade Guthriee4074dd2013-10-30 11:00:48 -0700710 @param client_mac string containing the mac address of the client to be
711 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700712
713 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700714 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700715 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800716 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700717
718
Peter Qiuc4beba02014-03-24 14:46:24 -0700719 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -0700720 """Injects a management frame into an active hostapd session.
721
722 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -0700723 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -0700724 @param instance int indicating which hostapd instance to inject into.
725
726 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700727 hostap_interface = self.hostapd_instances[instance].interface
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800728 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700729 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -0700730 self.router.run('%s -i %s -t %s -c %d' %
731 (self.cmd_send_management_frame, interface, frame_type,
732 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800733 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700734
735
Peter Qiuc4beba02014-03-24 14:46:24 -0700736 def setup_management_frame_interface(self, channel):
737 """
738 Setup interface for injecting management frames.
739
740 @param channel int channel to inject the frames.
741
742 @return string name of the interface.
743
744 """
745 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
746 channel)
747 interface = self.get_wlanif(frequency, 'monitor')
Peter Qiuc4beba02014-03-24 14:46:24 -0700748 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Peter Qiu39e38f62014-09-30 15:33:10 -0700749 self.iw_runner.set_freq(interface, frequency)
Peter Qiuc4beba02014-03-24 14:46:24 -0700750 return interface
751
752
753 def send_management_frame(self, interface, frame_type, channel,
754 ssid_prefix=None, num_bss=None,
755 frame_count=None, delay=None):
756 """
757 Injects management frames on specify channel |frequency|.
758
759 This function will spawn off a new process to inject specified
760 management frames |frame_type| at the specified interface |interface|.
761
762 @param interface string interface to inject frames.
763 @param frame_type string message type.
764 @param channel int targeted channel.
765 @param ssid_prefix string SSID prefix.
766 @param num_bss int number of BSS.
767 @param frame_count int number of frames to send.
768 @param delay int milliseconds delay between frames.
769
770 @return int PID of the newly created process.
771
772 """
773 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
774 interface, frame_type, channel)
775 if ssid_prefix is not None:
776 command += ' -s %s' % (ssid_prefix)
777 if num_bss is not None:
778 command += ' -b %d' % (num_bss)
779 if frame_count is not None:
780 command += ' -n %d' % (frame_count)
781 if delay is not None:
782 command += ' -d %d' % (delay)
783 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
784 pid = int(self.router.run(command).stdout)
785 return pid
786
787
Paul Stewart25536942013-08-15 17:33:42 -0700788 def detect_client_deauth(self, client_mac, instance=0):
789 """Detects whether hostapd has logged a deauthentication from
790 |client_mac|.
791
792 @param client_mac string the MAC address of the client to detect.
793 @param instance int indicating which hostapd instance to query.
794
795 """
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700796 interface = self.hostapd_instances[instance].interface
Paul Stewart25536942013-08-15 17:33:42 -0700797 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700798 log_file = self.hostapd_instances[instance].log_file
Paul Stewart25536942013-08-15 17:33:42 -0700799 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
800 ignore_status=True)
801 return result.exit_status == 0
802
803
Paul Stewart4ae471e2013-09-04 15:42:35 -0700804 def detect_client_coexistence_report(self, client_mac, instance=0):
805 """Detects whether hostapd has logged an action frame from
806 |client_mac| indicating information about 20/40MHz BSS coexistence.
807
808 @param client_mac string the MAC address of the client to detect.
809 @param instance int indicating which hostapd instance to query.
810
811 """
812 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
813 '.. .. .. .. .. .. .. .. .. .. %s '
814 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
815 ' '.join(client_mac.split(':')))
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700816 log_file = self.hostapd_instances[instance].log_file
Paul Stewart4ae471e2013-09-04 15:42:35 -0700817 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
818 ignore_status=True)
819 return result.exit_status == 0
820
821
Paul Stewart6ddeba72013-11-18 10:08:23 -0800822 def add_connected_peer(self, instance=0):
823 """Configure a station connected to a running AP instance.
824
825 Extract relevant configuration objects from the hostap
826 configuration for |instance| and generate a wpa_supplicant
827 instance that connects to it. This allows the DUT to interact
828 with a client entity that is also connected to the same AP. A
829 full wpa_supplicant instance is necessary here (instead of just
830 using the "iw" command to connect) since we want to enable
831 advanced features such as TDLS.
832
833 @param instance int indicating which hostapd instance to connect to.
834
835 """
836 if not self.hostapd_instances:
837 raise error.TestFail('Hostapd is not configured.')
838
Christopher Wiley408d1812014-01-13 15:27:43 -0800839 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800840 raise error.TestFail('Station is already configured.')
841
Christopher Wiley408d1812014-01-13 15:27:43 -0800842 ssid = self.get_ssid(instance)
Christopher Wileyd6e503c2014-06-23 15:53:47 -0700843 hostap_conf = self.hostapd_instances[instance].config_dict
Paul Stewart6ddeba72013-11-18 10:08:23 -0800844 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
845 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800846 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800847
848 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
849 # require them...
850 supplicant_config = (
851 'network={\n'
852 ' ssid="%(ssid)s"\n'
853 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800854 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800855 )
856
Christopher Wileyeea12362013-12-12 17:24:29 -0800857 conf_file = self.STATION_CONF_FILE_PATTERN % interface
858 log_file = self.STATION_LOG_FILE_PATTERN % interface
859 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800860
861 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
862 (conf_file, supplicant_config))
863
864 # Connect the station.
865 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
866 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
867 (self.cmd_wpa_supplicant,
868 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800869 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800870 self.router.run(start_command)
871 self.iw_runner.wait_for_link(interface)
872
873 # Assign an IP address to this interface.
874 self.router.run('%s addr add %s/24 dev %s' %
875 (self.cmd_ip, self.local_peer_ip_address(instance),
876 interface))
877
878 # Since we now have two network interfaces connected to the same
879 # network, we need to disable the kernel's protection against
880 # incoming packets to an "unexpected" interface.
881 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
882 interface)
883
Paul Stewartb01839b2013-12-06 15:49:56 -0800884 # Similarly, we'd like to prevent the hostap interface from
885 # replying to ARP requests for the peer IP address and vice
886 # versa.
887 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
888 interface)
889 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
890 hostap_conf['interface'])
891
Christopher Wiley408d1812014-01-13 15:27:43 -0800892 self.station_instances.append(
893 StationInstance(ssid=ssid, interface=interface,
894 dev_type='managed'))
Eric Carusoeddedd32014-10-13 14:41:49 -0700895
896
897 def send_magic_packet(self, dest_ip, dest_mac):
898 """Sends a magic packet to the NIC with the given IP and MAC addresses.
899
900 @param dest_ip the IP address of the device to send the packet to
901 @param dest_mac the hardware MAC address of the device
902
903 """
904 # magic packet is 6 0xff bytes followed by the hardware address
905 # 16 times
906 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')])
907 magic_packet = '\xff' * 6 + mac_bytes * 16
908
909 logging.info('Sending magic packet to %s...', dest_ip)
910 self.host.run('python -uc "import socket, sys;'
911 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);'
912 's.sendto(sys.stdin.read(), (\'%s\', %d))"' %
913 (dest_ip, UDP_DISCARD_PORT),
914 stdin=magic_packet)
Paul Stewart65fb9212014-12-01 19:54:20 -0800915
916
917 def set_beacon_footer(self, interface, footer=''):
918 """Sets the beacon footer (appended IE information) for this interface.
919
920 @param interface string interface to set the footer on.
921 @param footer string footer to be set on the interface.
922
923 """
924 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' %
925 self.iw_runner.get_interface(interface).phy)
926 if self.router.run('test -e %s' % footer_file,
927 ignore_status=True).exit_status != 0:
928 logging.info('Beacon footer file does not exist. Ignoring.')
929 return
930 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file))
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800931
932
933 def setup_bridge_mode_dhcp_server(self):
934 """Setup an DHCP server for bridge mode.
935
936 Setup an DHCP server on the master interface of the virtual ethernet
937 pair, with peer interface connected to the bridge interface. This is
938 used for testing APs in bridge mode.
939
940 """
941 # Start a local server on master interface of virtual ethernet pair.
942 self.start_local_server(
943 self.get_virtual_ethernet_master_interface())
944 # Add peer interface to the bridge.
945 self.add_interface_to_bridge(
946 self.get_virtual_ethernet_peer_interface())