blob: 70b6655f8905fd9e31e85ef5c15c2a31fa553f74 [file] [log] [blame]
Sam Leffler6969d1d2010-03-15 16:07:11 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Christopher Wiley14796b32013-04-03 14:53:33 -07005import logging
Christopher Wiley3166e432013-08-06 09:53:12 -07006import random
Christopher Wiley3166e432013-08-06 09:53:12 -07007import string
Christopher Wileyeea12362013-12-12 17:24:29 -08008import time
Christopher Wiley14796b32013-04-03 14:53:33 -07009
Paul Stewartc9628b32010-08-11 13:03:51 -070010from autotest_lib.client.common_lib import error
Paul Stewart6ddeba72013-11-18 10:08:23 -080011from autotest_lib.client.common_lib.cros.network import interface
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070012from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070013from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070014from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070015
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070016class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070017 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070018
19 This class implements test methods/steps that communicate with a
20 router implemented with Linux/mac80211. The router must
21 be pre-configured to enable ssh access and have a mac80211-based
22 wireless device. We also assume hostapd 0.7.x and iw are present
23 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070024
Sam Leffler6969d1d2010-03-15 16:07:11 -070025 """
26
Christopher Wiley3166e432013-08-06 09:53:12 -070027 KNOWN_TEST_PREFIX = 'network_WiFi'
Christopher Wiley1defc242013-09-18 10:28:37 -070028 STARTUP_POLLING_INTERVAL_SECONDS = 0.5
29 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070030 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070031 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070032
Christopher Wileyeea12362013-12-12 17:24:29 -080033 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
34 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
35 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
36 HOSTAPD_DRIVER_NAME = 'nl80211'
37
38 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
39 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
40 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
41
Paul Stewart51b0f382013-06-12 09:03:02 -070042 def get_capabilities(self):
43 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080044 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070045 try:
46 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
47 self.router, '/usr/bin/send_management_frame')
48 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
49 except error.TestFail:
50 pass
51 return super(LinuxRouter, self).get_capabilities().union(caps)
52
53
Christopher Wiley3166e432013-08-06 09:53:12 -070054 def __init__(self, host, params, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070055 """Build a LinuxRouter.
56
57 @param host Host object representing the remote machine.
58 @param params dict of settings from site_wifitest based tests.
Christopher Wiley3166e432013-08-06 09:53:12 -070059 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070060
61 """
Christopher Wileyeea12362013-12-12 17:24:29 -080062 params = params.copy()
63 params.update({
64 'phy_bus_preference': {
65 'monitor': 'usb',
66 'managed': 'pci'
67 }})
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070068 site_linux_system.LinuxSystem.__init__(self, host, params, 'router')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -070069 self._remove_interfaces()
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070070
Wade Guthrie24d1e312012-04-24 16:53:40 -070071 # Router host.
72 self.router = host
73
Christopher Wileyeea12362013-12-12 17:24:29 -080074 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070075 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080076 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070077 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080078 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -080079 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080080 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070081 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
82 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070083
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070084 # hostapd configuration persists throughout the test, subsequent
85 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -070086 self.ssid_prefix = test_name
87 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
88 # Many of our tests start with an uninteresting prefix.
89 # Remove it so we can have more unique bytes.
90 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
91 self.ssid_prefix = self.ssid_prefix.lstrip('_')
92 self.ssid_prefix += '_'
93
Christopher Wileyeea12362013-12-12 17:24:29 -080094 self._total_hostapd_instances = 0
Paul Stewartc2b3de82011-03-03 14:45:31 -080095 self.station = {
96 'configured': False,
Paul Stewart6ddeba72013-11-18 10:08:23 -080097 'config_file': "/tmp/wpa-supplicant-test-%s.conf",
98 'log_file': "/tmp/wpa-supplicant-test-%s.log",
99 'pid_file': "/tmp/wpa-supplicant-test-%s.pid",
Christopher Wiley3166e432013-08-06 09:53:12 -0700100 'conf': {},
Paul Stewartc2b3de82011-03-03 14:45:31 -0800101 }
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700102 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800103 self.hostapd_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."""
117 self.destroy()
118 super(LinuxRouter, self).close()
119
120
Christopher Wiley14796b32013-04-03 14:53:33 -0700121 def create_wifi_device(self, device_type='hostap'):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700122 """Create a wifi device of the specified type.
Christopher Wiley14796b32013-04-03 14:53:33 -0700123
124 Defaults to creating a hostap managed device.
125
126 @param device_type string device type.
127
128 """
Sam Leffler6969d1d2010-03-15 16:07:11 -0700129 #
130 # AP mode is handled entirely by hostapd so we only
131 # have to setup others (mapping the bsd type to what
132 # iw wants)
133 #
134 # map from bsd types to iw types
Christopher Wiley14796b32013-04-03 14:53:33 -0700135 self.apmode = device_type in ('ap', 'hostap')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800136 if not self.apmode:
Christopher Wiley14796b32013-04-03 14:53:33 -0700137 self.station['type'] = device_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700138 self.phytype = {
Christopher Wiley14796b32013-04-03 14:53:33 -0700139 'sta' : 'managed',
140 'monitor' : 'monitor',
141 'adhoc' : 'adhoc',
142 'ibss' : 'ibss',
143 'ap' : 'managed', # NB: handled by hostapd
144 'hostap' : 'managed', # NB: handled by hostapd
145 'mesh' : 'mesh',
146 'wds' : 'wds',
147 }[device_type]
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700148
Sam Leffler6969d1d2010-03-15 16:07:11 -0700149
Christopher Wileyeea12362013-12-12 17:24:29 -0800150 def destroy(self):
151 """Destroy a previously created device."""
152 self.deconfig()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700153
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700154
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700155 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700156 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700157 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700158
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700159
Christopher Wileyeea12362013-12-12 17:24:29 -0800160 def start_hostapd(self, hostapd_conf_dict, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700161 """Start a hostapd instance described by conf.
162
Christopher Wileyeea12362013-12-12 17:24:29 -0800163 @param hostapd_conf_dict dict of hostapd configuration parameters.
164 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700165
166 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800167 logging.info('Starting hostapd with parameters: %r',
168 hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800169 # Figure out the correct interface.
Christopher Wileyeea12362013-12-12 17:24:29 -0800170 interface = self._get_wlanif(configuration.frequency,
Paul Stewart326badb2012-12-18 14:18:54 -0800171 self.phytype,
Christopher Wileyeea12362013-12-12 17:24:29 -0800172 configuration.hw_mode)
Paul Stewart326badb2012-12-18 14:18:54 -0800173
Christopher Wileyeea12362013-12-12 17:24:29 -0800174 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
175 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
176 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
177 hostapd_conf_dict['interface'] = interface
Paul Stewart548cf452012-11-27 17:46:23 -0800178
179 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800180 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
181 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800182 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800183
184 # Run hostapd.
185 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700186 self.router.run('rm %s' % log_file, ignore_status=True)
187 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800188 self.router.run('stop wpasupplicant', ignore_status=True)
189 start_command = '%s -dd -B -t -f %s -P %s %s' % (
190 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700191 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800192 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800193 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800194 'conf_file': conf_file,
195 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700196 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800197 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800198 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800199 })
200
Christopher Wileyeea12362013-12-12 17:24:29 -0800201 # Wait for confirmation that the router came up.
202 pid = int(self.router.run('cat %s' % pid_file).stdout)
203 logging.info('Waiting for hostapd to startup.')
204 start_time = time.time()
205 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
206 success = self.router.run(
207 'grep "Completing interface initialization" %s' % log_file,
208 ignore_status=True).exit_status == 0
209 if success:
210 break
211
212 # A common failure is an invalid router configuration.
213 # Detect this and exit early if we see it.
214 bad_config = self.router.run(
215 'grep "Interface initialization failed" %s' % log_file,
216 ignore_status=True).exit_status == 0
217 if bad_config:
218 raise error.TestFail('hostapd failed to initialize AP '
219 'interface.')
220
221 if pid:
222 early_exit = self.router.run('kill -0 %d' % pid,
223 ignore_status=True).exit_status
224 if early_exit:
225 raise error.TestFail('hostapd process terminated.')
226
227 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
228 else:
229 raise error.TestFail('Timed out while waiting for hostapd '
230 'to start.')
231
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700232
Paul Stewart326badb2012-12-18 14:18:54 -0800233 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700234 """Kill a process on the router.
235
Paul Stewart326badb2012-12-18 14:18:54 -0800236 Kills program named |process|, optionally only a specific
237 |instance|. If |wait| is specified, we makes sure |process| exits
238 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700239
240 @param process string name of process to kill.
241 @param instance string instance of process to kill.
242 @param wait int timeout in seconds to wait for.
243
Thieu Le7b23a542012-01-27 15:54:48 -0800244 """
Paul Stewart21737812012-12-06 11:03:32 -0800245 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800246 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800247 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800248 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800249
Paul Stewart326badb2012-12-18 14:18:54 -0800250 cmd = "pkill %s >/dev/null 2>&1" % search_arg
251
252 if wait:
253 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
254 search_arg)
255 self.router.run(cmd, timeout=wait, ignore_status=True)
256 else:
257 self.router.run(cmd, ignore_status=True)
258
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700259
Paul Stewart326badb2012-12-18 14:18:54 -0800260 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700261 """Kills a hostapd instance.
262
263 @param instance string instance to kill.
264
265 """
Paul Stewart326badb2012-12-18 14:18:54 -0800266 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800267
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700268
Paul Stewart21737812012-12-06 11:03:32 -0800269 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700270 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800271 self.kill_hostapd_instance(None)
272
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700273
274 def __get_default_hostap_config(self):
275 """@return dict of default options for hostapd."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800276 return {'hw_mode': 'g',
277 'ctrl_interface': '/tmp/hostapd-test.control',
278 'logger_syslog': '-1',
279 'logger_syslog_level': '0',
280 # default RTS and frag threshold to ``off''
281 'rts_threshold': '2347',
282 'fragm_threshold': '2346',
283 'driver': self.HOSTAPD_DRIVER_NAME,
284 'ssid': self._build_ssid('') }
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700285
286
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700287 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700288 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
289 for x in range(5)])
290 return (self.ssid_prefix + unique_salt + suffix)[-32:]
291
292
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700293 def hostap_configure(self, configuration, multi_interface=None):
294 """Build up a hostapd configuration file and start hostapd.
295
296 Also setup a local server if this router supports them.
297
298 @param configuration HosetapConfig object.
299 @param multi_interface bool True iff multiple interfaces allowed.
300
301 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800302 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700303 self.station['configured']):
304 self.deconfig()
305 # Start with the default hostapd config parameters.
306 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700307 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700308 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700309 if configuration.bssid:
310 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700311 conf['channel'] = configuration.channel
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700312 conf['hw_mode'] = configuration.hw_mode
313 if configuration.hide_ssid:
314 conf['ignore_broadcast_ssid'] = 1
315 if configuration.is_11n:
316 conf['ieee80211n'] = 1
Christopher Wiley32fbb7a2013-09-18 14:30:50 -0700317 conf['ht_capab'] = configuration.hostapd_ht_capabilities
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700318 if configuration.wmm_enabled:
319 conf['wmm_enabled'] = 1
320 if configuration.require_ht:
321 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700322 if configuration.beacon_interval:
323 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700324 if configuration.dtim_period:
325 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700326 if configuration.frag_threshold:
327 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700328 if configuration.pmf_support:
329 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700330 if configuration.obss_interval:
331 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700332 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileyeea12362013-12-12 17:24:29 -0800333 self.start_hostapd(conf, configuration)
334 interface = self.hostapd_instances[-1]['interface']
335 self.iw_runner.set_tx_power(interface, 'auto')
336 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700337 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700338
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700339
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700340 @staticmethod
341 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700342 """Simple IPv4 calculator.
343
344 Takes host address in "IP/bits" notation and returns netmask, broadcast
345 address as well as integer offsets into the address range.
346
347 @param netblock string host address in "IP/bits" notation.
348 @param idx string describing what to return.
349 @return string containing something you hopefully requested.
350
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700351 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700352 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700353 addr = map(int, addr_str.split('.'))
354 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
355 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700356 if idx == 'local':
357 return addr_str
358 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700359 return '.'.join(map(str, mask))
360 elif idx == 'broadcast':
361 offset = [m ^ 0xff for m in mask]
362 else:
363 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
364 return '.'.join(map(str, [(a & m) + o
365 for a, m, o in zip(addr, mask, offset)]))
366
367
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700368 def ibss_configure(self, config):
369 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700370
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700371 Extract relevant configuration objects from |config| despite not
372 actually being a hostap managed endpoint.
373
374 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700375
376 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800377 if self.station['configured'] or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700378 self.deconfig()
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700379 interface = self._get_wlanif(config.frequency, self.phytype,
380 config.hw_mode)
Christopher Wiley3166e432013-08-06 09:53:12 -0700381 self.station['conf']['ssid'] = (config.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700382 self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800383 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700384 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700385 self.iw_runner.ibss_join(
386 interface, self.station['conf']['ssid'], config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700387 # Always start a local server.
388 self.start_local_server(interface)
389 # Remember that this interface is up.
Paul Stewartc2b3de82011-03-03 14:45:31 -0800390 self.station['configured'] = True
391 self.station['interface'] = interface
392
393
Paul Stewart2bd823b2012-11-21 15:03:37 -0800394 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700395 """Get the local server address for an interface.
396
397 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700398 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700399
400 @param index int describing which local server this is for.
401
402 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700403 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800404
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700405
Paul Stewart6ddeba72013-11-18 10:08:23 -0800406 def local_peer_ip_address(self, index):
407 """Get the IP address allocated for the peer associated to the AP.
408
409 This address is assigned to a locally associated peer device that
410 is created for the DUT to perform connectivity tests with.
411 When we have multiple local servers, we give them static IP addresses
412 like 192.168.*.253.
413
414 @param index int describing which local server this is for.
415
416 """
417 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
418
419
420 def local_peer_mac_address(self):
421 """Get the MAC address of the peer interface.
422
423 @return string MAC address of the peer interface.
424 """
425 iface = interface.Interface(self.station['interface'], self.router)
426 return iface.mac_address
427
428
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700429 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700430 """Start a local server on an interface.
431
432 @param interface string (e.g. wlan0)
433
434 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700435 logging.info("Starting up local server...")
436
437 if len(self.local_servers) >= 256:
438 raise error.TestFail('Exhausted available local servers')
439
Paul Stewart2bd823b2012-11-21 15:03:37 -0800440 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700441
442 params = {}
443 params['netblock'] = netblock
444 params['subnet'] = self.ip_addr(netblock, 0)
445 params['netmask'] = self.ip_addr(netblock, 'netmask')
446 params['dhcp_range'] = ' '.join(
447 (self.ip_addr(netblock, self.dhcp_low),
448 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700449 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700450
451 params['ip_params'] = ("%s broadcast %s dev %s" %
452 (netblock,
453 self.ip_addr(netblock, 'broadcast'),
454 interface))
455 self.local_servers.append(params)
456
457 self.router.run("%s addr flush %s" %
458 (self.cmd_ip, interface))
459 self.router.run("%s addr add %s" %
460 (self.cmd_ip, params['ip_params']))
461 self.router.run("%s link set %s up" %
462 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800463 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700464
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700465
Paul Stewart548cf452012-11-27 17:46:23 -0800466 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700467 """Start a dhcp server on an interface.
468
469 @param interface string (e.g. wlan0)
470
471 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800472 for server in self.local_servers:
473 if server['interface'] == interface:
474 params = server
475 break
476 else:
477 raise error.TestFail('Could not find local server '
478 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700479
Christopher Wileyeea12362013-12-12 17:24:29 -0800480 dhcpd_conf_file = self.dhcpd_conf % interface
481 dhcp_conf = '\n'.join([
482 'port=0', # disables DNS server
483 'bind-interfaces',
484 'log-dhcp',
485 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
486 'interface=%s' % params['interface'],
487 'dhcp-leasefile=%s' % self.dhcpd_leases])
488 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
489 (dhcpd_conf_file, dhcp_conf))
490 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700491
492
Paul Stewart326badb2012-12-18 14:18:54 -0800493 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700494 """Stop a dhcp server on the router.
495
496 @param instance string instance to kill.
497
498 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800499 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800500
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700501
Paul Stewart548cf452012-11-27 17:46:23 -0800502 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700503 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800504 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800505
506
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800507 def get_wifi_channel(self, ap_num):
508 """Return channel of BSS corresponding to |ap_num|.
509
510 @param ap_num int which BSS to get the channel of.
511 @return int primary channel of BSS.
512
513 """
514 instance = self.hostapd_instances[ap_num]
515 return instance['config_dict']['channel']
516
517
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700518 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700519 """Return IP address on the WiFi subnet of a local server on the router.
520
521 If no local servers are configured (e.g. for an RSPro), a TestFail will
522 be raised.
523
524 @param ap_num int which local server to get an address from.
525
526 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700527 if self.local_servers:
528 return self.ip_addr(self.local_servers[ap_num]['netblock'],
529 'local')
530 else:
531 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700532
533
Paul Stewart17350be2012-12-14 13:34:54 -0800534 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700535 """Return the MAC address of an AP in the test.
536
537 @param ap_num int index of local server to read the MAC address from.
538 @return string MAC address like 00:11:22:33:44:55.
539
540 """
Paul Stewart17350be2012-12-14 13:34:54 -0800541 instance = self.hostapd_instances[ap_num]
542 interface = instance['interface']
543 result = self.router.run('%s addr show %s' % (self.cmd_ip, interface))
544 # Example response:
545 # 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 UP qlen 1000
546 # link/ether 99:88:77:66:55:44 brd ff:ff:ff:ff:ff:ff
547 # inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
548 # inet6 fe80::6a7f:74ff:fe66:5544/64 scope link
549 # we want the MAC address after the "link/ether" above.
550 parts = result.stdout.split(' ')
551 return parts[parts.index('link/ether') + 1]
552
553
Christopher Wileyeea12362013-12-12 17:24:29 -0800554 def deconfig(self):
555 """A legacy, deprecated alias for deconfig_aps."""
556 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800557
558
559 def deconfig_aps(self, instance=None, silent=False):
560 """De-configure an AP (will also bring wlan down).
561
562 @param instance: int or None. If instance is None, will bring down all
563 instances of hostapd.
564 @param silent: True if instances should be brought without de-authing
565 the DUT.
566
567 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800568 if not self.hostapd_instances and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700569 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700570
Christopher Wileyeea12362013-12-12 17:24:29 -0800571 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800572 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800573 if instance is not None:
574 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800575 for server in self.local_servers:
576 if server['interface'] == instances[0]['interface']:
577 local_servers = [server]
578 self.local_servers.remove(server)
579 break
Paul Stewart21737812012-12-06 11:03:32 -0800580 else:
581 instances = self.hostapd_instances
582 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800583 local_servers = self.local_servers
584 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700585
Paul Stewart21737812012-12-06 11:03:32 -0800586 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800587 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800588 # Deconfigure without notifying DUT. Remove the interface
589 # hostapd uses to send beacon and DEAUTH packets.
590 self._remove_interface(instance['interface'], True)
591
Paul Stewart326badb2012-12-18 14:18:54 -0800592 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700593 if wifi_test_utils.is_installed(self.host,
594 instance['log_file']):
595 self.router.get_file(instance['log_file'],
596 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800597 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700598 instance['interface']))
599 else:
600 logging.error('Did not collect hostapd log file because '
601 'it was missing.')
Paul Stewart548cf452012-11-27 17:46:23 -0800602 self._release_wlanif(instance['interface'])
603# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800604 self._total_hostapd_instances += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800605 if self.station['configured']:
Christopher Wiley05262d62013-04-17 17:53:59 -0700606 local_servers = self.local_servers
607 self.local_servers = []
Paul Stewartc2b3de82011-03-03 14:45:31 -0800608 if self.station['type'] == 'ibss':
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700609 self.iw_runner.ibss_leave(self.station['interface'])
Christopher Wiley4f49a5d2013-11-25 18:20:54 -0800610 elif self.station['type'] == 'supplicant':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800611 self._kill_process_instance('wpa_supplicant',
612 self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800613 else:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700614 self.iw_runner.disconnect_station(self.station['interface'])
Paul Stewartc2b3de82011-03-03 14:45:31 -0800615 self.router.run("%s link set %s down" % (self.cmd_ip,
616 self.station['interface']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700617
Paul Stewart326badb2012-12-18 14:18:54 -0800618 for server in local_servers:
619 self.stop_dhcp_server(server['interface'])
620 self.router.run("%s addr del %s" %
621 (self.cmd_ip, server['ip_params']),
622 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700623
Paul Stewartc2b3de82011-03-03 14:45:31 -0800624 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700625
626
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800627 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700628 """Verify that the PMKSA auth was cached on a hostapd instance.
629
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800630 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700631
632 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800633 log_file = self.hostapd_instances[instance]['log_file']
634 pmksa_match = 'PMK from PMKSA cache'
635 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
636 ignore_status=True)
637 if result.exit_status:
638 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800639
640
Christopher Wileye0afecb2013-11-11 10:54:23 -0800641 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700642 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800643 if instance is None:
644 instance = 0
645 if len(self.hostapd_instances) > 1:
646 raise error.TestFail('No instance of hostapd specified with '
647 'multiple instances present.')
648
Christopher Wiley3099be72013-11-06 16:49:02 -0800649 if self.hostapd_instances:
650 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700651
Christopher Wiley3166e432013-08-06 09:53:12 -0700652 if not 'ssid' in self.station['conf']:
653 raise error.TestFail('Requested ssid of an unconfigured AP.')
654
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700655 return self.station['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700656
657
Wade Guthriee4074dd2013-10-30 11:00:48 -0700658 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700659 """Deauthenticates a client described in params.
660
Wade Guthriee4074dd2013-10-30 11:00:48 -0700661 @param client_mac string containing the mac address of the client to be
662 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700663
664 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700665 self.router.run('%s -p%s deauthenticate %s' %
666 (self.cmd_hostapd_cli,
Christopher Wileyeea12362013-12-12 17:24:29 -0800667 self.hostapd_instances[-1]['ctrl_interface'],
Wade Guthriee4074dd2013-10-30 11:00:48 -0700668 client_mac))
669
670
Paul Stewart51b0f382013-06-12 09:03:02 -0700671 def send_management_frame(self, frame_type, instance=0):
672 """Injects a management frame into an active hostapd session.
673
674 @param frame_type string the type of frame to send.
675 @param instance int indicating which hostapd instance to inject into.
676
677 """
678 hostap_interface = self.hostapd_instances[instance]['interface']
679 interface = self._get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
680 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
681 self.router.run('%s %s %s' %
682 (self.cmd_send_management_frame, interface, frame_type))
683 self._release_wlanif(interface)
684
685
Paul Stewart25536942013-08-15 17:33:42 -0700686 def detect_client_deauth(self, client_mac, instance=0):
687 """Detects whether hostapd has logged a deauthentication from
688 |client_mac|.
689
690 @param client_mac string the MAC address of the client to detect.
691 @param instance int indicating which hostapd instance to query.
692
693 """
694 interface = self.hostapd_instances[instance]['interface']
695 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
696 log_file = self.hostapd_instances[instance]['log_file']
697 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
698 ignore_status=True)
699 return result.exit_status == 0
700
701
Paul Stewart4ae471e2013-09-04 15:42:35 -0700702 def detect_client_coexistence_report(self, client_mac, instance=0):
703 """Detects whether hostapd has logged an action frame from
704 |client_mac| indicating information about 20/40MHz BSS coexistence.
705
706 @param client_mac string the MAC address of the client to detect.
707 @param instance int indicating which hostapd instance to query.
708
709 """
710 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
711 '.. .. .. .. .. .. .. .. .. .. %s '
712 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
713 ' '.join(client_mac.split(':')))
714 log_file = self.hostapd_instances[instance]['log_file']
715 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
716 ignore_status=True)
717 return result.exit_status == 0
718
719
Paul Stewart6ddeba72013-11-18 10:08:23 -0800720 def add_connected_peer(self, instance=0):
721 """Configure a station connected to a running AP instance.
722
723 Extract relevant configuration objects from the hostap
724 configuration for |instance| and generate a wpa_supplicant
725 instance that connects to it. This allows the DUT to interact
726 with a client entity that is also connected to the same AP. A
727 full wpa_supplicant instance is necessary here (instead of just
728 using the "iw" command to connect) since we want to enable
729 advanced features such as TDLS.
730
731 @param instance int indicating which hostapd instance to connect to.
732
733 """
734 if not self.hostapd_instances:
735 raise error.TestFail('Hostapd is not configured.')
736
737 if self.station['configured']:
738 raise error.TestFail('Station is already configured.')
739
740 client_conf = self.station['conf']
741 client_conf['ssid'] = self.get_ssid(instance)
742
743 hostap_conf = self.hostapd_instances[instance]['config_dict']
744 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
745 hostap_conf['channel'])
746 interface = self._get_wlanif(
747 frequency, 'managed', hostap_conf['hw_mode'])
748 client_conf['interface'] = interface
749
750 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
751 # require them...
752 supplicant_config = (
753 'network={\n'
754 ' ssid="%(ssid)s"\n'
755 ' key_mgmt=NONE\n'
756 '}\n' % client_conf
757 )
758
Christopher Wileyeea12362013-12-12 17:24:29 -0800759 conf_file = self.STATION_CONF_FILE_PATTERN % interface
760 log_file = self.STATION_LOG_FILE_PATTERN % interface
761 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800762
763 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
764 (conf_file, supplicant_config))
765
766 # Connect the station.
767 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
768 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
769 (self.cmd_wpa_supplicant,
770 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800771 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800772 self.router.run(start_command)
773 self.iw_runner.wait_for_link(interface)
774
775 # Assign an IP address to this interface.
776 self.router.run('%s addr add %s/24 dev %s' %
777 (self.cmd_ip, self.local_peer_ip_address(instance),
778 interface))
779
780 # Since we now have two network interfaces connected to the same
781 # network, we need to disable the kernel's protection against
782 # incoming packets to an "unexpected" interface.
783 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
784 interface)
785
Paul Stewartb01839b2013-12-06 15:49:56 -0800786 # Similarly, we'd like to prevent the hostap interface from
787 # replying to ARP requests for the peer IP address and vice
788 # versa.
789 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
790 interface)
791 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
792 hostap_conf['interface'])
793
Paul Stewart6ddeba72013-11-18 10:08:23 -0800794 self.station['configured'] = True
795 self.station['type'] = 'supplicant'
796 self.station['interface'] = interface