site_linux_router.py revision 6b1d9e7eb7cc38012597d1ce4dcae2a6f39175a3
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.network import interface 13from autotest_lib.client.common_lib.cros.network import netblock 14from autotest_lib.client.common_lib.cros.network import ping_runner 15from autotest_lib.server import hosts 16from autotest_lib.server import site_linux_system 17from autotest_lib.server.cros import dnsname_mangler 18from autotest_lib.server.cros import wifi_test_utils 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 = wifi_test_utils.must_be_installed( 108 self.host, '/usr/bin/send_management_frame') 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 = wifi_test_utils.must_be_installed( 149 host, '/usr/sbin/hostapd') 150 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed( 151 host, '/usr/sbin/hostapd_cli') 152 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed( 153 host, '/usr/sbin/wpa_supplicant') 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(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 self.start_hostapd(configuration) 387 interface = self.hostapd_instances[-1].interface 388 self.iw_runner.set_tx_power(interface, 'auto') 389 self.set_beacon_footer(interface, configuration.beacon_footer) 390 self.start_local_server(interface) 391 logging.info('AP configured.') 392 393 394 def ibss_configure(self, config): 395 """Configure a station based AP in IBSS mode. 396 397 Extract relevant configuration objects from |config| despite not 398 actually being a hostap managed endpoint. 399 400 @param config HostapConfig object. 401 402 """ 403 if self.station_instances or self.hostapd_instances: 404 self.deconfig() 405 interface = self.get_wlanif(config.frequency, 'ibss') 406 ssid = (config.ssid or self._build_unique_ssid(config.ssid_suffix)) 407 # Connect the station 408 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 409 self.iw_runner.ibss_join(interface, ssid, config.frequency) 410 # Always start a local server. 411 self.start_local_server(interface) 412 # Remember that this interface is up. 413 self.station_instances.append( 414 StationInstance(ssid=ssid, interface=interface, 415 dev_type='ibss')) 416 417 418 def local_server_address(self, index): 419 """Get the local server address for an interface. 420 421 When we multiple local servers, we give them static IP addresses 422 like 192.168.*.254. 423 424 @param index int describing which local server this is for. 425 426 """ 427 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) 428 429 430 def local_peer_ip_address(self, index): 431 """Get the IP address allocated for the peer associated to the AP. 432 433 This address is assigned to a locally associated peer device that 434 is created for the DUT to perform connectivity tests with. 435 When we have multiple local servers, we give them static IP addresses 436 like 192.168.*.253. 437 438 @param index int describing which local server this is for. 439 440 """ 441 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) 442 443 444 def local_peer_mac_address(self): 445 """Get the MAC address of the peer interface. 446 447 @return string MAC address of the peer interface. 448 449 """ 450 iface = interface.Interface(self.station_instances[0].interface, 451 self.router) 452 return iface.mac_address 453 454 455 def start_local_server(self, interface): 456 """Start a local server on an interface. 457 458 @param interface string (e.g. wlan0) 459 460 """ 461 logging.info('Starting up local server...') 462 463 if len(self.local_servers) >= 256: 464 raise error.TestFail('Exhausted available local servers') 465 466 server_addr = netblock.Netblock.from_addr( 467 self.local_server_address(len(self.local_servers)), 468 prefix_len=24) 469 470 params = {} 471 params['netblock'] = server_addr 472 params['dhcp_range'] = ' '.join( 473 (server_addr.get_addr_in_block(1), 474 server_addr.get_addr_in_block(128))) 475 params['interface'] = interface 476 params['ip_params'] = ('%s broadcast %s dev %s' % 477 (server_addr.netblock, 478 server_addr.broadcast, 479 interface)) 480 self.local_servers.append(params) 481 482 self.router.run('%s addr flush %s' % 483 (self.cmd_ip, interface)) 484 self.router.run('%s addr add %s' % 485 (self.cmd_ip, params['ip_params'])) 486 self.router.run('%s link set %s up' % 487 (self.cmd_ip, interface)) 488 self.start_dhcp_server(interface) 489 490 491 def start_dhcp_server(self, interface): 492 """Start a dhcp server on an interface. 493 494 @param interface string (e.g. wlan0) 495 496 """ 497 for server in self.local_servers: 498 if server['interface'] == interface: 499 params = server 500 break 501 else: 502 raise error.TestFail('Could not find local server ' 503 'to match interface: %r' % interface) 504 server_addr = params['netblock'] 505 dhcpd_conf_file = self.dhcpd_conf % interface 506 dhcp_conf = '\n'.join([ 507 'port=0', # disables DNS server 508 'bind-interfaces', 509 'log-dhcp', 510 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1), 511 server_addr.get_addr_in_block(128))), 512 'interface=%s' % params['interface'], 513 'dhcp-leasefile=%s' % self.dhcpd_leases]) 514 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 515 (dhcpd_conf_file, dhcp_conf)) 516 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file) 517 518 519 def stop_dhcp_server(self, instance=None): 520 """Stop a dhcp server on the router. 521 522 @param instance string instance to kill. 523 524 """ 525 self._kill_process_instance('dnsmasq', instance=instance) 526 527 528 def get_wifi_channel(self, ap_num): 529 """Return channel of BSS corresponding to |ap_num|. 530 531 @param ap_num int which BSS to get the channel of. 532 @return int primary channel of BSS. 533 534 """ 535 instance = self.hostapd_instances[ap_num] 536 return instance.config_dict['channel'] 537 538 539 def get_wifi_ip(self, ap_num): 540 """Return IP address on the WiFi subnet of a local server on the router. 541 542 If no local servers are configured (e.g. for an RSPro), a TestFail will 543 be raised. 544 545 @param ap_num int which local server to get an address from. 546 547 """ 548 if not self.local_servers: 549 raise error.TestError('No IP address assigned') 550 551 return self.local_servers[ap_num]['netblock'].addr 552 553 554 def get_wifi_ip_subnet(self, ap_num): 555 """Return subnet of WiFi AP instance. 556 557 If no APs are configured a TestError will be raised. 558 559 @param ap_num int which local server to get an address from. 560 561 """ 562 if not self.local_servers: 563 raise error.TestError('No APs configured.') 564 565 return self.local_servers[ap_num]['netblock'].subnet 566 567 568 def get_hostapd_interface(self, ap_num): 569 """Get the name of the interface associated with a hostapd instance. 570 571 @param ap_num: int hostapd instance number. 572 @return string interface name (e.g. 'managed0'). 573 574 """ 575 if ap_num not in range(len(self.hostapd_instances)): 576 raise error.TestFail('Invalid instance number (%d) with %d ' 577 'instances configured.' % 578 (ap_num, len(self.hostapd_instances))) 579 580 instance = self.hostapd_instances[ap_num] 581 return instance.interface 582 583 584 def get_hostapd_mac(self, ap_num): 585 """Return the MAC address of an AP in the test. 586 587 @param ap_num int index of local server to read the MAC address from. 588 @return string MAC address like 00:11:22:33:44:55. 589 590 """ 591 interface_name = self.get_hostapd_interface(ap_num) 592 ap_interface = interface.Interface(interface_name, self.host) 593 return ap_interface.mac_address 594 595 596 def get_hostapd_phy(self, ap_num): 597 """Get name of phy for hostapd instance. 598 599 @param ap_num int index of hostapd instance. 600 @return string phy name of phy corresponding to hostapd's 601 managed interface. 602 603 """ 604 interface = self.iw_runner.get_interface( 605 self.get_hostapd_interface(ap_num)) 606 return interface.phy 607 608 609 def deconfig(self): 610 """A legacy, deprecated alias for deconfig_aps.""" 611 self.deconfig_aps() 612 613 614 def deconfig_aps(self, instance=None, silent=False): 615 """De-configure an AP (will also bring wlan down). 616 617 @param instance: int or None. If instance is None, will bring down all 618 instances of hostapd. 619 @param silent: True if instances should be brought without de-authing 620 the DUT. 621 622 """ 623 if not self.hostapd_instances and not self.station_instances: 624 return 625 626 if self.hostapd_instances: 627 local_servers = [] 628 if instance is not None: 629 instances = [ self.hostapd_instances.pop(instance) ] 630 for server in self.local_servers: 631 if server['interface'] == instances[0].interface: 632 local_servers = [server] 633 self.local_servers.remove(server) 634 break 635 else: 636 instances = self.hostapd_instances 637 self.hostapd_instances = [] 638 local_servers = self.local_servers 639 self.local_servers = [] 640 641 for instance in instances: 642 if silent: 643 # Deconfigure without notifying DUT. Remove the interface 644 # hostapd uses to send beacon and DEAUTH packets. 645 self.remove_interface(instance.interface) 646 647 self.kill_hostapd_instance(instance) 648 self.release_interface(instance.interface) 649 if self.station_instances: 650 local_servers = self.local_servers 651 self.local_servers = [] 652 instance = self.station_instances.pop() 653 if instance.dev_type == 'ibss': 654 self.iw_runner.ibss_leave(instance.interface) 655 elif instance.dev_type == 'managed': 656 self._kill_process_instance(self.cmd_wpa_supplicant, 657 instance=instance.interface) 658 else: 659 self.iw_runner.disconnect_station(instance.interface) 660 self.router.run('%s link set %s down' % 661 (self.cmd_ip, instance.interface)) 662 663 for server in local_servers: 664 self.stop_dhcp_server(server['interface']) 665 self.router.run("%s addr del %s" % 666 (self.cmd_ip, server['ip_params']), 667 ignore_status=True) 668 669 670 def confirm_pmksa_cache_use(self, instance=0): 671 """Verify that the PMKSA auth was cached on a hostapd instance. 672 673 @param instance int router instance number. 674 675 """ 676 log_file = self.hostapd_instances[instance].log_file 677 pmksa_match = 'PMK from PMKSA cache' 678 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), 679 ignore_status=True) 680 if result.exit_status: 681 raise error.TestFail('PMKSA cache was not used in roaming.') 682 683 684 def get_ssid(self, instance=None): 685 """@return string ssid for the network stemming from this router.""" 686 if instance is None: 687 instance = 0 688 if len(self.hostapd_instances) > 1: 689 raise error.TestFail('No instance of hostapd specified with ' 690 'multiple instances present.') 691 692 if self.hostapd_instances: 693 return self.hostapd_instances[instance].ssid 694 695 if self.station_instances: 696 return self.station_instances[0].ssid 697 698 raise error.TestFail('Requested ssid of an unconfigured AP.') 699 700 701 def deauth_client(self, client_mac): 702 """Deauthenticates a client described in params. 703 704 @param client_mac string containing the mac address of the client to be 705 deauthenticated. 706 707 """ 708 control_if = self.hostapd_instances[-1].config_dict['ctrl_interface'] 709 self.router.run('%s -p%s deauthenticate %s' % 710 (self.cmd_hostapd_cli, control_if, client_mac)) 711 712 713 def send_management_frame_on_ap(self, frame_type, channel, instance=0): 714 """Injects a management frame into an active hostapd session. 715 716 @param frame_type string the type of frame to send. 717 @param channel int targeted channel 718 @param instance int indicating which hostapd instance to inject into. 719 720 """ 721 hostap_interface = self.hostapd_instances[instance].interface 722 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 723 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 724 self.router.run('%s -i %s -t %s -c %d' % 725 (self.cmd_send_management_frame, interface, frame_type, 726 channel)) 727 self.release_interface(interface) 728 729 730 def setup_management_frame_interface(self, channel): 731 """ 732 Setup interface for injecting management frames. 733 734 @param channel int channel to inject the frames. 735 736 @return string name of the interface. 737 738 """ 739 frequency = hostap_config.HostapConfig.get_frequency_for_channel( 740 channel) 741 interface = self.get_wlanif(frequency, 'monitor') 742 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 743 self.iw_runner.set_freq(interface, frequency) 744 return interface 745 746 747 def send_management_frame(self, interface, frame_type, channel, 748 ssid_prefix=None, num_bss=None, 749 frame_count=None, delay=None): 750 """ 751 Injects management frames on specify channel |frequency|. 752 753 This function will spawn off a new process to inject specified 754 management frames |frame_type| at the specified interface |interface|. 755 756 @param interface string interface to inject frames. 757 @param frame_type string message type. 758 @param channel int targeted channel. 759 @param ssid_prefix string SSID prefix. 760 @param num_bss int number of BSS. 761 @param frame_count int number of frames to send. 762 @param delay int milliseconds delay between frames. 763 764 @return int PID of the newly created process. 765 766 """ 767 command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame, 768 interface, frame_type, channel) 769 if ssid_prefix is not None: 770 command += ' -s %s' % (ssid_prefix) 771 if num_bss is not None: 772 command += ' -b %d' % (num_bss) 773 if frame_count is not None: 774 command += ' -n %d' % (frame_count) 775 if delay is not None: 776 command += ' -d %d' % (delay) 777 command += ' > %s 2>&1 & echo $!' % (self.MGMT_FRAME_SENDER_LOG_FILE) 778 pid = int(self.router.run(command).stdout) 779 return pid 780 781 782 def detect_client_deauth(self, client_mac, instance=0): 783 """Detects whether hostapd has logged a deauthentication from 784 |client_mac|. 785 786 @param client_mac string the MAC address of the client to detect. 787 @param instance int indicating which hostapd instance to query. 788 789 """ 790 interface = self.hostapd_instances[instance].interface 791 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 792 log_file = self.hostapd_instances[instance].log_file 793 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 794 ignore_status=True) 795 return result.exit_status == 0 796 797 798 def detect_client_coexistence_report(self, client_mac, instance=0): 799 """Detects whether hostapd has logged an action frame from 800 |client_mac| indicating information about 20/40MHz BSS coexistence. 801 802 @param client_mac string the MAC address of the client to detect. 803 @param instance int indicating which hostapd instance to query. 804 805 """ 806 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 807 '.. .. .. .. .. .. .. .. .. .. %s ' 808 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 809 ' '.join(client_mac.split(':'))) 810 log_file = self.hostapd_instances[instance].log_file 811 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 812 ignore_status=True) 813 return result.exit_status == 0 814 815 816 def add_connected_peer(self, instance=0): 817 """Configure a station connected to a running AP instance. 818 819 Extract relevant configuration objects from the hostap 820 configuration for |instance| and generate a wpa_supplicant 821 instance that connects to it. This allows the DUT to interact 822 with a client entity that is also connected to the same AP. A 823 full wpa_supplicant instance is necessary here (instead of just 824 using the "iw" command to connect) since we want to enable 825 advanced features such as TDLS. 826 827 @param instance int indicating which hostapd instance to connect to. 828 829 """ 830 if not self.hostapd_instances: 831 raise error.TestFail('Hostapd is not configured.') 832 833 if self.station_instances: 834 raise error.TestFail('Station is already configured.') 835 836 ssid = self.get_ssid(instance) 837 hostap_conf = self.hostapd_instances[instance].config_dict 838 frequency = hostap_config.HostapConfig.get_frequency_for_channel( 839 hostap_conf['channel']) 840 interface = self.get_wlanif(frequency, 'managed') 841 842 # TODO(pstew): Configure other bits like PSK, 802.11n if tests 843 # require them... 844 supplicant_config = ( 845 'network={\n' 846 ' ssid="%(ssid)s"\n' 847 ' key_mgmt=NONE\n' 848 '}\n' % {'ssid': ssid} 849 ) 850 851 conf_file = self.STATION_CONF_FILE_PATTERN % interface 852 log_file = self.STATION_LOG_FILE_PATTERN % interface 853 pid_file = self.STATION_PID_FILE_PATTERN % interface 854 855 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 856 (conf_file, supplicant_config)) 857 858 # Connect the station. 859 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 860 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' % 861 (self.cmd_wpa_supplicant, 862 interface, pid_file, conf_file, 863 self.HOSTAPD_DRIVER_NAME, log_file)) 864 self.router.run(start_command) 865 self.iw_runner.wait_for_link(interface) 866 867 # Assign an IP address to this interface. 868 self.router.run('%s addr add %s/24 dev %s' % 869 (self.cmd_ip, self.local_peer_ip_address(instance), 870 interface)) 871 872 # Since we now have two network interfaces connected to the same 873 # network, we need to disable the kernel's protection against 874 # incoming packets to an "unexpected" interface. 875 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' % 876 interface) 877 878 # Similarly, we'd like to prevent the hostap interface from 879 # replying to ARP requests for the peer IP address and vice 880 # versa. 881 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 882 interface) 883 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 884 hostap_conf['interface']) 885 886 self.station_instances.append( 887 StationInstance(ssid=ssid, interface=interface, 888 dev_type='managed')) 889 890 891 def send_magic_packet(self, dest_ip, dest_mac): 892 """Sends a magic packet to the NIC with the given IP and MAC addresses. 893 894 @param dest_ip the IP address of the device to send the packet to 895 @param dest_mac the hardware MAC address of the device 896 897 """ 898 # magic packet is 6 0xff bytes followed by the hardware address 899 # 16 times 900 mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')]) 901 magic_packet = '\xff' * 6 + mac_bytes * 16 902 903 logging.info('Sending magic packet to %s...', dest_ip) 904 self.host.run('python -uc "import socket, sys;' 905 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);' 906 's.sendto(sys.stdin.read(), (\'%s\', %d))"' % 907 (dest_ip, UDP_DISCARD_PORT), 908 stdin=magic_packet) 909 910 911 def set_beacon_footer(self, interface, footer=''): 912 """Sets the beacon footer (appended IE information) for this interface. 913 914 @param interface string interface to set the footer on. 915 @param footer string footer to be set on the interface. 916 917 """ 918 footer_file = ('/sys/kernel/debug/ieee80211/%s/beacon_footer' % 919 self.iw_runner.get_interface(interface).phy) 920 if self.router.run('test -e %s' % footer_file, 921 ignore_status=True).exit_status != 0: 922 logging.info('Beacon footer file does not exist. Ignoring.') 923 return 924 self.host.run('echo -ne %s > %s' % ('%r' % footer, footer_file)) 925