blob: 669d1786c35b8a19341f3ac147d225d165f3a96f [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'
42 HOSTAPD_DRIVER_NAME = 'nl80211'
43
44 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
45 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
46 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
47
Paul Stewart51b0f382013-06-12 09:03:02 -070048 def get_capabilities(self):
49 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080050 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070051 try:
52 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080053 self.host, '/usr/bin/send_management_frame')
Paul Stewart51b0f382013-06-12 09:03:02 -070054 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
55 except error.TestFail:
56 pass
57 return super(LinuxRouter, self).get_capabilities().union(caps)
58
59
Christopher Wiley408d1812014-01-13 15:27:43 -080060 @property
61 def router(self):
62 """Deprecated. Use self.host instead.
63
64 @return Host object representing the remote router.
65
66 """
67 return self.host
68
69
70 def __init__(self, host, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070071 """Build a LinuxRouter.
72
73 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -070074 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070075
76 """
Christopher Wiley408d1812014-01-13 15:27:43 -080077 super(LinuxRouter, self).__init__(host, 'router')
Wade Guthrie24d1e312012-04-24 16:53:40 -070078
Christopher Wileyeea12362013-12-12 17:24:29 -080079 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070080 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080081 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070082 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080083 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -080084 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080085 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070086 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
87 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070088
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070089 # hostapd configuration persists throughout the test, subsequent
90 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -070091 self.ssid_prefix = test_name
92 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
93 # Many of our tests start with an uninteresting prefix.
94 # Remove it so we can have more unique bytes.
95 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
96 self.ssid_prefix = self.ssid_prefix.lstrip('_')
97 self.ssid_prefix += '_'
98
Christopher Wileyeea12362013-12-12 17:24:29 -080099 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700100 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800101 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800102 self.station_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."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800116 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800117 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700118
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700119
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700120 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700121 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700122 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700123
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700124
Christopher Wileyeea12362013-12-12 17:24:29 -0800125 def start_hostapd(self, hostapd_conf_dict, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700126 """Start a hostapd instance described by conf.
127
Christopher Wileyeea12362013-12-12 17:24:29 -0800128 @param hostapd_conf_dict dict of hostapd configuration parameters.
129 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700130
131 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800132 logging.info('Starting hostapd with parameters: %r',
133 hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800134 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800135 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800136
Christopher Wileyeea12362013-12-12 17:24:29 -0800137 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
138 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
139 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
140 hostapd_conf_dict['interface'] = interface
Paul Stewart548cf452012-11-27 17:46:23 -0800141
142 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800143 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
144 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800145 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800146
147 # Run hostapd.
148 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700149 self.router.run('rm %s' % log_file, ignore_status=True)
150 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800151 self.router.run('stop wpasupplicant', ignore_status=True)
152 start_command = '%s -dd -B -t -f %s -P %s %s' % (
153 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700154 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800155 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800156 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800157 'conf_file': conf_file,
158 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700159 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800160 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800161 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800162 })
163
Christopher Wileyeea12362013-12-12 17:24:29 -0800164 # Wait for confirmation that the router came up.
165 pid = int(self.router.run('cat %s' % pid_file).stdout)
166 logging.info('Waiting for hostapd to startup.')
167 start_time = time.time()
168 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
169 success = self.router.run(
170 'grep "Completing interface initialization" %s' % log_file,
171 ignore_status=True).exit_status == 0
172 if success:
173 break
174
175 # A common failure is an invalid router configuration.
176 # Detect this and exit early if we see it.
177 bad_config = self.router.run(
178 'grep "Interface initialization failed" %s' % log_file,
179 ignore_status=True).exit_status == 0
180 if bad_config:
181 raise error.TestFail('hostapd failed to initialize AP '
182 'interface.')
183
184 if pid:
185 early_exit = self.router.run('kill -0 %d' % pid,
186 ignore_status=True).exit_status
187 if early_exit:
188 raise error.TestFail('hostapd process terminated.')
189
190 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
191 else:
192 raise error.TestFail('Timed out while waiting for hostapd '
193 'to start.')
194
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700195
Paul Stewart326badb2012-12-18 14:18:54 -0800196 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700197 """Kill a process on the router.
198
Paul Stewart326badb2012-12-18 14:18:54 -0800199 Kills program named |process|, optionally only a specific
200 |instance|. If |wait| is specified, we makes sure |process| exits
201 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700202
203 @param process string name of process to kill.
204 @param instance string instance of process to kill.
205 @param wait int timeout in seconds to wait for.
206
Thieu Le7b23a542012-01-27 15:54:48 -0800207 """
Paul Stewart21737812012-12-06 11:03:32 -0800208 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800209 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800210 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800211 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800212
Paul Stewart326badb2012-12-18 14:18:54 -0800213 cmd = "pkill %s >/dev/null 2>&1" % search_arg
214
215 if wait:
216 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
217 search_arg)
218 self.router.run(cmd, timeout=wait, ignore_status=True)
219 else:
220 self.router.run(cmd, ignore_status=True)
221
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700222
Paul Stewart326badb2012-12-18 14:18:54 -0800223 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700224 """Kills a hostapd instance.
225
226 @param instance string instance to kill.
227
228 """
Paul Stewart326badb2012-12-18 14:18:54 -0800229 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800230
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700231
Paul Stewart21737812012-12-06 11:03:32 -0800232 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700233 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800234 self.kill_hostapd_instance(None)
235
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700236
237 def __get_default_hostap_config(self):
238 """@return dict of default options for hostapd."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800239 return {'hw_mode': 'g',
240 'ctrl_interface': '/tmp/hostapd-test.control',
241 'logger_syslog': '-1',
242 'logger_syslog_level': '0',
243 # default RTS and frag threshold to ``off''
244 'rts_threshold': '2347',
245 'fragm_threshold': '2346',
246 'driver': self.HOSTAPD_DRIVER_NAME,
247 'ssid': self._build_ssid('') }
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700248
249
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700250 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700251 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
252 for x in range(5)])
253 return (self.ssid_prefix + unique_salt + suffix)[-32:]
254
255
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700256 def hostap_configure(self, configuration, multi_interface=None):
257 """Build up a hostapd configuration file and start hostapd.
258
259 Also setup a local server if this router supports them.
260
261 @param configuration HosetapConfig object.
262 @param multi_interface bool True iff multiple interfaces allowed.
263
264 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800265 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800266 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700267 self.deconfig()
268 # Start with the default hostapd config parameters.
269 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700270 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700271 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700272 if configuration.bssid:
273 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700274 conf['channel'] = configuration.channel
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700275 conf['hw_mode'] = configuration.hw_mode
276 if configuration.hide_ssid:
277 conf['ignore_broadcast_ssid'] = 1
278 if configuration.is_11n:
279 conf['ieee80211n'] = 1
Christopher Wiley32fbb7a2013-09-18 14:30:50 -0700280 conf['ht_capab'] = configuration.hostapd_ht_capabilities
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700281 if configuration.wmm_enabled:
282 conf['wmm_enabled'] = 1
283 if configuration.require_ht:
284 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700285 if configuration.beacon_interval:
286 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700287 if configuration.dtim_period:
288 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700289 if configuration.frag_threshold:
290 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700291 if configuration.pmf_support:
292 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700293 if configuration.obss_interval:
294 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700295 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileyeea12362013-12-12 17:24:29 -0800296 self.start_hostapd(conf, configuration)
297 interface = self.hostapd_instances[-1]['interface']
298 self.iw_runner.set_tx_power(interface, 'auto')
299 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700300 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700301
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700302
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700303 @staticmethod
304 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700305 """Simple IPv4 calculator.
306
307 Takes host address in "IP/bits" notation and returns netmask, broadcast
308 address as well as integer offsets into the address range.
309
310 @param netblock string host address in "IP/bits" notation.
311 @param idx string describing what to return.
312 @return string containing something you hopefully requested.
313
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700314 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700315 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700316 addr = map(int, addr_str.split('.'))
317 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
318 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700319 if idx == 'local':
320 return addr_str
321 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700322 return '.'.join(map(str, mask))
323 elif idx == 'broadcast':
324 offset = [m ^ 0xff for m in mask]
325 else:
326 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
327 return '.'.join(map(str, [(a & m) + o
328 for a, m, o in zip(addr, mask, offset)]))
329
330
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700331 def ibss_configure(self, config):
332 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700333
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700334 Extract relevant configuration objects from |config| despite not
335 actually being a hostap managed endpoint.
336
337 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700338
339 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800340 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700341 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800342 interface = self.get_wlanif(config.frequency, 'ibss')
343 ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800344 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700345 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800346 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700347 # Always start a local server.
348 self.start_local_server(interface)
349 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800350 self.station_instances.append(
351 StationInstance(ssid=ssid, interface=interface,
352 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800353
354
Paul Stewart2bd823b2012-11-21 15:03:37 -0800355 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700356 """Get the local server address for an interface.
357
358 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700359 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700360
361 @param index int describing which local server this is for.
362
363 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700364 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800365
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700366
Paul Stewart6ddeba72013-11-18 10:08:23 -0800367 def local_peer_ip_address(self, index):
368 """Get the IP address allocated for the peer associated to the AP.
369
370 This address is assigned to a locally associated peer device that
371 is created for the DUT to perform connectivity tests with.
372 When we have multiple local servers, we give them static IP addresses
373 like 192.168.*.253.
374
375 @param index int describing which local server this is for.
376
377 """
378 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
379
380
381 def local_peer_mac_address(self):
382 """Get the MAC address of the peer interface.
383
384 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800385
Paul Stewart6ddeba72013-11-18 10:08:23 -0800386 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800387 iface = interface.Interface(self.station_instances[0].interface,
388 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800389 return iface.mac_address
390
391
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700392 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700393 """Start a local server on an interface.
394
395 @param interface string (e.g. wlan0)
396
397 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700398 logging.info("Starting up local server...")
399
400 if len(self.local_servers) >= 256:
401 raise error.TestFail('Exhausted available local servers')
402
Paul Stewart2bd823b2012-11-21 15:03:37 -0800403 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700404
405 params = {}
406 params['netblock'] = netblock
407 params['subnet'] = self.ip_addr(netblock, 0)
408 params['netmask'] = self.ip_addr(netblock, 'netmask')
409 params['dhcp_range'] = ' '.join(
410 (self.ip_addr(netblock, self.dhcp_low),
411 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700412 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700413
414 params['ip_params'] = ("%s broadcast %s dev %s" %
415 (netblock,
416 self.ip_addr(netblock, 'broadcast'),
417 interface))
418 self.local_servers.append(params)
419
420 self.router.run("%s addr flush %s" %
421 (self.cmd_ip, interface))
422 self.router.run("%s addr add %s" %
423 (self.cmd_ip, params['ip_params']))
424 self.router.run("%s link set %s up" %
425 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800426 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700427
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700428
Paul Stewart548cf452012-11-27 17:46:23 -0800429 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700430 """Start a dhcp server on an interface.
431
432 @param interface string (e.g. wlan0)
433
434 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800435 for server in self.local_servers:
436 if server['interface'] == interface:
437 params = server
438 break
439 else:
440 raise error.TestFail('Could not find local server '
441 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700442
Christopher Wileyeea12362013-12-12 17:24:29 -0800443 dhcpd_conf_file = self.dhcpd_conf % interface
444 dhcp_conf = '\n'.join([
445 'port=0', # disables DNS server
446 'bind-interfaces',
447 'log-dhcp',
448 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
449 'interface=%s' % params['interface'],
450 'dhcp-leasefile=%s' % self.dhcpd_leases])
451 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
452 (dhcpd_conf_file, dhcp_conf))
453 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700454
455
Paul Stewart326badb2012-12-18 14:18:54 -0800456 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700457 """Stop a dhcp server on the router.
458
459 @param instance string instance to kill.
460
461 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800462 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800463
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700464
Paul Stewart548cf452012-11-27 17:46:23 -0800465 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700466 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800467 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800468
469
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800470 def get_wifi_channel(self, ap_num):
471 """Return channel of BSS corresponding to |ap_num|.
472
473 @param ap_num int which BSS to get the channel of.
474 @return int primary channel of BSS.
475
476 """
477 instance = self.hostapd_instances[ap_num]
478 return instance['config_dict']['channel']
479
480
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700481 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700482 """Return IP address on the WiFi subnet of a local server on the router.
483
484 If no local servers are configured (e.g. for an RSPro), a TestFail will
485 be raised.
486
487 @param ap_num int which local server to get an address from.
488
489 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700490 if self.local_servers:
491 return self.ip_addr(self.local_servers[ap_num]['netblock'],
492 'local')
493 else:
494 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700495
496
Paul Stewart17350be2012-12-14 13:34:54 -0800497 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700498 """Return the MAC address of an AP in the test.
499
500 @param ap_num int index of local server to read the MAC address from.
501 @return string MAC address like 00:11:22:33:44:55.
502
503 """
Christopher Wiley5689d362014-01-07 15:21:25 -0800504 if not self.local_servers:
505 raise error.TestFail('Cannot retrieve MAC: '
506 'no AP instances configured.')
507
Paul Stewart17350be2012-12-14 13:34:54 -0800508 instance = self.hostapd_instances[ap_num]
Christopher Wiley5689d362014-01-07 15:21:25 -0800509 ap_interface = interface.Interface(instance['interface'], self.host)
510 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800511
512
Christopher Wileyeea12362013-12-12 17:24:29 -0800513 def deconfig(self):
514 """A legacy, deprecated alias for deconfig_aps."""
515 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800516
517
518 def deconfig_aps(self, instance=None, silent=False):
519 """De-configure an AP (will also bring wlan down).
520
521 @param instance: int or None. If instance is None, will bring down all
522 instances of hostapd.
523 @param silent: True if instances should be brought without de-authing
524 the DUT.
525
526 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800527 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700528 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700529
Christopher Wileyeea12362013-12-12 17:24:29 -0800530 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800531 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800532 if instance is not None:
533 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800534 for server in self.local_servers:
535 if server['interface'] == instances[0]['interface']:
536 local_servers = [server]
537 self.local_servers.remove(server)
538 break
Paul Stewart21737812012-12-06 11:03:32 -0800539 else:
540 instances = self.hostapd_instances
541 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800542 local_servers = self.local_servers
543 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700544
Paul Stewart21737812012-12-06 11:03:32 -0800545 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800546 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800547 # Deconfigure without notifying DUT. Remove the interface
548 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800549 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800550
Paul Stewart326badb2012-12-18 14:18:54 -0800551 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700552 if wifi_test_utils.is_installed(self.host,
553 instance['log_file']):
554 self.router.get_file(instance['log_file'],
555 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800556 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700557 instance['interface']))
558 else:
559 logging.error('Did not collect hostapd log file because '
560 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800561 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800562# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800563 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800564 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700565 local_servers = self.local_servers
566 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800567 instance = self.station_instances.pop()
568 if instance.dev_type == 'ibss':
569 self.iw_runner.ibss_leave(instance.interface)
570 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800571 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800572 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800573 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800574 self.iw_runner.disconnect_station(instance.interface)
575 self.router.run('%s link set %s down' %
576 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700577
Paul Stewart326badb2012-12-18 14:18:54 -0800578 for server in local_servers:
579 self.stop_dhcp_server(server['interface'])
580 self.router.run("%s addr del %s" %
581 (self.cmd_ip, server['ip_params']),
582 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700583
Paul Stewart7cb1f062010-06-10 15:46:20 -0700584
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800585 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700586 """Verify that the PMKSA auth was cached on a hostapd instance.
587
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800588 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700589
590 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800591 log_file = self.hostapd_instances[instance]['log_file']
592 pmksa_match = 'PMK from PMKSA cache'
593 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
594 ignore_status=True)
595 if result.exit_status:
596 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800597
598
Christopher Wileye0afecb2013-11-11 10:54:23 -0800599 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700600 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800601 if instance is None:
602 instance = 0
603 if len(self.hostapd_instances) > 1:
604 raise error.TestFail('No instance of hostapd specified with '
605 'multiple instances present.')
606
Christopher Wiley3099be72013-11-06 16:49:02 -0800607 if self.hostapd_instances:
608 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700609
Christopher Wiley408d1812014-01-13 15:27:43 -0800610 if self.station_instances:
611 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700612
Christopher Wiley408d1812014-01-13 15:27:43 -0800613 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700614
615
Wade Guthriee4074dd2013-10-30 11:00:48 -0700616 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700617 """Deauthenticates a client described in params.
618
Wade Guthriee4074dd2013-10-30 11:00:48 -0700619 @param client_mac string containing the mac address of the client to be
620 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700621
622 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700623 self.router.run('%s -p%s deauthenticate %s' %
624 (self.cmd_hostapd_cli,
Christopher Wileyeea12362013-12-12 17:24:29 -0800625 self.hostapd_instances[-1]['ctrl_interface'],
Wade Guthriee4074dd2013-10-30 11:00:48 -0700626 client_mac))
627
628
Paul Stewart51b0f382013-06-12 09:03:02 -0700629 def send_management_frame(self, frame_type, instance=0):
630 """Injects a management frame into an active hostapd session.
631
632 @param frame_type string the type of frame to send.
633 @param instance int indicating which hostapd instance to inject into.
634
635 """
636 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800637 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700638 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
639 self.router.run('%s %s %s' %
640 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800641 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700642
643
Paul Stewart25536942013-08-15 17:33:42 -0700644 def detect_client_deauth(self, client_mac, instance=0):
645 """Detects whether hostapd has logged a deauthentication from
646 |client_mac|.
647
648 @param client_mac string the MAC address of the client to detect.
649 @param instance int indicating which hostapd instance to query.
650
651 """
652 interface = self.hostapd_instances[instance]['interface']
653 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
654 log_file = self.hostapd_instances[instance]['log_file']
655 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
656 ignore_status=True)
657 return result.exit_status == 0
658
659
Paul Stewart4ae471e2013-09-04 15:42:35 -0700660 def detect_client_coexistence_report(self, client_mac, instance=0):
661 """Detects whether hostapd has logged an action frame from
662 |client_mac| indicating information about 20/40MHz BSS coexistence.
663
664 @param client_mac string the MAC address of the client to detect.
665 @param instance int indicating which hostapd instance to query.
666
667 """
668 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
669 '.. .. .. .. .. .. .. .. .. .. %s '
670 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
671 ' '.join(client_mac.split(':')))
672 log_file = self.hostapd_instances[instance]['log_file']
673 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
674 ignore_status=True)
675 return result.exit_status == 0
676
677
Paul Stewart6ddeba72013-11-18 10:08:23 -0800678 def add_connected_peer(self, instance=0):
679 """Configure a station connected to a running AP instance.
680
681 Extract relevant configuration objects from the hostap
682 configuration for |instance| and generate a wpa_supplicant
683 instance that connects to it. This allows the DUT to interact
684 with a client entity that is also connected to the same AP. A
685 full wpa_supplicant instance is necessary here (instead of just
686 using the "iw" command to connect) since we want to enable
687 advanced features such as TDLS.
688
689 @param instance int indicating which hostapd instance to connect to.
690
691 """
692 if not self.hostapd_instances:
693 raise error.TestFail('Hostapd is not configured.')
694
Christopher Wiley408d1812014-01-13 15:27:43 -0800695 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800696 raise error.TestFail('Station is already configured.')
697
Christopher Wiley408d1812014-01-13 15:27:43 -0800698 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800699 hostap_conf = self.hostapd_instances[instance]['config_dict']
700 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
701 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800702 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800703
704 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
705 # require them...
706 supplicant_config = (
707 'network={\n'
708 ' ssid="%(ssid)s"\n'
709 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800710 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800711 )
712
Christopher Wileyeea12362013-12-12 17:24:29 -0800713 conf_file = self.STATION_CONF_FILE_PATTERN % interface
714 log_file = self.STATION_LOG_FILE_PATTERN % interface
715 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800716
717 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
718 (conf_file, supplicant_config))
719
720 # Connect the station.
721 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
722 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
723 (self.cmd_wpa_supplicant,
724 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800725 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800726 self.router.run(start_command)
727 self.iw_runner.wait_for_link(interface)
728
729 # Assign an IP address to this interface.
730 self.router.run('%s addr add %s/24 dev %s' %
731 (self.cmd_ip, self.local_peer_ip_address(instance),
732 interface))
733
734 # Since we now have two network interfaces connected to the same
735 # network, we need to disable the kernel's protection against
736 # incoming packets to an "unexpected" interface.
737 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
738 interface)
739
Paul Stewartb01839b2013-12-06 15:49:56 -0800740 # Similarly, we'd like to prevent the hostap interface from
741 # replying to ARP requests for the peer IP address and vice
742 # versa.
743 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
744 interface)
745 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
746 hostap_conf['interface'])
747
Christopher Wiley408d1812014-01-13 15:27:43 -0800748 self.station_instances.append(
749 StationInstance(ssid=ssid, interface=interface,
750 dev_type='managed'))