1# 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 5import collections 6import copy 7import logging 8import random 9import string 10import tempfile 11import time 12 13from autotest_lib.client.common_lib import error 14from autotest_lib.client.common_lib import utils 15from autotest_lib.client.common_lib.cros import path_utils 16from autotest_lib.client.common_lib.cros.network import interface 17from autotest_lib.client.common_lib.cros.network import netblock 18from autotest_lib.client.common_lib.cros.network import ping_runner 19from autotest_lib.server import hosts 20from autotest_lib.server import site_linux_system 21from autotest_lib.server.cros import dnsname_mangler 22from autotest_lib.server.cros.network import hostap_config 23 24 25StationInstance = collections.namedtuple('StationInstance', 26 ['ssid', 'interface', 'dev_type']) 27HostapdInstance = collections.namedtuple('HostapdInstance', 28 ['ssid', 'conf_file', 'log_file', 29 'interface', 'config_dict', 30 'stderr_log_file', 31 'scenario_name']) 32 33# Send magic packets here, so they can wake up the system but are otherwise 34# dropped. 35UDP_DISCARD_PORT = 9 36 37def build_router_hostname(client_hostname=None, router_hostname=None): 38 """Build a router hostname from a client hostname. 39 40 @param client_hostname: string hostname of DUT connected to a router. 41 @param router_hostname: string hostname of router. 42 @return string hostname of connected router or None if the hostname 43 cannot be inferred from the client hostname. 44 45 """ 46 if not router_hostname and not client_hostname: 47 raise error.TestError('Either client_hostname or router_hostname must ' 48 'be specified to build_router_hostname.') 49 50 return dnsname_mangler.get_router_addr(client_hostname, 51 cmdline_override=router_hostname) 52 53 54def build_router_proxy(test_name='', client_hostname=None, router_addr=None, 55 enable_avahi=False): 56 """Build up a LinuxRouter object. 57 58 Verifies that the remote host responds to ping. 59 Either client_hostname or router_addr must be specified. 60 61 @param test_name: string name of this test (e.g. 'network_WiFi_TestName'). 62 @param client_hostname: string hostname of DUT if we're in the lab. 63 @param router_addr: string DNS/IPv4 address to use for router host object. 64 @param enable_avahi: boolean True iff avahi should be started on the router. 65 66 @return LinuxRouter or raise error.TestError on failure. 67 68 """ 69 router_hostname = build_router_hostname(client_hostname=client_hostname, 70 router_hostname=router_addr) 71 logging.info('Connecting to router at %s', router_hostname) 72 ping_helper = ping_runner.PingRunner() 73 if not ping_helper.simple_ping(router_hostname): 74 raise error.TestError('Router at %s is not pingable.' % 75 router_hostname) 76 77 # Use CrosHost for all router hosts and avoid host detection. 78 # Host detection would use JetstreamHost for Whirlwind routers. 79 # JetstreamHost assumes ap-daemons are running. 80 # Testbed routers run the testbed-ap profile with no ap-daemons. 81 # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection. 82 return LinuxRouter(hosts.create_host(router_hostname, 83 host_class=hosts.CrosHost), 84 test_name, 85 enable_avahi=enable_avahi) 86 87 88class LinuxRouter(site_linux_system.LinuxSystem): 89 """Linux/mac80211-style WiFi Router support for WiFiTest class. 90 91 This class implements test methods/steps that communicate with a 92 router implemented with Linux/mac80211. The router must 93 be pre-configured to enable ssh access and have a mac80211-based 94 wireless device. We also assume hostapd 0.7.x and iw are present 95 and any necessary modules are pre-loaded. 96 97 """ 98 99 KNOWN_TEST_PREFIX = 'network_WiFi_' 100 POLLING_INTERVAL_SECONDS = 0.5 101 STARTUP_TIMEOUT_SECONDS = 30 102 SUFFIX_LETTERS = string.ascii_lowercase + string.digits 103 SUBNET_PREFIX_OCTETS = (192, 168) 104 105 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf' 106 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log' 107 HOSTAPD_STDERR_LOG_FILE_PATTERN = '/tmp/hostapd-stderr-test-%s.log' 108 HOSTAPD_CONTROL_INTERFACE_PATTERN = '/tmp/hostapd-test-%s.ctrl' 109 HOSTAPD_DRIVER_NAME = 'nl80211' 110 111 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf' 112 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log' 113 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid' 114 115 MGMT_FRAME_SENDER_LOG_FILE = '/tmp/send_management_frame-test.log' 116 117 PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer' 118 119 _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available' 120 _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current' 121 122 def get_capabilities(self): 123 """@return iterable object of AP capabilities for this system.""" 124 caps = set() 125 try: 126 self.cmd_send_management_frame = path_utils.must_be_installed( 127 '/usr/bin/send_management_frame', host=self.host) 128 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) 129 except error.TestFail: 130 pass 131 return super(LinuxRouter, self).get_capabilities().union(caps) 132 133 134 @property 135 def router(self): 136 """Deprecated. Use self.host instead. 137 138 @return Host object representing the remote router. 139 140 """ 141 return self.host 142 143 144 @property 145 def wifi_ip(self): 146 """Simple accessor for the WiFi IP when there is only one AP. 147 148 @return string IP of WiFi interface. 149 150 """ 151 if len(self.local_servers) != 1: 152 raise error.TestError('Could not pick a WiFi IP to return.') 153 154 return self.get_wifi_ip(0) 155 156 157 def __init__(self, host, test_name, enable_avahi=False): 158 """Build a LinuxRouter. 159 160 @param host Host object representing the remote machine. 161 @param test_name string name of this test. Used in SSID creation. 162 @param enable_avahi: boolean True iff avahi should be started on the 163 router. 164 165 """ 166 super(LinuxRouter, self).__init__(host, 'router') 167 self._ssid_prefix = test_name 168 self._enable_avahi = enable_avahi 169 self.__setup() 170 171 172 def __setup(self): 173 """Set up this system. 174 175 Can be used either to complete initialization of a LinuxRouter 176 object, or to re-establish a good state after a reboot. 177 178 """ 179 self.cmd_dhcpd = '/usr/sbin/dhcpd' 180 self.cmd_hostapd = path_utils.must_be_installed( 181 '/usr/sbin/hostapd', host=self.host) 182 self.cmd_hostapd_cli = path_utils.must_be_installed( 183 '/usr/sbin/hostapd_cli', host=self.host) 184 self.cmd_wpa_supplicant = path_utils.must_be_installed( 185 '/usr/sbin/wpa_supplicant', host=self.host) 186 self.dhcpd_conf = '/tmp/dhcpd.%s.conf' 187 self.dhcpd_leases = '/tmp/dhcpd.leases' 188 189 # TODO(crbug.com/839164): some routers fill their stateful partition 190 # with uncollected metrics. 191 self.host.run('rm -f /var/lib/metrics/uma-events', ignore_status=True) 192 193 # Log the most recent message on the router so that we can rebuild the 194 # suffix relevant to us when debugging failures. 195 last_log_line = self.host.run('tail -1 /var/log/messages', 196 ignore_status=True).stdout 197 # We're trying to get the timestamp from: 198 # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah 199 self._log_start_timestamp = last_log_line.strip().partition(' ')[0] 200 if self._log_start_timestamp: 201 logging.debug('Will only retrieve logs after %s.', 202 self._log_start_timestamp) 203 else: 204 # If syslog is empty, we just use a wildcard pattern, to grab 205 # everything. 206 logging.debug('Empty or corrupt log; will retrieve whole log') 207 self._log_start_timestamp = '.' 208 209 # hostapd configuration persists throughout the test, subsequent 210 # 'config' commands only modify it. 211 if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX): 212 # Many of our tests start with an uninteresting prefix. 213 # Remove it so we can have more unique bytes. 214 self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):] 215 self._number_unique_ssids = 0 216 217 self._total_hostapd_instances = 0 218 self.local_servers = [] 219 self.server_address_index = [] 220 self.hostapd_instances = [] 221 self.station_instances = [] 222 self.dhcp_low = 1 223 self.dhcp_high = 128 224 225 # Tear down hostapbr bridge interfaces 226 result = self.host.run('ls -d /sys/class/net/%s*' % 227 self.HOSTAP_BRIDGE_INTERFACE_PREFIX, 228 ignore_status=True) 229 if result.exit_status == 0: 230 for path in result.stdout.splitlines(): 231 self.delete_link(path.split('/')[-1]) 232 233 # Kill hostapd and dhcp server if already running. 234 self._kill_process_instance('hostapd', timeout_seconds=30) 235 self.stop_dhcp_server(instance=None) 236 237 # Place us in the US by default 238 self.iw_runner.set_regulatory_domain('US') 239 240 self.enable_all_antennas() 241 242 # Some tests want this functionality, but otherwise, it's a distraction. 243 if self._enable_avahi: 244 self.host.run('start avahi', ignore_status=True) 245 else: 246 self.host.run('stop avahi', ignore_status=True) 247 248 # Some routers have bad (slow?) random number generators. 249 self.rng_configure() 250 251 252 def close(self): 253 """Close global resources held by this system.""" 254 self.deconfig() 255 # dnsmasq and hostapd cause interesting events to go to system logs. 256 # Retrieve only the suffix of the logs after the timestamp we stored on 257 # router creation. 258 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >/tmp/router_log" % 259 self._log_start_timestamp, ignore_status=True) 260 self.host.get_file('/tmp/router_log', 'debug/router_host_messages') 261 super(LinuxRouter, self).close() 262 263 264 def reboot(self, timeout): 265 """Reboot this router, and restore it to a known-good state. 266 267 @param timeout Maximum seconds to wait for router to return. 268 269 """ 270 super(LinuxRouter, self).reboot(timeout) 271 self.__setup() 272 273 274 def has_local_server(self): 275 """@return True iff this router has local servers configured.""" 276 return bool(self.local_servers) 277 278 279 def start_hostapd(self, configuration): 280 """Start a hostapd instance described by conf. 281 282 @param configuration HostapConfig object. 283 284 """ 285 # Figure out the correct interface. 286 interface = self.get_wlanif(configuration.frequency, 'managed', 287 configuration.min_streams) 288 phy_name = self.iw_runner.get_interface(interface).phy 289 290 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface 291 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface 292 stderr_log_file = self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface 293 control_interface = self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface 294 hostapd_conf_dict = configuration.generate_dict( 295 interface, control_interface, 296 self.build_unique_ssid(suffix=configuration.ssid_suffix)) 297 logging.debug('hostapd parameters: %r', hostapd_conf_dict) 298 299 # Generate hostapd.conf. 300 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 301 (conf_file, '\n'.join( 302 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems()))) 303 304 # Run hostapd. 305 logging.info('Starting hostapd on %s(%s) channel=%s...', 306 interface, phy_name, configuration.channel) 307 self.router.run('rm %s' % log_file, ignore_status=True) 308 self.router.run('stop wpasupplicant', ignore_status=True) 309 start_command = '%s -dd -t %s > %s 2> %s & echo $!' % ( 310 self.cmd_hostapd, conf_file, log_file, stderr_log_file) 311 pid = int(self.router.run(start_command).stdout.strip()) 312 self.hostapd_instances.append(HostapdInstance( 313 hostapd_conf_dict['ssid'], 314 conf_file, 315 log_file, 316 interface, 317 hostapd_conf_dict.copy(), 318 stderr_log_file, 319 configuration.scenario_name)) 320 321 # Wait for confirmation that the router came up. 322 logging.info('Waiting for hostapd to startup.') 323 utils.poll_for_condition( 324 condition=lambda: self._has_hostapd_started(log_file, pid), 325 exception=error.TestFail('Timed out while waiting for hostapd ' 326 'to start.'), 327 timeout=self.STARTUP_TIMEOUT_SECONDS, 328 sleep_interval=self.POLLING_INTERVAL_SECONDS) 329 330 if configuration.frag_threshold: 331 threshold = self.iw_runner.get_fragmentation_threshold(phy_name) 332 if threshold != configuration.frag_threshold: 333 raise error.TestNAError('Router does not support setting ' 334 'fragmentation threshold') 335 336 337 def _has_hostapd_started(self, log_file, pid): 338 """Determines if hostapd has started. 339 340 @return Whether or not hostapd has started. 341 @raise error.TestFail if there was a bad config or hostapd terminated. 342 """ 343 success = self.router.run( 344 'grep "Setup of interface done" %s' % log_file, 345 ignore_status=True).exit_status == 0 346 if success: 347 return True 348 349 # A common failure is an invalid router configuration. 350 # Detect this and exit early if we see it. 351 bad_config = self.router.run( 352 'grep "Interface initialization failed" %s' % log_file, 353 ignore_status=True).exit_status == 0 354 if bad_config: 355 raise error.TestFail('hostapd failed to initialize AP ' 356 'interface.') 357 358 if pid: 359 early_exit = self.router.run('kill -0 %d' % pid, 360 ignore_status=True).exit_status 361 if early_exit: 362 raise error.TestFail('hostapd process terminated.') 363 364 return False 365 366 367 def _kill_process_instance(self, 368 process, 369 instance=None, 370 timeout_seconds=10, 371 ignore_timeouts=False): 372 """Kill a process on the router. 373 374 Kills remote program named |process| (optionally only a specific 375 |instance|). Wait |timeout_seconds| for |process| to die 376 before returning. If |ignore_timeouts| is False, raise 377 a TestError on timeouts. 378 379 @param process: string name of process to kill. 380 @param instance: string fragment of the command line unique to 381 this instance of the remote process. 382 @param timeout_seconds: float timeout in seconds to wait. 383 @param ignore_timeouts: True iff we should ignore failures to 384 kill processes. 385 @return True iff the specified process has exited. 386 387 """ 388 if instance is not None: 389 search_arg = '-f "^%s.*%s"' % (process, instance) 390 else: 391 search_arg = process 392 393 self.host.run('pkill %s' % search_arg, ignore_status=True) 394 395 # Wait for process to die 396 time.sleep(self.POLLING_INTERVAL_SECONDS) 397 try: 398 utils.poll_for_condition( 399 condition=lambda: self.host.run( 400 'pgrep -l %s' % search_arg, 401 ignore_status=True).exit_status != 0, 402 timeout=timeout_seconds, 403 sleep_interval=self.POLLING_INTERVAL_SECONDS) 404 except utils.TimeoutError: 405 if ignore_timeouts: 406 return False 407 408 raise error.TestError( 409 'Timed out waiting for %s%s to die' % 410 (process, 411 '' if instance is None else ' (instance=%s)' % instance)) 412 return True 413 414 415 def kill_hostapd_instance(self, instance): 416 """Kills a hostapd instance. 417 418 @param instance HostapdInstance object. 419 420 """ 421 is_dead = self._kill_process_instance( 422 self.cmd_hostapd, 423 instance=instance.conf_file, 424 timeout_seconds=30, 425 ignore_timeouts=True) 426 if instance.scenario_name: 427 log_identifier = instance.scenario_name 428 else: 429 log_identifier = '%d_%s' % ( 430 self._total_hostapd_instances, instance.interface) 431 files_to_copy = [(instance.log_file, 432 'debug/hostapd_router_%s.log' % log_identifier), 433 (instance.stderr_log_file, 434 'debug/hostapd_router_%s.stderr.log' % 435 log_identifier)] 436 for remote_file, local_file in files_to_copy: 437 if self.host.run('ls %s >/dev/null 2>&1' % remote_file, 438 ignore_status=True).exit_status: 439 logging.error('Did not collect hostapd log file because ' 440 'it was missing.') 441 else: 442 self.router.get_file(remote_file, local_file) 443 self._total_hostapd_instances += 1 444 if not is_dead: 445 raise error.TestError('Timed out killing hostapd.') 446 447 448 def build_unique_ssid(self, suffix=''): 449 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding 450 the number of APs we've constructed already. 451 452 @param suffix string to append to SSID 453 454 """ 455 base = len(self.SUFFIX_LETTERS) 456 number = self._number_unique_ssids 457 self._number_unique_ssids += 1 458 unique = '' 459 while number or not unique: 460 unique = self.SUFFIX_LETTERS[number % base] + unique 461 number = number / base 462 # And salt the SSID so that tests running in adjacent cells are unlikely 463 # to pick the same SSID and we're resistent to beacons leaking out of 464 # cells. 465 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)]) 466 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:] 467 468 469 def rng_configure(self): 470 """Configure the random generator to our liking. 471 472 Some routers (particularly, Gale) seem to have bad Random Number 473 Generators, such that hostapd can't always generate keys fast enough. 474 The on-board TPM seems to serve as a better generator, so we try to 475 switch to that if available. 476 477 Symptoms of a slow RNG: hostapd complains with: 478 479 WPA: Not enough entropy in random pool to proceed - reject first 480 4-way handshake 481 482 Ref: 483 https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854 484 485 Linux devices may have RNG parameters at 486 /sys/class/misc/hw_random/rng_{available,current}. See: 487 488 https://www.kernel.org/doc/Documentation/hw_random.txt 489 490 """ 491 492 available = self.host.run('cat %s' % self._RNG_AVAILABLE, \ 493 ignore_status=True).stdout.strip().split(' ') 494 # System may not have HWRNG support. Just skip this. 495 if available == "": 496 return 497 current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \ 498 strip() 499 want_rng = "tpm-rng" 500 501 logging.debug("Available / current RNGs on router: %r / %s", 502 available, current) 503 if want_rng in available and want_rng != current: 504 logging.debug("Switching RNGs: %s -> %s", current, want_rng) 505 self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT)) 506 507 508 def hostap_configure(self, configuration, multi_interface=None): 509 """Build up a hostapd configuration file and start hostapd. 510 511 Also setup a local server if this router supports them. 512 513 @param configuration HosetapConfig object. 514 @param multi_interface bool True iff multiple interfaces allowed. 515 516 """ 517 if multi_interface is None and (self.hostapd_instances or 518 self.station_instances): 519 self.deconfig() 520 if configuration.is_11ac: 521 router_caps = self.get_capabilities() 522 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps: 523 raise error.TestNAError('Router does not have AC support') 524 525 if configuration.use_bridge: 526 configuration._bridge = self.get_brif() 527 528 self.start_hostapd(configuration) 529 interface = self.hostapd_instances[-1].interface 530 self.iw_runner.set_tx_power(interface, 'auto') 531 self.set_beacon_footer(interface, configuration.beacon_footer) 532 self.start_local_server(interface, bridge=configuration.bridge) 533 logging.info('AP configured.') 534 535 536 def ibss_configure(self, config): 537 """Configure a station based AP in IBSS mode. 538 539 Extract relevant configuration objects from |config| despite not 540 actually being a hostap managed endpoint. 541 542 @param config HostapConfig object. 543 544 """ 545 if self.station_instances or self.hostapd_instances: 546 self.deconfig() 547 interface = self.get_wlanif(config.frequency, 'ibss') 548 ssid = (config.ssid or 549 self.build_unique_ssid(suffix=config.ssid_suffix)) 550 # Connect the station 551 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 552 self.iw_runner.ibss_join(interface, ssid, config.frequency) 553 # Always start a local server. 554 self.start_local_server(interface) 555 # Remember that this interface is up. 556 self.station_instances.append( 557 StationInstance(ssid=ssid, interface=interface, 558 dev_type='ibss')) 559 560 561 def local_server_address(self, index): 562 """Get the local server address for an interface. 563 564 When we multiple local servers, we give them static IP addresses 565 like 192.168.*.254. 566 567 @param index int describing which local server this is for. 568 569 """ 570 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) 571 572 573 def local_peer_ip_address(self, index): 574 """Get the IP address allocated for the peer associated to the AP. 575 576 This address is assigned to a locally associated peer device that 577 is created for the DUT to perform connectivity tests with. 578 When we have multiple local servers, we give them static IP addresses 579 like 192.168.*.253. 580 581 @param index int describing which local server this is for. 582 583 """ 584 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) 585 586 def local_bridge_address(self, index): 587 """Get the bridge address for an interface. 588 589 This address is assigned to a local bridge device. 590 591 @param index int describing which local server this is for. 592 593 """ 594 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252)) 595 596 def local_peer_mac_address(self): 597 """Get the MAC address of the peer interface. 598 599 @return string MAC address of the peer interface. 600 601 """ 602 iface = interface.Interface(self.station_instances[0].interface, 603 self.router) 604 return iface.mac_address 605 606 607 def _get_unused_server_address_index(self): 608 """@return an unused server address index.""" 609 for address_index in range(0, 256): 610 if address_index not in self.server_address_index: 611 return address_index 612 raise error.TestFail('No available server address index') 613 614 615 def change_server_address_index(self, ap_num=0, server_address_index=None): 616 """Restart the local server with a different server address index. 617 618 This will restart the local server with different gateway IP address 619 and DHCP address ranges. 620 621 @param ap_num: int hostapd instance number. 622 @param server_address_index: int server address index. 623 624 """ 625 interface = self.local_servers[ap_num]['interface']; 626 # Get an unused server address index if one is not specified, which 627 # will be different from the one that's currently in used. 628 if server_address_index is None: 629 server_address_index = self._get_unused_server_address_index() 630 631 # Restart local server with the new server address index. 632 self.stop_local_server(self.local_servers[ap_num]) 633 self.start_local_server(interface, 634 ap_num=ap_num, 635 server_address_index=server_address_index) 636 637 638 def start_local_server(self, 639 interface, 640 ap_num=None, 641 server_address_index=None, 642 bridge=None): 643 """Start a local server on an interface. 644 645 @param interface string (e.g. wlan0) 646 @param ap_num int the ap instance to start the server for 647 @param server_address_index int server address index 648 @param bridge string (e.g. br0) 649 650 """ 651 logging.info('Starting up local server...') 652 653 if len(self.local_servers) >= 256: 654 raise error.TestFail('Exhausted available local servers') 655 656 # Get an unused server address index if one is not specified. 657 # Validate server address index if one is specified. 658 if server_address_index is None: 659 server_address_index = self._get_unused_server_address_index() 660 elif server_address_index in self.server_address_index: 661 raise error.TestFail('Server address index %d already in used' % 662 server_address_index) 663 664 server_addr = netblock.from_addr( 665 self.local_server_address(server_address_index), 666 prefix_len=24) 667 668 params = {} 669 params['address_index'] = server_address_index 670 params['netblock'] = server_addr 671 params['dhcp_range'] = ' '.join( 672 (server_addr.get_addr_in_block(1), 673 server_addr.get_addr_in_block(128))) 674 params['interface'] = interface 675 params['bridge'] = bridge 676 params['ip_params'] = ('%s broadcast %s dev %s' % 677 (server_addr.netblock, 678 server_addr.broadcast, 679 interface)) 680 if ap_num is None: 681 self.local_servers.append(params) 682 else: 683 self.local_servers.insert(ap_num, params) 684 self.server_address_index.append(server_address_index) 685 686 self.router.run('%s addr flush %s' % 687 (self.cmd_ip, interface)) 688 self.router.run('%s addr add %s' % 689 (self.cmd_ip, params['ip_params'])) 690 self.router.run('%s link set %s up' % 691 (self.cmd_ip, interface)) 692 if params['bridge']: 693 bridge_addr = netblock.from_addr( 694 self.local_bridge_address(server_address_index), 695 prefix_len=24) 696 self.router.run("ifconfig %s %s" % 697 (params['bridge'], bridge_addr.netblock)) 698 self.start_dhcp_server(interface) 699 700 701 def stop_local_server(self, server): 702 """Stop a local server on the router 703 704 @param server object server configuration parameters. 705 706 """ 707 self.stop_dhcp_server(server['interface']) 708 self.router.run("%s addr del %s" % 709 (self.cmd_ip, server['ip_params']), 710 ignore_status=True) 711 self.server_address_index.remove(server['address_index']) 712 self.local_servers.remove(server) 713 714 715 def start_dhcp_server(self, interface): 716 """Start a dhcp server on an interface. 717 718 @param interface string (e.g. wlan0) 719 720 """ 721 for server in self.local_servers: 722 if server['interface'] == interface: 723 params = server 724 break 725 else: 726 raise error.TestFail('Could not find local server ' 727 'to match interface: %r' % interface) 728 server_addr = params['netblock'] 729 dhcpd_conf_file = self.dhcpd_conf % interface 730 dhcp_conf = '\n'.join([ 731 'port=0', # disables DNS server 732 'bind-interfaces', 733 'log-dhcp', 734 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1), 735 server_addr.get_addr_in_block(128))), 736 'interface=%s' % (params['bridge'] or params['interface']), 737 'dhcp-leasefile=%s' % self.dhcpd_leases]) 738 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 739 (dhcpd_conf_file, dhcp_conf)) 740 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file) 741 742 743 def stop_dhcp_server(self, instance=None): 744 """Stop a dhcp server on the router. 745 746 @param instance string instance to kill. 747 748 """ 749 self._kill_process_instance('dnsmasq', instance=instance) 750 751 752 def get_wifi_channel(self, ap_num): 753 """Return channel of BSS corresponding to |ap_num|. 754 755 @param ap_num int which BSS to get the channel of. 756 @return int primary channel of BSS. 757 758 """ 759 instance = self.hostapd_instances[ap_num] 760 return instance.config_dict['channel'] 761 762 763 def get_wifi_ip(self, ap_num): 764 """Return IP address on the WiFi subnet of a local server on the router. 765 766 If no local servers are configured (e.g. for an RSPro), a TestFail will 767 be raised. 768 769 @param ap_num int which local server to get an address from. 770 771 """ 772 if not self.local_servers: 773 raise error.TestError('No IP address assigned') 774 775 return self.local_servers[ap_num]['netblock'].addr 776 777 778 def get_wifi_ip_subnet(self, ap_num): 779 """Return subnet of WiFi AP instance. 780 781 If no APs are configured a TestError will be raised. 782 783 @param ap_num int which local server to get an address from. 784 785 """ 786 if not self.local_servers: 787 raise error.TestError('No APs configured.') 788 789 return self.local_servers[ap_num]['netblock'].subnet 790 791 792 def get_hostapd_interface(self, ap_num): 793 """Get the name of the interface associated with a hostapd instance. 794 795 @param ap_num: int hostapd instance number. 796 @return string interface name (e.g. 'managed0'). 797 798 """ 799 if ap_num not in range(len(self.hostapd_instances)): 800 raise error.TestFail('Invalid instance number (%d) with %d ' 801 'instances configured.' % 802 (ap_num, len(self.hostapd_instances))) 803 804 instance = self.hostapd_instances[ap_num] 805 return instance.interface 806 807 808 def get_station_interface(self, instance): 809 """Get the name of the interface associated with a station. 810 811 @param instance: int station instance number. 812 @return string interface name (e.g. 'managed0'). 813 814 """ 815 if instance not in range(len(self.station_instances)): 816 raise error.TestFail('Invalid instance number (%d) with %d ' 817 'instances configured.' % 818 (instance, len(self.station_instances))) 819 820 instance = self.station_instances[instance] 821 return instance.interface 822 823 824 def get_hostapd_mac(self, ap_num): 825 """Return the MAC address of an AP in the test. 826 827 @param ap_num int index of local server to read the MAC address from. 828 @return string MAC address like 00:11:22:33:44:55. 829 830 """ 831 interface_name = self.get_hostapd_interface(ap_num) 832 ap_interface = interface.Interface(interface_name, self.host) 833 return ap_interface.mac_address 834 835 836 def get_hostapd_phy(self, ap_num): 837 """Get name of phy for hostapd instance. 838 839 @param ap_num int index of hostapd instance. 840 @return string phy name of phy corresponding to hostapd's 841 managed interface. 842 843 """ 844 interface = self.iw_runner.get_interface( 845 self.get_hostapd_interface(ap_num)) 846 return interface.phy 847 848 849 def deconfig(self): 850 """A legacy, deprecated alias for deconfig_aps.""" 851 self.deconfig_aps() 852 853 854 def deconfig_aps(self, instance=None, silent=False): 855 """De-configure an AP (will also bring wlan down). 856 857 @param instance: int or None. If instance is None, will bring down all 858 instances of hostapd. 859 @param silent: True if instances should be brought without de-authing 860 the DUT. 861 862 """ 863 if not self.hostapd_instances and not self.station_instances: 864 return 865 866 if self.hostapd_instances: 867 local_servers = [] 868 if instance is not None: 869 instances = [ self.hostapd_instances.pop(instance) ] 870 for server in self.local_servers: 871 if server['interface'] == instances[0].interface: 872 local_servers = [server] 873 break 874 else: 875 instances = self.hostapd_instances 876 self.hostapd_instances = [] 877 local_servers = copy.copy(self.local_servers) 878 879 for instance in instances: 880 if silent: 881 # Deconfigure without notifying DUT. Remove the interface 882 # hostapd uses to send beacon and DEAUTH packets. 883 self.remove_interface(instance.interface) 884 885 self.kill_hostapd_instance(instance) 886 self.release_interface(instance.interface) 887 if self.station_instances: 888 local_servers = copy.copy(self.local_servers) 889 instance = self.station_instances.pop() 890 if instance.dev_type == 'ibss': 891 self.iw_runner.ibss_leave(instance.interface) 892 elif instance.dev_type == 'managed': 893 self._kill_process_instance(self.cmd_wpa_supplicant, 894 instance=instance.interface) 895 else: 896 self.iw_runner.disconnect_station(instance.interface) 897 self.router.run('%s link set %s down' % 898 (self.cmd_ip, instance.interface)) 899 900 for server in local_servers: 901 self.stop_local_server(server) 902 903 for brif in range(self._brif_index): 904 self.delete_link('%s%d' % 905 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif)) 906 907 908 def delete_link(self, name): 909 """Delete link using the `ip` command. 910 911 @param name string link name. 912 913 """ 914 self.host.run('%s link del %s' % (self.cmd_ip, name), 915 ignore_status=True) 916 917 918 def set_ap_interface_down(self, instance=0): 919 """Bring down the hostapd interface. 920 921 @param instance int router instance number. 922 923 """ 924 self.host.run('%s link set %s down' % 925 (self.cmd_ip, self.get_hostapd_interface(instance))) 926 927 928 def confirm_pmksa_cache_use(self, instance=0): 929 """Verify that the PMKSA auth was cached on a hostapd instance. 930 931 @param instance int router instance number. 932 933 """ 934 log_file = self.hostapd_instances[instance].log_file 935 pmksa_match = 'PMK from PMKSA cache' 936 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), 937 ignore_status=True) 938 if result.exit_status: 939 raise error.TestFail('PMKSA cache was not used in roaming.') 940 941 942 def get_ssid(self, instance=None): 943 """@return string ssid for the network stemming from this router.""" 944 if instance is None: 945 instance = 0 946 if len(self.hostapd_instances) > 1: 947 raise error.TestFail('No instance of hostapd specified with ' 948 'multiple instances present.') 949 950 if self.hostapd_instances: 951 return self.hostapd_instances[instance].ssid 952 953 if self.station_instances: 954 return self.station_instances[0].ssid 955 956 raise error.TestFail('Requested ssid of an unconfigured AP.') 957 958 959 def deauth_client(self, client_mac): 960 """Deauthenticates a client described in params. 961 962 @param client_mac string containing the mac address of the client to be 963 deauthenticated. 964 965 """ 966 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface'] 967 self.router.run('%s -p%s deauthenticate %s' % 968 (self.cmd_hostapd_cli, control_if, client_mac)) 969 970 def send_bss_tm_req(self, client_mac, neighbor_list): 971 """Send a BSS Transition Management Request to a client. 972 973 @param client_mac string containing the mac address of the client. 974 @param neighbor_list list of strings containing mac addresses of 975 candidate APs. 976 @return bool True if BSS_TM_REQ is sent successfully. 977 978 """ 979 control_if = self.hostapd_instances[0].config_dict['ctrl_interface'] 980 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' % 981 (self.cmd_hostapd_cli, control_if, client_mac, 982 ',0,0,0,0 neighbor='.join(neighbor_list))) 983 ret = self.router.run(command).stdout 984 if ret.splitlines()[-1] != 'OK': 985 return False 986 return True 987 988 def _prep_probe_response_footer(self, footer): 989 """Write probe response footer temporarily to a local file and copy 990 over to test router. 991 992 @param footer string containing bytes for the probe response footer. 993 @raises AutoservRunError: If footer file copy fails. 994 995 """ 996 with tempfile.NamedTemporaryFile() as fp: 997 fp.write(footer) 998 fp.flush() 999 try: 1000 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE) 1001 except error.AutoservRunError: 1002 logging.error('failed to copy footer file to AP') 1003 raise 1004 1005 1006 def send_management_frame_on_ap(self, frame_type, channel, instance=0): 1007 """Injects a management frame into an active hostapd session. 1008 1009 @param frame_type string the type of frame to send. 1010 @param channel int targeted channel 1011 @param instance int indicating which hostapd instance to inject into. 1012 1013 """ 1014 hostap_interface = self.hostapd_instances[instance].interface 1015 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 1016 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 1017 self.router.run('%s -i %s -t %s -c %d' % 1018 (self.cmd_send_management_frame, interface, frame_type, 1019 channel)) 1020 self.release_interface(interface) 1021 1022 1023 def send_management_frame(self, interface, frame_type, channel, 1024 ssid_prefix=None, num_bss=None, 1025 frame_count=None, delay=None, 1026 dest_addr=None, probe_resp_footer=None): 1027 """ 1028 Injects management frames on specify channel |frequency|. 1029 1030 This function will spawn off a new process to inject specified 1031 management frames |frame_type| at the specified interface |interface|. 1032 1033 @param interface string interface to inject frames. 1034 @param frame_type string message type. 1035 @param channel int targeted channel. 1036 @param ssid_prefix string SSID prefix. 1037 @param num_bss int number of BSS. 1038 @param frame_count int number of frames to send. 1039 @param delay int milliseconds delay between frames. 1040 @param dest_addr string destination address (DA) MAC address. 1041 @param probe_resp_footer string footer for probe response. 1042 1043 @return int PID of the newly created process. 1044 1045 """ 1046 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame, 1047 interface, frame_type, channel) 1048 if ssid_prefix is not None: 1049 command += ' -s %s' % (ssid_prefix) 1050 if num_bss is not None: 1051 command += ' -b %d' % (num_bss) 1052 if frame_count is not None: 1053 command += ' -n %d' % (frame_count) 1054 if delay is not None: 1055 command += ' -d %d' % (delay) 1056 if dest_addr is not None: 1057 command += ' -a %s' % (dest_addr) 1058 if probe_resp_footer is not None: 1059 self._prep_probe_response_footer(footer=probe_resp_footer) 1060 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE) 1061 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE) 1062 pid = int(self.router.run(command).stdout) 1063 return pid 1064 1065 1066 def detect_client_deauth(self, client_mac, instance=0): 1067 """Detects whether hostapd has logged a deauthentication from 1068 |client_mac|. 1069 1070 @param client_mac string the MAC address of the client to detect. 1071 @param instance int indicating which hostapd instance to query. 1072 1073 """ 1074 interface = self.hostapd_instances[instance].interface 1075 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 1076 log_file = self.hostapd_instances[instance].log_file 1077 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 1078 ignore_status=True) 1079 return result.exit_status == 0 1080 1081 1082 def detect_client_coexistence_report(self, client_mac, instance=0): 1083 """Detects whether hostapd has logged an action frame from 1084 |client_mac| indicating information about 20/40MHz BSS coexistence. 1085 1086 @param client_mac string the MAC address of the client to detect. 1087 @param instance int indicating which hostapd instance to query. 1088 1089 """ 1090 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 1091 '.. .. .. .. .. .. .. .. .. .. %s ' 1092 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 1093 ' '.join(client_mac.split(':'))) 1094 log_file = self.hostapd_instances[instance].log_file 1095 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 1096 ignore_status=True) 1097 return result.exit_status == 0 1098 1099 1100 def add_connected_peer(self, instance=0): 1101 """Configure a station connected to a running AP instance. 1102 1103 Extract relevant configuration objects from the hostap 1104 configuration for |instance| and generate a wpa_supplicant 1105 instance that connects to it. This allows the DUT to interact 1106 with a client entity that is also connected to the same AP. A 1107 full wpa_supplicant instance is necessary here (instead of just 1108 using the "iw" command to connect) since we want to enable 1109 advanced features such as TDLS. 1110 1111 @param instance int indicating which hostapd instance to connect to. 1112 1113 """ 1114 if not self.hostapd_instances: 1115 raise error.TestFail('Hostapd is not configured.') 1116 1117 if self.station_instances: 1118 raise error.TestFail('Station is already configured.') 1119 1120 ssid = self.get_ssid(instance) 1121 hostap_conf = self.hostapd_instances[instance].config_dict 1122 frequency = hostap_config.HostapConfig.get_frequency_for_channel( 1123 hostap_conf['channel']) 1124 self.configure_managed_station( 1125 ssid, frequency, self.local_peer_ip_address(instance)) 1126 interface = self.station_instances[0].interface 1127 # Since we now have two network interfaces connected to the same 1128 # network, we need to disable the kernel's protection against 1129 # incoming packets to an "unexpected" interface. 1130 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' % 1131 interface) 1132 1133 # Similarly, we'd like to prevent the hostap interface from 1134 # replying to ARP requests for the peer IP address and vice 1135 # versa. 1136 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 1137 interface) 1138 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 1139 hostap_conf['interface']) 1140 1141 1142 def configure_managed_station(self, ssid, frequency, ip_addr): 1143 """Configure a router interface to connect as a client to a network. 1144 1145 @param ssid: string SSID of network to join. 1146 @param frequency: int frequency required to join the network. 1147 @param ip_addr: IP address to assign to this interface 1148 (e.g. '192.168.1.200'). 1149 1150 """ 1151 interface = self.get_wlanif(frequency, 'managed') 1152 1153 # TODO(pstew): Configure other bits like PSK, 802.11n if tests 1154 # require them... 1155 supplicant_config = ( 1156 'network={\n' 1157 ' ssid="%(ssid)s"\n' 1158 ' key_mgmt=NONE\n' 1159 '}\n' % {'ssid': ssid} 1160 ) 1161 1162 conf_file = self.STATION_CONF_FILE_PATTERN % interface 1163 log_file = self.STATION_LOG_FILE_PATTERN % interface 1164 pid_file = self.STATION_PID_FILE_PATTERN % interface 1165 1166 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 1167 (conf_file, supplicant_config)) 1168 1169 # Connect the station. 1170 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 1171 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s >%s 2>&1 &' % 1172 (self.cmd_wpa_supplicant, 1173 interface, pid_file, conf_file, 1174 self.HOSTAPD_DRIVER_NAME, log_file)) 1175 self.router.run(start_command) 1176 self.iw_runner.wait_for_link(interface) 1177 1178 # Assign an IP address to this interface. 1179 self.router.run('%s addr add %s/24 dev %s' % 1180 (self.cmd_ip, ip_addr, interface)) 1181 self.station_instances.append( 1182 StationInstance(ssid=ssid, interface=interface, 1183 dev_type='managed')) 1184 1185 1186 def send_magic_packet(self, dest_ip, dest_mac): 1187 """Sends a magic packet to the NIC with the given IP and MAC addresses. 1188 1189 @param dest_ip the IP address of the device to send the packet to 1190 @param dest_mac the hardware MAC address of the device 1191 1192 """ 1193 # magic packet is 6 0xff bytes followed by the hardware address 1194 # 16 times 1195 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')]) 1196 magic_packet = '\xff' * 6 + mac_bytes * 16 1197 1198 logging.info('Sending magic packet to %s...', dest_ip) 1199 self.host.run('python -uc "import socket, sys;' 1200 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);' 1201 's.sendto(sys.stdin.read(), (\'%s\', %d))"' % 1202 (dest_ip, UDP_DISCARD_PORT), 1203 stdin=magic_packet) 1204 1205 1206 def set_beacon_footer(self, interface, footer=''): 1207 """Sets the beacon footer (appended IE information) for this interface. 1208 1209 @param interface string interface to set the footer on. 1210 @param footer string footer to be set on the interface. 1211 1212 """ 1213 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' % 1214 self.iw_runner.get_interface(interface).phy) 1215 if self.router.run('test -e %s' % footer_file, 1216 ignore_status=True).exit_status != 0: 1217 logging.info('Beacon footer file does not exist. Ignoring.') 1218 return 1219 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file)) 1220 1221 1222 def setup_bridge_mode_dhcp_server(self): 1223 """Setup an DHCP server for bridge mode. 1224 1225 Setup an DHCP server on the master interface of the virtual ethernet 1226 pair, with peer interface connected to the bridge interface. This is 1227 used for testing APs in bridge mode. 1228 1229 """ 1230 # Start a local server on master interface of virtual ethernet pair. 1231 self.start_local_server( 1232 self.get_virtual_ethernet_master_interface()) 1233 # Add peer interface to the bridge. 1234 self.add_interface_to_bridge( 1235 self.get_virtual_ethernet_peer_interface()) 1236