blob: 9ca886f9a797d8bff5c52d90fd203c1c0db9e76b [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 Wiley14796b32013-04-03 14:53:33 -07005import logging
Christopher Wiley3166e432013-08-06 09:53:12 -07006import random
Christopher Wiley3166e432013-08-06 09:53:12 -07007import string
Christopher Wileyeea12362013-12-12 17:24:29 -08008import time
Christopher Wiley14796b32013-04-03 14:53:33 -07009
Paul Stewartc9628b32010-08-11 13:03:51 -070010from autotest_lib.client.common_lib import error
Paul Stewart6ddeba72013-11-18 10:08:23 -080011from autotest_lib.client.common_lib.cros.network import interface
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070012from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070013from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070014from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070015
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070016class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070017 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070018
19 This class implements test methods/steps that communicate with a
20 router implemented with Linux/mac80211. The router must
21 be pre-configured to enable ssh access and have a mac80211-based
22 wireless device. We also assume hostapd 0.7.x and iw are present
23 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070024
Sam Leffler6969d1d2010-03-15 16:07:11 -070025 """
26
Christopher Wiley3166e432013-08-06 09:53:12 -070027 KNOWN_TEST_PREFIX = 'network_WiFi'
Christopher Wiley1defc242013-09-18 10:28:37 -070028 STARTUP_POLLING_INTERVAL_SECONDS = 0.5
29 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070030 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070031 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070032
Christopher Wileyeea12362013-12-12 17:24:29 -080033 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
34 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
35 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
36 HOSTAPD_DRIVER_NAME = 'nl80211'
37
38 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
39 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
40 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
41
Paul Stewart51b0f382013-06-12 09:03:02 -070042 def get_capabilities(self):
43 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080044 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070045 try:
46 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
47 self.router, '/usr/bin/send_management_frame')
48 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
49 except error.TestFail:
50 pass
51 return super(LinuxRouter, self).get_capabilities().union(caps)
52
53
Christopher Wiley3166e432013-08-06 09:53:12 -070054 def __init__(self, host, params, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070055 """Build a LinuxRouter.
56
57 @param host Host object representing the remote machine.
58 @param params dict of settings from site_wifitest based tests.
Christopher Wiley3166e432013-08-06 09:53:12 -070059 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070060
61 """
Christopher Wileyeea12362013-12-12 17:24:29 -080062 params = params.copy()
63 params.update({
64 'phy_bus_preference': {
65 'monitor': 'usb',
66 'managed': 'pci'
67 }})
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070068 site_linux_system.LinuxSystem.__init__(self, host, params, 'router')
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070069
Wade Guthrie24d1e312012-04-24 16:53:40 -070070 # Router host.
71 self.router = host
72
Christopher Wileyeea12362013-12-12 17:24:29 -080073 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070074 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080075 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070076 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080077 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -080078 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080079 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070080 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
81 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070082
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070083 # hostapd configuration persists throughout the test, subsequent
84 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -070085 self.ssid_prefix = test_name
86 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
87 # Many of our tests start with an uninteresting prefix.
88 # Remove it so we can have more unique bytes.
89 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
90 self.ssid_prefix = self.ssid_prefix.lstrip('_')
91 self.ssid_prefix += '_'
92
Christopher Wileyeea12362013-12-12 17:24:29 -080093 self._total_hostapd_instances = 0
Paul Stewartc2b3de82011-03-03 14:45:31 -080094 self.station = {
95 'configured': False,
Paul Stewart6ddeba72013-11-18 10:08:23 -080096 'config_file': "/tmp/wpa-supplicant-test-%s.conf",
97 'log_file': "/tmp/wpa-supplicant-test-%s.log",
98 'pid_file': "/tmp/wpa-supplicant-test-%s.pid",
Christopher Wiley3166e432013-08-06 09:53:12 -070099 'conf': {},
Paul Stewartc2b3de82011-03-03 14:45:31 -0800100 }
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700101 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800102 self.hostapd_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700103 self.dhcp_low = 1
104 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700105
Paul Stewart548cf452012-11-27 17:46:23 -0800106 # Kill hostapd and dhcp server if already running.
Thieu Le7b23a542012-01-27 15:54:48 -0800107 self.kill_hostapd()
Paul Stewart548cf452012-11-27 17:46:23 -0800108 self.stop_dhcp_servers()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700109
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700110 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700111 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700112
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700113
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700114 def close(self):
115 """Close global resources held by this system."""
116 self.destroy()
117 super(LinuxRouter, self).close()
118
119
Christopher Wiley14796b32013-04-03 14:53:33 -0700120 def create_wifi_device(self, device_type='hostap'):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700121 """Create a wifi device of the specified type.
Christopher Wiley14796b32013-04-03 14:53:33 -0700122
123 Defaults to creating a hostap managed device.
124
125 @param device_type string device type.
126
127 """
Sam Leffler6969d1d2010-03-15 16:07:11 -0700128 #
129 # AP mode is handled entirely by hostapd so we only
130 # have to setup others (mapping the bsd type to what
131 # iw wants)
132 #
133 # map from bsd types to iw types
Christopher Wiley14796b32013-04-03 14:53:33 -0700134 self.apmode = device_type in ('ap', 'hostap')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800135 if not self.apmode:
Christopher Wiley14796b32013-04-03 14:53:33 -0700136 self.station['type'] = device_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700137 self.phytype = {
Christopher Wiley14796b32013-04-03 14:53:33 -0700138 'sta' : 'managed',
139 'monitor' : 'monitor',
140 'adhoc' : 'adhoc',
141 'ibss' : 'ibss',
142 'ap' : 'managed', # NB: handled by hostapd
143 'hostap' : 'managed', # NB: handled by hostapd
144 'mesh' : 'mesh',
145 'wds' : 'wds',
146 }[device_type]
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700147
Sam Leffler6969d1d2010-03-15 16:07:11 -0700148
Christopher Wileyeea12362013-12-12 17:24:29 -0800149 def destroy(self):
150 """Destroy a previously created device."""
151 self.deconfig()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700152
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700153
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700154 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700155 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700156 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700157
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700158
Christopher Wileyeea12362013-12-12 17:24:29 -0800159 def start_hostapd(self, hostapd_conf_dict, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700160 """Start a hostapd instance described by conf.
161
Christopher Wileyeea12362013-12-12 17:24:29 -0800162 @param hostapd_conf_dict dict of hostapd configuration parameters.
163 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700164
165 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800166 logging.info('Starting hostapd with parameters: %r',
167 hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800168 # Figure out the correct interface.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800169 interface = self.get_wlanif(configuration.frequency,
170 self.phytype,
171 configuration.hw_mode)
Paul Stewart326badb2012-12-18 14:18:54 -0800172
Christopher Wileyeea12362013-12-12 17:24:29 -0800173 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
174 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
175 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
176 hostapd_conf_dict['interface'] = interface
Paul Stewart548cf452012-11-27 17:46:23 -0800177
178 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800179 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
180 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800181 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800182
183 # Run hostapd.
184 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700185 self.router.run('rm %s' % log_file, ignore_status=True)
186 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800187 self.router.run('stop wpasupplicant', ignore_status=True)
188 start_command = '%s -dd -B -t -f %s -P %s %s' % (
189 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700190 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800191 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800192 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800193 'conf_file': conf_file,
194 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700195 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800196 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800197 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800198 })
199
Christopher Wileyeea12362013-12-12 17:24:29 -0800200 # Wait for confirmation that the router came up.
201 pid = int(self.router.run('cat %s' % pid_file).stdout)
202 logging.info('Waiting for hostapd to startup.')
203 start_time = time.time()
204 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
205 success = self.router.run(
206 'grep "Completing interface initialization" %s' % log_file,
207 ignore_status=True).exit_status == 0
208 if success:
209 break
210
211 # A common failure is an invalid router configuration.
212 # Detect this and exit early if we see it.
213 bad_config = self.router.run(
214 'grep "Interface initialization failed" %s' % log_file,
215 ignore_status=True).exit_status == 0
216 if bad_config:
217 raise error.TestFail('hostapd failed to initialize AP '
218 'interface.')
219
220 if pid:
221 early_exit = self.router.run('kill -0 %d' % pid,
222 ignore_status=True).exit_status
223 if early_exit:
224 raise error.TestFail('hostapd process terminated.')
225
226 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
227 else:
228 raise error.TestFail('Timed out while waiting for hostapd '
229 'to start.')
230
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700231
Paul Stewart326badb2012-12-18 14:18:54 -0800232 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700233 """Kill a process on the router.
234
Paul Stewart326badb2012-12-18 14:18:54 -0800235 Kills program named |process|, optionally only a specific
236 |instance|. If |wait| is specified, we makes sure |process| exits
237 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700238
239 @param process string name of process to kill.
240 @param instance string instance of process to kill.
241 @param wait int timeout in seconds to wait for.
242
Thieu Le7b23a542012-01-27 15:54:48 -0800243 """
Paul Stewart21737812012-12-06 11:03:32 -0800244 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800245 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800246 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800247 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800248
Paul Stewart326badb2012-12-18 14:18:54 -0800249 cmd = "pkill %s >/dev/null 2>&1" % search_arg
250
251 if wait:
252 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
253 search_arg)
254 self.router.run(cmd, timeout=wait, ignore_status=True)
255 else:
256 self.router.run(cmd, ignore_status=True)
257
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700258
Paul Stewart326badb2012-12-18 14:18:54 -0800259 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700260 """Kills a hostapd instance.
261
262 @param instance string instance to kill.
263
264 """
Paul Stewart326badb2012-12-18 14:18:54 -0800265 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800266
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700267
Paul Stewart21737812012-12-06 11:03:32 -0800268 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700269 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800270 self.kill_hostapd_instance(None)
271
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700272
273 def __get_default_hostap_config(self):
274 """@return dict of default options for hostapd."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800275 return {'hw_mode': 'g',
276 'ctrl_interface': '/tmp/hostapd-test.control',
277 'logger_syslog': '-1',
278 'logger_syslog_level': '0',
279 # default RTS and frag threshold to ``off''
280 'rts_threshold': '2347',
281 'fragm_threshold': '2346',
282 'driver': self.HOSTAPD_DRIVER_NAME,
283 'ssid': self._build_ssid('') }
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700284
285
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700286 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700287 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
288 for x in range(5)])
289 return (self.ssid_prefix + unique_salt + suffix)[-32:]
290
291
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700292 def hostap_configure(self, configuration, multi_interface=None):
293 """Build up a hostapd configuration file and start hostapd.
294
295 Also setup a local server if this router supports them.
296
297 @param configuration HosetapConfig object.
298 @param multi_interface bool True iff multiple interfaces allowed.
299
300 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800301 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700302 self.station['configured']):
303 self.deconfig()
304 # Start with the default hostapd config parameters.
305 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700306 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700307 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700308 if configuration.bssid:
309 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700310 conf['channel'] = configuration.channel
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700311 conf['hw_mode'] = configuration.hw_mode
312 if configuration.hide_ssid:
313 conf['ignore_broadcast_ssid'] = 1
314 if configuration.is_11n:
315 conf['ieee80211n'] = 1
Christopher Wiley32fbb7a2013-09-18 14:30:50 -0700316 conf['ht_capab'] = configuration.hostapd_ht_capabilities
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700317 if configuration.wmm_enabled:
318 conf['wmm_enabled'] = 1
319 if configuration.require_ht:
320 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700321 if configuration.beacon_interval:
322 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700323 if configuration.dtim_period:
324 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700325 if configuration.frag_threshold:
326 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700327 if configuration.pmf_support:
328 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700329 if configuration.obss_interval:
330 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700331 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileyeea12362013-12-12 17:24:29 -0800332 self.start_hostapd(conf, configuration)
333 interface = self.hostapd_instances[-1]['interface']
334 self.iw_runner.set_tx_power(interface, 'auto')
335 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700336 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700337
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700338
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700339 @staticmethod
340 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700341 """Simple IPv4 calculator.
342
343 Takes host address in "IP/bits" notation and returns netmask, broadcast
344 address as well as integer offsets into the address range.
345
346 @param netblock string host address in "IP/bits" notation.
347 @param idx string describing what to return.
348 @return string containing something you hopefully requested.
349
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700350 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700351 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700352 addr = map(int, addr_str.split('.'))
353 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
354 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700355 if idx == 'local':
356 return addr_str
357 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700358 return '.'.join(map(str, mask))
359 elif idx == 'broadcast':
360 offset = [m ^ 0xff for m in mask]
361 else:
362 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
363 return '.'.join(map(str, [(a & m) + o
364 for a, m, o in zip(addr, mask, offset)]))
365
366
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700367 def ibss_configure(self, config):
368 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700369
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700370 Extract relevant configuration objects from |config| despite not
371 actually being a hostap managed endpoint.
372
373 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700374
375 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800376 if self.station['configured'] or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700377 self.deconfig()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800378 interface = self.get_wlanif(config.frequency, self.phytype,
379 config.hw_mode)
Christopher Wiley3166e432013-08-06 09:53:12 -0700380 self.station['conf']['ssid'] = (config.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700381 self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800382 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700383 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700384 self.iw_runner.ibss_join(
385 interface, self.station['conf']['ssid'], config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700386 # Always start a local server.
387 self.start_local_server(interface)
388 # Remember that this interface is up.
Paul Stewartc2b3de82011-03-03 14:45:31 -0800389 self.station['configured'] = True
390 self.station['interface'] = interface
391
392
Paul Stewart2bd823b2012-11-21 15:03:37 -0800393 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700394 """Get the local server address for an interface.
395
396 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700397 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700398
399 @param index int describing which local server this is for.
400
401 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700402 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800403
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700404
Paul Stewart6ddeba72013-11-18 10:08:23 -0800405 def local_peer_ip_address(self, index):
406 """Get the IP address allocated for the peer associated to the AP.
407
408 This address is assigned to a locally associated peer device that
409 is created for the DUT to perform connectivity tests with.
410 When we have multiple local servers, we give them static IP addresses
411 like 192.168.*.253.
412
413 @param index int describing which local server this is for.
414
415 """
416 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
417
418
419 def local_peer_mac_address(self):
420 """Get the MAC address of the peer interface.
421
422 @return string MAC address of the peer interface.
423 """
424 iface = interface.Interface(self.station['interface'], self.router)
425 return iface.mac_address
426
427
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700428 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700429 """Start a local server on an interface.
430
431 @param interface string (e.g. wlan0)
432
433 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700434 logging.info("Starting up local server...")
435
436 if len(self.local_servers) >= 256:
437 raise error.TestFail('Exhausted available local servers')
438
Paul Stewart2bd823b2012-11-21 15:03:37 -0800439 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700440
441 params = {}
442 params['netblock'] = netblock
443 params['subnet'] = self.ip_addr(netblock, 0)
444 params['netmask'] = self.ip_addr(netblock, 'netmask')
445 params['dhcp_range'] = ' '.join(
446 (self.ip_addr(netblock, self.dhcp_low),
447 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700448 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700449
450 params['ip_params'] = ("%s broadcast %s dev %s" %
451 (netblock,
452 self.ip_addr(netblock, 'broadcast'),
453 interface))
454 self.local_servers.append(params)
455
456 self.router.run("%s addr flush %s" %
457 (self.cmd_ip, interface))
458 self.router.run("%s addr add %s" %
459 (self.cmd_ip, params['ip_params']))
460 self.router.run("%s link set %s up" %
461 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800462 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700463
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700464
Paul Stewart548cf452012-11-27 17:46:23 -0800465 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700466 """Start a dhcp server on an interface.
467
468 @param interface string (e.g. wlan0)
469
470 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800471 for server in self.local_servers:
472 if server['interface'] == interface:
473 params = server
474 break
475 else:
476 raise error.TestFail('Could not find local server '
477 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700478
Christopher Wileyeea12362013-12-12 17:24:29 -0800479 dhcpd_conf_file = self.dhcpd_conf % interface
480 dhcp_conf = '\n'.join([
481 'port=0', # disables DNS server
482 'bind-interfaces',
483 'log-dhcp',
484 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
485 'interface=%s' % params['interface'],
486 'dhcp-leasefile=%s' % self.dhcpd_leases])
487 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
488 (dhcpd_conf_file, dhcp_conf))
489 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700490
491
Paul Stewart326badb2012-12-18 14:18:54 -0800492 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700493 """Stop a dhcp server on the router.
494
495 @param instance string instance to kill.
496
497 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800498 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800499
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700500
Paul Stewart548cf452012-11-27 17:46:23 -0800501 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700502 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800503 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800504
505
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800506 def get_wifi_channel(self, ap_num):
507 """Return channel of BSS corresponding to |ap_num|.
508
509 @param ap_num int which BSS to get the channel of.
510 @return int primary channel of BSS.
511
512 """
513 instance = self.hostapd_instances[ap_num]
514 return instance['config_dict']['channel']
515
516
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700517 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700518 """Return IP address on the WiFi subnet of a local server on the router.
519
520 If no local servers are configured (e.g. for an RSPro), a TestFail will
521 be raised.
522
523 @param ap_num int which local server to get an address from.
524
525 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700526 if self.local_servers:
527 return self.ip_addr(self.local_servers[ap_num]['netblock'],
528 'local')
529 else:
530 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700531
532
Paul Stewart17350be2012-12-14 13:34:54 -0800533 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700534 """Return the MAC address of an AP in the test.
535
536 @param ap_num int index of local server to read the MAC address from.
537 @return string MAC address like 00:11:22:33:44:55.
538
539 """
Paul Stewart17350be2012-12-14 13:34:54 -0800540 instance = self.hostapd_instances[ap_num]
541 interface = instance['interface']
542 result = self.router.run('%s addr show %s' % (self.cmd_ip, interface))
543 # Example response:
544 # 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 UP qlen 1000
545 # link/ether 99:88:77:66:55:44 brd ff:ff:ff:ff:ff:ff
546 # inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
547 # inet6 fe80::6a7f:74ff:fe66:5544/64 scope link
548 # we want the MAC address after the "link/ether" above.
549 parts = result.stdout.split(' ')
550 return parts[parts.index('link/ether') + 1]
551
552
Christopher Wileyeea12362013-12-12 17:24:29 -0800553 def deconfig(self):
554 """A legacy, deprecated alias for deconfig_aps."""
555 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800556
557
558 def deconfig_aps(self, instance=None, silent=False):
559 """De-configure an AP (will also bring wlan down).
560
561 @param instance: int or None. If instance is None, will bring down all
562 instances of hostapd.
563 @param silent: True if instances should be brought without de-authing
564 the DUT.
565
566 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800567 if not self.hostapd_instances and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700568 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700569
Christopher Wileyeea12362013-12-12 17:24:29 -0800570 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800571 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800572 if instance is not None:
573 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800574 for server in self.local_servers:
575 if server['interface'] == instances[0]['interface']:
576 local_servers = [server]
577 self.local_servers.remove(server)
578 break
Paul Stewart21737812012-12-06 11:03:32 -0800579 else:
580 instances = self.hostapd_instances
581 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800582 local_servers = self.local_servers
583 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700584
Paul Stewart21737812012-12-06 11:03:32 -0800585 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800586 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800587 # Deconfigure without notifying DUT. Remove the interface
588 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800589 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800590
Paul Stewart326badb2012-12-18 14:18:54 -0800591 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700592 if wifi_test_utils.is_installed(self.host,
593 instance['log_file']):
594 self.router.get_file(instance['log_file'],
595 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800596 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700597 instance['interface']))
598 else:
599 logging.error('Did not collect hostapd log file because '
600 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800601 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800602# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800603 self._total_hostapd_instances += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800604 if self.station['configured']:
Christopher Wiley05262d62013-04-17 17:53:59 -0700605 local_servers = self.local_servers
606 self.local_servers = []
Paul Stewartc2b3de82011-03-03 14:45:31 -0800607 if self.station['type'] == 'ibss':
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700608 self.iw_runner.ibss_leave(self.station['interface'])
Christopher Wiley4f49a5d2013-11-25 18:20:54 -0800609 elif self.station['type'] == 'supplicant':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800610 self._kill_process_instance('wpa_supplicant',
611 self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800612 else:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700613 self.iw_runner.disconnect_station(self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800614 self.router.run("%s link set %s down" % (self.cmd_ip,
615 self.station['interface']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700616
Paul Stewart326badb2012-12-18 14:18:54 -0800617 for server in local_servers:
618 self.stop_dhcp_server(server['interface'])
619 self.router.run("%s addr del %s" %
620 (self.cmd_ip, server['ip_params']),
621 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700622
Paul Stewartc2b3de82011-03-03 14:45:31 -0800623 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700624
625
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800626 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700627 """Verify that the PMKSA auth was cached on a hostapd instance.
628
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800629 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700630
631 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800632 log_file = self.hostapd_instances[instance]['log_file']
633 pmksa_match = 'PMK from PMKSA cache'
634 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
635 ignore_status=True)
636 if result.exit_status:
637 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800638
639
Christopher Wileye0afecb2013-11-11 10:54:23 -0800640 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700641 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800642 if instance is None:
643 instance = 0
644 if len(self.hostapd_instances) > 1:
645 raise error.TestFail('No instance of hostapd specified with '
646 'multiple instances present.')
647
Christopher Wiley3099be72013-11-06 16:49:02 -0800648 if self.hostapd_instances:
649 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700650
Christopher Wiley3166e432013-08-06 09:53:12 -0700651 if not 'ssid' in self.station['conf']:
652 raise error.TestFail('Requested ssid of an unconfigured AP.')
653
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700654 return self.station['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700655
656
Wade Guthriee4074dd2013-10-30 11:00:48 -0700657 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700658 """Deauthenticates a client described in params.
659
Wade Guthriee4074dd2013-10-30 11:00:48 -0700660 @param client_mac string containing the mac address of the client to be
661 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700662
663 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700664 self.router.run('%s -p%s deauthenticate %s' %
665 (self.cmd_hostapd_cli,
Christopher Wileyeea12362013-12-12 17:24:29 -0800666 self.hostapd_instances[-1]['ctrl_interface'],
Wade Guthriee4074dd2013-10-30 11:00:48 -0700667 client_mac))
668
669
Paul Stewart51b0f382013-06-12 09:03:02 -0700670 def send_management_frame(self, frame_type, instance=0):
671 """Injects a management frame into an active hostapd session.
672
673 @param frame_type string the type of frame to send.
674 @param instance int indicating which hostapd instance to inject into.
675
676 """
677 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800678 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700679 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
680 self.router.run('%s %s %s' %
681 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800682 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700683
684
Paul Stewart25536942013-08-15 17:33:42 -0700685 def detect_client_deauth(self, client_mac, instance=0):
686 """Detects whether hostapd has logged a deauthentication from
687 |client_mac|.
688
689 @param client_mac string the MAC address of the client to detect.
690 @param instance int indicating which hostapd instance to query.
691
692 """
693 interface = self.hostapd_instances[instance]['interface']
694 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
695 log_file = self.hostapd_instances[instance]['log_file']
696 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
697 ignore_status=True)
698 return result.exit_status == 0
699
700
Paul Stewart4ae471e2013-09-04 15:42:35 -0700701 def detect_client_coexistence_report(self, client_mac, instance=0):
702 """Detects whether hostapd has logged an action frame from
703 |client_mac| indicating information about 20/40MHz BSS coexistence.
704
705 @param client_mac string the MAC address of the client to detect.
706 @param instance int indicating which hostapd instance to query.
707
708 """
709 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
710 '.. .. .. .. .. .. .. .. .. .. %s '
711 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
712 ' '.join(client_mac.split(':')))
713 log_file = self.hostapd_instances[instance]['log_file']
714 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
715 ignore_status=True)
716 return result.exit_status == 0
717
718
Paul Stewart6ddeba72013-11-18 10:08:23 -0800719 def add_connected_peer(self, instance=0):
720 """Configure a station connected to a running AP instance.
721
722 Extract relevant configuration objects from the hostap
723 configuration for |instance| and generate a wpa_supplicant
724 instance that connects to it. This allows the DUT to interact
725 with a client entity that is also connected to the same AP. A
726 full wpa_supplicant instance is necessary here (instead of just
727 using the "iw" command to connect) since we want to enable
728 advanced features such as TDLS.
729
730 @param instance int indicating which hostapd instance to connect to.
731
732 """
733 if not self.hostapd_instances:
734 raise error.TestFail('Hostapd is not configured.')
735
736 if self.station['configured']:
737 raise error.TestFail('Station is already configured.')
738
739 client_conf = self.station['conf']
740 client_conf['ssid'] = self.get_ssid(instance)
741
742 hostap_conf = self.hostapd_instances[instance]['config_dict']
743 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
744 hostap_conf['channel'])
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800745 interface = self.get_wlanif(
Paul Stewart6ddeba72013-11-18 10:08:23 -0800746 frequency, 'managed', hostap_conf['hw_mode'])
747 client_conf['interface'] = interface
748
749 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
750 # require them...
751 supplicant_config = (
752 'network={\n'
753 ' ssid="%(ssid)s"\n'
754 ' key_mgmt=NONE\n'
755 '}\n' % client_conf
756 )
757
Christopher Wileyeea12362013-12-12 17:24:29 -0800758 conf_file = self.STATION_CONF_FILE_PATTERN % interface
759 log_file = self.STATION_LOG_FILE_PATTERN % interface
760 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800761
762 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
763 (conf_file, supplicant_config))
764
765 # Connect the station.
766 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
767 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
768 (self.cmd_wpa_supplicant,
769 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800770 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800771 self.router.run(start_command)
772 self.iw_runner.wait_for_link(interface)
773
774 # Assign an IP address to this interface.
775 self.router.run('%s addr add %s/24 dev %s' %
776 (self.cmd_ip, self.local_peer_ip_address(instance),
777 interface))
778
779 # Since we now have two network interfaces connected to the same
780 # network, we need to disable the kernel's protection against
781 # incoming packets to an "unexpected" interface.
782 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
783 interface)
784
Paul Stewartb01839b2013-12-06 15:49:56 -0800785 # Similarly, we'd like to prevent the hostap interface from
786 # replying to ARP requests for the peer IP address and vice
787 # versa.
788 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
789 interface)
790 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
791 hostap_conf['interface'])
792
Paul Stewart6ddeba72013-11-18 10:08:23 -0800793 self.station['configured'] = True
794 self.station['type'] = 'supplicant'
795 self.station['interface'] = interface