site_linux_router.py revision 5689d3618fd41cb5951a5fc2cc5f772c8997dab9
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 logging 6import random 7import string 8import time 9 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.common_lib.cros.network import interface 12from autotest_lib.server import site_linux_system 13from autotest_lib.server.cros import wifi_test_utils 14from autotest_lib.server.cros.network import hostap_config 15 16class LinuxRouter(site_linux_system.LinuxSystem): 17 """Linux/mac80211-style WiFi Router support for WiFiTest class. 18 19 This class implements test methods/steps that communicate with a 20 router implemented with Linux/mac80211. The router must 21 be pre-configured to enable ssh access and have a mac80211-based 22 wireless device. We also assume hostapd 0.7.x and iw are present 23 and any necessary modules are pre-loaded. 24 25 """ 26 27 KNOWN_TEST_PREFIX = 'network_WiFi' 28 STARTUP_POLLING_INTERVAL_SECONDS = 0.5 29 STARTUP_TIMEOUT_SECONDS = 10 30 SUFFIX_LETTERS = string.ascii_lowercase + string.digits 31 SUBNET_PREFIX_OCTETS = (192, 168) 32 33 HOSTAPD_CONF_FILE_PATTERN = '/tmp/hostapd-test-%s.conf' 34 HOSTAPD_LOG_FILE_PATTERN = '/tmp/hostapd-test-%s.log' 35 HOSTAPD_PID_FILE_PATTERN = '/tmp/hostapd-test-%s.pid' 36 HOSTAPD_DRIVER_NAME = 'nl80211' 37 38 STATION_CONF_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.conf' 39 STATION_LOG_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.log' 40 STATION_PID_FILE_PATTERN = '/tmp/wpa-supplicant-test-%s.pid' 41 42 def get_capabilities(self): 43 """@return iterable object of AP capabilities for this system.""" 44 caps = set([self.CAPABILITY_IBSS]) 45 try: 46 self.cmd_send_management_frame = wifi_test_utils.must_be_installed( 47 self.router, '/usr/bin/send_management_frame') 48 caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) 49 except error.TestFail: 50 pass 51 return super(LinuxRouter, self).get_capabilities().union(caps) 52 53 54 def __init__(self, host, params, test_name): 55 """Build a LinuxRouter. 56 57 @param host Host object representing the remote machine. 58 @param params dict of settings from site_wifitest based tests. 59 @param test_name string name of this test. Used in SSID creation. 60 61 """ 62 params = params.copy() 63 params.update({ 64 'phy_bus_preference': { 65 'monitor': 'usb', 66 'managed': 'pci' 67 }}) 68 site_linux_system.LinuxSystem.__init__(self, host, params, 'router') 69 70 # Router host. 71 self.router = host 72 73 self.cmd_dhcpd = '/usr/sbin/dhcpd' 74 self.cmd_hostapd = wifi_test_utils.must_be_installed( 75 host, '/usr/sbin/hostapd') 76 self.cmd_hostapd_cli = wifi_test_utils.must_be_installed( 77 host, '/usr/sbin/hostapd_cli') 78 self.cmd_wpa_supplicant = wifi_test_utils.must_be_installed( 79 host, '/usr/sbin/wpa_supplicant') 80 self.dhcpd_conf = '/tmp/dhcpd.%s.conf' 81 self.dhcpd_leases = '/tmp/dhcpd.leases' 82 83 # hostapd configuration persists throughout the test, subsequent 84 # 'config' commands only modify it. 85 self.ssid_prefix = test_name 86 if self.ssid_prefix.startswith(self.KNOWN_TEST_PREFIX): 87 # Many of our tests start with an uninteresting prefix. 88 # Remove it so we can have more unique bytes. 89 self.ssid_prefix = self.ssid_prefix[len(self.KNOWN_TEST_PREFIX):] 90 self.ssid_prefix = self.ssid_prefix.lstrip('_') 91 self.ssid_prefix += '_' 92 93 self._total_hostapd_instances = 0 94 self.station = { 95 'configured': False, 96 'config_file': "/tmp/wpa-supplicant-test-%s.conf", 97 'log_file': "/tmp/wpa-supplicant-test-%s.log", 98 'pid_file': "/tmp/wpa-supplicant-test-%s.pid", 99 'conf': {}, 100 } 101 self.local_servers = [] 102 self.hostapd_instances = [] 103 self.dhcp_low = 1 104 self.dhcp_high = 128 105 106 # Kill hostapd and dhcp server if already running. 107 self.kill_hostapd() 108 self.stop_dhcp_servers() 109 110 # Place us in the US by default 111 self.iw_runner.set_regulatory_domain('US') 112 113 114 def close(self): 115 """Close global resources held by this system.""" 116 self.destroy() 117 super(LinuxRouter, self).close() 118 119 120 def create_wifi_device(self, device_type='hostap'): 121 """Create a wifi device of the specified type. 122 123 Defaults to creating a hostap managed device. 124 125 @param device_type string device type. 126 127 """ 128 # 129 # AP mode is handled entirely by hostapd so we only 130 # have to setup others (mapping the bsd type to what 131 # iw wants) 132 # 133 # map from bsd types to iw types 134 self.apmode = device_type in ('ap', 'hostap') 135 if not self.apmode: 136 self.station['type'] = device_type 137 self.phytype = { 138 'sta' : 'managed', 139 'monitor' : 'monitor', 140 'adhoc' : 'adhoc', 141 'ibss' : 'ibss', 142 'ap' : 'managed', # NB: handled by hostapd 143 'hostap' : 'managed', # NB: handled by hostapd 144 'mesh' : 'mesh', 145 'wds' : 'wds', 146 }[device_type] 147 148 149 def destroy(self): 150 """Destroy a previously created device.""" 151 self.deconfig() 152 153 154 def has_local_server(self): 155 """@return True iff this router has local servers configured.""" 156 return bool(self.local_servers) 157 158 159 def start_hostapd(self, hostapd_conf_dict, configuration): 160 """Start a hostapd instance described by conf. 161 162 @param hostapd_conf_dict dict of hostapd configuration parameters. 163 @param configuration HostapConfig object. 164 165 """ 166 logging.info('Starting hostapd with parameters: %r', 167 hostapd_conf_dict) 168 # Figure out the correct interface. 169 interface = self.get_wlanif(configuration.frequency, 170 self.phytype, 171 configuration.hw_mode) 172 173 conf_file = self.HOSTAPD_CONF_FILE_PATTERN % interface 174 log_file = self.HOSTAPD_LOG_FILE_PATTERN % interface 175 pid_file = self.HOSTAPD_PID_FILE_PATTERN % interface 176 hostapd_conf_dict['interface'] = interface 177 178 # Generate hostapd.conf. 179 self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 180 (conf_file, '\n'.join( 181 "%s=%s" % kv for kv in hostapd_conf_dict.iteritems()))) 182 183 # Run hostapd. 184 logging.info("Starting hostapd...") 185 self.router.run('rm %s' % log_file, ignore_status=True) 186 self.router.run('rm %s' % pid_file, ignore_status=True) 187 self.router.run('stop wpasupplicant', ignore_status=True) 188 start_command = '%s -dd -B -t -f %s -P %s %s' % ( 189 self.cmd_hostapd, log_file, pid_file, conf_file) 190 self.router.run(start_command) 191 self.hostapd_instances.append({ 192 'ssid': hostapd_conf_dict['ssid'], 193 'conf_file': conf_file, 194 'log_file': log_file, 195 'interface': interface, 196 'pid_file': pid_file, 197 'config_dict': hostapd_conf_dict.copy() 198 }) 199 200 # Wait for confirmation that the router came up. 201 pid = int(self.router.run('cat %s' % pid_file).stdout) 202 logging.info('Waiting for hostapd to startup.') 203 start_time = time.time() 204 while time.time() - start_time < self.STARTUP_TIMEOUT_SECONDS: 205 success = self.router.run( 206 'grep "Completing interface initialization" %s' % log_file, 207 ignore_status=True).exit_status == 0 208 if success: 209 break 210 211 # A common failure is an invalid router configuration. 212 # Detect this and exit early if we see it. 213 bad_config = self.router.run( 214 'grep "Interface initialization failed" %s' % log_file, 215 ignore_status=True).exit_status == 0 216 if bad_config: 217 raise error.TestFail('hostapd failed to initialize AP ' 218 'interface.') 219 220 if pid: 221 early_exit = self.router.run('kill -0 %d' % pid, 222 ignore_status=True).exit_status 223 if early_exit: 224 raise error.TestFail('hostapd process terminated.') 225 226 time.sleep(self.STARTUP_POLLING_INTERVAL_SECONDS) 227 else: 228 raise error.TestFail('Timed out while waiting for hostapd ' 229 'to start.') 230 231 232 def _kill_process_instance(self, process, instance=None, wait=0): 233 """Kill a process on the router. 234 235 Kills program named |process|, optionally only a specific 236 |instance|. If |wait| is specified, we makes sure |process| exits 237 before returning. 238 239 @param process string name of process to kill. 240 @param instance string instance of process to kill. 241 @param wait int timeout in seconds to wait for. 242 243 """ 244 if instance: 245 search_arg = '-f "%s.*%s"' % (process, instance) 246 else: 247 search_arg = process 248 249 cmd = "pkill %s >/dev/null 2>&1" % search_arg 250 251 if wait: 252 cmd += (" && while pgrep %s &> /dev/null; do sleep 1; done" % 253 search_arg) 254 self.router.run(cmd, timeout=wait, ignore_status=True) 255 else: 256 self.router.run(cmd, ignore_status=True) 257 258 259 def kill_hostapd_instance(self, instance): 260 """Kills a hostapd instance. 261 262 @param instance string instance to kill. 263 264 """ 265 self._kill_process_instance('hostapd', instance, 30) 266 267 268 def kill_hostapd(self): 269 """Kill all hostapd instances.""" 270 self.kill_hostapd_instance(None) 271 272 273 def __get_default_hostap_config(self): 274 """@return dict of default options for hostapd.""" 275 return {'hw_mode': 'g', 276 'ctrl_interface': '/tmp/hostapd-test.control', 277 'logger_syslog': '-1', 278 'logger_syslog_level': '0', 279 # default RTS and frag threshold to ``off'' 280 'rts_threshold': '2347', 281 'fragm_threshold': '2346', 282 'driver': self.HOSTAPD_DRIVER_NAME, 283 'ssid': self._build_ssid('') } 284 285 286 def _build_ssid(self, suffix): 287 unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS) 288 for x in range(5)]) 289 return (self.ssid_prefix + unique_salt + suffix)[-32:] 290 291 292 def hostap_configure(self, configuration, multi_interface=None): 293 """Build up a hostapd configuration file and start hostapd. 294 295 Also setup a local server if this router supports them. 296 297 @param configuration HosetapConfig object. 298 @param multi_interface bool True iff multiple interfaces allowed. 299 300 """ 301 if multi_interface is None and (self.hostapd_instances or 302 self.station['configured']): 303 self.deconfig() 304 # Start with the default hostapd config parameters. 305 conf = self.__get_default_hostap_config() 306 conf['ssid'] = (configuration.ssid or 307 self._build_ssid(configuration.ssid_suffix)) 308 if configuration.bssid: 309 conf['bssid'] = configuration.bssid 310 conf['channel'] = configuration.channel 311 conf['hw_mode'] = configuration.hw_mode 312 if configuration.hide_ssid: 313 conf['ignore_broadcast_ssid'] = 1 314 if configuration.is_11n: 315 conf['ieee80211n'] = 1 316 conf['ht_capab'] = configuration.hostapd_ht_capabilities 317 if configuration.wmm_enabled: 318 conf['wmm_enabled'] = 1 319 if configuration.require_ht: 320 conf['require_ht'] = 1 321 if configuration.beacon_interval: 322 conf['beacon_int'] = configuration.beacon_interval 323 if configuration.dtim_period: 324 conf['dtim_period'] = configuration.dtim_period 325 if configuration.frag_threshold: 326 conf['fragm_threshold'] = configuration.frag_threshold 327 if configuration.pmf_support: 328 conf['ieee80211w'] = configuration.pmf_support 329 if configuration.obss_interval: 330 conf['obss_interval'] = configuration.obss_interval 331 conf.update(configuration.get_security_hostapd_conf()) 332 self.start_hostapd(conf, configuration) 333 interface = self.hostapd_instances[-1]['interface'] 334 self.iw_runner.set_tx_power(interface, 'auto') 335 self.start_local_server(interface) 336 logging.info('AP configured.') 337 338 339 @staticmethod 340 def ip_addr(netblock, idx): 341 """Simple IPv4 calculator. 342 343 Takes host address in "IP/bits" notation and returns netmask, broadcast 344 address as well as integer offsets into the address range. 345 346 @param netblock string host address in "IP/bits" notation. 347 @param idx string describing what to return. 348 @return string containing something you hopefully requested. 349 350 """ 351 addr_str,bits = netblock.split('/') 352 addr = map(int, addr_str.split('.')) 353 mask_bits = (-1 << (32-int(bits))) & 0xffffffff 354 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)] 355 if idx == 'local': 356 return addr_str 357 elif idx == 'netmask': 358 return '.'.join(map(str, mask)) 359 elif idx == 'broadcast': 360 offset = [m ^ 0xff for m in mask] 361 else: 362 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)] 363 return '.'.join(map(str, [(a & m) + o 364 for a, m, o in zip(addr, mask, offset)])) 365 366 367 def ibss_configure(self, config): 368 """Configure a station based AP in IBSS mode. 369 370 Extract relevant configuration objects from |config| despite not 371 actually being a hostap managed endpoint. 372 373 @param config HostapConfig object. 374 375 """ 376 if self.station['configured'] or self.hostapd_instances: 377 self.deconfig() 378 interface = self.get_wlanif(config.frequency, self.phytype, 379 config.hw_mode) 380 self.station['conf']['ssid'] = (config.ssid or 381 self._build_ssid(config.ssid_suffix)) 382 # Connect the station 383 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 384 self.iw_runner.ibss_join( 385 interface, self.station['conf']['ssid'], config.frequency) 386 # Always start a local server. 387 self.start_local_server(interface) 388 # Remember that this interface is up. 389 self.station['configured'] = True 390 self.station['interface'] = interface 391 392 393 def local_server_address(self, index): 394 """Get the local server address for an interface. 395 396 When we multiple local servers, we give them static IP addresses 397 like 192.168.*.254. 398 399 @param index int describing which local server this is for. 400 401 """ 402 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) 403 404 405 def local_peer_ip_address(self, index): 406 """Get the IP address allocated for the peer associated to the AP. 407 408 This address is assigned to a locally associated peer device that 409 is created for the DUT to perform connectivity tests with. 410 When we have multiple local servers, we give them static IP addresses 411 like 192.168.*.253. 412 413 @param index int describing which local server this is for. 414 415 """ 416 return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) 417 418 419 def local_peer_mac_address(self): 420 """Get the MAC address of the peer interface. 421 422 @return string MAC address of the peer interface. 423 """ 424 iface = interface.Interface(self.station['interface'], self.router) 425 return iface.mac_address 426 427 428 def start_local_server(self, interface): 429 """Start a local server on an interface. 430 431 @param interface string (e.g. wlan0) 432 433 """ 434 logging.info("Starting up local server...") 435 436 if len(self.local_servers) >= 256: 437 raise error.TestFail('Exhausted available local servers') 438 439 netblock = '%s/24' % self.local_server_address(len(self.local_servers)) 440 441 params = {} 442 params['netblock'] = netblock 443 params['subnet'] = self.ip_addr(netblock, 0) 444 params['netmask'] = self.ip_addr(netblock, 'netmask') 445 params['dhcp_range'] = ' '.join( 446 (self.ip_addr(netblock, self.dhcp_low), 447 self.ip_addr(netblock, self.dhcp_high))) 448 params['interface'] = interface 449 450 params['ip_params'] = ("%s broadcast %s dev %s" % 451 (netblock, 452 self.ip_addr(netblock, 'broadcast'), 453 interface)) 454 self.local_servers.append(params) 455 456 self.router.run("%s addr flush %s" % 457 (self.cmd_ip, interface)) 458 self.router.run("%s addr add %s" % 459 (self.cmd_ip, params['ip_params'])) 460 self.router.run("%s link set %s up" % 461 (self.cmd_ip, interface)) 462 self.start_dhcp_server(interface) 463 464 465 def start_dhcp_server(self, interface): 466 """Start a dhcp server on an interface. 467 468 @param interface string (e.g. wlan0) 469 470 """ 471 for server in self.local_servers: 472 if server['interface'] == interface: 473 params = server 474 break 475 else: 476 raise error.TestFail('Could not find local server ' 477 'to match interface: %r' % interface) 478 479 dhcpd_conf_file = self.dhcpd_conf % interface 480 dhcp_conf = '\n'.join([ 481 'port=0', # disables DNS server 482 'bind-interfaces', 483 'log-dhcp', 484 'dhcp-range=%s' % params['dhcp_range'].replace(' ', ','), 485 'interface=%s' % params['interface'], 486 'dhcp-leasefile=%s' % self.dhcpd_leases]) 487 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 488 (dhcpd_conf_file, dhcp_conf)) 489 self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file) 490 491 492 def stop_dhcp_server(self, instance=None): 493 """Stop a dhcp server on the router. 494 495 @param instance string instance to kill. 496 497 """ 498 self._kill_process_instance('dnsmasq', instance, 0) 499 500 501 def stop_dhcp_servers(self): 502 """Stop all dhcp servers on the router.""" 503 self.stop_dhcp_server(None) 504 505 506 def get_wifi_channel(self, ap_num): 507 """Return channel of BSS corresponding to |ap_num|. 508 509 @param ap_num int which BSS to get the channel of. 510 @return int primary channel of BSS. 511 512 """ 513 instance = self.hostapd_instances[ap_num] 514 return instance['config_dict']['channel'] 515 516 517 def get_wifi_ip(self, ap_num): 518 """Return IP address on the WiFi subnet of a local server on the router. 519 520 If no local servers are configured (e.g. for an RSPro), a TestFail will 521 be raised. 522 523 @param ap_num int which local server to get an address from. 524 525 """ 526 if self.local_servers: 527 return self.ip_addr(self.local_servers[ap_num]['netblock'], 528 'local') 529 else: 530 raise error.TestFail("No IP address assigned") 531 532 533 def get_hostapd_mac(self, ap_num): 534 """Return the MAC address of an AP in the test. 535 536 @param ap_num int index of local server to read the MAC address from. 537 @return string MAC address like 00:11:22:33:44:55. 538 539 """ 540 if not self.local_servers: 541 raise error.TestFail('Cannot retrieve MAC: ' 542 'no AP instances configured.') 543 544 instance = self.hostapd_instances[ap_num] 545 ap_interface = interface.Interface(instance['interface'], self.host) 546 return ap_interface.mac_address 547 548 549 def deconfig(self): 550 """A legacy, deprecated alias for deconfig_aps.""" 551 self.deconfig_aps() 552 553 554 def deconfig_aps(self, instance=None, silent=False): 555 """De-configure an AP (will also bring wlan down). 556 557 @param instance: int or None. If instance is None, will bring down all 558 instances of hostapd. 559 @param silent: True if instances should be brought without de-authing 560 the DUT. 561 562 """ 563 if not self.hostapd_instances and not self.station['configured']: 564 return 565 566 if self.hostapd_instances: 567 local_servers = [] 568 if instance is not None: 569 instances = [ self.hostapd_instances.pop(instance) ] 570 for server in self.local_servers: 571 if server['interface'] == instances[0]['interface']: 572 local_servers = [server] 573 self.local_servers.remove(server) 574 break 575 else: 576 instances = self.hostapd_instances 577 self.hostapd_instances = [] 578 local_servers = self.local_servers 579 self.local_servers = [] 580 581 for instance in instances: 582 if silent: 583 # Deconfigure without notifying DUT. Remove the interface 584 # hostapd uses to send beacon and DEAUTH packets. 585 self.remove_interface(instance['interface']) 586 587 self.kill_hostapd_instance(instance['conf_file']) 588 if wifi_test_utils.is_installed(self.host, 589 instance['log_file']): 590 self.router.get_file(instance['log_file'], 591 'debug/hostapd_router_%d_%s.log' % 592 (self._total_hostapd_instances, 593 instance['interface'])) 594 else: 595 logging.error('Did not collect hostapd log file because ' 596 'it was missing.') 597 self.release_interface(instance['interface']) 598# self.router.run("rm -f %(log_file)s %(conf_file)s" % instance) 599 self._total_hostapd_instances += 1 600 if self.station['configured']: 601 local_servers = self.local_servers 602 self.local_servers = [] 603 if self.station['type'] == 'ibss': 604 self.iw_runner.ibss_leave(self.station['interface']) 605 elif self.station['type'] == 'supplicant': 606 self._kill_process_instance('wpa_supplicant', 607 self.station['interface']) 608 else: 609 self.iw_runner.disconnect_station(self.station['interface']) 610 self.router.run("%s link set %s down" % (self.cmd_ip, 611 self.station['interface'])) 612 613 for server in local_servers: 614 self.stop_dhcp_server(server['interface']) 615 self.router.run("%s addr del %s" % 616 (self.cmd_ip, server['ip_params']), 617 ignore_status=True) 618 619 self.station['configured'] = False 620 621 622 def confirm_pmksa_cache_use(self, instance=0): 623 """Verify that the PMKSA auth was cached on a hostapd instance. 624 625 @param instance int router instance number. 626 627 """ 628 log_file = self.hostapd_instances[instance]['log_file'] 629 pmksa_match = 'PMK from PMKSA cache' 630 result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), 631 ignore_status=True) 632 if result.exit_status: 633 raise error.TestFail('PMKSA cache was not used in roaming.') 634 635 636 def get_ssid(self, instance=None): 637 """@return string ssid for the network stemming from this router.""" 638 if instance is None: 639 instance = 0 640 if len(self.hostapd_instances) > 1: 641 raise error.TestFail('No instance of hostapd specified with ' 642 'multiple instances present.') 643 644 if self.hostapd_instances: 645 return self.hostapd_instances[instance]['ssid'] 646 647 if not 'ssid' in self.station['conf']: 648 raise error.TestFail('Requested ssid of an unconfigured AP.') 649 650 return self.station['conf']['ssid'] 651 652 653 def deauth_client(self, client_mac): 654 """Deauthenticates a client described in params. 655 656 @param client_mac string containing the mac address of the client to be 657 deauthenticated. 658 659 """ 660 self.router.run('%s -p%s deauthenticate %s' % 661 (self.cmd_hostapd_cli, 662 self.hostapd_instances[-1]['ctrl_interface'], 663 client_mac)) 664 665 666 def send_management_frame(self, frame_type, instance=0): 667 """Injects a management frame into an active hostapd session. 668 669 @param frame_type string the type of frame to send. 670 @param instance int indicating which hostapd instance to inject into. 671 672 """ 673 hostap_interface = self.hostapd_instances[instance]['interface'] 674 interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 675 self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 676 self.router.run('%s %s %s' % 677 (self.cmd_send_management_frame, interface, frame_type)) 678 self.release_interface(interface) 679 680 681 def detect_client_deauth(self, client_mac, instance=0): 682 """Detects whether hostapd has logged a deauthentication from 683 |client_mac|. 684 685 @param client_mac string the MAC address of the client to detect. 686 @param instance int indicating which hostapd instance to query. 687 688 """ 689 interface = self.hostapd_instances[instance]['interface'] 690 deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 691 log_file = self.hostapd_instances[instance]['log_file'] 692 result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 693 ignore_status=True) 694 return result.exit_status == 0 695 696 697 def detect_client_coexistence_report(self, client_mac, instance=0): 698 """Detects whether hostapd has logged an action frame from 699 |client_mac| indicating information about 20/40MHz BSS coexistence. 700 701 @param client_mac string the MAC address of the client to detect. 702 @param instance int indicating which hostapd instance to query. 703 704 """ 705 coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 706 '.. .. .. .. .. .. .. .. .. .. %s ' 707 '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 708 ' '.join(client_mac.split(':'))) 709 log_file = self.hostapd_instances[instance]['log_file'] 710 result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 711 ignore_status=True) 712 return result.exit_status == 0 713 714 715 def add_connected_peer(self, instance=0): 716 """Configure a station connected to a running AP instance. 717 718 Extract relevant configuration objects from the hostap 719 configuration for |instance| and generate a wpa_supplicant 720 instance that connects to it. This allows the DUT to interact 721 with a client entity that is also connected to the same AP. A 722 full wpa_supplicant instance is necessary here (instead of just 723 using the "iw" command to connect) since we want to enable 724 advanced features such as TDLS. 725 726 @param instance int indicating which hostapd instance to connect to. 727 728 """ 729 if not self.hostapd_instances: 730 raise error.TestFail('Hostapd is not configured.') 731 732 if self.station['configured']: 733 raise error.TestFail('Station is already configured.') 734 735 client_conf = self.station['conf'] 736 client_conf['ssid'] = self.get_ssid(instance) 737 738 hostap_conf = self.hostapd_instances[instance]['config_dict'] 739 frequency = hostap_config.HostapConfig.get_frequency_for_channel( 740 hostap_conf['channel']) 741 interface = self.get_wlanif( 742 frequency, 'managed', hostap_conf['hw_mode']) 743 client_conf['interface'] = interface 744 745 # TODO(pstew): Configure other bits like PSK, 802.11n if tests 746 # require them... 747 supplicant_config = ( 748 'network={\n' 749 ' ssid="%(ssid)s"\n' 750 ' key_mgmt=NONE\n' 751 '}\n' % client_conf 752 ) 753 754 conf_file = self.STATION_CONF_FILE_PATTERN % interface 755 log_file = self.STATION_LOG_FILE_PATTERN % interface 756 pid_file = self.STATION_PID_FILE_PATTERN % interface 757 758 self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 759 (conf_file, supplicant_config)) 760 761 # Connect the station. 762 self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 763 start_command = ('%s -dd -t -i%s -P%s -c%s -D%s &> %s &' % 764 (self.cmd_wpa_supplicant, 765 interface, pid_file, conf_file, 766 self.HOSTAPD_DRIVER_NAME, log_file)) 767 self.router.run(start_command) 768 self.iw_runner.wait_for_link(interface) 769 770 # Assign an IP address to this interface. 771 self.router.run('%s addr add %s/24 dev %s' % 772 (self.cmd_ip, self.local_peer_ip_address(instance), 773 interface)) 774 775 # Since we now have two network interfaces connected to the same 776 # network, we need to disable the kernel's protection against 777 # incoming packets to an "unexpected" interface. 778 self.router.run('echo 2 > /proc/sys/net/ipv4/conf/%s/rp_filter' % 779 interface) 780 781 # Similarly, we'd like to prevent the hostap interface from 782 # replying to ARP requests for the peer IP address and vice 783 # versa. 784 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 785 interface) 786 self.router.run('echo 1 > /proc/sys/net/ipv4/conf/%s/arp_ignore' % 787 hostap_conf['interface']) 788 789 self.station['configured'] = True 790 self.station['type'] = 'supplicant' 791 self.station['interface'] = interface 792