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