blob: 9defce506a987471bfc986066591baeb139dbf25 [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 """
Christopher Wiley5689d362014-01-07 15:21:25 -0800540 if not self.local_servers:
541 raise error.TestFail('Cannot retrieve MAC: '
542 'no AP instances configured.')
543
Paul Stewart17350be2012-12-14 13:34:54 -0800544 instance = self.hostapd_instances[ap_num]
Christopher Wiley5689d362014-01-07 15:21:25 -0800545 ap_interface = interface.Interface(instance['interface'], self.host)
546 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800547
548
Christopher Wileyeea12362013-12-12 17:24:29 -0800549 def deconfig(self):
550 """A legacy, deprecated alias for deconfig_aps."""
551 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800552
553
554 def deconfig_aps(self, instance=None, silent=False):
555 """De-configure an AP (will also bring wlan down).
556
557 @param instance: int or None. If instance is None, will bring down all
558 instances of hostapd.
559 @param silent: True if instances should be brought without de-authing
560 the DUT.
561
562 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800563 if not self.hostapd_instances and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700564 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700565
Christopher Wileyeea12362013-12-12 17:24:29 -0800566 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800567 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800568 if instance is not None:
569 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800570 for server in self.local_servers:
571 if server['interface'] == instances[0]['interface']:
572 local_servers = [server]
573 self.local_servers.remove(server)
574 break
Paul Stewart21737812012-12-06 11:03:32 -0800575 else:
576 instances = self.hostapd_instances
577 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800578 local_servers = self.local_servers
579 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700580
Paul Stewart21737812012-12-06 11:03:32 -0800581 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800582 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800583 # Deconfigure without notifying DUT. Remove the interface
584 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800585 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800586
Paul Stewart326badb2012-12-18 14:18:54 -0800587 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700588 if wifi_test_utils.is_installed(self.host,
589 instance['log_file']):
590 self.router.get_file(instance['log_file'],
591 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800592 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700593 instance['interface']))
594 else:
595 logging.error('Did not collect hostapd log file because '
596 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800597 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800598# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800599 self._total_hostapd_instances += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800600 if self.station['configured']:
Christopher Wiley05262d62013-04-17 17:53:59 -0700601 local_servers = self.local_servers
602 self.local_servers = []
Paul Stewartc2b3de82011-03-03 14:45:31 -0800603 if self.station['type'] == 'ibss':
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700604 self.iw_runner.ibss_leave(self.station['interface'])
Christopher Wiley4f49a5d2013-11-25 18:20:54 -0800605 elif self.station['type'] == 'supplicant':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800606 self._kill_process_instance('wpa_supplicant',
607 self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800608 else:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700609 self.iw_runner.disconnect_station(self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800610 self.router.run("%s link set %s down" % (self.cmd_ip,
611 self.station['interface']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700612
Paul Stewart326badb2012-12-18 14:18:54 -0800613 for server in local_servers:
614 self.stop_dhcp_server(server['interface'])
615 self.router.run("%s addr del %s" %
616 (self.cmd_ip, server['ip_params']),
617 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700618
Paul Stewartc2b3de82011-03-03 14:45:31 -0800619 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700620
621
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800622 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700623 """Verify that the PMKSA auth was cached on a hostapd instance.
624
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800625 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700626
627 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800628 log_file = self.hostapd_instances[instance]['log_file']
629 pmksa_match = 'PMK from PMKSA cache'
630 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
631 ignore_status=True)
632 if result.exit_status:
633 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800634
635
Christopher Wileye0afecb2013-11-11 10:54:23 -0800636 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700637 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800638 if instance is None:
639 instance = 0
640 if len(self.hostapd_instances) > 1:
641 raise error.TestFail('No instance of hostapd specified with '
642 'multiple instances present.')
643
Christopher Wiley3099be72013-11-06 16:49:02 -0800644 if self.hostapd_instances:
645 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700646
Christopher Wiley3166e432013-08-06 09:53:12 -0700647 if not 'ssid' in self.station['conf']:
648 raise error.TestFail('Requested ssid of an unconfigured AP.')
649
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700650 return self.station['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700651
652
Wade Guthriee4074dd2013-10-30 11:00:48 -0700653 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700654 """Deauthenticates a client described in params.
655
Wade Guthriee4074dd2013-10-30 11:00:48 -0700656 @param client_mac string containing the mac address of the client to be
657 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700658
659 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700660 self.router.run('%s -p%s deauthenticate %s' %
661 (self.cmd_hostapd_cli,
Christopher Wileyeea12362013-12-12 17:24:29 -0800662 self.hostapd_instances[-1]['ctrl_interface'],
Wade Guthriee4074dd2013-10-30 11:00:48 -0700663 client_mac))
664
665
Paul Stewart51b0f382013-06-12 09:03:02 -0700666 def send_management_frame(self, frame_type, instance=0):
667 """Injects a management frame into an active hostapd session.
668
669 @param frame_type string the type of frame to send.
670 @param instance int indicating which hostapd instance to inject into.
671
672 """
673 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800674 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700675 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
676 self.router.run('%s %s %s' %
677 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800678 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700679
680
Paul Stewart25536942013-08-15 17:33:42 -0700681 def detect_client_deauth(self, client_mac, instance=0):
682 """Detects whether hostapd has logged a deauthentication from
683 |client_mac|.
684
685 @param client_mac string the MAC address of the client to detect.
686 @param instance int indicating which hostapd instance to query.
687
688 """
689 interface = self.hostapd_instances[instance]['interface']
690 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
691 log_file = self.hostapd_instances[instance]['log_file']
692 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
693 ignore_status=True)
694 return result.exit_status == 0
695
696
Paul Stewart4ae471e2013-09-04 15:42:35 -0700697 def detect_client_coexistence_report(self, client_mac, instance=0):
698 """Detects whether hostapd has logged an action frame from
699 |client_mac| indicating information about 20/40MHz BSS coexistence.
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 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
706 '.. .. .. .. .. .. .. .. .. .. %s '
707 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
708 ' '.join(client_mac.split(':')))
709 log_file = self.hostapd_instances[instance]['log_file']
710 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
711 ignore_status=True)
712 return result.exit_status == 0
713
714
Paul Stewart6ddeba72013-11-18 10:08:23 -0800715 def add_connected_peer(self, instance=0):
716 """Configure a station connected to a running AP instance.
717
718 Extract relevant configuration objects from the hostap
719 configuration for |instance| and generate a wpa_supplicant
720 instance that connects to it. This allows the DUT to interact
721 with a client entity that is also connected to the same AP. A
722 full wpa_supplicant instance is necessary here (instead of just
723 using the "iw" command to connect) since we want to enable
724 advanced features such as TDLS.
725
726 @param instance int indicating which hostapd instance to connect to.
727
728 """
729 if not self.hostapd_instances:
730 raise error.TestFail('Hostapd is not configured.')
731
732 if self.station['configured']:
733 raise error.TestFail('Station is already configured.')
734
735 client_conf = self.station['conf']
736 client_conf['ssid'] = self.get_ssid(instance)
737
738 hostap_conf = self.hostapd_instances[instance]['config_dict']
739 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
740 hostap_conf['channel'])
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800741 interface = self.get_wlanif(
Paul Stewart6ddeba72013-11-18 10:08:23 -0800742 frequency, 'managed', hostap_conf['hw_mode'])
743 client_conf['interface'] = interface
744
745 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
746 # require them...
747 supplicant_config = (
748 'network={\n'
749 ' ssid="%(ssid)s"\n'
750 ' key_mgmt=NONE\n'
751 '}\n' % client_conf
752 )
753
Christopher Wileyeea12362013-12-12 17:24:29 -0800754 conf_file = self.STATION_CONF_FILE_PATTERN % interface
755 log_file = self.STATION_LOG_FILE_PATTERN % interface
756 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800757
758 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
759 (conf_file, supplicant_config))
760
761 # Connect the station.
762 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
763 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
764 (self.cmd_wpa_supplicant,
765 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800766 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800767 self.router.run(start_command)
768 self.iw_runner.wait_for_link(interface)
769
770 # Assign an IP address to this interface.
771 self.router.run('%s addr add %s/24 dev %s' %
772 (self.cmd_ip, self.local_peer_ip_address(instance),
773 interface))
774
775 # Since we now have two network interfaces connected to the same
776 # network, we need to disable the kernel's protection against
777 # incoming packets to an "unexpected" interface.
778 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
779 interface)
780
Paul Stewartb01839b2013-12-06 15:49:56 -0800781 # Similarly, we'd like to prevent the hostap interface from
782 # replying to ARP requests for the peer IP address and vice
783 # versa.
784 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
785 interface)
786 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
787 hostap_conf['interface'])
788
Paul Stewart6ddeba72013-11-18 10:08:23 -0800789 self.station['configured'] = True
790 self.station['type'] = 'supplicant'
791 self.station['interface'] = interface