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