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