blob: ae89ecc8a213eaab793500f6355d88011c146299 [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
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700114
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700115 def close(self):
116 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800117 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800118 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700119
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700120
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700121 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700122 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700123 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700124
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700125
Christopher Wileyeea12362013-12-12 17:24:29 -0800126 def start_hostapd(self, hostapd_conf_dict, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700127 """Start a hostapd instance described by conf.
128
Christopher Wileyeea12362013-12-12 17:24:29 -0800129 @param hostapd_conf_dict dict of hostapd configuration parameters.
130 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700131
132 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800133 logging.info('Starting hostapd with parameters: %r',
134 hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800135 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800136 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800137
Christopher Wileyeea12362013-12-12 17:24:29 -0800138 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
139 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
140 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800141 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Christopher Wileyeea12362013-12-12 17:24:29 -0800142 hostapd_conf_dict['interface'] = interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800143 hostapd_conf_dict['ctrl_interface'] = control_interface
Paul Stewart548cf452012-11-27 17:46:23 -0800144
145 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800146 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
147 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800148 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800149
150 # Run hostapd.
151 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700152 self.router.run('rm %s' % log_file, ignore_status=True)
153 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800154 self.router.run('stop wpasupplicant', ignore_status=True)
155 start_command = '%s -dd -B -t -f %s -P %s %s' % (
156 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700157 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800158 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800159 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800160 'conf_file': conf_file,
161 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700162 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800163 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800164 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800165 })
166
Christopher Wileyeea12362013-12-12 17:24:29 -0800167 # Wait for confirmation that the router came up.
168 pid = int(self.router.run('cat %s' % pid_file).stdout)
169 logging.info('Waiting for hostapd to startup.')
170 start_time = time.time()
171 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
172 success = self.router.run(
173 'grep "Completing interface initialization" %s' % log_file,
174 ignore_status=True).exit_status == 0
175 if success:
176 break
177
178 # A common failure is an invalid router configuration.
179 # Detect this and exit early if we see it.
180 bad_config = self.router.run(
181 'grep "Interface initialization failed" %s' % log_file,
182 ignore_status=True).exit_status == 0
183 if bad_config:
184 raise error.TestFail('hostapd failed to initialize AP '
185 'interface.')
186
187 if pid:
188 early_exit = self.router.run('kill -0 %d' % pid,
189 ignore_status=True).exit_status
190 if early_exit:
191 raise error.TestFail('hostapd process terminated.')
192
193 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
194 else:
195 raise error.TestFail('Timed out while waiting for hostapd '
196 'to start.')
197
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700198
Paul Stewart326badb2012-12-18 14:18:54 -0800199 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700200 """Kill a process on the router.
201
Paul Stewart326badb2012-12-18 14:18:54 -0800202 Kills program named |process|, optionally only a specific
203 |instance|. If |wait| is specified, we makes sure |process| exits
204 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700205
206 @param process string name of process to kill.
207 @param instance string instance of process to kill.
208 @param wait int timeout in seconds to wait for.
209
Thieu Le7b23a542012-01-27 15:54:48 -0800210 """
Paul Stewart21737812012-12-06 11:03:32 -0800211 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800212 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800213 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800214 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800215
Paul Stewart326badb2012-12-18 14:18:54 -0800216 cmd = "pkill %s >/dev/null 2>&1" % search_arg
217
218 if wait:
219 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
220 search_arg)
221 self.router.run(cmd, timeout=wait, ignore_status=True)
222 else:
223 self.router.run(cmd, ignore_status=True)
224
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700225
Paul Stewart326badb2012-12-18 14:18:54 -0800226 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700227 """Kills a hostapd instance.
228
229 @param instance string instance to kill.
230
231 """
Paul Stewart326badb2012-12-18 14:18:54 -0800232 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800233
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700234
Paul Stewart21737812012-12-06 11:03:32 -0800235 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700236 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800237 self.kill_hostapd_instance(None)
238
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700239
240 def __get_default_hostap_config(self):
241 """@return dict of default options for hostapd."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800242 return {'hw_mode': 'g',
Christopher Wileyeea12362013-12-12 17:24:29 -0800243 'logger_syslog': '-1',
244 'logger_syslog_level': '0',
245 # default RTS and frag threshold to ``off''
246 'rts_threshold': '2347',
247 'fragm_threshold': '2346',
248 'driver': self.HOSTAPD_DRIVER_NAME,
249 'ssid': self._build_ssid('') }
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700250
251
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700252 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700253 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
254 for x in range(5)])
255 return (self.ssid_prefix + unique_salt + suffix)[-32:]
256
257
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700258 def hostap_configure(self, configuration, multi_interface=None):
259 """Build up a hostapd configuration file and start hostapd.
260
261 Also setup a local server if this router supports them.
262
263 @param configuration HosetapConfig object.
264 @param multi_interface bool True iff multiple interfaces allowed.
265
266 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800267 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800268 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700269 self.deconfig()
270 # Start with the default hostapd config parameters.
271 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700272 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700273 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700274 if configuration.bssid:
275 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700276 conf['channel'] = configuration.channel
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700277 conf['hw_mode'] = configuration.hw_mode
278 if configuration.hide_ssid:
279 conf['ignore_broadcast_ssid'] = 1
280 if configuration.is_11n:
281 conf['ieee80211n'] = 1
Christopher Wiley32fbb7a2013-09-18 14:30:50 -0700282 conf['ht_capab'] = configuration.hostapd_ht_capabilities
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700283 if configuration.wmm_enabled:
284 conf['wmm_enabled'] = 1
285 if configuration.require_ht:
286 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700287 if configuration.beacon_interval:
288 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700289 if configuration.dtim_period:
290 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700291 if configuration.frag_threshold:
292 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700293 if configuration.pmf_support:
294 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700295 if configuration.obss_interval:
296 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700297 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileyeea12362013-12-12 17:24:29 -0800298 self.start_hostapd(conf, configuration)
299 interface = self.hostapd_instances[-1]['interface']
300 self.iw_runner.set_tx_power(interface, 'auto')
301 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700302 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700303
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700304
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700305 @staticmethod
306 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700307 """Simple IPv4 calculator.
308
309 Takes host address in "IP/bits" notation and returns netmask, broadcast
310 address as well as integer offsets into the address range.
311
312 @param netblock string host address in "IP/bits" notation.
313 @param idx string describing what to return.
314 @return string containing something you hopefully requested.
315
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700316 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700317 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700318 addr = map(int, addr_str.split('.'))
319 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
320 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700321 if idx == 'local':
322 return addr_str
323 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700324 return '.'.join(map(str, mask))
325 elif idx == 'broadcast':
326 offset = [m ^ 0xff for m in mask]
327 else:
328 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
329 return '.'.join(map(str, [(a & m) + o
330 for a, m, o in zip(addr, mask, offset)]))
331
332
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700333 def ibss_configure(self, config):
334 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700335
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700336 Extract relevant configuration objects from |config| despite not
337 actually being a hostap managed endpoint.
338
339 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700340
341 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800342 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700343 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800344 interface = self.get_wlanif(config.frequency, 'ibss')
345 ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800346 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700347 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800348 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700349 # Always start a local server.
350 self.start_local_server(interface)
351 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800352 self.station_instances.append(
353 StationInstance(ssid=ssid, interface=interface,
354 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800355
356
Paul Stewart2bd823b2012-11-21 15:03:37 -0800357 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700358 """Get the local server address for an interface.
359
360 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700361 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700362
363 @param index int describing which local server this is for.
364
365 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700366 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800367
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700368
Paul Stewart6ddeba72013-11-18 10:08:23 -0800369 def local_peer_ip_address(self, index):
370 """Get the IP address allocated for the peer associated to the AP.
371
372 This address is assigned to a locally associated peer device that
373 is created for the DUT to perform connectivity tests with.
374 When we have multiple local servers, we give them static IP addresses
375 like 192.168.*.253.
376
377 @param index int describing which local server this is for.
378
379 """
380 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
381
382
383 def local_peer_mac_address(self):
384 """Get the MAC address of the peer interface.
385
386 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800387
Paul Stewart6ddeba72013-11-18 10:08:23 -0800388 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800389 iface = interface.Interface(self.station_instances[0].interface,
390 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800391 return iface.mac_address
392
393
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700394 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700395 """Start a local server on an interface.
396
397 @param interface string (e.g. wlan0)
398
399 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700400 logging.info("Starting up local server...")
401
402 if len(self.local_servers) >= 256:
403 raise error.TestFail('Exhausted available local servers')
404
Paul Stewart2bd823b2012-11-21 15:03:37 -0800405 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700406
407 params = {}
408 params['netblock'] = netblock
409 params['subnet'] = self.ip_addr(netblock, 0)
410 params['netmask'] = self.ip_addr(netblock, 'netmask')
411 params['dhcp_range'] = ' '.join(
412 (self.ip_addr(netblock, self.dhcp_low),
413 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700414 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700415
416 params['ip_params'] = ("%s broadcast %s dev %s" %
417 (netblock,
418 self.ip_addr(netblock, 'broadcast'),
419 interface))
420 self.local_servers.append(params)
421
422 self.router.run("%s addr flush %s" %
423 (self.cmd_ip, interface))
424 self.router.run("%s addr add %s" %
425 (self.cmd_ip, params['ip_params']))
426 self.router.run("%s link set %s up" %
427 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800428 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700429
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700430
Paul Stewart548cf452012-11-27 17:46:23 -0800431 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700432 """Start a dhcp server on an interface.
433
434 @param interface string (e.g. wlan0)
435
436 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800437 for server in self.local_servers:
438 if server['interface'] == interface:
439 params = server
440 break
441 else:
442 raise error.TestFail('Could not find local server '
443 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700444
Christopher Wileyeea12362013-12-12 17:24:29 -0800445 dhcpd_conf_file = self.dhcpd_conf % interface
446 dhcp_conf = '\n'.join([
447 'port=0', # disables DNS server
448 'bind-interfaces',
449 'log-dhcp',
450 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
451 'interface=%s' % params['interface'],
452 'dhcp-leasefile=%s' % self.dhcpd_leases])
453 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
454 (dhcpd_conf_file, dhcp_conf))
455 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700456
457
Paul Stewart326badb2012-12-18 14:18:54 -0800458 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700459 """Stop a dhcp server on the router.
460
461 @param instance string instance to kill.
462
463 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800464 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800465
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700466
Paul Stewart548cf452012-11-27 17:46:23 -0800467 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700468 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800469 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800470
471
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800472 def get_wifi_channel(self, ap_num):
473 """Return channel of BSS corresponding to |ap_num|.
474
475 @param ap_num int which BSS to get the channel of.
476 @return int primary channel of BSS.
477
478 """
479 instance = self.hostapd_instances[ap_num]
480 return instance['config_dict']['channel']
481
482
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700483 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700484 """Return IP address on the WiFi subnet of a local server on the router.
485
486 If no local servers are configured (e.g. for an RSPro), a TestFail will
487 be raised.
488
489 @param ap_num int which local server to get an address from.
490
491 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700492 if self.local_servers:
493 return self.ip_addr(self.local_servers[ap_num]['netblock'],
494 'local')
495 else:
496 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700497
498
Paul Stewart17350be2012-12-14 13:34:54 -0800499 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700500 """Return the MAC address of an AP in the test.
501
502 @param ap_num int index of local server to read the MAC address from.
503 @return string MAC address like 00:11:22:33:44:55.
504
505 """
Christopher Wiley5689d362014-01-07 15:21:25 -0800506 if not self.local_servers:
507 raise error.TestFail('Cannot retrieve MAC: '
508 'no AP instances configured.')
509
Paul Stewart17350be2012-12-14 13:34:54 -0800510 instance = self.hostapd_instances[ap_num]
Christopher Wiley5689d362014-01-07 15:21:25 -0800511 ap_interface = interface.Interface(instance['interface'], self.host)
512 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800513
514
Christopher Wileyeea12362013-12-12 17:24:29 -0800515 def deconfig(self):
516 """A legacy, deprecated alias for deconfig_aps."""
517 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800518
519
520 def deconfig_aps(self, instance=None, silent=False):
521 """De-configure an AP (will also bring wlan down).
522
523 @param instance: int or None. If instance is None, will bring down all
524 instances of hostapd.
525 @param silent: True if instances should be brought without de-authing
526 the DUT.
527
528 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800529 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700530 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700531
Christopher Wileyeea12362013-12-12 17:24:29 -0800532 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800533 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800534 if instance is not None:
535 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800536 for server in self.local_servers:
537 if server['interface'] == instances[0]['interface']:
538 local_servers = [server]
539 self.local_servers.remove(server)
540 break
Paul Stewart21737812012-12-06 11:03:32 -0800541 else:
542 instances = self.hostapd_instances
543 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800544 local_servers = self.local_servers
545 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700546
Paul Stewart21737812012-12-06 11:03:32 -0800547 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800548 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800549 # Deconfigure without notifying DUT. Remove the interface
550 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800551 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800552
Paul Stewart326badb2012-12-18 14:18:54 -0800553 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700554 if wifi_test_utils.is_installed(self.host,
555 instance['log_file']):
556 self.router.get_file(instance['log_file'],
557 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800558 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700559 instance['interface']))
560 else:
561 logging.error('Did not collect hostapd log file because '
562 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800563 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800564# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800565 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800566 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700567 local_servers = self.local_servers
568 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800569 instance = self.station_instances.pop()
570 if instance.dev_type == 'ibss':
571 self.iw_runner.ibss_leave(instance.interface)
572 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800573 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800574 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800575 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800576 self.iw_runner.disconnect_station(instance.interface)
577 self.router.run('%s link set %s down' %
578 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700579
Paul Stewart326badb2012-12-18 14:18:54 -0800580 for server in local_servers:
581 self.stop_dhcp_server(server['interface'])
582 self.router.run("%s addr del %s" %
583 (self.cmd_ip, server['ip_params']),
584 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700585
Paul Stewart7cb1f062010-06-10 15:46:20 -0700586
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800587 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700588 """Verify that the PMKSA auth was cached on a hostapd instance.
589
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800590 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700591
592 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800593 log_file = self.hostapd_instances[instance]['log_file']
594 pmksa_match = 'PMK from PMKSA cache'
595 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
596 ignore_status=True)
597 if result.exit_status:
598 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800599
600
Christopher Wileye0afecb2013-11-11 10:54:23 -0800601 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700602 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800603 if instance is None:
604 instance = 0
605 if len(self.hostapd_instances) > 1:
606 raise error.TestFail('No instance of hostapd specified with '
607 'multiple instances present.')
608
Christopher Wiley3099be72013-11-06 16:49:02 -0800609 if self.hostapd_instances:
610 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700611
Christopher Wiley408d1812014-01-13 15:27:43 -0800612 if self.station_instances:
613 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700614
Christopher Wiley408d1812014-01-13 15:27:43 -0800615 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700616
617
Wade Guthriee4074dd2013-10-30 11:00:48 -0700618 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700619 """Deauthenticates a client described in params.
620
Wade Guthriee4074dd2013-10-30 11:00:48 -0700621 @param client_mac string containing the mac address of the client to be
622 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700623
624 """
Paul Stewart80bb3372014-01-22 15:06:08 -0800625 control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700626 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800627 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700628
629
Paul Stewart51b0f382013-06-12 09:03:02 -0700630 def send_management_frame(self, frame_type, instance=0):
631 """Injects a management frame into an active hostapd session.
632
633 @param frame_type string the type of frame to send.
634 @param instance int indicating which hostapd instance to inject into.
635
636 """
637 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800638 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700639 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
640 self.router.run('%s %s %s' %
641 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800642 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700643
644
Paul Stewart25536942013-08-15 17:33:42 -0700645 def detect_client_deauth(self, client_mac, instance=0):
646 """Detects whether hostapd has logged a deauthentication from
647 |client_mac|.
648
649 @param client_mac string the MAC address of the client to detect.
650 @param instance int indicating which hostapd instance to query.
651
652 """
653 interface = self.hostapd_instances[instance]['interface']
654 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
655 log_file = self.hostapd_instances[instance]['log_file']
656 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
657 ignore_status=True)
658 return result.exit_status == 0
659
660
Paul Stewart4ae471e2013-09-04 15:42:35 -0700661 def detect_client_coexistence_report(self, client_mac, instance=0):
662 """Detects whether hostapd has logged an action frame from
663 |client_mac| indicating information about 20/40MHz BSS coexistence.
664
665 @param client_mac string the MAC address of the client to detect.
666 @param instance int indicating which hostapd instance to query.
667
668 """
669 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
670 '.. .. .. .. .. .. .. .. .. .. %s '
671 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
672 ' '.join(client_mac.split(':')))
673 log_file = self.hostapd_instances[instance]['log_file']
674 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
675 ignore_status=True)
676 return result.exit_status == 0
677
678
Paul Stewart6ddeba72013-11-18 10:08:23 -0800679 def add_connected_peer(self, instance=0):
680 """Configure a station connected to a running AP instance.
681
682 Extract relevant configuration objects from the hostap
683 configuration for |instance| and generate a wpa_supplicant
684 instance that connects to it. This allows the DUT to interact
685 with a client entity that is also connected to the same AP. A
686 full wpa_supplicant instance is necessary here (instead of just
687 using the "iw" command to connect) since we want to enable
688 advanced features such as TDLS.
689
690 @param instance int indicating which hostapd instance to connect to.
691
692 """
693 if not self.hostapd_instances:
694 raise error.TestFail('Hostapd is not configured.')
695
Christopher Wiley408d1812014-01-13 15:27:43 -0800696 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800697 raise error.TestFail('Station is already configured.')
698
Christopher Wiley408d1812014-01-13 15:27:43 -0800699 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800700 hostap_conf = self.hostapd_instances[instance]['config_dict']
701 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
702 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800703 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800704
705 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
706 # require them...
707 supplicant_config = (
708 'network={\n'
709 ' ssid="%(ssid)s"\n'
710 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800711 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800712 )
713
Christopher Wileyeea12362013-12-12 17:24:29 -0800714 conf_file = self.STATION_CONF_FILE_PATTERN % interface
715 log_file = self.STATION_LOG_FILE_PATTERN % interface
716 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800717
718 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
719 (conf_file, supplicant_config))
720
721 # Connect the station.
722 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
723 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
724 (self.cmd_wpa_supplicant,
725 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800726 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800727 self.router.run(start_command)
728 self.iw_runner.wait_for_link(interface)
729
730 # Assign an IP address to this interface.
731 self.router.run('%s addr add %s/24 dev %s' %
732 (self.cmd_ip, self.local_peer_ip_address(instance),
733 interface))
734
735 # Since we now have two network interfaces connected to the same
736 # network, we need to disable the kernel's protection against
737 # incoming packets to an "unexpected" interface.
738 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
739 interface)
740
Paul Stewartb01839b2013-12-06 15:49:56 -0800741 # Similarly, we'd like to prevent the hostap interface from
742 # replying to ARP requests for the peer IP address and vice
743 # versa.
744 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
745 interface)
746 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
747 hostap_conf['interface'])
748
Christopher Wiley408d1812014-01-13 15:27:43 -0800749 self.station_instances.append(
750 StationInstance(ssid=ssid, interface=interface,
751 dev_type='managed'))