blob: afc93d8530047ac475a6ce9714b229647a57cf79 [file] [log] [blame]
Sam Leffler6969d1d2010-03-15 16:07:11 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Christopher Wiley408d1812014-01-13 15:27:43 -08005import collections
Christopher Wiley14796b32013-04-03 14:53:33 -07006import logging
Christopher Wiley3166e432013-08-06 09:53:12 -07007import random
Christopher Wiley3166e432013-08-06 09:53:12 -07008import string
Christopher Wileyeea12362013-12-12 17:24:29 -08009import time
Christopher Wiley14796b32013-04-03 14:53:33 -070010
Paul Stewartc9628b32010-08-11 13:03:51 -070011from autotest_lib.client.common_lib import error
Paul Stewart6ddeba72013-11-18 10:08:23 -080012from autotest_lib.client.common_lib.cros.network import interface
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070013from autotest_lib.server import site_linux_system
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070014from autotest_lib.server.cros import wifi_test_utils
Christopher Wiley99d42c92013-07-09 16:40:16 -070015from autotest_lib.server.cros.network import hostap_config
Sam Leffler19bb0a72010-04-12 08:51:08 -070016
Christopher Wiley408d1812014-01-13 15:27:43 -080017
18StationInstance = collections.namedtuple('StationInstance',
19 ['ssid', 'interface', 'dev_type'])
20
21
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070022class LinuxRouter(site_linux_system.LinuxSystem):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070023 """Linux/mac80211-style WiFi Router support for WiFiTest class.
Sam Leffler6969d1d2010-03-15 16:07:11 -070024
25 This class implements test methods/steps that communicate with a
26 router implemented with Linux/mac80211. The router must
27 be pre-configured to enable ssh access and have a mac80211-based
28 wireless device. We also assume hostapd 0.7.x and iw are present
29 and any necessary modules are pre-loaded.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070030
Sam Leffler6969d1d2010-03-15 16:07:11 -070031 """
32
Christopher Wiley3166e432013-08-06 09:53:12 -070033 KNOWN_TEST_PREFIX = 'network_WiFi'
Christopher Wiley1defc242013-09-18 10:28:37 -070034 STARTUP_POLLING_INTERVAL_SECONDS = 0.5
35 STARTUP_TIMEOUT_SECONDS = 10
Christopher Wiley3166e432013-08-06 09:53:12 -070036 SUFFIX_LETTERS = string.ascii_lowercase + string.digits
Christopher Wileyb1ade0a2013-09-16 13:09:55 -070037 SUBNET_PREFIX_OCTETS = (192, 168)
Sam Leffler6969d1d2010-03-15 16:07:11 -070038
Christopher Wileyeea12362013-12-12 17:24:29 -080039 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf'
40 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log'
41 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid'
Paul Stewart80bb3372014-01-22 15:06:08 -080042 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl'
Christopher Wileyeea12362013-12-12 17:24:29 -080043 HOSTAPD_DRIVER_NAME = 'nl80211'
44
45 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf'
46 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log'
47 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid'
48
Paul Stewart51b0f382013-06-12 09:03:02 -070049 def get_capabilities(self):
50 """@return iterable object of AP capabilities for this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -080051 caps = set([self.CAPABILITY_IBSS])
Paul Stewart51b0f382013-06-12 09:03:02 -070052 try:
53 self.cmd_send_management_frame = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080054 self.host, '/usr/bin/send_management_frame')
Paul Stewart51b0f382013-06-12 09:03:02 -070055 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME)
56 except error.TestFail:
57 pass
58 return super(LinuxRouter, self).get_capabilities().union(caps)
59
60
Christopher Wiley408d1812014-01-13 15:27:43 -080061 @property
62 def router(self):
63 """Deprecated. Use self.host instead.
64
65 @return Host object representing the remote router.
66
67 """
68 return self.host
69
70
Christopher Wileyaeef9b52014-03-11 12:24:11 -070071 @property
72 def wifi_ip(self):
73 """Simple accessor for the WiFi IP when there is only one AP.
74
75 @return string IP of WiFi interface.
76
77 """
78 if len(self.local_servers) != 1:
79 raise error.TestError('Could not pick a WiFi IP to return.')
80
81 return self.get_wifi_ip(0)
82
83
Christopher Wiley408d1812014-01-13 15:27:43 -080084 def __init__(self, host, test_name):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070085 """Build a LinuxRouter.
86
87 @param host Host object representing the remote machine.
Christopher Wiley3166e432013-08-06 09:53:12 -070088 @param test_name string name of this test. Used in SSID creation.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070089
90 """
Christopher Wiley408d1812014-01-13 15:27:43 -080091 super(LinuxRouter, self).__init__(host, 'router')
Wade Guthrie24d1e312012-04-24 16:53:40 -070092
Christopher Wileyeea12362013-12-12 17:24:29 -080093 self.cmd_dhcpd = '/usr/sbin/dhcpd'
Christopher Wileyf99e6cc2013-04-19 10:12:43 -070094 self.cmd_hostapd = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080095 host, '/usr/sbin/hostapd')
Christopher Wiley7337ff62013-10-03 17:21:46 -070096 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080097 host, '/usr/sbin/hostapd_cli')
Paul Stewart6ddeba72013-11-18 10:08:23 -080098 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed(
Christopher Wileyeea12362013-12-12 17:24:29 -080099 host, '/usr/sbin/wpa_supplicant')
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700100 self.dhcpd_conf = '/tmp/dhcpd.%s.conf'
101 self.dhcpd_leases = '/tmp/dhcpd.leases'
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700102
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700103 # hostapd configuration persists throughout the test, subsequent
104 # 'config' commands only modify it.
Christopher Wiley3166e432013-08-06 09:53:12 -0700105 self.ssid_prefix = test_name
106 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX):
107 # Many of our tests start with an uninteresting prefix.
108 # Remove it so we can have more unique bytes.
109 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):]
110 self.ssid_prefix = self.ssid_prefix.lstrip('_')
111 self.ssid_prefix += '_'
112
Christopher Wileyeea12362013-12-12 17:24:29 -0800113 self._total_hostapd_instances = 0
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700114 self.local_servers = []
Paul Stewart548cf452012-11-27 17:46:23 -0800115 self.hostapd_instances = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800116 self.station_instances = []
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700117 self.dhcp_low = 1
118 self.dhcp_high = 128
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700119
Paul Stewart548cf452012-11-27 17:46:23 -0800120 # Kill hostapd and dhcp server if already running.
Thieu Le7b23a542012-01-27 15:54:48 -0800121 self.kill_hostapd()
Paul Stewart548cf452012-11-27 17:46:23 -0800122 self.stop_dhcp_servers()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700123
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -0700124 # Place us in the US by default
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700125 self.iw_runner.set_regulatory_domain('US')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700126
Peter Qiu2f973252014-02-20 15:30:37 -0800127 # Reset all antennas to be active
128 self.set_default_antenna_bitmap()
129
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700130
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700131 def close(self):
132 """Close global resources held by this system."""
Christopher Wileyeea12362013-12-12 17:24:29 -0800133 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800134 super(LinuxRouter, self).close()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700135
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700136
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700137 def has_local_server(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700138 """@return True iff this router has local servers configured."""
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700139 return bool(self.local_servers)
Sam Leffler6969d1d2010-03-15 16:07:11 -0700140
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700141
Peter Qiuc89c9a22014-02-27 10:03:55 -0800142 def start_hostapd(self, configuration):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700143 """Start a hostapd instance described by conf.
144
Christopher Wileyeea12362013-12-12 17:24:29 -0800145 @param configuration HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700146
147 """
Paul Stewart548cf452012-11-27 17:46:23 -0800148 # Figure out the correct interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800149 interface = self.get_wlanif(configuration.frequency, 'managed')
Paul Stewart326badb2012-12-18 14:18:54 -0800150
Christopher Wileyeea12362013-12-12 17:24:29 -0800151 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface
152 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface
153 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface
Paul Stewart80bb3372014-01-22 15:06:08 -0800154 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface
Peter Qiuc89c9a22014-02-27 10:03:55 -0800155 hostapd_conf_dict = configuration.generate_dict(
156 interface, control_interface,
157 self._build_ssid(configuration.ssid_suffix))
158 logging.info('Starting hostapd with parameters: %r', hostapd_conf_dict)
Paul Stewart548cf452012-11-27 17:46:23 -0800159
160 # Generate hostapd.conf.
Paul Stewart548cf452012-11-27 17:46:23 -0800161 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
162 (conf_file, '\n'.join(
Christopher Wileyeea12362013-12-12 17:24:29 -0800163 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems())))
Paul Stewart548cf452012-11-27 17:46:23 -0800164
165 # Run hostapd.
166 logging.info("Starting hostapd...")
Christopher Wiley1defc242013-09-18 10:28:37 -0700167 self.router.run('rm %s' % log_file, ignore_status=True)
168 self.router.run('rm %s' % pid_file, ignore_status=True)
Christopher Wileyeea12362013-12-12 17:24:29 -0800169 self.router.run('stop wpasupplicant', ignore_status=True)
170 start_command = '%s -dd -B -t -f %s -P %s %s' % (
171 self.cmd_hostapd, log_file, pid_file, conf_file)
Christopher Wiley7337ff62013-10-03 17:21:46 -0700172 self.router.run(start_command)
Paul Stewart548cf452012-11-27 17:46:23 -0800173 self.hostapd_instances.append({
Christopher Wileyeea12362013-12-12 17:24:29 -0800174 'ssid': hostapd_conf_dict['ssid'],
Paul Stewart548cf452012-11-27 17:46:23 -0800175 'conf_file': conf_file,
176 'log_file': log_file,
Christopher Wiley1defc242013-09-18 10:28:37 -0700177 'interface': interface,
Paul Stewart6ddeba72013-11-18 10:08:23 -0800178 'pid_file': pid_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800179 'config_dict': hostapd_conf_dict.copy()
Paul Stewart548cf452012-11-27 17:46:23 -0800180 })
181
Christopher Wileyeea12362013-12-12 17:24:29 -0800182 # Wait for confirmation that the router came up.
183 pid = int(self.router.run('cat %s' % pid_file).stdout)
184 logging.info('Waiting for hostapd to startup.')
185 start_time = time.time()
186 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS:
187 success = self.router.run(
188 'grep "Completing interface initialization" %s' % log_file,
189 ignore_status=True).exit_status == 0
190 if success:
191 break
192
193 # A common failure is an invalid router configuration.
194 # Detect this and exit early if we see it.
195 bad_config = self.router.run(
196 'grep "Interface initialization failed" %s' % log_file,
197 ignore_status=True).exit_status == 0
198 if bad_config:
199 raise error.TestFail('hostapd failed to initialize AP '
200 'interface.')
201
202 if pid:
203 early_exit = self.router.run('kill -0 %d' % pid,
204 ignore_status=True).exit_status
205 if early_exit:
206 raise error.TestFail('hostapd process terminated.')
207
208 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS)
209 else:
210 raise error.TestFail('Timed out while waiting for hostapd '
211 'to start.')
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
Christopher Wiley0ff28ab2013-08-12 13:23:04 -0700255 def _build_ssid(self, suffix):
Christopher Wiley3166e432013-08-06 09:53:12 -0700256 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
257 for x in range(5)])
258 return (self.ssid_prefix + unique_salt + suffix)[-32:]
259
260
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700261 def hostap_configure(self, configuration, multi_interface=None):
262 """Build up a hostapd configuration file and start hostapd.
263
264 Also setup a local server if this router supports them.
265
266 @param configuration HosetapConfig object.
267 @param multi_interface bool True iff multiple interfaces allowed.
268
269 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800270 if multi_interface is None and (self.hostapd_instances or
Christopher Wiley408d1812014-01-13 15:27:43 -0800271 self.station_instances):
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700272 self.deconfig()
Peter Qiuc89c9a22014-02-27 10:03:55 -0800273 self.start_hostapd(configuration)
Christopher Wileyeea12362013-12-12 17:24:29 -0800274 interface = self.hostapd_instances[-1]['interface']
275 self.iw_runner.set_tx_power(interface, 'auto')
276 self.start_local_server(interface)
Christopher Wiley7d414cc2013-04-17 17:26:32 -0700277 logging.info('AP configured.')
Sam Leffler6969d1d2010-03-15 16:07:11 -0700278
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700279
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700280 @staticmethod
281 def ip_addr(netblock, idx):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700282 """Simple IPv4 calculator.
283
284 Takes host address in "IP/bits" notation and returns netmask, broadcast
285 address as well as integer offsets into the address range.
286
287 @param netblock string host address in "IP/bits" notation.
288 @param idx string describing what to return.
289 @return string containing something you hopefully requested.
290
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700291 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700292 addr_str,bits = netblock.split('/')
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700293 addr = map(int, addr_str.split('.'))
294 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
295 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
Paul Stewart5977da92011-06-01 19:14:08 -0700296 if idx == 'local':
297 return addr_str
298 elif idx == 'netmask':
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700299 return '.'.join(map(str, mask))
300 elif idx == 'broadcast':
301 offset = [m ^ 0xff for m in mask]
302 else:
303 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
304 return '.'.join(map(str, [(a & m) + o
305 for a, m, o in zip(addr, mask, offset)]))
306
307
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700308 def ibss_configure(self, config):
309 """Configure a station based AP in IBSS mode.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700310
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700311 Extract relevant configuration objects from |config| despite not
312 actually being a hostap managed endpoint.
313
314 @param config HostapConfig object.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700315
316 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800317 if self.station_instances or self.hostapd_instances:
Christopher Wileyd89b5282013-04-10 15:21:26 -0700318 self.deconfig()
Christopher Wiley408d1812014-01-13 15:27:43 -0800319 interface = self.get_wlanif(config.frequency, 'ibss')
320 ssid = (config.ssid or self._build_ssid(config.ssid_suffix))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800321 # Connect the station
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700322 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
Christopher Wiley408d1812014-01-13 15:27:43 -0800323 self.iw_runner.ibss_join(interface, ssid, config.frequency)
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700324 # Always start a local server.
325 self.start_local_server(interface)
326 # Remember that this interface is up.
Christopher Wiley408d1812014-01-13 15:27:43 -0800327 self.station_instances.append(
328 StationInstance(ssid=ssid, interface=interface,
329 dev_type='ibss'))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800330
331
Paul Stewart2bd823b2012-11-21 15:03:37 -0800332 def local_server_address(self, index):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700333 """Get the local server address for an interface.
334
335 When we multiple local servers, we give them static IP addresses
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700336 like 192.168.*.254.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700337
338 @param index int describing which local server this is for.
339
340 """
Christopher Wileyb1ade0a2013-09-16 13:09:55 -0700341 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254))
Paul Stewart2bd823b2012-11-21 15:03:37 -0800342
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700343
Paul Stewart6ddeba72013-11-18 10:08:23 -0800344 def local_peer_ip_address(self, index):
345 """Get the IP address allocated for the peer associated to the AP.
346
347 This address is assigned to a locally associated peer device that
348 is created for the DUT to perform connectivity tests with.
349 When we have multiple local servers, we give them static IP addresses
350 like 192.168.*.253.
351
352 @param index int describing which local server this is for.
353
354 """
355 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
356
357
358 def local_peer_mac_address(self):
359 """Get the MAC address of the peer interface.
360
361 @return string MAC address of the peer interface.
Christopher Wiley408d1812014-01-13 15:27:43 -0800362
Paul Stewart6ddeba72013-11-18 10:08:23 -0800363 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800364 iface = interface.Interface(self.station_instances[0].interface,
365 self.router)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800366 return iface.mac_address
367
368
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700369 def start_local_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700370 """Start a local server on an interface.
371
372 @param interface string (e.g. wlan0)
373
374 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700375 logging.info("Starting up local server...")
376
377 if len(self.local_servers) >= 256:
378 raise error.TestFail('Exhausted available local servers')
379
Paul Stewart2bd823b2012-11-21 15:03:37 -0800380 netblock = '%s/24' % self.local_server_address(len(self.local_servers))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700381
382 params = {}
383 params['netblock'] = netblock
384 params['subnet'] = self.ip_addr(netblock, 0)
385 params['netmask'] = self.ip_addr(netblock, 'netmask')
386 params['dhcp_range'] = ' '.join(
387 (self.ip_addr(netblock, self.dhcp_low),
388 self.ip_addr(netblock, self.dhcp_high)))
mukesh agrawal05c455a2011-10-12 13:40:27 -0700389 params['interface'] = interface
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700390
391 params['ip_params'] = ("%s broadcast %s dev %s" %
392 (netblock,
393 self.ip_addr(netblock, 'broadcast'),
394 interface))
395 self.local_servers.append(params)
396
397 self.router.run("%s addr flush %s" %
398 (self.cmd_ip, interface))
399 self.router.run("%s addr add %s" %
400 (self.cmd_ip, params['ip_params']))
401 self.router.run("%s link set %s up" %
402 (self.cmd_ip, interface))
Paul Stewart548cf452012-11-27 17:46:23 -0800403 self.start_dhcp_server(interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700404
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700405
Paul Stewart548cf452012-11-27 17:46:23 -0800406 def start_dhcp_server(self, interface):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700407 """Start a dhcp server on an interface.
408
409 @param interface string (e.g. wlan0)
410
411 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800412 for server in self.local_servers:
413 if server['interface'] == interface:
414 params = server
415 break
416 else:
417 raise error.TestFail('Could not find local server '
418 'to match interface: %r' % interface)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700419
Christopher Wileyeea12362013-12-12 17:24:29 -0800420 dhcpd_conf_file = self.dhcpd_conf % interface
421 dhcp_conf = '\n'.join([
422 'port=0', # disables DNS server
423 'bind-interfaces',
424 'log-dhcp',
425 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','),
426 'interface=%s' % params['interface'],
427 'dhcp-leasefile=%s' % self.dhcpd_leases])
428 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
429 (dhcpd_conf_file, dhcp_conf))
430 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file)
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700431
432
Paul Stewart326badb2012-12-18 14:18:54 -0800433 def stop_dhcp_server(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700434 """Stop a dhcp server on the router.
435
436 @param instance string instance to kill.
437
438 """
Christopher Wileyeea12362013-12-12 17:24:29 -0800439 self._kill_process_instance('dnsmasq', instance, 0)
Paul Stewart326badb2012-12-18 14:18:54 -0800440
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700441
Paul Stewart548cf452012-11-27 17:46:23 -0800442 def stop_dhcp_servers(self):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700443 """Stop all dhcp servers on the router."""
Paul Stewart326badb2012-12-18 14:18:54 -0800444 self.stop_dhcp_server(None)
Paul Stewart548cf452012-11-27 17:46:23 -0800445
446
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800447 def get_wifi_channel(self, ap_num):
448 """Return channel of BSS corresponding to |ap_num|.
449
450 @param ap_num int which BSS to get the channel of.
451 @return int primary channel of BSS.
452
453 """
454 instance = self.hostapd_instances[ap_num]
455 return instance['config_dict']['channel']
456
457
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700458 def get_wifi_ip(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700459 """Return IP address on the WiFi subnet of a local server on the router.
460
461 If no local servers are configured (e.g. for an RSPro), a TestFail will
462 be raised.
463
464 @param ap_num int which local server to get an address from.
465
466 """
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700467 if self.local_servers:
468 return self.ip_addr(self.local_servers[ap_num]['netblock'],
469 'local')
470 else:
471 raise error.TestFail("No IP address assigned")
Paul Stewart5977da92011-06-01 19:14:08 -0700472
473
Christopher Wileya3effac2014-02-05 11:16:11 -0800474 def get_hostapd_interface(self, ap_num):
475 """Get the name of the interface associated with a hostapd instance.
476
477 @param ap_num: int hostapd instance number.
478 @return string interface name (e.g. 'managed0').
479
480 """
481 if ap_num not in range(len(self.hostapd_instances)):
482 raise error.TestFail('Invalid instance number (%d) with %d '
483 'instances configured.' %
484 (ap_num, len(self.hostapd_instances)))
485
486 instance = self.hostapd_instances[ap_num]
487 return instance['interface']
488
489
Paul Stewart17350be2012-12-14 13:34:54 -0800490 def get_hostapd_mac(self, ap_num):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700491 """Return the MAC address of an AP in the test.
492
493 @param ap_num int index of local server to read the MAC address from.
494 @return string MAC address like 00:11:22:33:44:55.
495
496 """
Paul Stewart2e5313a2014-02-14 09:12:02 -0800497 interface_name = self.get_hostapd_interface(ap_num)
498 ap_interface = interface.Interface(interface_name, self.host)
Christopher Wiley5689d362014-01-07 15:21:25 -0800499 return ap_interface.mac_address
Paul Stewart17350be2012-12-14 13:34:54 -0800500
501
Christopher Wileya3effac2014-02-05 11:16:11 -0800502 def get_hostapd_phy(self, ap_num):
503 """Get name of phy for hostapd instance.
504
505 @param ap_num int index of hostapd instance.
506 @return string phy name of phy corresponding to hostapd's
507 managed interface.
508
509 """
510 interface = self.iw_runner.get_interface(
511 self.get_hostapd_interface(ap_num))
512 return interface.phy
513
514
Christopher Wileyeea12362013-12-12 17:24:29 -0800515 def deconfig(self):
516 """A legacy, deprecated alias for deconfig_aps."""
517 self.deconfig_aps()
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800518
519
520 def deconfig_aps(self, instance=None, silent=False):
521 """De-configure an AP (will also bring wlan down).
522
523 @param instance: int or None. If instance is None, will bring down all
524 instances of hostapd.
525 @param silent: True if instances should be brought without de-authing
526 the DUT.
527
528 """
Christopher Wiley408d1812014-01-13 15:27:43 -0800529 if not self.hostapd_instances and not self.station_instances:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700530 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700531
Christopher Wileyeea12362013-12-12 17:24:29 -0800532 if self.hostapd_instances:
Paul Stewart326badb2012-12-18 14:18:54 -0800533 local_servers = []
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800534 if instance is not None:
535 instances = [ self.hostapd_instances.pop(instance) ]
Paul Stewart326badb2012-12-18 14:18:54 -0800536 for server in self.local_servers:
537 if server['interface'] == instances[0]['interface']:
538 local_servers = [server]
539 self.local_servers.remove(server)
540 break
Paul Stewart21737812012-12-06 11:03:32 -0800541 else:
542 instances = self.hostapd_instances
543 self.hostapd_instances = []
Paul Stewart326badb2012-12-18 14:18:54 -0800544 local_servers = self.local_servers
545 self.local_servers = []
Paul Stewart64cc4292011-06-01 10:59:36 -0700546
Paul Stewart21737812012-12-06 11:03:32 -0800547 for instance in instances:
Christopher Wiley4cd4b3f2013-11-11 17:27:28 -0800548 if silent:
Paul Stewart21737812012-12-06 11:03:32 -0800549 # Deconfigure without notifying DUT. Remove the interface
550 # hostapd uses to send beacon and DEAUTH packets.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800551 self.remove_interface(instance['interface'])
Paul Stewart21737812012-12-06 11:03:32 -0800552
Paul Stewart326badb2012-12-18 14:18:54 -0800553 self.kill_hostapd_instance(instance['conf_file'])
Christopher Wiley7337ff62013-10-03 17:21:46 -0700554 if wifi_test_utils.is_installed(self.host,
555 instance['log_file']):
556 self.router.get_file(instance['log_file'],
557 'debug/hostapd_router_%d_%s.log' %
Christopher Wileyeea12362013-12-12 17:24:29 -0800558 (self._total_hostapd_instances,
Christopher Wiley7337ff62013-10-03 17:21:46 -0700559 instance['interface']))
560 else:
561 logging.error('Did not collect hostapd log file because '
562 'it was missing.')
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800563 self.release_interface(instance['interface'])
Paul Stewart548cf452012-11-27 17:46:23 -0800564# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance)
Christopher Wileyeea12362013-12-12 17:24:29 -0800565 self._total_hostapd_instances += 1
Christopher Wiley408d1812014-01-13 15:27:43 -0800566 if self.station_instances:
Christopher Wiley05262d62013-04-17 17:53:59 -0700567 local_servers = self.local_servers
568 self.local_servers = []
Christopher Wiley408d1812014-01-13 15:27:43 -0800569 instance = self.station_instances.pop()
570 if instance.dev_type == 'ibss':
571 self.iw_runner.ibss_leave(instance.interface)
572 elif instance.dev_type == 'managed':
Paul Stewart6ddeba72013-11-18 10:08:23 -0800573 self._kill_process_instance('wpa_supplicant',
Christopher Wiley408d1812014-01-13 15:27:43 -0800574 instance.interface)
Paul Stewartc2b3de82011-03-03 14:45:31 -0800575 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800576 self.iw_runner.disconnect_station(instance.interface)
577 self.router.run('%s link set %s down' %
578 (self.cmd_ip, instance.interface))
mukesh agrawalfe0e85b2011-08-09 14:24:15 -0700579
Paul Stewart326badb2012-12-18 14:18:54 -0800580 for server in local_servers:
581 self.stop_dhcp_server(server['interface'])
582 self.router.run("%s addr del %s" %
583 (self.cmd_ip, server['ip_params']),
584 ignore_status=True)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700585
Paul Stewart7cb1f062010-06-10 15:46:20 -0700586
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800587 def confirm_pmksa_cache_use(self, instance=0):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700588 """Verify that the PMKSA auth was cached on a hostapd instance.
589
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800590 @param instance int router instance number.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700591
592 """
Christopher Wiley8c5ae9a2013-12-04 14:57:56 -0800593 log_file = self.hostapd_instances[instance]['log_file']
594 pmksa_match = 'PMK from PMKSA cache'
595 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file),
596 ignore_status=True)
597 if result.exit_status:
598 raise error.TestFail('PMKSA cache was not used in roaming.')
Paul Stewart17350be2012-12-14 13:34:54 -0800599
600
Christopher Wileye0afecb2013-11-11 10:54:23 -0800601 def get_ssid(self, instance=None):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700602 """@return string ssid for the network stemming from this router."""
Christopher Wileye0afecb2013-11-11 10:54:23 -0800603 if instance is None:
604 instance = 0
605 if len(self.hostapd_instances) > 1:
606 raise error.TestFail('No instance of hostapd specified with '
607 'multiple instances present.')
608
Christopher Wiley3099be72013-11-06 16:49:02 -0800609 if self.hostapd_instances:
610 return self.hostapd_instances[instance]['ssid']
Christopher Wiley1febd6a2013-06-03 13:59:48 -0700611
Christopher Wiley408d1812014-01-13 15:27:43 -0800612 if self.station_instances:
613 return self.station_instances[0].ssid
Christopher Wiley3166e432013-08-06 09:53:12 -0700614
Christopher Wiley408d1812014-01-13 15:27:43 -0800615 raise error.TestFail('Requested ssid of an unconfigured AP.')
Paul Stewart98022e22010-10-22 10:33:14 -0700616
617
Wade Guthriee4074dd2013-10-30 11:00:48 -0700618 def deauth_client(self, client_mac):
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700619 """Deauthenticates a client described in params.
620
Wade Guthriee4074dd2013-10-30 11:00:48 -0700621 @param client_mac string containing the mac address of the client to be
622 deauthenticated.
Christopher Wileyf99e6cc2013-04-19 10:12:43 -0700623
624 """
Paul Stewart80bb3372014-01-22 15:06:08 -0800625 control_if = self.hostapd_instances[-1]['config_dict']['ctrl_interface']
Paul Stewartaa52e8c2011-05-24 08:46:23 -0700626 self.router.run('%s -p%s deauthenticate %s' %
Paul Stewart80bb3372014-01-22 15:06:08 -0800627 (self.cmd_hostapd_cli, control_if, client_mac))
Wade Guthriee4074dd2013-10-30 11:00:48 -0700628
629
Paul Stewart51b0f382013-06-12 09:03:02 -0700630 def send_management_frame(self, frame_type, instance=0):
631 """Injects a management frame into an active hostapd session.
632
633 @param frame_type string the type of frame to send.
634 @param instance int indicating which hostapd instance to inject into.
635
636 """
637 hostap_interface = self.hostapd_instances[instance]['interface']
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800638 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700639 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
640 self.router.run('%s %s %s' %
641 (self.cmd_send_management_frame, interface, frame_type))
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800642 self.release_interface(interface)
Paul Stewart51b0f382013-06-12 09:03:02 -0700643
644
Paul Stewart25536942013-08-15 17:33:42 -0700645 def detect_client_deauth(self, client_mac, instance=0):
646 """Detects whether hostapd has logged a deauthentication from
647 |client_mac|.
648
649 @param client_mac string the MAC address of the client to detect.
650 @param instance int indicating which hostapd instance to query.
651
652 """
653 interface = self.hostapd_instances[instance]['interface']
654 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac)
655 log_file = self.hostapd_instances[instance]['log_file']
656 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file),
657 ignore_status=True)
658 return result.exit_status == 0
659
660
Paul Stewart4ae471e2013-09-04 15:42:35 -0700661 def detect_client_coexistence_report(self, client_mac, instance=0):
662 """Detects whether hostapd has logged an action frame from
663 |client_mac| indicating information about 20/40MHz BSS coexistence.
664
665 @param client_mac string the MAC address of the client to detect.
666 @param instance int indicating which hostapd instance to query.
667
668 """
669 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): '
670 '.. .. .. .. .. .. .. .. .. .. %s '
671 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' %
672 ' '.join(client_mac.split(':')))
673 log_file = self.hostapd_instances[instance]['log_file']
674 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file),
675 ignore_status=True)
676 return result.exit_status == 0
677
678
Paul Stewart6ddeba72013-11-18 10:08:23 -0800679 def add_connected_peer(self, instance=0):
680 """Configure a station connected to a running AP instance.
681
682 Extract relevant configuration objects from the hostap
683 configuration for |instance| and generate a wpa_supplicant
684 instance that connects to it. This allows the DUT to interact
685 with a client entity that is also connected to the same AP. A
686 full wpa_supplicant instance is necessary here (instead of just
687 using the "iw" command to connect) since we want to enable
688 advanced features such as TDLS.
689
690 @param instance int indicating which hostapd instance to connect to.
691
692 """
693 if not self.hostapd_instances:
694 raise error.TestFail('Hostapd is not configured.')
695
Christopher Wiley408d1812014-01-13 15:27:43 -0800696 if self.station_instances:
Paul Stewart6ddeba72013-11-18 10:08:23 -0800697 raise error.TestFail('Station is already configured.')
698
Christopher Wiley408d1812014-01-13 15:27:43 -0800699 ssid = self.get_ssid(instance)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800700 hostap_conf = self.hostapd_instances[instance]['config_dict']
701 frequency = hostap_config.HostapConfig.get_frequency_for_channel(
702 hostap_conf['channel'])
Christopher Wiley408d1812014-01-13 15:27:43 -0800703 interface = self.get_wlanif(frequency, 'managed')
Paul Stewart6ddeba72013-11-18 10:08:23 -0800704
705 # TODO(pstew): Configure other bits like PSK, 802.11n if tests
706 # require them...
707 supplicant_config = (
708 'network={\n'
709 ' ssid="%(ssid)s"\n'
710 ' key_mgmt=NONE\n'
Christopher Wiley408d1812014-01-13 15:27:43 -0800711 '}\n' % {'ssid': ssid}
Paul Stewart6ddeba72013-11-18 10:08:23 -0800712 )
713
Christopher Wileyeea12362013-12-12 17:24:29 -0800714 conf_file = self.STATION_CONF_FILE_PATTERN % interface
715 log_file = self.STATION_LOG_FILE_PATTERN % interface
716 pid_file = self.STATION_PID_FILE_PATTERN % interface
Paul Stewart6ddeba72013-11-18 10:08:23 -0800717
718 self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
719 (conf_file, supplicant_config))
720
721 # Connect the station.
722 self.router.run('%s link set %s up' % (self.cmd_ip, interface))
723 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' %
724 (self.cmd_wpa_supplicant,
725 interface, pid_file, conf_file,
Christopher Wileyeea12362013-12-12 17:24:29 -0800726 self.HOSTAPD_DRIVER_NAME, log_file))
Paul Stewart6ddeba72013-11-18 10:08:23 -0800727 self.router.run(start_command)
728 self.iw_runner.wait_for_link(interface)
729
730 # Assign an IP address to this interface.
731 self.router.run('%s addr add %s/24 dev %s' %
732 (self.cmd_ip, self.local_peer_ip_address(instance),
733 interface))
734
735 # Since we now have two network interfaces connected to the same
736 # network, we need to disable the kernel's protection against
737 # incoming packets to an "unexpected" interface.
738 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' %
739 interface)
740
Paul Stewartb01839b2013-12-06 15:49:56 -0800741 # Similarly, we'd like to prevent the hostap interface from
742 # replying to ARP requests for the peer IP address and vice
743 # versa.
744 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
745 interface)
746 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' %
747 hostap_conf['interface'])
748
Christopher Wiley408d1812014-01-13 15:27:43 -0800749 self.station_instances.append(
750 StationInstance(ssid=ssid, interface=interface,
751 dev_type='managed'))