1# Lint as: python2, python3 2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9 10import collections 11import copy 12import logging 13import os 14import random 15import string 16import tempfile 17import time 18 19from autotest_lib.client.common_lib import error 20from autotest_lib.client.common_lib import utils 21from autotest_lib.client.common_lib.cros import path_utils 22from autotest_lib.client.common_lib.cros.network import interface 23from autotest_lib.client.common_lib.cros.network import netblock 24from autotest_lib.client.common_lib.cros.network import ping_runner 25from autotest_lib.server import hosts 26from autotest_lib.server import site_linux_system 27from autotest_lib.server.cros import dnsname_mangler 28import six 29from six.moves import range 30 31 32StationInstance = collections.namedtuple('StationInstance', 33 ['ssid', 'interface', 'dev_type']) 34HostapdInstance = collections.namedtuple('HostapdInstance', 35 ['ssid', 'conf_file', 'log_file', 36 'interface', 'config_dict', 37 'stderr_log_file', 38 'scenario_name']) 39 40# Send magic packets here, so they can wake up the system but are otherwise 41# dropped. 42UDP_DISCARD_PORT = 9 43 44def build_router_hostname(client_hostname=None, router_hostname=None): 45 """Build a router hostname from a client hostname. 46 47 @param client_hostname: string hostname of DUT connected to a router. 48 @param router_hostname: string hostname of router. 49 @return string hostname of connected router or None if the hostname 50 cannot be inferred from the client hostname. 51 52 """ 53 if not router_hostname and not client_hostname: 54 raise error.TestError('Either client_hostname or router_hostname must ' 55 'be specified to build_router_hostname.') 56 57 return dnsname_mangler.get_router_addr(client_hostname, 58 cmdline_override=router_hostname) 59 60 61def build_router_proxy(test_name='', client_hostname=None, router_addr=None, 62 enable_avahi=False): 63 """Build up a LinuxRouter object. 64 65 Verifies that the remote host responds to ping. 66 Either client_hostname or router_addr must be specified. 67 68 @param test_name: string name of this test (e.g. 'network_WiFi_TestName'). 69 @param client_hostname: string hostname of DUT if we're in the lab. 70 @param router_addr: string DNS/IPv4 address to use for router host object. 71 @param enable_avahi: boolean True iff avahi should be started on the router. 72 73 @return LinuxRouter or raise error.TestError on failure. 74 75 """ 76 router_hostname = build_router_hostname(client_hostname=client_hostname, 77 router_hostname=router_addr) 78 logging.info('Connecting to router at %s', router_hostname) 79 ping_helper = ping_runner.PingRunner() 80 if not ping_helper.simple_ping(router_hostname): 81 raise error.TestError('Router at %s is not pingable.' % 82 router_hostname) 83 84 # Use CrosHost for all router hosts and avoid host detection. 85 # Host detection would use JetstreamHost for Whirlwind routers. 86 # JetstreamHost assumes ap-daemons are running. 87 # Testbed routers run the testbed-ap profile with no ap-daemons. 88 # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection. 89 return LinuxRouter(hosts.create_host(router_hostname, 90 host_class=hosts.CrosHost), 91 test_name, 92 enable_avahi=enable_avahi) 93 94 95class LinuxRouter(site_linux_system.LinuxSystem): 96 """Linux/mac80211-style WiFi Router support for WiFiTest class. 97 98 This class implements test methods/steps that communicate with a 99 router implemented with Linux/mac80211. The router must 100 be pre-configured to enable ssh access and have a mac80211-based 101 wireless device. We also assume hostapd 0.7.x and iw are present 102 and any necessary modules are pre-loaded. 103 104 """ 105 106 KNOWN_TEST_PREFIX = 'network_WiFi_' 107 POLLING_INTERVAL_SECONDS = 0.5 108 STARTUP_TIMEOUT_SECONDS = 30 109 SUFFIX_LETTERS = string.ascii_lowercase + string.digits 110 SUBNET_PREFIX_OCTETS = (192, 168) 111 112 HOSTAPD_CONF_FILE_PATTERN = 'hostapd-test-%s.conf' 113 HOSTAPD_LOG_FILE_PATTERN = 'hostapd-test-%s.log' 114 HOSTAPD_STDERR_LOG_FILE_PATTERN = 'hostapd-stderr-test-%s.log' 115 HOSTAPD_CONTROL_INTERFACE_PATTERN = 'hostapd-test-%s.ctrl' 116 HOSTAPD_DRIVER_NAME = 'nl80211' 117 118 MGMT_FRAME_SENDER_LOG_FILE = 'send_management_frame-test.log' 119 120 PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer' 121 122 _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available' 123 _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current' 124 125 def get_capabilities(self): 126 """@return iterable object of AP capabilities for this system.""" 127 caps = set() 128 try: 129 self.cmd_send_management_frame = path_utils.must_be_installed( 130 '/usr/bin/send_management_frame', host=self.host) 131 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) 132 except error.TestFail: 133 pass 134 return super(LinuxRouter, self).get_capabilities().union(caps) 135 136 137 @property 138 def router(self): 139 """Deprecated. Use self.host instead. 140 141 @return Host object representing the remote router. 142 143 """ 144 return self.host 145 146 147 @property 148 def wifi_ip(self): 149 """Simple accessor for the WiFi IP when there is only one AP. 150 151 @return string IP of WiFi interface. 152 153 """ 154 if len(self.local_servers) != 1: 155 raise error.TestError('Could not pick a WiFi IP to return.') 156 157 return self.get_wifi_ip(0) 158 159 160 def __init__(self, host, test_name, enable_avahi=False, role='router'): 161 """Build a LinuxRouter. 162 163 @param host Host object representing the remote machine. 164 @param test_name string name of this test. Used in SSID creation. 165 @param enable_avahi: boolean True iff avahi should be started on the 166 router. 167 @param role string description of host (e.g. router, pcap) 168 169 """ 170 super(LinuxRouter, self).__init__(host, role) 171 self._ssid_prefix = test_name 172 self._enable_avahi = enable_avahi 173 self.__setup() 174 175 176 def __setup(self): 177 """Set up this system. 178 179 Can be used either to complete initialization of a LinuxRouter 180 object, or to re-establish a good state after a reboot. 181 182 """ 183 self.cmd_dhcpd = '/usr/sbin/dhcpd' 184 self.cmd_hostapd = path_utils.must_be_installed( 185 '/usr/sbin/hostapd', host=self.host) 186 self.cmd_hostapd_cli = path_utils.must_be_installed( 187 '/usr/sbin/hostapd_cli', host=self.host) 188 self.cmd_wpa_supplicant = path_utils.must_be_installed( 189 '/usr/sbin/wpa_supplicant', host=self.host) 190 self.dhcpd_conf = os.path.join(self.logdir, 'dhcpd.%s.conf') 191 self.dhcpd_leases = os.path.join(self.logdir, 'dhcpd.leases') 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._brif_index = 0 218 219 self._total_hostapd_instances = 0 220 self.local_servers = [] 221 self.server_address_index = [] 222 self.hostapd_instances = [] 223 self.station_instances = [] 224 self.dhcp_low = 1 225 self.dhcp_high = 128 226 227 # Kill hostapd and dhcp server if already running. 228 self._kill_process_instance('hostapd', timeout_seconds=30) 229 self.stop_dhcp_server(instance=None) 230 231 # Place us in the US by default 232 self.iw_runner.set_regulatory_domain('US') 233 234 self.enable_all_antennas() 235 236 # Some tests want this functionality, but otherwise, it's a distraction. 237 if self._enable_avahi: 238 self.host.run('start avahi', ignore_status=True) 239 else: 240 self.host.run('stop avahi', ignore_status=True) 241 242 # Some routers have bad (slow?) random number generators. 243 self.rng_configure() 244 245 246 def close(self): 247 """Close global resources held by this system.""" 248 router_log = os.path.join(self.logdir, 'router_log') 249 self.deconfig() 250 # dnsmasq and hostapd cause interesting events to go to system logs. 251 # Retrieve only the suffix of the logs after the timestamp we stored on 252 # router creation. 253 self.host.run("sed -n -e '/%s/,$p' /var/log/messages >%s" % 254 (self._log_start_timestamp, router_log), 255 ignore_status=True) 256 self.host.get_file(router_log, 'debug/%s_host_messages' % self.role) 257 super(LinuxRouter, self).close() 258 259 260 def reboot(self, timeout): 261 """Reboot this router, and restore it to a known-good state. 262 263 @param timeout Maximum seconds to wait for router to return. 264 265 """ 266 super(LinuxRouter, self).reboot(timeout) 267 self.__setup() 268 269 270 def has_local_server(self): 271 """@return True iff this router has local servers configured.""" 272 return bool(self.local_servers) 273 274 275 def start_hostapd(self, configuration): 276 """Start a hostapd instance described by conf. 277 278 @param configuration HostapConfig object. 279 280 """ 281 # Figure out the correct interface. 282 interface = self.get_wlanif(configuration.frequency, 'managed', 283 configuration.min_streams) 284 phy_name = self.iw_runner.get_interface(interface).phy 285 286 conf_file = os.path.join(self.logdir, 287 self.HOSTAPD_CONF_FILE_PATTERN % interface) 288 log_file = os.path.join(self.logdir, 289 self.HOSTAPD_LOG_FILE_PATTERN % interface) 290 stderr_log_file = os.path.join(self.logdir, 291 self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface) 292 control_interface = os.path.join(self.logdir, 293 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 six.iteritems(hostapd_conf_dict)))) 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 = ( 310 'OPENSSL_CONF=/etc/ssl/openssl.cnf.compat ' 311 'OPENSSL_CHROMIUM_SKIP_TRUSTED_PURPOSE_CHECK=1 ' 312 '%s -dd -t -K %s > %s 2> %s & echo $!' % ( 313 self.cmd_hostapd, conf_file, log_file, stderr_log_file)) 314 pid = int(self.router.run(start_command).stdout.strip()) 315 self.hostapd_instances.append(HostapdInstance( 316 hostapd_conf_dict['ssid'], 317 conf_file, 318 log_file, 319 interface, 320 hostapd_conf_dict.copy(), 321 stderr_log_file, 322 configuration.scenario_name)) 323 324 # Wait for confirmation that the router came up. 325 logging.info('Waiting for hostapd to startup.') 326 utils.poll_for_condition( 327 condition=lambda: self._has_hostapd_started(log_file, pid), 328 exception=error.TestFail('Timed out while waiting for hostapd ' 329 'to start.'), 330 timeout=self.STARTUP_TIMEOUT_SECONDS, 331 sleep_interval=self.POLLING_INTERVAL_SECONDS) 332 333 if configuration.frag_threshold: 334 threshold = self.iw_runner.get_fragmentation_threshold(phy_name) 335 if threshold != configuration.frag_threshold: 336 raise error.TestNAError('Router does not support setting ' 337 'fragmentation threshold') 338 339 340 def _has_hostapd_started(self, log_file, pid): 341 """Determines if hostapd has started. 342 343 @return Whether or not hostapd has started. 344 @raise error.TestFail if there was a bad config or hostapd terminated. 345 """ 346 success = self.router.run( 347 'grep "Setup of interface done" %s' % log_file, 348 ignore_status=True).exit_status == 0 349 if success: 350 return True 351 352 # A common failure is an invalid router configuration. 353 # Detect this and exit early if we see it. 354 bad_config = self.router.run( 355 'grep "Interface initialization failed" %s' % log_file, 356 ignore_status=True).exit_status == 0 357 if bad_config: 358 raise error.TestFail('hostapd failed to initialize AP ' 359 'interface.') 360 361 if pid: 362 early_exit = self.router.run('kill -0 %d' % pid, 363 ignore_status=True).exit_status 364 if early_exit: 365 raise error.TestFail('hostapd process terminated.') 366 367 return False 368 369 370 def _kill_process_instance(self, 371 process, 372 instance=None, 373 timeout_seconds=10, 374 ignore_timeouts=False): 375 """Kill a process on the router. 376 377 Kills remote program named |process| (optionally only a specific 378 |instance|). Wait |timeout_seconds| for |process| to die 379 before returning. If |ignore_timeouts| is False, raise 380 a TestError on timeouts. 381 382 @param process: string name of process to kill. 383 @param instance: string fragment of the command line unique to 384 this instance of the remote process. 385 @param timeout_seconds: float timeout in seconds to wait. 386 @param ignore_timeouts: True iff we should ignore failures to 387 kill processes. 388 @return True iff the specified process has exited. 389 390 """ 391 if instance is not None: 392 search_arg = '-f "^%s.*%s"' % (process, instance) 393 else: 394 search_arg = process 395 396 self.host.run('pkill %s' % search_arg, ignore_status=True) 397 398 # Wait for process to die 399 time.sleep(self.POLLING_INTERVAL_SECONDS) 400 try: 401 utils.poll_for_condition( 402 condition=lambda: self.host.run( 403 'pgrep -l %s' % search_arg, 404 ignore_status=True).exit_status != 0, 405 timeout=timeout_seconds, 406 sleep_interval=self.POLLING_INTERVAL_SECONDS) 407 except utils.TimeoutError: 408 if ignore_timeouts: 409 return False 410 411 raise error.TestError( 412 'Timed out waiting for %s%s to die' % 413 (process, 414 '' if instance is None else ' (instance=%s)' % instance)) 415 return True 416 417 418 def kill_hostapd_instance(self, instance): 419 """Kills a hostapd instance. 420 421 @param instance HostapdInstance object. 422 423 """ 424 is_dead = self._kill_process_instance( 425 self.cmd_hostapd, 426 instance=instance.conf_file, 427 timeout_seconds=30, 428 ignore_timeouts=True) 429 if instance.scenario_name: 430 log_identifier = instance.scenario_name 431 else: 432 log_identifier = '%d_%s' % ( 433 self._total_hostapd_instances, instance.interface) 434 files_to_copy = [(instance.log_file, 435 'debug/hostapd_%s_%s.log' % 436 (self.role, log_identifier)), 437 (instance.stderr_log_file, 438 'debug/hostapd_%s_%s.stderr.log' % 439 (self.role, log_identifier))] 440 for remote_file, local_file in files_to_copy: 441 if self.host.run('ls %s >/dev/null 2>&1' % remote_file, 442 ignore_status=True).exit_status: 443 logging.error('Did not collect hostapd log file because ' 444 'it was missing.') 445 else: 446 self.router.get_file(remote_file, local_file) 447 self._total_hostapd_instances += 1 448 if not is_dead: 449 raise error.TestError('Timed out killing hostapd.') 450 451 452 def build_unique_ssid(self, suffix=''): 453 """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding 454 the number of APs we've constructed already. 455 456 @param suffix string to append to SSID 457 458 """ 459 base = len(self.SUFFIX_LETTERS) 460 number = self._number_unique_ssids 461 self._number_unique_ssids += 1 462 unique = '' 463 while number or not unique: 464 unique = self.SUFFIX_LETTERS[number % base] + unique 465 number = number // base 466 # And salt the SSID so that tests running in adjacent cells are unlikely 467 # to pick the same SSID and we're resistent to beacons leaking out of 468 # cells. 469 salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)]) 470 return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:] 471 472 473 def rng_configure(self): 474 """Configure the random generator to our liking. 475 476 Some routers (particularly, Gale) seem to have bad Random Number 477 Generators, such that hostapd can't always generate keys fast enough. 478 The on-board TPM seems to serve as a better generator, so we try to 479 switch to that if available. 480 481 Symptoms of a slow RNG: hostapd complains with: 482 483 WPA: Not enough entropy in random pool to proceed - reject first 484 4-way handshake 485 486 Ref: 487 https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854 488 489 Linux devices may have RNG parameters at 490 /sys/class/misc/hw_random/rng_{available,current}. See: 491 492 https://www.kernel.org/doc/Documentation/hw_random.txt 493 494 """ 495 496 available = self.host.run('cat %s' % self._RNG_AVAILABLE, \ 497 ignore_status=True).stdout.strip().split(' ') 498 # System may not have HWRNG support. Just skip this. 499 if available == "": 500 return 501 current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \ 502 strip() 503 want_rng = "tpm-rng" 504 505 logging.debug("Available / current RNGs on router: %r / %s", 506 available, current) 507 if want_rng in available and want_rng != current: 508 logging.debug("Switching RNGs: %s -> %s", current, want_rng) 509 self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT)) 510 511 512 def hostap_configure(self, configuration, multi_interface=None): 513 """Build up a hostapd configuration file and start hostapd. 514 515 Also setup a local server if this router supports them. 516 517 @param configuration HosetapConfig object. 518 @param multi_interface bool True iff multiple interfaces allowed. 519 520 """ 521 if multi_interface is None and (self.hostapd_instances or 522 self.station_instances): 523 self.deconfig() 524 if configuration.is_11ac: 525 router_caps = self.get_capabilities() 526 if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps: 527 raise error.TestNAError('Router does not have AC support') 528 529 self.start_hostapd(configuration) 530 interface = self.hostapd_instances[-1].interface 531 self.iw_runner.set_tx_power(interface, 'auto') 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 list(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 list(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 set_ap_interface_down(self, instance=0): 909 """Bring down the hostapd interface. 910 911 @param instance int router instance number. 912 913 """ 914 self.host.run('%s link set %s down' % 915 (self.cmd_ip, self.get_hostapd_interface(instance))) 916 917 918 def confirm_pmksa_cache_use(self, instance=0): 919 """Verify that the PMKSA auth was cached on a hostapd instance. 920 921 @param instance int router instance number. 922 923 """ 924 log_file = self.hostapd_instances[instance].log_file 925 pmksa_match = 'PMK from PMKSA cache' 926 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), 927 ignore_status=True) 928 if result.exit_status: 929 raise error.TestFail('PMKSA cache was not used in roaming.') 930 931 932 def get_ssid(self, instance=None): 933 """@return string ssid for the network stemming from this router.""" 934 if instance is None: 935 instance = 0 936 if len(self.hostapd_instances) > 1: 937 raise error.TestFail('No instance of hostapd specified with ' 938 'multiple instances present.') 939 940 if self.hostapd_instances: 941 return self.hostapd_instances[instance].ssid 942 943 if self.station_instances: 944 return self.station_instances[0].ssid 945 946 raise error.TestFail('Requested ssid of an unconfigured AP.') 947 948 949 def deauth_client(self, client_mac): 950 """Deauthenticates a client described in params. 951 952 @param client_mac string containing the mac address of the client to be 953 deauthenticated. 954 955 """ 956 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface'] 957 self.router.run('%s -p%s deauthenticate %s' % 958 (self.cmd_hostapd_cli, control_if, client_mac)) 959 960 def send_bss_tm_req(self, client_mac, neighbor_list): 961 """Send a BSS Transition Management Request to a client. 962 963 @param client_mac string containing the mac address of the client. 964 @param neighbor_list list of strings containing mac addresses of 965 candidate APs. 966 @return string reply received from command 967 968 """ 969 control_if = self.hostapd_instances[0].config_dict['ctrl_interface'] 970 command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' % 971 (self.cmd_hostapd_cli, control_if, client_mac, 972 ',0,0,0,0 neighbor='.join(neighbor_list))) 973 ret = self.router.run(command).stdout 974 return ret.splitlines()[-1] 975 976 def _prep_probe_response_footer(self, footer): 977 """Write probe response footer temporarily to a local file and copy 978 over to test router. 979 980 @param footer string containing bytes for the probe response footer. 981 @raises AutoservRunError: If footer file copy fails. 982 983 """ 984 with tempfile.NamedTemporaryFile() as fp: 985 fp.write(footer) 986 fp.flush() 987 try: 988 self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE) 989 except error.AutoservRunError: 990 logging.error('failed to copy footer file to AP') 991 raise 992 993 994 def send_management_frame_on_ap(self, frame_type, channel, instance=0): 995 """Injects a management frame into an active hostapd session. 996 997 @param frame_type string the type of frame to send. 998 @param channel int targeted channel 999 @param instance int indicating which hostapd instance to inject into. 1000 1001 """ 1002 hostap_interface = self.hostapd_instances[instance].interface 1003 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 1004 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 1005 self.router.run('%s -i %s -t %s -c %d' % 1006 (self.cmd_send_management_frame, interface, frame_type, 1007 channel)) 1008 self.release_interface(interface) 1009 1010 1011 def send_management_frame(self, interface, frame_type, channel, 1012 ssid_prefix=None, num_bss=None, 1013 frame_count=None, delay=None, 1014 dest_addr=None, probe_resp_footer=None): 1015 """ 1016 Injects management frames on specify channel |frequency|. 1017 1018 This function will spawn off a new process to inject specified 1019 management frames |frame_type| at the specified interface |interface|. 1020 1021 @param interface string interface to inject frames. 1022 @param frame_type string message type. 1023 @param channel int targeted channel. 1024 @param ssid_prefix string SSID prefix. 1025 @param num_bss int number of BSS. 1026 @param frame_count int number of frames to send. 1027 @param delay int milliseconds delay between frames. 1028 @param dest_addr string destination address (DA) MAC address. 1029 @param probe_resp_footer string footer for probe response. 1030 1031 @return int PID of the newly created process. 1032 1033 """ 1034 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame, 1035 interface, frame_type, channel) 1036 if ssid_prefix is not None: 1037 command += ' -s %s' % (ssid_prefix) 1038 if num_bss is not None: 1039 command += ' -b %d' % (num_bss) 1040 if frame_count is not None: 1041 command += ' -n %d' % (frame_count) 1042 if delay is not None: 1043 command += ' -d %d' % (delay) 1044 if dest_addr is not None: 1045 command += ' -a %s' % (dest_addr) 1046 if probe_resp_footer is not None: 1047 self._prep_probe_response_footer(footer=probe_resp_footer) 1048 command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE) 1049 command += ' > %s 2>&1 & echo $!' % (os.path.join(self.logdir, 1050 self.MGMT_FRAME_SENDER_LOG_FILE)) 1051 pid = int(self.router.run(command).stdout) 1052 return pid 1053 1054 1055 def detect_client_deauth(self, client_mac, instance=0): 1056 """Detects whether hostapd has logged a deauthentication from 1057 |client_mac|. 1058 1059 @param client_mac string the MAC address of the client to detect. 1060 @param instance int indicating which hostapd instance to query. 1061 1062 """ 1063 interface = self.hostapd_instances[instance].interface 1064 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 1065 log_file = self.hostapd_instances[instance].log_file 1066 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 1067 ignore_status=True) 1068 return result.exit_status == 0 1069 1070 1071 def detect_client_coexistence_report(self, client_mac, instance=0): 1072 """Detects whether hostapd has logged an action frame from 1073 |client_mac| indicating information about 20/40MHz BSS coexistence. 1074 1075 @param client_mac string the MAC address of the client to detect. 1076 @param instance int indicating which hostapd instance to query. 1077 1078 """ 1079 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 1080 '.. .. .. .. .. .. .. .. .. .. %s ' 1081 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 1082 ' '.join(client_mac.split(':'))) 1083 log_file = self.hostapd_instances[instance].log_file 1084 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 1085 ignore_status=True) 1086 return result.exit_status == 0 1087 1088 1089 def send_magic_packet(self, dest_ip, dest_mac): 1090 """Sends a magic packet to the NIC with the given IP and MAC addresses. 1091 1092 @param dest_ip the IP address of the device to send the packet to 1093 @param dest_mac the hardware MAC address of the device 1094 1095 """ 1096 # magic packet is 6 0xff bytes followed by the hardware address 1097 # 16 times 1098 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')]) 1099 magic_packet = '\xff' * 6 + mac_bytes * 16 1100 1101 logging.info('Sending magic packet to %s...', dest_ip) 1102 self.host.run('python -uc "import socket, sys;' 1103 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);' 1104 's.sendto(sys.stdin.read(), (\'%s\', %d))"' % 1105 (dest_ip, UDP_DISCARD_PORT), 1106 stdin=magic_packet) 1107 1108 1109 def setup_bridge_mode_dhcp_server(self): 1110 """Setup an DHCP server for bridge mode. 1111 1112 Setup an DHCP server on the main interface of the virtual ethernet 1113 pair, with peer interface connected to the bridge interface. This is 1114 used for testing APs in bridge mode. 1115 1116 """ 1117 # Start a local server on main interface of virtual ethernet pair. 1118 self.start_local_server( 1119 self.get_virtual_ethernet_main_interface()) 1120 # Add peer interface to the bridge. 1121 self.add_interface_to_bridge( 1122 self.get_virtual_ethernet_peer_interface()) 1123 1124 1125 def create_brif(self): 1126 """Initialize a new bridge interface 1127 1128 @return string bridge interface name 1129 """ 1130 brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, 1131 self._brif_index) 1132 self._brif_index += 1 1133 self.host.run('brctl addbr %s' % brif_name) 1134 return brif_name 1135