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