blob: 697242575c2aa493931a02ba05a760f223e6a259 [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
Sam Leffler6969d1d2010-03-15 16:07:11 -0700115 def create(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700116 """Create a wifi device of the specified type.
Christopher Wiley14796b32013-04-03 14:53:33 -0700117
118 @param params dict containing the device type under key 'type'.
119
120 """
121 self.create_wifi_device(params['type'])
122
123
124 def create_wifi_device(self, device_type='hostap'):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700125 """Create a wifi device of the specified type.
Christopher Wiley14796b32013-04-03 14:53:33 -0700126
127 Defaults to creating a hostap managed device.
128
129 @param device_type string device type.
130
131 """
Sam Leffler6969d1d2010-03-15 16:07:11 -0700132 #
133 # AP mode is handled entirely by hostapd so we only
134 # have to setup others (mapping the bsd type to what
135 # iw wants)
136 #
137 # map from bsd types to iw types
Christopher Wiley14796b32013-04-03 14:53:33 -0700138 self.apmode = device_type in ('ap', 'hostap')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800139 if not self.apmode:
Christopher Wiley14796b32013-04-03 14:53:33 -0700140 self.station['type'] = device_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700141 self.phytype = {
Christopher Wiley14796b32013-04-03 14:53:33 -0700142 'sta' : 'managed',
143 'monitor' : 'monitor',
144 'adhoc' : 'adhoc',
145 'ibss' : 'ibss',
146 'ap' : 'managed', # NB: handled by hostapd
147 'hostap' : 'managed', # NB: handled by hostapd
148 'mesh' : 'mesh',
149 'wds' : 'wds',
150 }[device_type]
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700151
Sam Leffler6969d1d2010-03-15 16:07:11 -0700152
Christopher Wileyd89b5282013-04-10 15:21:26 -0700153 def destroy(self, params={}):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700154 """Destroy a previously created device.
155
156 @param params dict of site_wifitest parameters.
157
158 """
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700159 self.deconfig(params)
Paul Stewartd5aafa92013-03-24 19:06:14 -0700160 self.hostapd['conf'] = self.default_config.copy()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700161
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700162
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700163 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700164 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700165 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700166
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700167
Paul Stewart9e3ff0b2011-08-17 20:35:19 -0700168 def cleanup(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700169 """Clean up any resources in use.
170
171 @param params dict of site_wifitest parameters.
172
173 """
Paul Stewart9e3ff0b2011-08-17 20:35:19 -0700174 # For linux, this is a no-op
175 pass
176
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700177
Paul Stewart548cf452012-11-27 17:46:23 -0800178 def start_hostapd(self, conf, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700179 """Start a hostapd instance described by conf.
180
181 @param conf dict of hostapd configuration parameters.
182 @param params dict of site_wifitest parameters.
183
184 """
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700185 logging.info('Starting hostapd with parameters: %r', conf)
Paul Stewart548cf452012-11-27 17:46:23 -0800186 # Figure out the correct interface.
Paul Stewart326badb2012-12-18 14:18:54 -0800187 interface = self._get_wlanif(self.hostapd['frequency'],
188 self.phytype,
189 mode=conf.get('hw_mode', 'b'))
190
191 conf_file = self.hostapd['config_file'] % interface
192 log_file = self.hostapd['log_file'] % interface
193 conf['interface'] = interface
Paul Stewart548cf452012-11-27 17:46:23 -0800194
195 # Generate hostapd.conf.
196 self._pre_config_hook(conf)
197 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
198 (conf_file, '\n'.join(
199 "%s=%s" % kv for kv in conf.iteritems())))
200
201 # Run hostapd.
202 logging.info("Starting hostapd...")
203 self._pre_start_hook(params)
204 self.router.run("%s -dd %s &> %s &" %
205 (self.cmd_hostapd, conf_file, log_file))
206
207 self.hostapd_instances.append({
208 'conf_file': conf_file,
209 'log_file': log_file,
Paul Stewart326badb2012-12-18 14:18:54 -0800210 'interface': interface
Paul Stewart548cf452012-11-27 17:46:23 -0800211 })
212
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700213
Paul Stewart326badb2012-12-18 14:18:54 -0800214 def _kill_process_instance(self, process, instance=None, wait=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700215 """Kill a process on the router.
216
Paul Stewart326badb2012-12-18 14:18:54 -0800217 Kills program named |process|, optionally only a specific
218 |instance|. If |wait| is specified, we makes sure |process| exits
219 before returning.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700220
221 @param process string name of process to kill.
222 @param instance string instance of process to kill.
223 @param wait int timeout in seconds to wait for.
224
Thieu Le7b23a542012-01-27 15:54:48 -0800225 """
Paul Stewart21737812012-12-06 11:03:32 -0800226 if instance:
Paul Stewart326badb2012-12-18 14:18:54 -0800227 search_arg = '-f "%s.*%s"' % (process, instance)
Paul Stewart21737812012-12-06 11:03:32 -0800228 else:
Paul Stewart326badb2012-12-18 14:18:54 -0800229 search_arg = process
Paul Stewart21737812012-12-06 11:03:32 -0800230
Paul Stewart326badb2012-12-18 14:18:54 -0800231 cmd = "pkill %s >/dev/null 2>&1" % search_arg
232
233 if wait:
234 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" %
235 search_arg)
236 self.router.run(cmd, timeout=wait, ignore_status=True)
237 else:
238 self.router.run(cmd, ignore_status=True)
239
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700240
Paul Stewart326badb2012-12-18 14:18:54 -0800241 def kill_hostapd_instance(self, instance):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700242 """Kills a hostapd instance.
243
244 @param instance string instance to kill.
245
246 """
Paul Stewart326badb2012-12-18 14:18:54 -0800247 self._kill_process_instance('hostapd', instance, 30)
Thieu Le7b23a542012-01-27 15:54:48 -0800248
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700249
Paul Stewart21737812012-12-06 11:03:32 -0800250 def kill_hostapd(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700251 """Kill all hostapd instances."""
Paul Stewart21737812012-12-06 11:03:32 -0800252 self.kill_hostapd_instance(None)
253
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700254
255 def __get_default_hostap_config(self):
256 """@return dict of default options for hostapd."""
257 conf = self.hostapd['conf']
258 # default RTS and frag threshold to ``off''
259 conf['rts_threshold'] = '2347'
260 conf['fragm_threshold'] = '2346'
261 conf['driver'] = self.hostapd['driver']
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700262 conf['ssid'] = self._build_ssid('')
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700263 return conf
264
265
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700266 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700267 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
268 for x in range(5)])
269 return (self.ssid_prefix + unique_salt + suffix)[-32:]
270
271
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700272 def hostap_configure(self, configuration, multi_interface=None):
273 """Build up a hostapd configuration file and start hostapd.
274
275 Also setup a local server if this router supports them.
276
277 @param configuration HosetapConfig object.
278 @param multi_interface bool True iff multiple interfaces allowed.
279
280 """
281 if multi_interface is None and (self.hostapd['configured'] or
282 self.station['configured']):
283 self.deconfig()
284 # Start with the default hostapd config parameters.
285 conf = self.__get_default_hostap_config()
Christopher Wiley3166e432013-08-06 09:53:12 -0700286 conf['ssid'] = (configuration.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700287 self._build_ssid(configuration.ssid_suffix))
Christopher Wiley9b406202013-05-06 14:07:49 -0700288 if configuration.bssid:
289 conf['bssid'] = configuration.bssid
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700290 conf['channel'] = configuration.channel
291 self.hostapd['frequency'] = configuration.frequency
292 conf['hw_mode'] = configuration.hw_mode
293 if configuration.hide_ssid:
294 conf['ignore_broadcast_ssid'] = 1
295 if configuration.is_11n:
296 conf['ieee80211n'] = 1
297 conf['ht_capab'] = ''.join(configuration.n_capabilities)
298 if configuration.wmm_enabled:
299 conf['wmm_enabled'] = 1
300 if configuration.require_ht:
301 conf['require_ht'] = 1
Christopher Wiley9fa7c632013-05-01 11:58:06 -0700302 if configuration.beacon_interval:
303 conf['beacon_int'] = configuration.beacon_interval
Christopher Wileya51258e2013-05-03 13:05:06 -0700304 if configuration.dtim_period:
305 conf['dtim_period'] = configuration.dtim_period
Christopher Wileye1235b62013-05-03 15:09:34 -0700306 if configuration.frag_threshold:
307 conf['fragm_threshold'] = configuration.frag_threshold
Christopher Wileyebdc27d2013-06-28 14:35:41 -0700308 if configuration.pmf_support:
309 conf['ieee80211w'] = configuration.pmf_support
Christopher Wileyb8921c72013-06-13 09:51:47 -0700310 conf.update(configuration.get_security_hostapd_conf())
Christopher Wileya89706d2013-06-12 13:20:58 -0700311
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700312 self.start_hostapd(conf, {})
313 # Configure transmit power
314 tx_power_params = {'interface': conf['interface']}
315 # TODO(wiley) support for setting transmit power
316 self.set_txpower(tx_power_params)
317 if self.force_local_server:
318 self.start_local_server(conf['interface'])
319 self._post_start_hook({})
320 logging.info('AP configured.')
321 self.hostapd['configured'] = True
322
323
Paul Stewartc2b3de82011-03-03 14:45:31 -0800324 def hostap_config(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700325 """Configure the AP per test requirements.
Sam Leffler6969d1d2010-03-15 16:07:11 -0700326
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700327 @param params dict of site_wifitest parameters.
328
329 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700330 # keep parameter modifications local-only
331 orig_params = params
332 params = params.copy()
333
Paul Stewart45338d22010-10-21 10:57:02 -0700334 multi_interface = 'multi_interface' in params
335 if multi_interface:
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700336 # remove non-hostapd config item from params
Paul Stewart45338d22010-10-21 10:57:02 -0700337 params.pop('multi_interface')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800338 elif self.hostapd['configured'] or self.station['configured']:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700339 self.deconfig()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700340
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700341 local_server = params.pop('local_server', False)
342
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700343 conf = self.__get_default_hostap_config()
Paul Stewartc2b3de82011-03-03 14:45:31 -0800344 tx_power_params = {}
345 htcaps = set()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700346
Paul Stewartc2b3de82011-03-03 14:45:31 -0800347 for k, v in params.iteritems():
348 if k == 'ssid':
349 conf['ssid'] = v
350 elif k == 'ssid_suffix':
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700351 conf['ssid'] = self._build_ssid(v)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800352 elif k == 'channel':
353 freq = int(v)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700354 self.hostapd['frequency'] = freq
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700355
Paul Stewartc2b3de82011-03-03 14:45:31 -0800356 # 2.4GHz
357 if freq <= 2484:
358 # Make sure hw_mode is set
359 if conf.get('hw_mode') == 'a':
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700360 conf['hw_mode'] = 'g'
Paul Stewartc2b3de82011-03-03 14:45:31 -0800361
362 # Freq = 5 * chan + 2407, except channel 14
363 if freq == 2484:
364 conf['channel'] = 14
365 else:
366 conf['channel'] = (freq - 2407) / 5
367 # 5GHz
Sam Leffler6969d1d2010-03-15 16:07:11 -0700368 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800369 # Make sure hw_mode is set
370 conf['hw_mode'] = 'a'
371 # Freq = 5 * chan + 4000
372 if freq < 5000:
373 conf['channel'] = (freq - 4000) / 5
374 # Freq = 5 * chan + 5000
375 else:
376 conf['channel'] = (freq - 5000) / 5
Sam Leffler6969d1d2010-03-15 16:07:11 -0700377
Paul Stewartc2b3de82011-03-03 14:45:31 -0800378 elif k == 'country':
379 conf['country_code'] = v
380 elif k == 'dotd':
381 conf['ieee80211d'] = 1
382 elif k == '-dotd':
383 conf['ieee80211d'] = 0
384 elif k == 'mode':
385 if v == '11a':
386 conf['hw_mode'] = 'a'
387 elif v == '11g':
388 conf['hw_mode'] = 'g'
389 elif v == '11b':
390 conf['hw_mode'] = 'b'
391 elif v == '11n':
392 conf['ieee80211n'] = 1
393 elif k == 'bintval':
394 conf['beacon_int'] = v
395 elif k == 'dtimperiod':
396 conf['dtim_period'] = v
397 elif k == 'rtsthreshold':
398 conf['rts_threshold'] = v
399 elif k == 'fragthreshold':
400 conf['fragm_threshold'] = v
401 elif k == 'shortpreamble':
402 conf['preamble'] = 1
403 elif k == 'authmode':
404 if v == "open":
405 conf['auth_algs'] = 1
406 elif v == "shared":
407 conf['auth_algs'] = 2
408 elif k == 'hidessid':
409 conf['ignore_broadcast_ssid'] = 1
410 elif k == 'wme':
411 conf['wmm_enabled'] = 1
412 elif k == '-wme':
413 conf['wmm_enabled'] = 0
414 elif k == 'deftxkey':
415 conf['wep_default_key'] = v
416 elif k == 'ht20':
417 htcaps.add('') # NB: ensure 802.11n setup below
418 conf['wmm_enabled'] = 1
419 elif k == 'ht40':
420 htcaps.add('[HT40-]')
421 htcaps.add('[HT40+]')
422 conf['wmm_enabled'] = 1
Paul Stewartc1df8d62011-04-07 14:28:15 -0700423 elif k in ('ht40+', 'ht40-'):
424 htcaps.add('[%s]' % k.upper())
425 conf['wmm_enabled'] = 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800426 elif k == 'shortgi':
427 htcaps.add('[SHORT-GI-20]')
428 htcaps.add('[SHORT-GI-40]')
429 elif k == 'pureg':
430 pass # TODO(sleffler) need hostapd support
431 elif k == 'puren':
432 pass # TODO(sleffler) need hostapd support
433 elif k == 'protmode':
434 pass # TODO(sleffler) need hostapd support
435 elif k == 'ht':
436 htcaps.add('') # NB: ensure 802.11n setup below
437 elif k == 'htprotmode':
438 pass # TODO(sleffler) need hostapd support
439 elif k == 'rifs':
440 pass # TODO(sleffler) need hostapd support
441 elif k == 'wepmode':
442 pass # NB: meaningless for hostapd; ignore
443 elif k == '-ampdu':
444 pass # TODO(sleffler) need hostapd support
445 elif k == 'txpower':
446 tx_power_params['power'] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700447 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800448 conf[k] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700449
Paul Stewartc2b3de82011-03-03 14:45:31 -0800450 # Aggregate ht_capab.
451 if htcaps:
452 conf['ieee80211n'] = 1
453 conf['ht_capab'] = ''.join(htcaps)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700454
Paul Stewart548cf452012-11-27 17:46:23 -0800455 self.start_hostapd(conf, orig_params)
Paul Stewart1ae854b2011-02-08 15:10:14 -0800456
Paul Stewartc2b3de82011-03-03 14:45:31 -0800457 # Configure transmit power
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700458 tx_power_params['interface'] = conf['interface']
Paul Stewartc2b3de82011-03-03 14:45:31 -0800459 self.set_txpower(tx_power_params)
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700460
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700461 if self.force_local_server or local_server is not False:
462 self.start_local_server(conf['interface'])
Sam Leffler6969d1d2010-03-15 16:07:11 -0700463
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700464 self._post_start_hook(orig_params)
465
466 logging.info("AP configured.")
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700467 self.hostapd['configured'] = True
Sam Leffler6969d1d2010-03-15 16:07:11 -0700468
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700469
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700470 @staticmethod
471 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700472 """Simple IPv4 calculator.
473
474 Takes host address in "IP/bits" notation and returns netmask, broadcast
475 address as well as integer offsets into the address range.
476
477 @param netblock string host address in "IP/bits" notation.
478 @param idx string describing what to return.
479 @return string containing something you hopefully requested.
480
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700481 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700482 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700483 addr = map(int, addr_str.split('.'))
484 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
485 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700486 if idx == 'local':
487 return addr_str
488 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700489 return '.'.join(map(str, mask))
490 elif idx == 'broadcast':
491 offset = [m ^ 0xff for m in mask]
492 else:
493 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
494 return '.'.join(map(str, [(a & m) + o
495 for a, m, o in zip(addr, mask, offset)]))
496
497
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700498 def ibss_configure(self, config):
499 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700500
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700501 Extract relevant configuration objects from |config| despite not
502 actually being a hostap managed endpoint.
503
504 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700505
506 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700507 if self.station['configured'] or self.hostapd['configured']:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700508 self.deconfig()
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700509 interface = self._get_wlanif(config.frequency, self.phytype,
510 config.hw_mode)
Christopher Wiley3166e432013-08-06 09:53:12 -0700511 self.station['conf']['ssid'] = (config.ssid or
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700512 self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800513 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700514 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley3166e432013-08-06 09:53:12 -0700515 self.router.run('%s dev %s ibss join %s %d' % (
516 self.cmd_iw, interface, self.station['conf']['ssid'],
517 config.frequency))
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700518 # Always start a local server.
519 self.start_local_server(interface)
520 # Remember that this interface is up.
Paul Stewartc2b3de82011-03-03 14:45:31 -0800521 self.station['configured'] = True
522 self.station['interface'] = interface
523
524
Paul Stewart2bd823b2012-11-21 15:03:37 -0800525 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700526 """Get the local server address for an interface.
527
528 When we multiple local servers, we give them static IP addresses
529 like 192.158.*.254.
530
531 @param index int describing which local server this is for.
532
533 """
Paul Stewart2bd823b2012-11-21 15:03:37 -0800534 return '%d.%d.%d.%d' % (192, 168, index, 254)
535
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700536
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700537 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700538 """Start a local server on an interface.
539
540 @param interface string (e.g. wlan0)
541
542 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700543 logging.info("Starting up local server...")
544
545 if len(self.local_servers) >= 256:
546 raise error.TestFail('Exhausted available local servers')
547
Paul Stewart2bd823b2012-11-21 15:03:37 -0800548 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700549
550 params = {}
551 params['netblock'] = netblock
552 params['subnet'] = self.ip_addr(netblock, 0)
553 params['netmask'] = self.ip_addr(netblock, 'netmask')
554 params['dhcp_range'] = ' '.join(
555 (self.ip_addr(netblock, self.dhcp_low),
556 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700557 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700558
559 params['ip_params'] = ("%s broadcast %s dev %s" %
560 (netblock,
561 self.ip_addr(netblock, 'broadcast'),
562 interface))
563 self.local_servers.append(params)
564
565 self.router.run("%s addr flush %s" %
566 (self.cmd_ip, interface))
567 self.router.run("%s addr add %s" %
568 (self.cmd_ip, params['ip_params']))
569 self.router.run("%s link set %s up" %
570 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800571 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700572
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700573
Paul Stewart548cf452012-11-27 17:46:23 -0800574 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700575 """Start a dhcp server on an interface.
576
577 @param interface string (e.g. wlan0)
578
579 """
Paul Stewart326badb2012-12-18 14:18:54 -0800580 conf_file = self.dhcpd_conf % interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700581 dhcp_conf = '\n'.join(map(
582 lambda server_conf: \
583 "subnet %(subnet)s netmask %(netmask)s {\n" \
584 " range %(dhcp_range)s;\n" \
585 "}" % server_conf,
586 self.local_servers))
587 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
Paul Stewart326badb2012-12-18 14:18:54 -0800588 (conf_file,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700589 '\n'.join(('ddns-update-style none;', dhcp_conf))))
590 self.router.run("touch %s" % self.dhcpd_leases)
591
592 self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True)
593 self.router.run("%s -q -cf %s -lf %s" %
Paul Stewart326badb2012-12-18 14:18:54 -0800594 (self.cmd_dhcpd, conf_file, self.dhcpd_leases))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700595
596
Paul Stewart326badb2012-12-18 14:18:54 -0800597 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700598 """Stop a dhcp server on the router.
599
600 @param instance string instance to kill.
601
602 """
Paul Stewart326badb2012-12-18 14:18:54 -0800603 self._kill_process_instance('dhcpd', instance, 0)
604
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700605
Paul Stewart548cf452012-11-27 17:46:23 -0800606 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700607 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800608 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800609
610
Paul Stewartc2b3de82011-03-03 14:45:31 -0800611 def config(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700612 """Configure an AP based on site_wifitest parameters.
613
614 @param params dict of site_wifitest parameters.
615
616 """
Paul Stewartc2b3de82011-03-03 14:45:31 -0800617 if self.apmode:
618 self.hostap_config(params)
619 else:
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700620 config = hostap_config.HostapConfig(
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700621 frequency=int(params.get('channel', None)))
622 self.ibss_configure(config)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800623
624
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700625 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700626 """Return IP address on the WiFi subnet of a local server on the router.
627
628 If no local servers are configured (e.g. for an RSPro), a TestFail will
629 be raised.
630
631 @param ap_num int which local server to get an address from.
632
633 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700634 if self.local_servers:
635 return self.ip_addr(self.local_servers[ap_num]['netblock'],
636 'local')
637 else:
638 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700639
640
Paul Stewart17350be2012-12-14 13:34:54 -0800641 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700642 """Return the MAC address of an AP in the test.
643
644 @param ap_num int index of local server to read the MAC address from.
645 @return string MAC address like 00:11:22:33:44:55.
646
647 """
Paul Stewart17350be2012-12-14 13:34:54 -0800648 instance = self.hostapd_instances[ap_num]
649 interface = instance['interface']
650 result = self.router.run('%s addr show %s' % (self.cmd_ip, interface))
651 # Example response:
652 # 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 UP qlen 1000
653 # link/ether 99:88:77:66:55:44 brd ff:ff:ff:ff:ff:ff
654 # inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
655 # inet6 fe80::6a7f:74ff:fe66:5544/64 scope link
656 # we want the MAC address after the "link/ether" above.
657 parts = result.stdout.split(' ')
658 return parts[parts.index('link/ether') + 1]
659
660
Christopher Wileyd89b5282013-04-10 15:21:26 -0700661 def deconfig(self, params={}):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700662 """De-configure the AP (will also bring wlan down).
Sam Leffler6969d1d2010-03-15 16:07:11 -0700663
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700664 @param params dict of parameters from site_wifitest.
665
666 """
Paul Stewartc2b3de82011-03-03 14:45:31 -0800667 if not self.hostapd['configured'] and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700668 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700669
Paul Stewartc2b3de82011-03-03 14:45:31 -0800670 if self.hostapd['configured']:
Paul Stewart326badb2012-12-18 14:18:54 -0800671 local_servers = []
Paul Stewart21737812012-12-06 11:03:32 -0800672 if 'instance' in params:
673 instances = [ self.hostapd_instances.pop(params['instance']) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800674 for server in self.local_servers:
675 if server['interface'] == instances[0]['interface']:
676 local_servers = [server]
677 self.local_servers.remove(server)
678 break
Paul Stewart21737812012-12-06 11:03:32 -0800679 else:
680 instances = self.hostapd_instances
681 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800682 local_servers = self.local_servers
683 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700684
Paul Stewart21737812012-12-06 11:03:32 -0800685 for instance in instances:
686 if 'silent' in params:
687 # Deconfigure without notifying DUT. Remove the interface
688 # hostapd uses to send beacon and DEAUTH packets.
689 self._remove_interface(instance['interface'], True)
690
Paul Stewart326badb2012-12-18 14:18:54 -0800691 self.kill_hostapd_instance(instance['conf_file'])
Paul Stewart548cf452012-11-27 17:46:23 -0800692 self.router.get_file(instance['log_file'],
693 'debug/hostapd_router_%d_%s.log' %
694 (self.hostapd['log_count'],
695 instance['interface']))
696 self._release_wlanif(instance['interface'])
697# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Paul Stewartf854d2e2011-05-04 13:19:18 -0700698 self.hostapd['log_count'] += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800699 if self.station['configured']:
Christopher Wiley05262d62013-04-17 17:53:59 -0700700 local_servers = self.local_servers
701 self.local_servers = []
Paul Stewartc2b3de82011-03-03 14:45:31 -0800702 if self.station['type'] == 'ibss':
703 self.router.run("%s dev %s ibss leave" %
704 (self.cmd_iw, self.station['interface']))
705 else:
706 self.router.run("%s dev %s disconnect" %
707 (self.cmd_iw, self.station['interface']))
708 self.router.run("%s link set %s down" % (self.cmd_ip,
709 self.station['interface']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700710
Paul Stewart326badb2012-12-18 14:18:54 -0800711 for server in local_servers:
712 self.stop_dhcp_server(server['interface'])
713 self.router.run("%s addr del %s" %
714 (self.cmd_ip, server['ip_params']),
715 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700716
717 self.hostapd['configured'] = False
Paul Stewartc2b3de82011-03-03 14:45:31 -0800718 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700719
720
Paul Stewart17350be2012-12-14 13:34:54 -0800721 def verify_pmksa_auth(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700722 """Verify that the PMKSA auth was cached on a hostapd instance.
723
724 @param params dict with optional key 'instance' (defaults to 0).
725
726 """
Paul Stewart17350be2012-12-14 13:34:54 -0800727 instance_num = params.get('instance', 0)
728 instance = self.hostapd_instances[instance_num]
729 pmksa_match = 'PMK from PMKSA cache - skip IEEE 802.1X.EAP'
730 self.router.run('grep -q "%s" %s' % (pmksa_match, instance['log_file']))
731
732
Paul Stewart7cb1f062010-06-10 15:46:20 -0700733 def get_ssid(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700734 """@return string ssid for the network stemming from this router."""
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700735 if self.hostapd['configured']:
736 return self.hostapd['conf']['ssid']
737
Christopher Wiley3166e432013-08-06 09:53:12 -0700738 if not 'ssid' in self.station['conf']:
739 raise error.TestFail('Requested ssid of an unconfigured AP.')
740
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700741 return self.station['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700742
743
744 def set_txpower(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700745 """Set the transmission power for an interface.
746
747 Assumes that we want to refer to the first hostapd instance unless
748 'interface' is defined in params. Sets the transmission power to
749 'auto' if 'power' is not defined in params.
750
751 @param params dict of parameters as described above.
752
753 """
Paul Stewart548cf452012-11-27 17:46:23 -0800754 interface = params.get('interface',
755 self.hostapd_instances[0]['interface'])
756 power = params.get('power', 'auto')
Paul Stewart98022e22010-10-22 10:33:14 -0700757 self.router.run("%s dev %s set txpower %s" %
Paul Stewart548cf452012-11-27 17:46:23 -0800758 (self.cmd_iw, interface, power))
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700759
760
761 def deauth(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700762 """Deauthenticates a client described in params.
763
764 @param params dict containing a key 'client'.
765
766 """
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700767 self.router.run('%s -p%s deauthenticate %s' %
768 (self.cmd_hostapd_cli,
769 self.hostapd['conf']['ctrl_interface'],
770 params['client']))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700771
772
Paul Stewart51b0f382013-06-12 09:03:02 -0700773 def send_management_frame(self, frame_type, instance=0):
774 """Injects a management frame into an active hostapd session.
775
776 @param frame_type string the type of frame to send.
777 @param instance int indicating which hostapd instance to inject into.
778
779 """
780 hostap_interface = self.hostapd_instances[instance]['interface']
781 interface = self._get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
782 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
783 self.router.run('%s %s %s' %
784 (self.cmd_send_management_frame, interface, frame_type))
785 self._release_wlanif(interface)
786
787
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700788 def _pre_config_hook(self, config):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700789 """Hook for subclasses.
790
791 Run after gathering configuration parameters,
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700792 but before writing parameters to config file.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700793
794 @param config dict containing hostapd config parameters.
795
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700796 """
797 pass
798
799
800 def _pre_start_hook(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700801 """Hook for subclasses.
802
803 Run after generating hostapd config file, but before starting hostapd.
804
805 @param params dict parameters from site_wifitest.
806
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700807 """
808 pass
809
810
811 def _post_start_hook(self, params):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700812 """Hook for subclasses run after starting hostapd.
813
814 @param params dict parameters from site_wifitest.
815
816 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700817 pass