blob: 4e64a8cc770d40b87cdb17ed5de099fbe40029b4 [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
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070013from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070014from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070015from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070016
Christopher Wiley408d1812014-01-13 15:27:43 -080017
18StationInstance = collections.namedtuple('StationInstance',
19 ['ssid', 'interface', 'dev_type'])
20
21
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070022class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070023 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070024
25 This class implements test methods/steps that communicate with a
26 router implemented with Linux/mac80211. The router must
27 be pre-configured to enable ssh access and have a mac80211-based
28 wireless device. We also assume hostapd 0.7.x and iw are present
29 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070030
Sam Leffler6969d1d2010-03-15 16:07:11 -070031 """
32
Christopher Wiley3166e432013-08-06 09:53:12 -070033 KNOWN_TEST_PREFIX = 'network_WiFi'
Christopher Wiley1defc242013-09-18 10:28:37 -070034 STARTUP_POLLING_INTERVAL_SECONDS = 0.5
35 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070036 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070037 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070038
Christopher Wileyeea12362013-12-12 17:24:29 -080039 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
40 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
41 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
Paul Stewart80bb3372014-01-22 15:06:08 -080042 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -080043 HOSTAPD_DRIVER_NAME = 'nl80211'
44
45 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
46 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
47 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
48
Paul Stewart51b0f382013-06-12 09:03:02 -070049 def get_capabilities(self):
50 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080051 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070052 try:
53 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080054 self.host, '/usr/bin/send_management_frame')
Paul Stewart51b0f382013-06-12 09:03:02 -070055 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
56 except error.TestFail:
57 pass
58 return super(LinuxRouter, self).get_capabilities().union(caps)
59
60
Christopher Wiley408d1812014-01-13 15:27:43 -080061 @property
62 def router(self):
63 """Deprecated. Use self.host instead.
64
65 @return Host object representing the remote router.
66
67 """
68 return self.host
69
70
71 def __init__(self, host, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070072 """Build a LinuxRouter.
73
74 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -070075 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070076
77 """
Christopher Wiley408d1812014-01-13 15:27:43 -080078 super(LinuxRouter, self).__init__(host, 'router')
Wade Guthrie24d1e312012-04-24 16:53:40 -070079
Christopher Wileyeea12362013-12-12 17:24:29 -080080 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070081 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080082 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070083 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080084 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -080085 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080086 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070087 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
88 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070089
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070090 # hostapd configuration persists throughout the test, subsequent
91 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -070092 self.ssid_prefix = test_name
93 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
94 # Many of our tests start with an uninteresting prefix.
95 # Remove it so we can have more unique bytes.
96 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
97 self.ssid_prefix = self.ssid_prefix.lstrip('_')
98 self.ssid_prefix += '_'
99
Christopher Wileyeea12362013-12-12 17:24:29 -0800100 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700101 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800102 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800103 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700104 self.dhcp_low = 1
105 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700106
Paul Stewart548cf452012-11-27 17:46:23 -0800107 # Kill hostapd and dhcp server if already running.
Thieu Le7b23a542012-01-27 15:54:48 -0800108 self.kill_hostapd()
Paul Stewart548cf452012-11-27 17:46:23 -0800109 self.stop_dhcp_servers()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700110
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700111 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700112 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700113
Peter Qiu2f973252014-02-20 15:30:37 -0800114 # Reset all antennas to be active
115 self.set_default_antenna_bitmap()
116
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700117
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700118 def close(self):
119 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800120 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800121 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700122
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700123
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700124 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700125 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700126 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700127
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700128
Christopher Wileyeea12362013-12-12 17:24:29 -0800129 def start_hostapd(self, hostapd_conf_dict, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700130 """Start a hostapd instance described by conf.
131
Christopher Wileyeea12362013-12-12 17:24:29 -0800132 @param hostapd_conf_dict dict of hostapd configuration parameters.
133 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700134
135 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800136 logging.info('Starting hostapd with parameters: %r',
137 hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800138 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800139 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800140
Christopher Wileyeea12362013-12-12 17:24:29 -0800141 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
142 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
143 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800144 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800145 hostapd_conf_dict['interface'] = interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800146 hostapd_conf_dict['ctrl_interface'] = control_interface
Paul Stewart548cf452012-11-27 17:46:23 -0800147
148 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800149 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
150 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800151 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800152
153 # Run hostapd.
154 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700155 self.router.run('rm %s' % log_file, ignore_status=True)
156 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800157 self.router.run('stop wpasupplicant', ignore_status=True)
158 start_command = '%s -dd -B -t -f %s -P %s %s' % (
159 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700160 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800161 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800162 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800163 'conf_file': conf_file,
164 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700165 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800166 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800167 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800168 })
169
Christopher Wileyeea12362013-12-12 17:24:29 -0800170 # Wait for confirmation that the router came up.
171 pid = int(self.router.run('cat %s' % pid_file).stdout)
172 logging.info('Waiting for hostapd to startup.')
173 start_time = time.time()
174 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
175 success = self.router.run(
176 'grep "Completing interface initialization" %s' % log_file,
177 ignore_status=True).exit_status == 0
178 if success:
179 break
180
181 # A common failure is an invalid router configuration.
182 # Detect this and exit early if we see it.
183 bad_config = self.router.run(
184 'grep "Interface initialization failed" %s' % log_file,
185 ignore_status=True).exit_status == 0
186 if bad_config:
187 raise error.TestFail('hostapd failed to initialize AP '
188 'interface.')
189
190 if pid:
191 early_exit = self.router.run('kill -0 %d' % pid,
192 ignore_status=True).exit_status
193 if early_exit:
194 raise error.TestFail('hostapd process terminated.')
195
196 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
197 else:
198 raise error.TestFail('Timed out while waiting for hostapd '
199 'to start.')
200
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700201
Paul Stewart326badb2012-12-18 14:18:54 -0800202 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700203 """Kill a process on the router.
204
Paul Stewart326badb2012-12-18 14:18:54 -0800205 Kills program named |process|, optionally only a specific
206 |instance|. If |wait| is specified, we makes sure |process| exits
207 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700208
209 @param process string name of process to kill.
210 @param instance string instance of process to kill.
211 @param wait int timeout in seconds to wait for.
212
Thieu Le7b23a542012-01-27 15:54:48 -0800213 """
Paul Stewart21737812012-12-06 11:03:32 -0800214 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800215 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800216 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800217 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800218
Paul Stewart326badb2012-12-18 14:18:54 -0800219 cmd = "pkill %s >/dev/null 2>&1" % search_arg
220
221 if wait:
222 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
223 search_arg)
224 self.router.run(cmd, timeout=wait, ignore_status=True)
225 else:
226 self.router.run(cmd, ignore_status=True)
227
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700228
Paul Stewart326badb2012-12-18 14:18:54 -0800229 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700230 """Kills a hostapd instance.
231
232 @param instance string instance to kill.
233
234 """
Paul Stewart326badb2012-12-18 14:18:54 -0800235 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800236
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700237
Paul Stewart21737812012-12-06 11:03:32 -0800238 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700239 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800240 self.kill_hostapd_instance(None)
241
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700242
243 def __get_default_hostap_config(self):
244 """@return dict of default options for hostapd."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800245 return {'hw_mode': 'g',
Christopher Wileyeea12362013-12-12 17:24:29 -0800246 'logger_syslog': '-1',
247 'logger_syslog_level': '0',
248 # default RTS and frag threshold to ``off''
249 'rts_threshold': '2347',
250 'fragm_threshold': '2346',
251 'driver': self.HOSTAPD_DRIVER_NAME,
252 'ssid': self._build_ssid('') }
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700253
254
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700255 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700256 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
257 for x in range(5)])
258 return (self.ssid_prefix + unique_salt + suffix)[-32:]
259
260
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700261 def hostap_configure(self, configuration, multi_interface=None):
262 """Build up a hostapd configuration file and start hostapd.
263
264 Also setup a local server if this router supports them.
265
266 @param configuration HosetapConfig object.
267 @param multi_interface bool True iff multiple interfaces allowed.
268
269 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800270 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800271 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700272 self.deconfig()
273 # Start with the default hostapd config parameters.
274 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700275 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700276 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700277 if configuration.bssid:
278 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700279 conf['channel'] = configuration.channel
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700280 conf['hw_mode'] = configuration.hw_mode
281 if configuration.hide_ssid:
282 conf['ignore_broadcast_ssid'] = 1
283 if configuration.is_11n:
284 conf['ieee80211n'] = 1
Christopher Wiley32fbb7a2013-09-18 14:30:50 -0700285 conf['ht_capab'] = configuration.hostapd_ht_capabilities
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700286 if configuration.wmm_enabled:
287 conf['wmm_enabled'] = 1
288 if configuration.require_ht:
289 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700290 if configuration.beacon_interval:
291 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700292 if configuration.dtim_period:
293 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700294 if configuration.frag_threshold:
295 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700296 if configuration.pmf_support:
297 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700298 if configuration.obss_interval:
299 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700300 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileyeea12362013-12-12 17:24:29 -0800301 self.start_hostapd(conf, configuration)
302 interface = self.hostapd_instances[-1]['interface']
303 self.iw_runner.set_tx_power(interface, 'auto')
304 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700305 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700306
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700307
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700308 @staticmethod
309 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700310 """Simple IPv4 calculator.
311
312 Takes host address in "IP/bits" notation and returns netmask, broadcast
313 address as well as integer offsets into the address range.
314
315 @param netblock string host address in "IP/bits" notation.
316 @param idx string describing what to return.
317 @return string containing something you hopefully requested.
318
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700319 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700320 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700321 addr = map(int, addr_str.split('.'))
322 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
323 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700324 if idx == 'local':
325 return addr_str
326 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700327 return '.'.join(map(str, mask))
328 elif idx == 'broadcast':
329 offset = [m ^ 0xff for m in mask]
330 else:
331 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
332 return '.'.join(map(str, [(a & m) + o
333 for a, m, o in zip(addr, mask, offset)]))
334
335
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700336 def ibss_configure(self, config):
337 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700338
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700339 Extract relevant configuration objects from |config| despite not
340 actually being a hostap managed endpoint.
341
342 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700343
344 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800345 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700346 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800347 interface = self.get_wlanif(config.frequency, 'ibss')
348 ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800349 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700350 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800351 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700352 # Always start a local server.
353 self.start_local_server(interface)
354 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800355 self.station_instances.append(
356 StationInstance(ssid=ssid, interface=interface,
357 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800358
359
Paul Stewart2bd823b2012-11-21 15:03:37 -0800360 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700361 """Get the local server address for an interface.
362
363 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700364 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700365
366 @param index int describing which local server this is for.
367
368 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700369 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800370
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700371
Paul Stewart6ddeba72013-11-18 10:08:23 -0800372 def local_peer_ip_address(self, index):
373 """Get the IP address allocated for the peer associated to the AP.
374
375 This address is assigned to a locally associated peer device that
376 is created for the DUT to perform connectivity tests with.
377 When we have multiple local servers, we give them static IP addresses
378 like 192.168.*.253.
379
380 @param index int describing which local server this is for.
381
382 """
383 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
384
385
386 def local_peer_mac_address(self):
387 """Get the MAC address of the peer interface.
388
389 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800390
Paul Stewart6ddeba72013-11-18 10:08:23 -0800391 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800392 iface = interface.Interface(self.station_instances[0].interface,
393 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800394 return iface.mac_address
395
396
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700397 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700398 """Start a local server on an interface.
399
400 @param interface string (e.g. wlan0)
401
402 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700403 logging.info("Starting up local server...")
404
405 if len(self.local_servers) >= 256:
406 raise error.TestFail('Exhausted available local servers')
407
Paul Stewart2bd823b2012-11-21 15:03:37 -0800408 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700409
410 params = {}
411 params['netblock'] = netblock
412 params['subnet'] = self.ip_addr(netblock, 0)
413 params['netmask'] = self.ip_addr(netblock, 'netmask')
414 params['dhcp_range'] = ' '.join(
415 (self.ip_addr(netblock, self.dhcp_low),
416 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700417 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700418
419 params['ip_params'] = ("%s broadcast %s dev %s" %
420 (netblock,
421 self.ip_addr(netblock, 'broadcast'),
422 interface))
423 self.local_servers.append(params)
424
425 self.router.run("%s addr flush %s" %
426 (self.cmd_ip, interface))
427 self.router.run("%s addr add %s" %
428 (self.cmd_ip, params['ip_params']))
429 self.router.run("%s link set %s up" %
430 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800431 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700432
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700433
Paul Stewart548cf452012-11-27 17:46:23 -0800434 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700435 """Start a dhcp server on an interface.
436
437 @param interface string (e.g. wlan0)
438
439 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800440 for server in self.local_servers:
441 if server['interface'] == interface:
442 params = server
443 break
444 else:
445 raise error.TestFail('Could not find local server '
446 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700447
Christopher Wileyeea12362013-12-12 17:24:29 -0800448 dhcpd_conf_file = self.dhcpd_conf % interface
449 dhcp_conf = '\n'.join([
450 'port=0', # disables DNS server
451 'bind-interfaces',
452 'log-dhcp',
453 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
454 'interface=%s' % params['interface'],
455 'dhcp-leasefile=%s' % self.dhcpd_leases])
456 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
457 (dhcpd_conf_file, dhcp_conf))
458 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700459
460
Paul Stewart326badb2012-12-18 14:18:54 -0800461 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700462 """Stop a dhcp server on the router.
463
464 @param instance string instance to kill.
465
466 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800467 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800468
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700469
Paul Stewart548cf452012-11-27 17:46:23 -0800470 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700471 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800472 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800473
474
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800475 def get_wifi_channel(self, ap_num):
476 """Return channel of BSS corresponding to |ap_num|.
477
478 @param ap_num int which BSS to get the channel of.
479 @return int primary channel of BSS.
480
481 """
482 instance = self.hostapd_instances[ap_num]
483 return instance['config_dict']['channel']
484
485
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700486 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700487 """Return IP address on the WiFi subnet of a local server on the router.
488
489 If no local servers are configured (e.g. for an RSPro), a TestFail will
490 be raised.
491
492 @param ap_num int which local server to get an address from.
493
494 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700495 if self.local_servers:
496 return self.ip_addr(self.local_servers[ap_num]['netblock'],
497 'local')
498 else:
499 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700500
501
Christopher Wileya3effac2014-02-05 11:16:11 -0800502 def get_hostapd_interface(self, ap_num):
503 """Get the name of the interface associated with a hostapd instance.
504
505 @param ap_num: int hostapd instance number.
506 @return string interface name (e.g. 'managed0').
507
508 """
509 if ap_num not in range(len(self.hostapd_instances)):
510 raise error.TestFail('Invalid instance number (%d) with %d '
511 'instances configured.' %
512 (ap_num, len(self.hostapd_instances)))
513
514 instance = self.hostapd_instances[ap_num]
515 return instance['interface']
516
517
Paul Stewart17350be2012-12-14 13:34:54 -0800518 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700519 """Return the MAC address of an AP in the test.
520
521 @param ap_num int index of local server to read the MAC address from.
522 @return string MAC address like 00:11:22:33:44:55.
523
524 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800525 interface_name = self.get_hostapd_interface(ap_num)
526 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800527 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800528
529
Christopher Wileya3effac2014-02-05 11:16:11 -0800530 def get_hostapd_phy(self, ap_num):
531 """Get name of phy for hostapd instance.
532
533 @param ap_num int index of hostapd instance.
534 @return string phy name of phy corresponding to hostapd's
535 managed interface.
536
537 """
538 interface = self.iw_runner.get_interface(
539 self.get_hostapd_interface(ap_num))
540 return interface.phy
541
542
Christopher Wileyeea12362013-12-12 17:24:29 -0800543 def deconfig(self):
544 """A legacy, deprecated alias for deconfig_aps."""
545 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800546
547
548 def deconfig_aps(self, instance=None, silent=False):
549 """De-configure an AP (will also bring wlan down).
550
551 @param instance: int or None. If instance is None, will bring down all
552 instances of hostapd.
553 @param silent: True if instances should be brought without de-authing
554 the DUT.
555
556 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800557 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700558 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700559
Christopher Wileyeea12362013-12-12 17:24:29 -0800560 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800561 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800562 if instance is not None:
563 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800564 for server in self.local_servers:
565 if server['interface'] == instances[0]['interface']:
566 local_servers = [server]
567 self.local_servers.remove(server)
568 break
Paul Stewart21737812012-12-06 11:03:32 -0800569 else:
570 instances = self.hostapd_instances
571 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800572 local_servers = self.local_servers
573 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700574
Paul Stewart21737812012-12-06 11:03:32 -0800575 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800576 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800577 # Deconfigure without notifying DUT. Remove the interface
578 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800579 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800580
Paul Stewart326badb2012-12-18 14:18:54 -0800581 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700582 if wifi_test_utils.is_installed(self.host,
583 instance['log_file']):
584 self.router.get_file(instance['log_file'],
585 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800586 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700587 instance['interface']))
588 else:
589 logging.error('Did not collect hostapd log file because '
590 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800591 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800592# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800593 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800594 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700595 local_servers = self.local_servers
596 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800597 instance = self.station_instances.pop()
598 if instance.dev_type == 'ibss':
599 self.iw_runner.ibss_leave(instance.interface)
600 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800601 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800602 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800603 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800604 self.iw_runner.disconnect_station(instance.interface)
605 self.router.run('%s link set %s down' %
606 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700607
Paul Stewart326badb2012-12-18 14:18:54 -0800608 for server in local_servers:
609 self.stop_dhcp_server(server['interface'])
610 self.router.run("%s addr del %s" %
611 (self.cmd_ip, server['ip_params']),
612 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700613
Paul Stewart7cb1f062010-06-10 15:46:20 -0700614
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800615 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700616 """Verify that the PMKSA auth was cached on a hostapd instance.
617
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800618 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700619
620 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800621 log_file = self.hostapd_instances[instance]['log_file']
622 pmksa_match = 'PMK from PMKSA cache'
623 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
624 ignore_status=True)
625 if result.exit_status:
626 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800627
628
Christopher Wileye0afecb2013-11-11 10:54:23 -0800629 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700630 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800631 if instance is None:
632 instance = 0
633 if len(self.hostapd_instances) > 1:
634 raise error.TestFail('No instance of hostapd specified with '
635 'multiple instances present.')
636
Christopher Wiley3099be72013-11-06 16:49:02 -0800637 if self.hostapd_instances:
638 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700639
Christopher Wiley408d1812014-01-13 15:27:43 -0800640 if self.station_instances:
641 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700642
Christopher Wiley408d1812014-01-13 15:27:43 -0800643 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700644
645
Wade Guthriee4074dd2013-10-30 11:00:48 -0700646 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700647 """Deauthenticates a client described in params.
648
Wade Guthriee4074dd2013-10-30 11:00:48 -0700649 @param client_mac string containing the mac address of the client to be
650 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700651
652 """
Paul Stewart80bb3372014-01-22 15:06:08 -0800653 control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700654 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800655 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700656
657
Paul Stewart51b0f382013-06-12 09:03:02 -0700658 def send_management_frame(self, frame_type, instance=0):
659 """Injects a management frame into an active hostapd session.
660
661 @param frame_type string the type of frame to send.
662 @param instance int indicating which hostapd instance to inject into.
663
664 """
665 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800666 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700667 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
668 self.router.run('%s %s %s' %
669 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800670 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700671
672
Paul Stewart25536942013-08-15 17:33:42 -0700673 def detect_client_deauth(self, client_mac, instance=0):
674 """Detects whether hostapd has logged a deauthentication from
675 |client_mac|.
676
677 @param client_mac string the MAC address of the client to detect.
678 @param instance int indicating which hostapd instance to query.
679
680 """
681 interface = self.hostapd_instances[instance]['interface']
682 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
683 log_file = self.hostapd_instances[instance]['log_file']
684 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
685 ignore_status=True)
686 return result.exit_status == 0
687
688
Paul Stewart4ae471e2013-09-04 15:42:35 -0700689 def detect_client_coexistence_report(self, client_mac, instance=0):
690 """Detects whether hostapd has logged an action frame from
691 |client_mac| indicating information about 20/40MHz BSS coexistence.
692
693 @param client_mac string the MAC address of the client to detect.
694 @param instance int indicating which hostapd instance to query.
695
696 """
697 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
698 '.. .. .. .. .. .. .. .. .. .. %s '
699 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
700 ' '.join(client_mac.split(':')))
701 log_file = self.hostapd_instances[instance]['log_file']
702 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
703 ignore_status=True)
704 return result.exit_status == 0
705
706
Paul Stewart6ddeba72013-11-18 10:08:23 -0800707 def add_connected_peer(self, instance=0):
708 """Configure a station connected to a running AP instance.
709
710 Extract relevant configuration objects from the hostap
711 configuration for |instance| and generate a wpa_supplicant
712 instance that connects to it. This allows the DUT to interact
713 with a client entity that is also connected to the same AP. A
714 full wpa_supplicant instance is necessary here (instead of just
715 using the "iw" command to connect) since we want to enable
716 advanced features such as TDLS.
717
718 @param instance int indicating which hostapd instance to connect to.
719
720 """
721 if not self.hostapd_instances:
722 raise error.TestFail('Hostapd is not configured.')
723
Christopher Wiley408d1812014-01-13 15:27:43 -0800724 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800725 raise error.TestFail('Station is already configured.')
726
Christopher Wiley408d1812014-01-13 15:27:43 -0800727 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800728 hostap_conf = self.hostapd_instances[instance]['config_dict']
729 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
730 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800731 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800732
733 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
734 # require them...
735 supplicant_config = (
736 'network={\n'
737 ' ssid="%(ssid)s"\n'
738 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800739 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800740 )
741
Christopher Wileyeea12362013-12-12 17:24:29 -0800742 conf_file = self.STATION_CONF_FILE_PATTERN % interface
743 log_file = self.STATION_LOG_FILE_PATTERN % interface
744 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800745
746 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
747 (conf_file, supplicant_config))
748
749 # Connect the station.
750 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
751 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
752 (self.cmd_wpa_supplicant,
753 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800754 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800755 self.router.run(start_command)
756 self.iw_runner.wait_for_link(interface)
757
758 # Assign an IP address to this interface.
759 self.router.run('%s addr add %s/24 dev %s' %
760 (self.cmd_ip, self.local_peer_ip_address(instance),
761 interface))
762
763 # Since we now have two network interfaces connected to the same
764 # network, we need to disable the kernel's protection against
765 # incoming packets to an "unexpected" interface.
766 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
767 interface)
768
Paul Stewartb01839b2013-12-06 15:49:56 -0800769 # Similarly, we'd like to prevent the hostap interface from
770 # replying to ARP requests for the peer IP address and vice
771 # versa.
772 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
773 interface)
774 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
775 hostap_conf['interface'])
776
Christopher Wiley408d1812014-01-13 15:27:43 -0800777 self.station_instances.append(
778 StationInstance(ssid=ssid, interface=interface,
779 dev_type='managed'))