blob: 73edc6e23e0c0243ef6412e83aff293cf18bcb7d [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
Paul Stewart6ddeba72013-11-18 10:08:23 -080012from autotest_lib.client.common_lib.cros.network import interface
Christopher Wiley594570d2014-03-14 16:50:15 -070013from autotest_lib.client.common_lib.cros.network import netblock
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070014from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070015from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070016from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070017
Christopher Wiley408d1812014-01-13 15:27:43 -080018
19StationInstance = collections.namedtuple('StationInstance',
20 ['ssid', 'interface', 'dev_type'])
21
22
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070023class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070024 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070025
26 This class implements test methods/steps that communicate with a
27 router implemented with Linux/mac80211. The router must
28 be pre-configured to enable ssh access and have a mac80211-based
29 wireless device. We also assume hostapd 0.7.x and iw are present
30 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070031
Sam Leffler6969d1d2010-03-15 16:07:11 -070032 """
33
Christopher Wiley3166e432013-08-06 09:53:12 -070034 KNOWN_TEST_PREFIX = 'network_WiFi'
Christopher Wiley1defc242013-09-18 10:28:37 -070035 STARTUP_POLLING_INTERVAL_SECONDS = 0.5
36 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070037 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070038 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070039
Christopher Wileyeea12362013-12-12 17:24:29 -080040 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
41 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
42 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
Paul Stewart80bb3372014-01-22 15:06:08 -080043 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -080044 HOSTAPD_DRIVER_NAME = 'nl80211'
45
46 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
47 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
48 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
49
Peter Qiuc4beba02014-03-24 14:46:24 -070050 MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log'
51
Paul Stewart51b0f382013-06-12 09:03:02 -070052 def get_capabilities(self):
53 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080054 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070055 try:
56 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080057 self.host, '/usr/bin/send_management_frame')
Paul Stewart51b0f382013-06-12 09:03:02 -070058 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
59 except error.TestFail:
60 pass
61 return super(LinuxRouter, self).get_capabilities().union(caps)
62
63
Christopher Wiley408d1812014-01-13 15:27:43 -080064 @property
65 def router(self):
66 """Deprecated. Use self.host instead.
67
68 @return Host object representing the remote router.
69
70 """
71 return self.host
72
73
Christopher Wileyaeef9b52014-03-11 12:24:11 -070074 @property
75 def wifi_ip(self):
76 """Simple accessor for the WiFi IP when there is only one AP.
77
78 @return string IP of WiFi interface.
79
80 """
81 if len(self.local_servers) != 1:
82 raise error.TestError('Could not pick a WiFi IP to return.')
83
84 return self.get_wifi_ip(0)
85
86
Christopher Wiley408d1812014-01-13 15:27:43 -080087 def __init__(self, host, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070088 """Build a LinuxRouter.
89
90 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -070091 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070092
93 """
Christopher Wiley408d1812014-01-13 15:27:43 -080094 super(LinuxRouter, self).__init__(host, 'router')
Wade Guthrie24d1e312012-04-24 16:53:40 -070095
Christopher Wileyeea12362013-12-12 17:24:29 -080096 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070097 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080098 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070099 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -0800100 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800101 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -0800102 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700103 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
104 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700105
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700106 # hostapd configuration persists throughout the test, subsequent
107 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -0700108 self.ssid_prefix = test_name
109 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
110 # Many of our tests start with an uninteresting prefix.
111 # Remove it so we can have more unique bytes.
112 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
113 self.ssid_prefix = self.ssid_prefix.lstrip('_')
114 self.ssid_prefix += '_'
115
Christopher Wileyeea12362013-12-12 17:24:29 -0800116 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700117 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800118 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800119 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700120 self.dhcp_low = 1
121 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700122
Paul Stewart548cf452012-11-27 17:46:23 -0800123 # Kill hostapd and dhcp server if already running.
Thieu Le7b23a542012-01-27 15:54:48 -0800124 self.kill_hostapd()
Paul Stewart548cf452012-11-27 17:46:23 -0800125 self.stop_dhcp_servers()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700126
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700127 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700128 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700129
Peter Qiu2f973252014-02-20 15:30:37 -0800130 # Reset all antennas to be active
131 self.set_default_antenna_bitmap()
132
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700133
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700134 def close(self):
135 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800136 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800137 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700138
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700139
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700140 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700141 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700142 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700143
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700144
Peter Qiuc89c9a22014-02-27 10:03:55 -0800145 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700146 """Start a hostapd instance described by conf.
147
Christopher Wileyeea12362013-12-12 17:24:29 -0800148 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700149
150 """
Paul Stewart548cf452012-11-27 17:46:23 -0800151 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800152 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800153
Christopher Wileyeea12362013-12-12 17:24:29 -0800154 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
155 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
156 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800157 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800158 hostapd_conf_dict = configuration.generate_dict(
159 interface, control_interface,
160 self._build_ssid(configuration.ssid_suffix))
161 logging.info('Starting hostapd with parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800162
163 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800164 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
165 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800166 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800167
168 # Run hostapd.
169 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700170 self.router.run('rm %s' % log_file, ignore_status=True)
171 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800172 self.router.run('stop wpasupplicant', ignore_status=True)
173 start_command = '%s -dd -B -t -f %s -P %s %s' % (
174 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700175 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800176 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800177 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800178 'conf_file': conf_file,
179 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700180 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800181 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800182 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800183 })
184
Christopher Wileyeea12362013-12-12 17:24:29 -0800185 # Wait for confirmation that the router came up.
186 pid = int(self.router.run('cat %s' % pid_file).stdout)
187 logging.info('Waiting for hostapd to startup.')
188 start_time = time.time()
189 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
190 success = self.router.run(
191 'grep "Completing interface initialization" %s' % log_file,
192 ignore_status=True).exit_status == 0
193 if success:
194 break
195
196 # A common failure is an invalid router configuration.
197 # Detect this and exit early if we see it.
198 bad_config = self.router.run(
199 'grep "Interface initialization failed" %s' % log_file,
200 ignore_status=True).exit_status == 0
201 if bad_config:
202 raise error.TestFail('hostapd failed to initialize AP '
203 'interface.')
204
205 if pid:
206 early_exit = self.router.run('kill -0 %d' % pid,
207 ignore_status=True).exit_status
208 if early_exit:
209 raise error.TestFail('hostapd process terminated.')
210
211 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
212 else:
213 raise error.TestFail('Timed out while waiting for hostapd '
214 'to start.')
215
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700216
Paul Stewart326badb2012-12-18 14:18:54 -0800217 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700218 """Kill a process on the router.
219
Paul Stewart326badb2012-12-18 14:18:54 -0800220 Kills program named |process|, optionally only a specific
221 |instance|. If |wait| is specified, we makes sure |process| exits
222 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700223
224 @param process string name of process to kill.
225 @param instance string instance of process to kill.
226 @param wait int timeout in seconds to wait for.
227
Thieu Le7b23a542012-01-27 15:54:48 -0800228 """
Paul Stewart21737812012-12-06 11:03:32 -0800229 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800230 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800231 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800232 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800233
Paul Stewart326badb2012-12-18 14:18:54 -0800234 cmd = "pkill %s >/dev/null 2>&1" % search_arg
235
236 if wait:
237 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
238 search_arg)
239 self.router.run(cmd, timeout=wait, ignore_status=True)
240 else:
241 self.router.run(cmd, ignore_status=True)
242
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700243
Paul Stewart326badb2012-12-18 14:18:54 -0800244 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700245 """Kills a hostapd instance.
246
247 @param instance string instance to kill.
248
249 """
Paul Stewart326badb2012-12-18 14:18:54 -0800250 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800251
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700252
Paul Stewart21737812012-12-06 11:03:32 -0800253 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700254 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800255 self.kill_hostapd_instance(None)
256
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700257
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700258 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700259 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
260 for x in range(5)])
261 return (self.ssid_prefix + unique_salt + suffix)[-32:]
262
263
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700264 def hostap_configure(self, configuration, multi_interface=None):
265 """Build up a hostapd configuration file and start hostapd.
266
267 Also setup a local server if this router supports them.
268
269 @param configuration HosetapConfig object.
270 @param multi_interface bool True iff multiple interfaces allowed.
271
272 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800273 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800274 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700275 self.deconfig()
Peter Qiuc89c9a22014-02-27 10:03:55 -0800276 self.start_hostapd(configuration)
Christopher Wileyeea12362013-12-12 17:24:29 -0800277 interface = self.hostapd_instances[-1]['interface']
278 self.iw_runner.set_tx_power(interface, 'auto')
279 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700280 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700281
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700282
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700283 def ibss_configure(self, config):
284 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700285
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700286 Extract relevant configuration objects from |config| despite not
287 actually being a hostap managed endpoint.
288
289 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700290
291 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800292 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700293 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800294 interface = self.get_wlanif(config.frequency, 'ibss')
295 ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800296 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700297 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800298 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700299 # Always start a local server.
300 self.start_local_server(interface)
301 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800302 self.station_instances.append(
303 StationInstance(ssid=ssid, interface=interface,
304 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800305
306
Paul Stewart2bd823b2012-11-21 15:03:37 -0800307 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700308 """Get the local server address for an interface.
309
310 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700311 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700312
313 @param index int describing which local server this is for.
314
315 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700316 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800317
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700318
Paul Stewart6ddeba72013-11-18 10:08:23 -0800319 def local_peer_ip_address(self, index):
320 """Get the IP address allocated for the peer associated to the AP.
321
322 This address is assigned to a locally associated peer device that
323 is created for the DUT to perform connectivity tests with.
324 When we have multiple local servers, we give them static IP addresses
325 like 192.168.*.253.
326
327 @param index int describing which local server this is for.
328
329 """
330 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
331
332
333 def local_peer_mac_address(self):
334 """Get the MAC address of the peer interface.
335
336 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800337
Paul Stewart6ddeba72013-11-18 10:08:23 -0800338 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800339 iface = interface.Interface(self.station_instances[0].interface,
340 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800341 return iface.mac_address
342
343
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700344 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700345 """Start a local server on an interface.
346
347 @param interface string (e.g. wlan0)
348
349 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700350 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700351
352 if len(self.local_servers) >= 256:
353 raise error.TestFail('Exhausted available local servers')
354
Christopher Wiley594570d2014-03-14 16:50:15 -0700355 server_addr = netblock.Netblock.from_addr(
356 self.local_server_address(len(self.local_servers)),
357 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700358
359 params = {}
Christopher Wiley594570d2014-03-14 16:50:15 -0700360 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700361 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700362 (server_addr.get_addr_in_block(1),
363 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700364 params['interface'] = interface
Christopher Wiley594570d2014-03-14 16:50:15 -0700365 params['ip_params'] = ('%s broadcast %s dev %s' %
366 (server_addr.netblock,
367 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700368 interface))
369 self.local_servers.append(params)
370
Christopher Wiley594570d2014-03-14 16:50:15 -0700371 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700372 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700373 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700374 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700375 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700376 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800377 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700378
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700379
Paul Stewart548cf452012-11-27 17:46:23 -0800380 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700381 """Start a dhcp server on an interface.
382
383 @param interface string (e.g. wlan0)
384
385 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800386 for server in self.local_servers:
387 if server['interface'] == interface:
388 params = server
389 break
390 else:
391 raise error.TestFail('Could not find local server '
392 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700393 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800394 dhcpd_conf_file = self.dhcpd_conf % interface
395 dhcp_conf = '\n'.join([
396 'port=0', # disables DNS server
397 'bind-interfaces',
398 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700399 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
400 server_addr.get_addr_in_block(128))),
Christopher Wileyeea12362013-12-12 17:24:29 -0800401 'interface=%s' % params['interface'],
402 'dhcp-leasefile=%s' % self.dhcpd_leases])
403 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
404 (dhcpd_conf_file, dhcp_conf))
405 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700406
407
Paul Stewart326badb2012-12-18 14:18:54 -0800408 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700409 """Stop a dhcp server on the router.
410
411 @param instance string instance to kill.
412
413 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800414 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800415
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700416
Paul Stewart548cf452012-11-27 17:46:23 -0800417 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700418 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800419 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800420
421
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800422 def get_wifi_channel(self, ap_num):
423 """Return channel of BSS corresponding to |ap_num|.
424
425 @param ap_num int which BSS to get the channel of.
426 @return int primary channel of BSS.
427
428 """
429 instance = self.hostapd_instances[ap_num]
430 return instance['config_dict']['channel']
431
432
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700433 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700434 """Return IP address on the WiFi subnet of a local server on the router.
435
436 If no local servers are configured (e.g. for an RSPro), a TestFail will
437 be raised.
438
439 @param ap_num int which local server to get an address from.
440
441 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700442 if not self.local_servers:
443 raise error.TestError('No IP address assigned')
444
445 return self.local_servers[ap_num]['netblock'].addr
446
447
448 def get_wifi_ip_subnet(self, ap_num):
449 """Return subnet of WiFi AP instance.
450
451 If no APs are configured a TestError will be raised.
452
453 @param ap_num int which local server to get an address from.
454
455 """
456 if not self.local_servers:
457 raise error.TestError('No APs configured.')
458
459 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700460
461
Christopher Wileya3effac2014-02-05 11:16:11 -0800462 def get_hostapd_interface(self, ap_num):
463 """Get the name of the interface associated with a hostapd instance.
464
465 @param ap_num: int hostapd instance number.
466 @return string interface name (e.g. 'managed0').
467
468 """
469 if ap_num not in range(len(self.hostapd_instances)):
470 raise error.TestFail('Invalid instance number (%d) with %d '
471 'instances configured.' %
472 (ap_num, len(self.hostapd_instances)))
473
474 instance = self.hostapd_instances[ap_num]
475 return instance['interface']
476
477
Paul Stewart17350be2012-12-14 13:34:54 -0800478 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700479 """Return the MAC address of an AP in the test.
480
481 @param ap_num int index of local server to read the MAC address from.
482 @return string MAC address like 00:11:22:33:44:55.
483
484 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800485 interface_name = self.get_hostapd_interface(ap_num)
486 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800487 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800488
489
Christopher Wileya3effac2014-02-05 11:16:11 -0800490 def get_hostapd_phy(self, ap_num):
491 """Get name of phy for hostapd instance.
492
493 @param ap_num int index of hostapd instance.
494 @return string phy name of phy corresponding to hostapd's
495 managed interface.
496
497 """
498 interface = self.iw_runner.get_interface(
499 self.get_hostapd_interface(ap_num))
500 return interface.phy
501
502
Christopher Wileyeea12362013-12-12 17:24:29 -0800503 def deconfig(self):
504 """A legacy, deprecated alias for deconfig_aps."""
505 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800506
507
508 def deconfig_aps(self, instance=None, silent=False):
509 """De-configure an AP (will also bring wlan down).
510
511 @param instance: int or None. If instance is None, will bring down all
512 instances of hostapd.
513 @param silent: True if instances should be brought without de-authing
514 the DUT.
515
516 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800517 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700518 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700519
Christopher Wileyeea12362013-12-12 17:24:29 -0800520 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800521 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800522 if instance is not None:
523 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800524 for server in self.local_servers:
525 if server['interface'] == instances[0]['interface']:
526 local_servers = [server]
527 self.local_servers.remove(server)
528 break
Paul Stewart21737812012-12-06 11:03:32 -0800529 else:
530 instances = self.hostapd_instances
531 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800532 local_servers = self.local_servers
533 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700534
Paul Stewart21737812012-12-06 11:03:32 -0800535 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800536 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800537 # Deconfigure without notifying DUT. Remove the interface
538 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800539 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800540
Paul Stewart326badb2012-12-18 14:18:54 -0800541 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700542 if wifi_test_utils.is_installed(self.host,
543 instance['log_file']):
544 self.router.get_file(instance['log_file'],
545 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800546 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700547 instance['interface']))
548 else:
549 logging.error('Did not collect hostapd log file because '
550 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800551 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800552# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800553 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800554 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700555 local_servers = self.local_servers
556 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800557 instance = self.station_instances.pop()
558 if instance.dev_type == 'ibss':
559 self.iw_runner.ibss_leave(instance.interface)
560 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800561 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800562 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800563 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800564 self.iw_runner.disconnect_station(instance.interface)
565 self.router.run('%s link set %s down' %
566 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700567
Paul Stewart326badb2012-12-18 14:18:54 -0800568 for server in local_servers:
569 self.stop_dhcp_server(server['interface'])
570 self.router.run("%s addr del %s" %
571 (self.cmd_ip, server['ip_params']),
572 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700573
Paul Stewart7cb1f062010-06-10 15:46:20 -0700574
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800575 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700576 """Verify that the PMKSA auth was cached on a hostapd instance.
577
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800578 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700579
580 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800581 log_file = self.hostapd_instances[instance]['log_file']
582 pmksa_match = 'PMK from PMKSA cache'
583 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
584 ignore_status=True)
585 if result.exit_status:
586 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800587
588
Christopher Wileye0afecb2013-11-11 10:54:23 -0800589 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700590 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800591 if instance is None:
592 instance = 0
593 if len(self.hostapd_instances) > 1:
594 raise error.TestFail('No instance of hostapd specified with '
595 'multiple instances present.')
596
Christopher Wiley3099be72013-11-06 16:49:02 -0800597 if self.hostapd_instances:
598 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700599
Christopher Wiley408d1812014-01-13 15:27:43 -0800600 if self.station_instances:
601 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700602
Christopher Wiley408d1812014-01-13 15:27:43 -0800603 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700604
605
Wade Guthriee4074dd2013-10-30 11:00:48 -0700606 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700607 """Deauthenticates a client described in params.
608
Wade Guthriee4074dd2013-10-30 11:00:48 -0700609 @param client_mac string containing the mac address of the client to be
610 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700611
612 """
Paul Stewart80bb3372014-01-22 15:06:08 -0800613 control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700614 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800615 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700616
617
Peter Qiuc4beba02014-03-24 14:46:24 -0700618 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -0700619 """Injects a management frame into an active hostapd session.
620
621 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -0700622 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -0700623 @param instance int indicating which hostapd instance to inject into.
624
625 """
626 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800627 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700628 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -0700629 self.router.run('%s -i %s -t %s -c %d' %
630 (self.cmd_send_management_frame, interface, frame_type,
631 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800632 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700633
634
Peter Qiuc4beba02014-03-24 14:46:24 -0700635 def setup_management_frame_interface(self, channel):
636 """
637 Setup interface for injecting management frames.
638
639 @param channel int channel to inject the frames.
640
641 @return string name of the interface.
642
643 """
644 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
645 channel)
646 interface = self.get_wlanif(frequency, 'monitor')
647 self.iw_runner.set_freq(interface, frequency)
648 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
649 return interface
650
651
652 def send_management_frame(self, interface, frame_type, channel,
653 ssid_prefix=None, num_bss=None,
654 frame_count=None, delay=None):
655 """
656 Injects management frames on specify channel |frequency|.
657
658 This function will spawn off a new process to inject specified
659 management frames |frame_type| at the specified interface |interface|.
660
661 @param interface string interface to inject frames.
662 @param frame_type string message type.
663 @param channel int targeted channel.
664 @param ssid_prefix string SSID prefix.
665 @param num_bss int number of BSS.
666 @param frame_count int number of frames to send.
667 @param delay int milliseconds delay between frames.
668
669 @return int PID of the newly created process.
670
671 """
672 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
673 interface, frame_type, channel)
674 if ssid_prefix is not None:
675 command += ' -s %s' % (ssid_prefix)
676 if num_bss is not None:
677 command += ' -b %d' % (num_bss)
678 if frame_count is not None:
679 command += ' -n %d' % (frame_count)
680 if delay is not None:
681 command += ' -d %d' % (delay)
682 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
683 pid = int(self.router.run(command).stdout)
684 return pid
685
686
Paul Stewart25536942013-08-15 17:33:42 -0700687 def detect_client_deauth(self, client_mac, instance=0):
688 """Detects whether hostapd has logged a deauthentication from
689 |client_mac|.
690
691 @param client_mac string the MAC address of the client to detect.
692 @param instance int indicating which hostapd instance to query.
693
694 """
695 interface = self.hostapd_instances[instance]['interface']
696 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
697 log_file = self.hostapd_instances[instance]['log_file']
698 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
699 ignore_status=True)
700 return result.exit_status == 0
701
702
Paul Stewart4ae471e2013-09-04 15:42:35 -0700703 def detect_client_coexistence_report(self, client_mac, instance=0):
704 """Detects whether hostapd has logged an action frame from
705 |client_mac| indicating information about 20/40MHz BSS coexistence.
706
707 @param client_mac string the MAC address of the client to detect.
708 @param instance int indicating which hostapd instance to query.
709
710 """
711 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
712 '.. .. .. .. .. .. .. .. .. .. %s '
713 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
714 ' '.join(client_mac.split(':')))
715 log_file = self.hostapd_instances[instance]['log_file']
716 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
717 ignore_status=True)
718 return result.exit_status == 0
719
720
Paul Stewart6ddeba72013-11-18 10:08:23 -0800721 def add_connected_peer(self, instance=0):
722 """Configure a station connected to a running AP instance.
723
724 Extract relevant configuration objects from the hostap
725 configuration for |instance| and generate a wpa_supplicant
726 instance that connects to it. This allows the DUT to interact
727 with a client entity that is also connected to the same AP. A
728 full wpa_supplicant instance is necessary here (instead of just
729 using the "iw" command to connect) since we want to enable
730 advanced features such as TDLS.
731
732 @param instance int indicating which hostapd instance to connect to.
733
734 """
735 if not self.hostapd_instances:
736 raise error.TestFail('Hostapd is not configured.')
737
Christopher Wiley408d1812014-01-13 15:27:43 -0800738 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800739 raise error.TestFail('Station is already configured.')
740
Christopher Wiley408d1812014-01-13 15:27:43 -0800741 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800742 hostap_conf = self.hostapd_instances[instance]['config_dict']
743 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
744 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800745 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800746
747 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
748 # require them...
749 supplicant_config = (
750 'network={\n'
751 ' ssid="%(ssid)s"\n'
752 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800753 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800754 )
755
Christopher Wileyeea12362013-12-12 17:24:29 -0800756 conf_file = self.STATION_CONF_FILE_PATTERN % interface
757 log_file = self.STATION_LOG_FILE_PATTERN % interface
758 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800759
760 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
761 (conf_file, supplicant_config))
762
763 # Connect the station.
764 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
765 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
766 (self.cmd_wpa_supplicant,
767 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800768 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800769 self.router.run(start_command)
770 self.iw_runner.wait_for_link(interface)
771
772 # Assign an IP address to this interface.
773 self.router.run('%s addr add %s/24 dev %s' %
774 (self.cmd_ip, self.local_peer_ip_address(instance),
775 interface))
776
777 # Since we now have two network interfaces connected to the same
778 # network, we need to disable the kernel's protection against
779 # incoming packets to an "unexpected" interface.
780 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
781 interface)
782
Paul Stewartb01839b2013-12-06 15:49:56 -0800783 # Similarly, we'd like to prevent the hostap interface from
784 # replying to ARP requests for the peer IP address and vice
785 # versa.
786 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
787 interface)
788 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
789 hostap_conf['interface'])
790
Christopher Wiley408d1812014-01-13 15:27:43 -0800791 self.station_instances.append(
792 StationInstance(ssid=ssid, interface=interface,
793 dev_type='managed'))