blob: 7cdf371adb11527a34414fe7d4931b74ff13b888 [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 Wiley08aafb02014-04-22 17:38:21 -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 Wiley08aafb02014-04-22 17:38:21 -0700108 self._ssid_prefix = test_name
109 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
Christopher Wiley3166e432013-08-06 09:53:12 -0700110 # Many of our tests start with an uninteresting prefix.
111 # Remove it so we can have more unique bytes.
Christopher Wiley08aafb02014-04-22 17:38:21 -0700112 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
113 self._number_unique_ssids = 0
Christopher Wiley3166e432013-08-06 09:53:12 -0700114
Christopher Wileyeea12362013-12-12 17:24:29 -0800115 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700116 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800117 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800118 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700119 self.dhcp_low = 1
120 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700121
Paul Stewart548cf452012-11-27 17:46:23 -0800122 # Kill hostapd and dhcp server if already running.
Thieu Le7b23a542012-01-27 15:54:48 -0800123 self.kill_hostapd()
Paul Stewart548cf452012-11-27 17:46:23 -0800124 self.stop_dhcp_servers()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700125
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700126 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700127 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700128
Peter Qiu2f973252014-02-20 15:30:37 -0800129 # Reset all antennas to be active
130 self.set_default_antenna_bitmap()
131
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700132
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700133 def close(self):
134 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800135 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800136 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700137
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700138
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700139 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700140 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700141 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700142
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700143
Peter Qiuc89c9a22014-02-27 10:03:55 -0800144 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700145 """Start a hostapd instance described by conf.
146
Christopher Wileyeea12362013-12-12 17:24:29 -0800147 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700148
149 """
Paul Stewart548cf452012-11-27 17:46:23 -0800150 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800151 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800152
Christopher Wileyeea12362013-12-12 17:24:29 -0800153 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
154 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
155 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800156 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800157 hostapd_conf_dict = configuration.generate_dict(
158 interface, control_interface,
Christopher Wiley08aafb02014-04-22 17:38:21 -0700159 self._build_unique_ssid(configuration.ssid_suffix))
Peter Qiuc89c9a22014-02-27 10:03:55 -0800160 logging.info('Starting hostapd with parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800161
162 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800163 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
164 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800165 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800166
167 # Run hostapd.
168 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700169 self.router.run('rm %s' % log_file, ignore_status=True)
170 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800171 self.router.run('stop wpasupplicant', ignore_status=True)
172 start_command = '%s -dd -B -t -f %s -P %s %s' % (
173 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700174 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800175 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800176 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800177 'conf_file': conf_file,
178 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700179 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800180 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800181 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800182 })
183
Christopher Wileyeea12362013-12-12 17:24:29 -0800184 # Wait for confirmation that the router came up.
185 pid = int(self.router.run('cat %s' % pid_file).stdout)
186 logging.info('Waiting for hostapd to startup.')
187 start_time = time.time()
188 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
189 success = self.router.run(
190 'grep "Completing interface initialization" %s' % log_file,
191 ignore_status=True).exit_status == 0
192 if success:
193 break
194
195 # A common failure is an invalid router configuration.
196 # Detect this and exit early if we see it.
197 bad_config = self.router.run(
198 'grep "Interface initialization failed" %s' % log_file,
199 ignore_status=True).exit_status == 0
200 if bad_config:
201 raise error.TestFail('hostapd failed to initialize AP '
202 'interface.')
203
204 if pid:
205 early_exit = self.router.run('kill -0 %d' % pid,
206 ignore_status=True).exit_status
207 if early_exit:
208 raise error.TestFail('hostapd process terminated.')
209
210 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
211 else:
212 raise error.TestFail('Timed out while waiting for hostapd '
213 'to start.')
214
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700215
Paul Stewart326badb2012-12-18 14:18:54 -0800216 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700217 """Kill a process on the router.
218
Paul Stewart326badb2012-12-18 14:18:54 -0800219 Kills program named |process|, optionally only a specific
220 |instance|. If |wait| is specified, we makes sure |process| exits
221 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700222
223 @param process string name of process to kill.
224 @param instance string instance of process to kill.
225 @param wait int timeout in seconds to wait for.
226
Thieu Le7b23a542012-01-27 15:54:48 -0800227 """
Paul Stewart21737812012-12-06 11:03:32 -0800228 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800229 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800230 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800231 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800232
Paul Stewart326badb2012-12-18 14:18:54 -0800233 cmd = "pkill %s >/dev/null 2>&1" % search_arg
234
235 if wait:
236 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
237 search_arg)
238 self.router.run(cmd, timeout=wait, ignore_status=True)
239 else:
240 self.router.run(cmd, ignore_status=True)
241
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700242
Paul Stewart326badb2012-12-18 14:18:54 -0800243 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700244 """Kills a hostapd instance.
245
246 @param instance string instance to kill.
247
248 """
Paul Stewart326badb2012-12-18 14:18:54 -0800249 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800250
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700251
Paul Stewart21737812012-12-06 11:03:32 -0800252 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700253 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800254 self.kill_hostapd_instance(None)
255
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700256
Christopher Wiley08aafb02014-04-22 17:38:21 -0700257 def _build_unique_ssid(self, suffix):
258 # Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding
259 # the number of APs we've constructed already.
260 base = len(self.SUFFIX_LETTERS)
261 number = self._number_unique_ssids
262 self._number_unique_ssids += 1
263 unique = ''
264 while number or not unique:
265 unique = self.SUFFIX_LETTERS[number % base] + unique
266 number = number / base
267 # And salt the SSID so that tests running in adjacent cells are unlikely
268 # to pick the same SSID and we're resistent to beacons leaking out of
269 # cells.
270 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)])
271 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:]
Christopher Wiley3166e432013-08-06 09:53:12 -0700272
273
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700274 def hostap_configure(self, configuration, multi_interface=None):
275 """Build up a hostapd configuration file and start hostapd.
276
277 Also setup a local server if this router supports them.
278
279 @param configuration HosetapConfig object.
280 @param multi_interface bool True iff multiple interfaces allowed.
281
282 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800283 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800284 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700285 self.deconfig()
Peter Qiuc89c9a22014-02-27 10:03:55 -0800286 self.start_hostapd(configuration)
Christopher Wileyeea12362013-12-12 17:24:29 -0800287 interface = self.hostapd_instances[-1]['interface']
288 self.iw_runner.set_tx_power(interface, 'auto')
289 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700290 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700291
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700292
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700293 def ibss_configure(self, config):
294 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700295
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700296 Extract relevant configuration objects from |config| despite not
297 actually being a hostap managed endpoint.
298
299 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700300
301 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800302 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700303 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800304 interface = self.get_wlanif(config.frequency, 'ibss')
Christopher Wiley08aafb02014-04-22 17:38:21 -0700305 ssid = (config.ssid or self._build_unique_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800306 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700307 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800308 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700309 # Always start a local server.
310 self.start_local_server(interface)
311 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800312 self.station_instances.append(
313 StationInstance(ssid=ssid, interface=interface,
314 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800315
316
Paul Stewart2bd823b2012-11-21 15:03:37 -0800317 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700318 """Get the local server address for an interface.
319
320 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700321 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700322
323 @param index int describing which local server this is for.
324
325 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700326 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800327
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700328
Paul Stewart6ddeba72013-11-18 10:08:23 -0800329 def local_peer_ip_address(self, index):
330 """Get the IP address allocated for the peer associated to the AP.
331
332 This address is assigned to a locally associated peer device that
333 is created for the DUT to perform connectivity tests with.
334 When we have multiple local servers, we give them static IP addresses
335 like 192.168.*.253.
336
337 @param index int describing which local server this is for.
338
339 """
340 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
341
342
343 def local_peer_mac_address(self):
344 """Get the MAC address of the peer interface.
345
346 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800347
Paul Stewart6ddeba72013-11-18 10:08:23 -0800348 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800349 iface = interface.Interface(self.station_instances[0].interface,
350 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800351 return iface.mac_address
352
353
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700354 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700355 """Start a local server on an interface.
356
357 @param interface string (e.g. wlan0)
358
359 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700360 logging.info('Starting up local server...')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700361
362 if len(self.local_servers) >= 256:
363 raise error.TestFail('Exhausted available local servers')
364
Christopher Wiley594570d2014-03-14 16:50:15 -0700365 server_addr = netblock.Netblock.from_addr(
366 self.local_server_address(len(self.local_servers)),
367 prefix_len=24)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700368
369 params = {}
Christopher Wiley594570d2014-03-14 16:50:15 -0700370 params['netblock'] = server_addr
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700371 params['dhcp_range'] = ' '.join(
Christopher Wiley594570d2014-03-14 16:50:15 -0700372 (server_addr.get_addr_in_block(1),
373 server_addr.get_addr_in_block(128)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700374 params['interface'] = interface
Christopher Wiley594570d2014-03-14 16:50:15 -0700375 params['ip_params'] = ('%s broadcast %s dev %s' %
376 (server_addr.netblock,
377 server_addr.broadcast,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700378 interface))
379 self.local_servers.append(params)
380
Christopher Wiley594570d2014-03-14 16:50:15 -0700381 self.router.run('%s addr flush %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700382 (self.cmd_ip, interface))
Christopher Wiley594570d2014-03-14 16:50:15 -0700383 self.router.run('%s addr add %s' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700384 (self.cmd_ip, params['ip_params']))
Christopher Wiley594570d2014-03-14 16:50:15 -0700385 self.router.run('%s link set %s up' %
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700386 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800387 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700388
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700389
Paul Stewart548cf452012-11-27 17:46:23 -0800390 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700391 """Start a dhcp server on an interface.
392
393 @param interface string (e.g. wlan0)
394
395 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800396 for server in self.local_servers:
397 if server['interface'] == interface:
398 params = server
399 break
400 else:
401 raise error.TestFail('Could not find local server '
402 'to match interface: %r' % interface)
Christopher Wiley594570d2014-03-14 16:50:15 -0700403 server_addr = params['netblock']
Christopher Wileyeea12362013-12-12 17:24:29 -0800404 dhcpd_conf_file = self.dhcpd_conf % interface
405 dhcp_conf = '\n'.join([
406 'port=0', # disables DNS server
407 'bind-interfaces',
408 'log-dhcp',
Christopher Wiley594570d2014-03-14 16:50:15 -0700409 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
410 server_addr.get_addr_in_block(128))),
Christopher Wileyeea12362013-12-12 17:24:29 -0800411 'interface=%s' % params['interface'],
412 'dhcp-leasefile=%s' % self.dhcpd_leases])
413 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
414 (dhcpd_conf_file, dhcp_conf))
415 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700416
417
Paul Stewart326badb2012-12-18 14:18:54 -0800418 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700419 """Stop a dhcp server on the router.
420
421 @param instance string instance to kill.
422
423 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800424 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800425
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700426
Paul Stewart548cf452012-11-27 17:46:23 -0800427 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700428 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800429 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800430
431
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800432 def get_wifi_channel(self, ap_num):
433 """Return channel of BSS corresponding to |ap_num|.
434
435 @param ap_num int which BSS to get the channel of.
436 @return int primary channel of BSS.
437
438 """
439 instance = self.hostapd_instances[ap_num]
440 return instance['config_dict']['channel']
441
442
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700443 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700444 """Return IP address on the WiFi subnet of a local server on the router.
445
446 If no local servers are configured (e.g. for an RSPro), a TestFail will
447 be raised.
448
449 @param ap_num int which local server to get an address from.
450
451 """
Christopher Wiley594570d2014-03-14 16:50:15 -0700452 if not self.local_servers:
453 raise error.TestError('No IP address assigned')
454
455 return self.local_servers[ap_num]['netblock'].addr
456
457
458 def get_wifi_ip_subnet(self, ap_num):
459 """Return subnet of WiFi AP instance.
460
461 If no APs are configured a TestError will be raised.
462
463 @param ap_num int which local server to get an address from.
464
465 """
466 if not self.local_servers:
467 raise error.TestError('No APs configured.')
468
469 return self.local_servers[ap_num]['netblock'].subnet
Paul Stewart5977da92011-06-01 19:14:08 -0700470
471
Christopher Wileya3effac2014-02-05 11:16:11 -0800472 def get_hostapd_interface(self, ap_num):
473 """Get the name of the interface associated with a hostapd instance.
474
475 @param ap_num: int hostapd instance number.
476 @return string interface name (e.g. 'managed0').
477
478 """
479 if ap_num not in range(len(self.hostapd_instances)):
480 raise error.TestFail('Invalid instance number (%d) with %d '
481 'instances configured.' %
482 (ap_num, len(self.hostapd_instances)))
483
484 instance = self.hostapd_instances[ap_num]
485 return instance['interface']
486
487
Paul Stewart17350be2012-12-14 13:34:54 -0800488 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700489 """Return the MAC address of an AP in the test.
490
491 @param ap_num int index of local server to read the MAC address from.
492 @return string MAC address like 00:11:22:33:44:55.
493
494 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800495 interface_name = self.get_hostapd_interface(ap_num)
496 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800497 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800498
499
Christopher Wileya3effac2014-02-05 11:16:11 -0800500 def get_hostapd_phy(self, ap_num):
501 """Get name of phy for hostapd instance.
502
503 @param ap_num int index of hostapd instance.
504 @return string phy name of phy corresponding to hostapd's
505 managed interface.
506
507 """
508 interface = self.iw_runner.get_interface(
509 self.get_hostapd_interface(ap_num))
510 return interface.phy
511
512
Christopher Wileyeea12362013-12-12 17:24:29 -0800513 def deconfig(self):
514 """A legacy, deprecated alias for deconfig_aps."""
515 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800516
517
518 def deconfig_aps(self, instance=None, silent=False):
519 """De-configure an AP (will also bring wlan down).
520
521 @param instance: int or None. If instance is None, will bring down all
522 instances of hostapd.
523 @param silent: True if instances should be brought without de-authing
524 the DUT.
525
526 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800527 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700528 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700529
Christopher Wileyeea12362013-12-12 17:24:29 -0800530 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800531 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800532 if instance is not None:
533 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800534 for server in self.local_servers:
535 if server['interface'] == instances[0]['interface']:
536 local_servers = [server]
537 self.local_servers.remove(server)
538 break
Paul Stewart21737812012-12-06 11:03:32 -0800539 else:
540 instances = self.hostapd_instances
541 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800542 local_servers = self.local_servers
543 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700544
Paul Stewart21737812012-12-06 11:03:32 -0800545 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800546 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800547 # Deconfigure without notifying DUT. Remove the interface
548 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800549 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800550
Paul Stewart326badb2012-12-18 14:18:54 -0800551 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700552 if wifi_test_utils.is_installed(self.host,
553 instance['log_file']):
554 self.router.get_file(instance['log_file'],
555 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800556 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700557 instance['interface']))
558 else:
559 logging.error('Did not collect hostapd log file because '
560 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800561 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800562# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800563 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800564 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700565 local_servers = self.local_servers
566 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800567 instance = self.station_instances.pop()
568 if instance.dev_type == 'ibss':
569 self.iw_runner.ibss_leave(instance.interface)
570 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800571 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800572 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800573 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800574 self.iw_runner.disconnect_station(instance.interface)
575 self.router.run('%s link set %s down' %
576 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700577
Paul Stewart326badb2012-12-18 14:18:54 -0800578 for server in local_servers:
579 self.stop_dhcp_server(server['interface'])
580 self.router.run("%s addr del %s" %
581 (self.cmd_ip, server['ip_params']),
582 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700583
Paul Stewart7cb1f062010-06-10 15:46:20 -0700584
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800585 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700586 """Verify that the PMKSA auth was cached on a hostapd instance.
587
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800588 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700589
590 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800591 log_file = self.hostapd_instances[instance]['log_file']
592 pmksa_match = 'PMK from PMKSA cache'
593 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
594 ignore_status=True)
595 if result.exit_status:
596 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800597
598
Christopher Wileye0afecb2013-11-11 10:54:23 -0800599 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700600 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800601 if instance is None:
602 instance = 0
603 if len(self.hostapd_instances) > 1:
604 raise error.TestFail('No instance of hostapd specified with '
605 'multiple instances present.')
606
Christopher Wiley3099be72013-11-06 16:49:02 -0800607 if self.hostapd_instances:
608 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700609
Christopher Wiley408d1812014-01-13 15:27:43 -0800610 if self.station_instances:
611 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700612
Christopher Wiley408d1812014-01-13 15:27:43 -0800613 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700614
615
Wade Guthriee4074dd2013-10-30 11:00:48 -0700616 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700617 """Deauthenticates a client described in params.
618
Wade Guthriee4074dd2013-10-30 11:00:48 -0700619 @param client_mac string containing the mac address of the client to be
620 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700621
622 """
Paul Stewart80bb3372014-01-22 15:06:08 -0800623 control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700624 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800625 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700626
627
Peter Qiuc4beba02014-03-24 14:46:24 -0700628 def send_management_frame_on_ap(self, frame_type, channel, instance=0):
Paul Stewart51b0f382013-06-12 09:03:02 -0700629 """Injects a management frame into an active hostapd session.
630
631 @param frame_type string the type of frame to send.
Peter Qiuc4beba02014-03-24 14:46:24 -0700632 @param channel int targeted channel
Paul Stewart51b0f382013-06-12 09:03:02 -0700633 @param instance int indicating which hostapd instance to inject into.
634
635 """
636 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800637 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700638 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
Peter Qiuc4beba02014-03-24 14:46:24 -0700639 self.router.run('%s -i %s -t %s -c %d' %
640 (self.cmd_send_management_frame, interface, frame_type,
641 channel))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800642 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700643
644
Peter Qiuc4beba02014-03-24 14:46:24 -0700645 def setup_management_frame_interface(self, channel):
646 """
647 Setup interface for injecting management frames.
648
649 @param channel int channel to inject the frames.
650
651 @return string name of the interface.
652
653 """
654 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
655 channel)
656 interface = self.get_wlanif(frequency, 'monitor')
657 self.iw_runner.set_freq(interface, frequency)
658 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
659 return interface
660
661
662 def send_management_frame(self, interface, frame_type, channel,
663 ssid_prefix=None, num_bss=None,
664 frame_count=None, delay=None):
665 """
666 Injects management frames on specify channel |frequency|.
667
668 This function will spawn off a new process to inject specified
669 management frames |frame_type| at the specified interface |interface|.
670
671 @param interface string interface to inject frames.
672 @param frame_type string message type.
673 @param channel int targeted channel.
674 @param ssid_prefix string SSID prefix.
675 @param num_bss int number of BSS.
676 @param frame_count int number of frames to send.
677 @param delay int milliseconds delay between frames.
678
679 @return int PID of the newly created process.
680
681 """
682 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame,
683 interface, frame_type, channel)
684 if ssid_prefix is not None:
685 command += ' -s %s' % (ssid_prefix)
686 if num_bss is not None:
687 command += ' -b %d' % (num_bss)
688 if frame_count is not None:
689 command += ' -n %d' % (frame_count)
690 if delay is not None:
691 command += ' -d %d' % (delay)
692 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE)
693 pid = int(self.router.run(command).stdout)
694 return pid
695
696
Paul Stewart25536942013-08-15 17:33:42 -0700697 def detect_client_deauth(self, client_mac, instance=0):
698 """Detects whether hostapd has logged a deauthentication from
699 |client_mac|.
700
701 @param client_mac string the MAC address of the client to detect.
702 @param instance int indicating which hostapd instance to query.
703
704 """
705 interface = self.hostapd_instances[instance]['interface']
706 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
707 log_file = self.hostapd_instances[instance]['log_file']
708 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
709 ignore_status=True)
710 return result.exit_status == 0
711
712
Paul Stewart4ae471e2013-09-04 15:42:35 -0700713 def detect_client_coexistence_report(self, client_mac, instance=0):
714 """Detects whether hostapd has logged an action frame from
715 |client_mac| indicating information about 20/40MHz BSS coexistence.
716
717 @param client_mac string the MAC address of the client to detect.
718 @param instance int indicating which hostapd instance to query.
719
720 """
721 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
722 '.. .. .. .. .. .. .. .. .. .. %s '
723 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
724 ' '.join(client_mac.split(':')))
725 log_file = self.hostapd_instances[instance]['log_file']
726 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
727 ignore_status=True)
728 return result.exit_status == 0
729
730
Paul Stewart6ddeba72013-11-18 10:08:23 -0800731 def add_connected_peer(self, instance=0):
732 """Configure a station connected to a running AP instance.
733
734 Extract relevant configuration objects from the hostap
735 configuration for |instance| and generate a wpa_supplicant
736 instance that connects to it. This allows the DUT to interact
737 with a client entity that is also connected to the same AP. A
738 full wpa_supplicant instance is necessary here (instead of just
739 using the "iw" command to connect) since we want to enable
740 advanced features such as TDLS.
741
742 @param instance int indicating which hostapd instance to connect to.
743
744 """
745 if not self.hostapd_instances:
746 raise error.TestFail('Hostapd is not configured.')
747
Christopher Wiley408d1812014-01-13 15:27:43 -0800748 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800749 raise error.TestFail('Station is already configured.')
750
Christopher Wiley408d1812014-01-13 15:27:43 -0800751 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800752 hostap_conf = self.hostapd_instances[instance]['config_dict']
753 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
754 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800755 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800756
757 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
758 # require them...
759 supplicant_config = (
760 'network={\n'
761 ' ssid="%(ssid)s"\n'
762 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800763 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800764 )
765
Christopher Wileyeea12362013-12-12 17:24:29 -0800766 conf_file = self.STATION_CONF_FILE_PATTERN % interface
767 log_file = self.STATION_LOG_FILE_PATTERN % interface
768 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800769
770 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
771 (conf_file, supplicant_config))
772
773 # Connect the station.
774 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
775 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
776 (self.cmd_wpa_supplicant,
777 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800778 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800779 self.router.run(start_command)
780 self.iw_runner.wait_for_link(interface)
781
782 # Assign an IP address to this interface.
783 self.router.run('%s addr add %s/24 dev %s' %
784 (self.cmd_ip, self.local_peer_ip_address(instance),
785 interface))
786
787 # Since we now have two network interfaces connected to the same
788 # network, we need to disable the kernel's protection against
789 # incoming packets to an "unexpected" interface.
790 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
791 interface)
792
Paul Stewartb01839b2013-12-06 15:49:56 -0800793 # Similarly, we'd like to prevent the hostap interface from
794 # replying to ARP requests for the peer IP address and vice
795 # versa.
796 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
797 interface)
798 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
799 hostap_conf['interface'])
800
Christopher Wiley408d1812014-01-13 15:27:43 -0800801 self.station_instances.append(
802 StationInstance(ssid=ssid, interface=interface,
803 dev_type='managed'))