blob: 0a4117dd07c1ad0e8f8334a1270a01bc381af291 [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 Wiley14796b32013-04-03 14:53:33 -07007import re
Christopher Wiley3166e432013-08-06 09:53:12 -07008import string
Christopher Wiley14796b32013-04-03 14:53:33 -07009
Paul Stewartc9628b32010-08-11 13:03:51 -070010from autotest_lib.client.common_lib import error
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070011from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070012from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070013from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070014
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070015def isLinuxRouter(host):
16 """Check if host is a linux router.
17
18 @param host Host object representing the remote machine.
19 @return True iff remote system is a Linux system.
20
21 """
22 router_uname = host.run('uname').stdout
Sam Leffler19bb0a72010-04-12 08:51:08 -070023 return re.search('Linux', router_uname)
24
Christopher Wiley1febd6a2013-06-03 13:59:48 -070025
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070026class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070027 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070028
29 This class implements test methods/steps that communicate with a
30 router implemented with Linux/mac80211. The router must
31 be pre-configured to enable ssh access and have a mac80211-based
32 wireless device. We also assume hostapd 0.7.x and iw are present
33 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070034
Sam Leffler6969d1d2010-03-15 16:07:11 -070035 """
36
Christopher Wiley3166e432013-08-06 09:53:12 -070037 KNOWN_TEST_PREFIX = 'network_WiFi'
38 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Sam Leffler6969d1d2010-03-15 16:07:11 -070039
Paul Stewart51b0f382013-06-12 09:03:02 -070040 def get_capabilities(self):
41 """@return iterable object of AP capabilities for this system."""
42 caps = set()
43 try:
44 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
45 self.router, '/usr/bin/send_management_frame')
46 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
47 except error.TestFail:
48 pass
49 return super(LinuxRouter, self).get_capabilities().union(caps)
50
51
Christopher Wiley3166e432013-08-06 09:53:12 -070052 def __init__(self, host, params, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070053 """Build a LinuxRouter.
54
55 @param host Host object representing the remote machine.
56 @param params dict of settings from site_wifitest based tests.
Christopher Wiley3166e432013-08-06 09:53:12 -070057 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070058
59 """
60 site_linux_system.LinuxSystem.__init__(self, host, params, 'router')
mukesh agrawalfe0e85b2011-08-09 14:24:15 -070061 self._remove_interfaces()
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070062
Wade Guthrie24d1e312012-04-24 16:53:40 -070063 # Router host.
64 self.router = host
65
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070066 self.cmd_hostapd = wifi_test_utils.must_be_installed(
67 host, params.get('cmd_hostapd', '/usr/sbin/hostapd'))
68 self.cmd_hostapd_cli = params.get('cmd_hostapd_cli',
69 '/usr/sbin/hostapd_cli')
70 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
71 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070072
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070073 # hostapd configuration persists throughout the test, subsequent
74 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -070075 self.ssid_prefix = test_name
76 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
77 # Many of our tests start with an uninteresting prefix.
78 # Remove it so we can have more unique bytes.
79 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
80 self.ssid_prefix = self.ssid_prefix.lstrip('_')
81 self.ssid_prefix += '_'
82
Paul Stewartd5aafa92013-03-24 19:06:14 -070083 self.default_config = {
Paul Stewartd5aafa92013-03-24 19:06:14 -070084 'hw_mode': 'g',
85 'ctrl_interface': '/tmp/hostapd-test.control',
86 'logger_syslog': '-1',
87 'logger_syslog_level': '0'
88 }
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070089 self.hostapd = {
90 'configured': False,
Paul Stewart326badb2012-12-18 14:18:54 -080091 'config_file': "/tmp/hostapd-test-%s.conf",
92 'log_file': "/tmp/hostapd-test-%s.log",
Paul Stewartf854d2e2011-05-04 13:19:18 -070093 'log_count': 0,
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070094 'driver': "nl80211",
Paul Stewartd5aafa92013-03-24 19:06:14 -070095 'conf': self.default_config.copy()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070096 }
Paul Stewartc2b3de82011-03-03 14:45:31 -080097 self.station = {
98 'configured': False,
Christopher Wiley3166e432013-08-06 09:53:12 -070099 'conf': {},
Paul Stewartc2b3de82011-03-03 14:45:31 -0800100 }
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700101 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800102 self.hostapd_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700103 self.force_local_server = "force_local_server" in params
104 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
112 self.router.run("%s reg set US" % self.cmd_iw)
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
Sam Leffler6969d1d2010-03-15 16:07:11 -0700121 def create(self, params):
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 @param params dict containing the device type under key 'type'.
125
126 """
127 self.create_wifi_device(params['type'])
128
129
130 def create_wifi_device(self, device_type='hostap'):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700131 """Create a wifi device of the specified type.
Christopher Wiley14796b32013-04-03 14:53:33 -0700132
133 Defaults to creating a hostap managed device.
134
135 @param device_type string device type.
136
137 """
Sam Leffler6969d1d2010-03-15 16:07:11 -0700138 #
139 # AP mode is handled entirely by hostapd so we only
140 # have to setup others (mapping the bsd type to what
141 # iw wants)
142 #
143 # map from bsd types to iw types
Christopher Wiley14796b32013-04-03 14:53:33 -0700144 self.apmode = device_type in ('ap', 'hostap')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800145 if not self.apmode:
Christopher Wiley14796b32013-04-03 14:53:33 -0700146 self.station['type'] = device_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700147 self.phytype = {
Christopher Wiley14796b32013-04-03 14:53:33 -0700148 'sta' : 'managed',
149 'monitor' : 'monitor',
150 'adhoc' : 'adhoc',
151 'ibss' : 'ibss',
152 'ap' : 'managed', # NB: handled by hostapd
153 'hostap' : 'managed', # NB: handled by hostapd
154 'mesh' : 'mesh',
155 'wds' : 'wds',
156 }[device_type]
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700157
Sam Leffler6969d1d2010-03-15 16:07:11 -0700158
Christopher Wileyd89b5282013-04-10 15:21:26 -0700159 def destroy(self, params={}):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700160 """Destroy a previously created device.
161
162 @param params dict of site_wifitest parameters.
163
164 """
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700165 self.deconfig(params)
Paul Stewartd5aafa92013-03-24 19:06:14 -0700166 self.hostapd['conf'] = self.default_config.copy()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700167
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700168
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700169 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700170 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700171 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700172
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700173
Paul Stewart9e3ff0b2011-08-17 20:35:19 -0700174 def cleanup(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700175 """Clean up any resources in use.
176
177 @param params dict of site_wifitest parameters.
178
179 """
Paul Stewart9e3ff0b2011-08-17 20:35:19 -0700180 # For linux, this is a no-op
181 pass
182
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700183
Paul Stewart548cf452012-11-27 17:46:23 -0800184 def start_hostapd(self, conf, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700185 """Start a hostapd instance described by conf.
186
187 @param conf dict of hostapd configuration parameters.
188 @param params dict of site_wifitest parameters.
189
190 """
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700191 logging.info('Starting hostapd with parameters: %r', conf)
Paul Stewart548cf452012-11-27 17:46:23 -0800192 # Figure out the correct interface.
Paul Stewart326badb2012-12-18 14:18:54 -0800193 interface = self._get_wlanif(self.hostapd['frequency'],
194 self.phytype,
195 mode=conf.get('hw_mode', 'b'))
196
197 conf_file = self.hostapd['config_file'] % interface
198 log_file = self.hostapd['log_file'] % interface
199 conf['interface'] = interface
Paul Stewart548cf452012-11-27 17:46:23 -0800200
201 # Generate hostapd.conf.
202 self._pre_config_hook(conf)
203 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
204 (conf_file, '\n'.join(
205 "%s=%s" % kv for kv in conf.iteritems())))
206
207 # Run hostapd.
208 logging.info("Starting hostapd...")
209 self._pre_start_hook(params)
210 self.router.run("%s -dd %s &> %s &" %
211 (self.cmd_hostapd, conf_file, log_file))
212
213 self.hostapd_instances.append({
214 'conf_file': conf_file,
215 'log_file': log_file,
Paul Stewart326badb2012-12-18 14:18:54 -0800216 'interface': interface
Paul Stewart548cf452012-11-27 17:46:23 -0800217 })
218
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700219
Paul Stewart326badb2012-12-18 14:18:54 -0800220 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700221 """Kill a process on the router.
222
Paul Stewart326badb2012-12-18 14:18:54 -0800223 Kills program named |process|, optionally only a specific
224 |instance|. If |wait| is specified, we makes sure |process| exits
225 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700226
227 @param process string name of process to kill.
228 @param instance string instance of process to kill.
229 @param wait int timeout in seconds to wait for.
230
Thieu Le7b23a542012-01-27 15:54:48 -0800231 """
Paul Stewart21737812012-12-06 11:03:32 -0800232 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800233 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800234 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800235 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800236
Paul Stewart326badb2012-12-18 14:18:54 -0800237 cmd = "pkill %s >/dev/null 2>&1" % search_arg
238
239 if wait:
240 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
241 search_arg)
242 self.router.run(cmd, timeout=wait, ignore_status=True)
243 else:
244 self.router.run(cmd, ignore_status=True)
245
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700246
Paul Stewart326badb2012-12-18 14:18:54 -0800247 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700248 """Kills a hostapd instance.
249
250 @param instance string instance to kill.
251
252 """
Paul Stewart326badb2012-12-18 14:18:54 -0800253 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800254
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700255
Paul Stewart21737812012-12-06 11:03:32 -0800256 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700257 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800258 self.kill_hostapd_instance(None)
259
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700260
261 def __get_default_hostap_config(self):
262 """@return dict of default options for hostapd."""
263 conf = self.hostapd['conf']
264 # default RTS and frag threshold to ``off''
265 conf['rts_threshold'] = '2347'
266 conf['fragm_threshold'] = '2346'
267 conf['driver'] = self.hostapd['driver']
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700268 conf['ssid'] = self._build_ssid('')
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700269 return conf
270
271
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700272 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700273 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
274 for x in range(5)])
275 return (self.ssid_prefix + unique_salt + suffix)[-32:]
276
277
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700278 def hostap_configure(self, configuration, multi_interface=None):
279 """Build up a hostapd configuration file and start hostapd.
280
281 Also setup a local server if this router supports them.
282
283 @param configuration HosetapConfig object.
284 @param multi_interface bool True iff multiple interfaces allowed.
285
286 """
287 if multi_interface is None and (self.hostapd['configured'] or
288 self.station['configured']):
289 self.deconfig()
290 # Start with the default hostapd config parameters.
291 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700292 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700293 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700294 if configuration.bssid:
295 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700296 conf['channel'] = configuration.channel
297 self.hostapd['frequency'] = configuration.frequency
298 conf['hw_mode'] = configuration.hw_mode
299 if configuration.hide_ssid:
300 conf['ignore_broadcast_ssid'] = 1
301 if configuration.is_11n:
302 conf['ieee80211n'] = 1
303 conf['ht_capab'] = ''.join(configuration.n_capabilities)
304 if configuration.wmm_enabled:
305 conf['wmm_enabled'] = 1
306 if configuration.require_ht:
307 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700308 if configuration.beacon_interval:
309 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700310 if configuration.dtim_period:
311 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700312 if configuration.frag_threshold:
313 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700314 if configuration.pmf_support:
315 conf['ieee80211w'] = configuration.pmf_support
Paul Stewart4ae471e2013-09-04 15:42:35 -0700316 if configuration.obss_interval:
317 conf['obss_interval'] = configuration.obss_interval
Christopher Wileyb8921c72013-06-13 09:51:47 -0700318 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileya89706d2013-06-12 13:20:58 -0700319
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700320 self.start_hostapd(conf, {})
321 # Configure transmit power
322 tx_power_params = {'interface': conf['interface']}
323 # TODO(wiley) support for setting transmit power
324 self.set_txpower(tx_power_params)
325 if self.force_local_server:
326 self.start_local_server(conf['interface'])
327 self._post_start_hook({})
328 logging.info('AP configured.')
329 self.hostapd['configured'] = True
330
331
Paul Stewartc2b3de82011-03-03 14:45:31 -0800332 def hostap_config(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700333 """Configure the AP per test requirements.
Sam Leffler6969d1d2010-03-15 16:07:11 -0700334
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700335 @param params dict of site_wifitest parameters.
336
337 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700338 # keep parameter modifications local-only
339 orig_params = params
340 params = params.copy()
341
Paul Stewart45338d22010-10-21 10:57:02 -0700342 multi_interface = 'multi_interface' in params
343 if multi_interface:
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700344 # remove non-hostapd config item from params
Paul Stewart45338d22010-10-21 10:57:02 -0700345 params.pop('multi_interface')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800346 elif self.hostapd['configured'] or self.station['configured']:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700347 self.deconfig()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700348
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700349 local_server = params.pop('local_server', False)
350
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700351 conf = self.__get_default_hostap_config()
Paul Stewartc2b3de82011-03-03 14:45:31 -0800352 tx_power_params = {}
353 htcaps = set()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700354
Paul Stewartc2b3de82011-03-03 14:45:31 -0800355 for k, v in params.iteritems():
356 if k == 'ssid':
357 conf['ssid'] = v
358 elif k == 'ssid_suffix':
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700359 conf['ssid'] = self._build_ssid(v)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800360 elif k == 'channel':
361 freq = int(v)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700362 self.hostapd['frequency'] = freq
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700363
Paul Stewartc2b3de82011-03-03 14:45:31 -0800364 # 2.4GHz
365 if freq <= 2484:
366 # Make sure hw_mode is set
367 if conf.get('hw_mode') == 'a':
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700368 conf['hw_mode'] = 'g'
Paul Stewartc2b3de82011-03-03 14:45:31 -0800369
370 # Freq = 5 * chan + 2407, except channel 14
371 if freq == 2484:
372 conf['channel'] = 14
373 else:
374 conf['channel'] = (freq - 2407) / 5
375 # 5GHz
Sam Leffler6969d1d2010-03-15 16:07:11 -0700376 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800377 # Make sure hw_mode is set
378 conf['hw_mode'] = 'a'
379 # Freq = 5 * chan + 4000
380 if freq < 5000:
381 conf['channel'] = (freq - 4000) / 5
382 # Freq = 5 * chan + 5000
383 else:
384 conf['channel'] = (freq - 5000) / 5
Sam Leffler6969d1d2010-03-15 16:07:11 -0700385
Paul Stewartc2b3de82011-03-03 14:45:31 -0800386 elif k == 'country':
387 conf['country_code'] = v
388 elif k == 'dotd':
389 conf['ieee80211d'] = 1
390 elif k == '-dotd':
391 conf['ieee80211d'] = 0
392 elif k == 'mode':
393 if v == '11a':
394 conf['hw_mode'] = 'a'
395 elif v == '11g':
396 conf['hw_mode'] = 'g'
397 elif v == '11b':
398 conf['hw_mode'] = 'b'
399 elif v == '11n':
400 conf['ieee80211n'] = 1
401 elif k == 'bintval':
402 conf['beacon_int'] = v
403 elif k == 'dtimperiod':
404 conf['dtim_period'] = v
405 elif k == 'rtsthreshold':
406 conf['rts_threshold'] = v
407 elif k == 'fragthreshold':
408 conf['fragm_threshold'] = v
409 elif k == 'shortpreamble':
410 conf['preamble'] = 1
411 elif k == 'authmode':
412 if v == "open":
413 conf['auth_algs'] = 1
414 elif v == "shared":
415 conf['auth_algs'] = 2
416 elif k == 'hidessid':
417 conf['ignore_broadcast_ssid'] = 1
418 elif k == 'wme':
419 conf['wmm_enabled'] = 1
420 elif k == '-wme':
421 conf['wmm_enabled'] = 0
422 elif k == 'deftxkey':
423 conf['wep_default_key'] = v
424 elif k == 'ht20':
425 htcaps.add('') # NB: ensure 802.11n setup below
426 conf['wmm_enabled'] = 1
427 elif k == 'ht40':
428 htcaps.add('[HT40-]')
429 htcaps.add('[HT40+]')
430 conf['wmm_enabled'] = 1
Paul Stewartc1df8d62011-04-07 14:28:15 -0700431 elif k in ('ht40+', 'ht40-'):
432 htcaps.add('[%s]' % k.upper())
433 conf['wmm_enabled'] = 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800434 elif k == 'shortgi':
435 htcaps.add('[SHORT-GI-20]')
436 htcaps.add('[SHORT-GI-40]')
437 elif k == 'pureg':
438 pass # TODO(sleffler) need hostapd support
439 elif k == 'puren':
440 pass # TODO(sleffler) need hostapd support
441 elif k == 'protmode':
442 pass # TODO(sleffler) need hostapd support
443 elif k == 'ht':
444 htcaps.add('') # NB: ensure 802.11n setup below
445 elif k == 'htprotmode':
446 pass # TODO(sleffler) need hostapd support
447 elif k == 'rifs':
448 pass # TODO(sleffler) need hostapd support
449 elif k == 'wepmode':
450 pass # NB: meaningless for hostapd; ignore
451 elif k == '-ampdu':
452 pass # TODO(sleffler) need hostapd support
453 elif k == 'txpower':
454 tx_power_params['power'] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700455 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800456 conf[k] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700457
Paul Stewartc2b3de82011-03-03 14:45:31 -0800458 # Aggregate ht_capab.
459 if htcaps:
460 conf['ieee80211n'] = 1
461 conf['ht_capab'] = ''.join(htcaps)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700462
Paul Stewart548cf452012-11-27 17:46:23 -0800463 self.start_hostapd(conf, orig_params)
Paul Stewart1ae854b2011-02-08 15:10:14 -0800464
Paul Stewartc2b3de82011-03-03 14:45:31 -0800465 # Configure transmit power
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700466 tx_power_params['interface'] = conf['interface']
Paul Stewartc2b3de82011-03-03 14:45:31 -0800467 self.set_txpower(tx_power_params)
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700468
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700469 if self.force_local_server or local_server is not False:
470 self.start_local_server(conf['interface'])
Sam Leffler6969d1d2010-03-15 16:07:11 -0700471
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700472 self._post_start_hook(orig_params)
473
474 logging.info("AP configured.")
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700475 self.hostapd['configured'] = True
Sam Leffler6969d1d2010-03-15 16:07:11 -0700476
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700477
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700478 @staticmethod
479 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700480 """Simple IPv4 calculator.
481
482 Takes host address in "IP/bits" notation and returns netmask, broadcast
483 address as well as integer offsets into the address range.
484
485 @param netblock string host address in "IP/bits" notation.
486 @param idx string describing what to return.
487 @return string containing something you hopefully requested.
488
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700489 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700490 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700491 addr = map(int, addr_str.split('.'))
492 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
493 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700494 if idx == 'local':
495 return addr_str
496 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700497 return '.'.join(map(str, mask))
498 elif idx == 'broadcast':
499 offset = [m ^ 0xff for m in mask]
500 else:
501 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
502 return '.'.join(map(str, [(a & m) + o
503 for a, m, o in zip(addr, mask, offset)]))
504
505
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700506 def ibss_configure(self, config):
507 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700508
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700509 Extract relevant configuration objects from |config| despite not
510 actually being a hostap managed endpoint.
511
512 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700513
514 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700515 if self.station['configured'] or self.hostapd['configured']:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700516 self.deconfig()
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700517 interface = self._get_wlanif(config.frequency, self.phytype,
518 config.hw_mode)
Christopher Wiley3166e432013-08-06 09:53:12 -0700519 self.station['conf']['ssid'] = (config.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700520 self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800521 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700522 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley3166e432013-08-06 09:53:12 -0700523 self.router.run('%s dev %s ibss join %s %d' % (
524 self.cmd_iw, interface, self.station['conf']['ssid'],
525 config.frequency))
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700526 # Always start a local server.
527 self.start_local_server(interface)
528 # Remember that this interface is up.
Paul Stewartc2b3de82011-03-03 14:45:31 -0800529 self.station['configured'] = True
530 self.station['interface'] = interface
531
532
Paul Stewart2bd823b2012-11-21 15:03:37 -0800533 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700534 """Get the local server address for an interface.
535
536 When we multiple local servers, we give them static IP addresses
537 like 192.158.*.254.
538
539 @param index int describing which local server this is for.
540
541 """
Paul Stewart2bd823b2012-11-21 15:03:37 -0800542 return '%d.%d.%d.%d' % (192, 168, index, 254)
543
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700544
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700545 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700546 """Start a local server on an interface.
547
548 @param interface string (e.g. wlan0)
549
550 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700551 logging.info("Starting up local server...")
552
553 if len(self.local_servers) >= 256:
554 raise error.TestFail('Exhausted available local servers')
555
Paul Stewart2bd823b2012-11-21 15:03:37 -0800556 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700557
558 params = {}
559 params['netblock'] = netblock
560 params['subnet'] = self.ip_addr(netblock, 0)
561 params['netmask'] = self.ip_addr(netblock, 'netmask')
562 params['dhcp_range'] = ' '.join(
563 (self.ip_addr(netblock, self.dhcp_low),
564 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700565 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700566
567 params['ip_params'] = ("%s broadcast %s dev %s" %
568 (netblock,
569 self.ip_addr(netblock, 'broadcast'),
570 interface))
571 self.local_servers.append(params)
572
573 self.router.run("%s addr flush %s" %
574 (self.cmd_ip, interface))
575 self.router.run("%s addr add %s" %
576 (self.cmd_ip, params['ip_params']))
577 self.router.run("%s link set %s up" %
578 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800579 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700580
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700581
Paul Stewart548cf452012-11-27 17:46:23 -0800582 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700583 """Start a dhcp server on an interface.
584
585 @param interface string (e.g. wlan0)
586
587 """
Paul Stewart326badb2012-12-18 14:18:54 -0800588 conf_file = self.dhcpd_conf % interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700589 dhcp_conf = '\n'.join(map(
590 lambda server_conf: \
591 "subnet %(subnet)s netmask %(netmask)s {\n" \
592 " range %(dhcp_range)s;\n" \
593 "}" % server_conf,
594 self.local_servers))
595 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
Paul Stewart326badb2012-12-18 14:18:54 -0800596 (conf_file,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700597 '\n'.join(('ddns-update-style none;', dhcp_conf))))
598 self.router.run("touch %s" % self.dhcpd_leases)
599
600 self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True)
601 self.router.run("%s -q -cf %s -lf %s" %
Paul Stewart326badb2012-12-18 14:18:54 -0800602 (self.cmd_dhcpd, conf_file, self.dhcpd_leases))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700603
604
Paul Stewart326badb2012-12-18 14:18:54 -0800605 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700606 """Stop a dhcp server on the router.
607
608 @param instance string instance to kill.
609
610 """
Paul Stewart326badb2012-12-18 14:18:54 -0800611 self._kill_process_instance('dhcpd', instance, 0)
612
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700613
Paul Stewart548cf452012-11-27 17:46:23 -0800614 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700615 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800616 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800617
618
Paul Stewartc2b3de82011-03-03 14:45:31 -0800619 def config(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700620 """Configure an AP based on site_wifitest parameters.
621
622 @param params dict of site_wifitest parameters.
623
624 """
Paul Stewartc2b3de82011-03-03 14:45:31 -0800625 if self.apmode:
626 self.hostap_config(params)
627 else:
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700628 config = hostap_config.HostapConfig(
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700629 frequency=int(params.get('channel', None)))
630 self.ibss_configure(config)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800631
632
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700633 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700634 """Return IP address on the WiFi subnet of a local server on the router.
635
636 If no local servers are configured (e.g. for an RSPro), a TestFail will
637 be raised.
638
639 @param ap_num int which local server to get an address from.
640
641 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700642 if self.local_servers:
643 return self.ip_addr(self.local_servers[ap_num]['netblock'],
644 'local')
645 else:
646 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700647
648
Paul Stewart17350be2012-12-14 13:34:54 -0800649 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700650 """Return the MAC address of an AP in the test.
651
652 @param ap_num int index of local server to read the MAC address from.
653 @return string MAC address like 00:11:22:33:44:55.
654
655 """
Paul Stewart17350be2012-12-14 13:34:54 -0800656 instance = self.hostapd_instances[ap_num]
657 interface = instance['interface']
658 result = self.router.run('%s addr show %s' % (self.cmd_ip, interface))
659 # Example response:
660 # 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 UP qlen 1000
661 # link/ether 99:88:77:66:55:44 brd ff:ff:ff:ff:ff:ff
662 # inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
663 # inet6 fe80::6a7f:74ff:fe66:5544/64 scope link
664 # we want the MAC address after the "link/ether" above.
665 parts = result.stdout.split(' ')
666 return parts[parts.index('link/ether') + 1]
667
668
Christopher Wileyd89b5282013-04-10 15:21:26 -0700669 def deconfig(self, params={}):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700670 """De-configure the AP (will also bring wlan down).
Sam Leffler6969d1d2010-03-15 16:07:11 -0700671
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700672 @param params dict of parameters from site_wifitest.
673
674 """
Paul Stewartc2b3de82011-03-03 14:45:31 -0800675 if not self.hostapd['configured'] and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700676 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700677
Paul Stewartc2b3de82011-03-03 14:45:31 -0800678 if self.hostapd['configured']:
Paul Stewart326badb2012-12-18 14:18:54 -0800679 local_servers = []
Paul Stewart21737812012-12-06 11:03:32 -0800680 if 'instance' in params:
681 instances = [ self.hostapd_instances.pop(params['instance']) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800682 for server in self.local_servers:
683 if server['interface'] == instances[0]['interface']:
684 local_servers = [server]
685 self.local_servers.remove(server)
686 break
Paul Stewart21737812012-12-06 11:03:32 -0800687 else:
688 instances = self.hostapd_instances
689 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800690 local_servers = self.local_servers
691 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700692
Paul Stewart21737812012-12-06 11:03:32 -0800693 for instance in instances:
694 if 'silent' in params:
695 # Deconfigure without notifying DUT. Remove the interface
696 # hostapd uses to send beacon and DEAUTH packets.
697 self._remove_interface(instance['interface'], True)
698
Paul Stewart326badb2012-12-18 14:18:54 -0800699 self.kill_hostapd_instance(instance['conf_file'])
Paul Stewart548cf452012-11-27 17:46:23 -0800700 self.router.get_file(instance['log_file'],
701 'debug/hostapd_router_%d_%s.log' %
702 (self.hostapd['log_count'],
703 instance['interface']))
704 self._release_wlanif(instance['interface'])
705# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Paul Stewartf854d2e2011-05-04 13:19:18 -0700706 self.hostapd['log_count'] += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800707 if self.station['configured']:
Christopher Wiley05262d62013-04-17 17:53:59 -0700708 local_servers = self.local_servers
709 self.local_servers = []
Paul Stewartc2b3de82011-03-03 14:45:31 -0800710 if self.station['type'] == 'ibss':
711 self.router.run("%s dev %s ibss leave" %
712 (self.cmd_iw, self.station['interface']))
713 else:
714 self.router.run("%s dev %s disconnect" %
715 (self.cmd_iw, self.station['interface']))
716 self.router.run("%s link set %s down" % (self.cmd_ip,
717 self.station['interface']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700718
Paul Stewart326badb2012-12-18 14:18:54 -0800719 for server in local_servers:
720 self.stop_dhcp_server(server['interface'])
721 self.router.run("%s addr del %s" %
722 (self.cmd_ip, server['ip_params']),
723 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700724
725 self.hostapd['configured'] = False
Paul Stewartc2b3de82011-03-03 14:45:31 -0800726 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700727
728
Paul Stewart17350be2012-12-14 13:34:54 -0800729 def verify_pmksa_auth(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700730 """Verify that the PMKSA auth was cached on a hostapd instance.
731
732 @param params dict with optional key 'instance' (defaults to 0).
733
734 """
Paul Stewart17350be2012-12-14 13:34:54 -0800735 instance_num = params.get('instance', 0)
736 instance = self.hostapd_instances[instance_num]
737 pmksa_match = 'PMK from PMKSA cache - skip IEEE 802.1X.EAP'
738 self.router.run('grep -q "%s" %s' % (pmksa_match, instance['log_file']))
739
740
Paul Stewart7cb1f062010-06-10 15:46:20 -0700741 def get_ssid(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700742 """@return string ssid for the network stemming from this router."""
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700743 if self.hostapd['configured']:
744 return self.hostapd['conf']['ssid']
745
Christopher Wiley3166e432013-08-06 09:53:12 -0700746 if not 'ssid' in self.station['conf']:
747 raise error.TestFail('Requested ssid of an unconfigured AP.')
748
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700749 return self.station['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700750
751
752 def set_txpower(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700753 """Set the transmission power for an interface.
754
755 Assumes that we want to refer to the first hostapd instance unless
756 'interface' is defined in params. Sets the transmission power to
757 'auto' if 'power' is not defined in params.
758
759 @param params dict of parameters as described above.
760
761 """
Paul Stewart548cf452012-11-27 17:46:23 -0800762 interface = params.get('interface',
763 self.hostapd_instances[0]['interface'])
764 power = params.get('power', 'auto')
Paul Stewart98022e22010-10-22 10:33:14 -0700765 self.router.run("%s dev %s set txpower %s" %
Paul Stewart548cf452012-11-27 17:46:23 -0800766 (self.cmd_iw, interface, power))
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700767
768
769 def deauth(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700770 """Deauthenticates a client described in params.
771
772 @param params dict containing a key 'client'.
773
774 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700775 self.router.run('%s -p%s deauthenticate %s' %
776 (self.cmd_hostapd_cli,
777 self.hostapd['conf']['ctrl_interface'],
778 params['client']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700779
780
Paul Stewart51b0f382013-06-12 09:03:02 -0700781 def send_management_frame(self, frame_type, instance=0):
782 """Injects a management frame into an active hostapd session.
783
784 @param frame_type string the type of frame to send.
785 @param instance int indicating which hostapd instance to inject into.
786
787 """
788 hostap_interface = self.hostapd_instances[instance]['interface']
789 interface = self._get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
790 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
791 self.router.run('%s %s %s' %
792 (self.cmd_send_management_frame, interface, frame_type))
793 self._release_wlanif(interface)
794
795
Paul Stewart25536942013-08-15 17:33:42 -0700796 def detect_client_deauth(self, client_mac, instance=0):
797 """Detects whether hostapd has logged a deauthentication from
798 |client_mac|.
799
800 @param client_mac string the MAC address of the client to detect.
801 @param instance int indicating which hostapd instance to query.
802
803 """
804 interface = self.hostapd_instances[instance]['interface']
805 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
806 log_file = self.hostapd_instances[instance]['log_file']
807 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
808 ignore_status=True)
809 return result.exit_status == 0
810
811
Paul Stewart4ae471e2013-09-04 15:42:35 -0700812 def detect_client_coexistence_report(self, client_mac, instance=0):
813 """Detects whether hostapd has logged an action frame from
814 |client_mac| indicating information about 20/40MHz BSS coexistence.
815
816 @param client_mac string the MAC address of the client to detect.
817 @param instance int indicating which hostapd instance to query.
818
819 """
820 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
821 '.. .. .. .. .. .. .. .. .. .. %s '
822 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
823 ' '.join(client_mac.split(':')))
824 log_file = self.hostapd_instances[instance]['log_file']
825 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
826 ignore_status=True)
827 return result.exit_status == 0
828
829
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700830 def _pre_config_hook(self, config):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700831 """Hook for subclasses.
832
833 Run after gathering configuration parameters,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700834 but before writing parameters to config file.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700835
836 @param config dict containing hostapd config parameters.
837
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700838 """
839 pass
840
841
842 def _pre_start_hook(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700843 """Hook for subclasses.
844
845 Run after generating hostapd config file, but before starting hostapd.
846
847 @param params dict parameters from site_wifitest.
848
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700849 """
850 pass
851
852
853 def _post_start_hook(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700854 """Hook for subclasses run after starting hostapd.
855
856 @param params dict parameters from site_wifitest.
857
858 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700859 pass