1# Copyright (c) 2013 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 math 7import os 8import re 9import time 10 11from contextlib import contextmanager 12from collections import namedtuple 13 14from autotest_lib.client.bin import site_utils as client_site_utils 15from autotest_lib.client.bin import utils 16from autotest_lib.client.common_lib import base_utils 17from autotest_lib.client.common_lib import error 18from autotest_lib.client.common_lib.cros.network import interface 19from autotest_lib.client.common_lib.cros.network import iw_runner 20from autotest_lib.client.common_lib.cros.network import ping_runner 21from autotest_lib.client.cros import constants 22from autotest_lib.server import autotest 23from autotest_lib.server import frontend 24from autotest_lib.server import site_linux_system 25from autotest_lib.server import site_utils 26from autotest_lib.server.cros.network import wpa_cli_proxy 27from autotest_lib.server.hosts import adb_host 28 29# Wake-on-WiFi feature strings 30WAKE_ON_WIFI_NONE = 'none' 31WAKE_ON_WIFI_PACKET = 'packet' 32WAKE_ON_WIFI_DARKCONNECT = 'darkconnect' 33WAKE_ON_WIFI_PACKET_DARKCONNECT = 'packet_and_darkconnect' 34WAKE_ON_WIFI_NOT_SUPPORTED = 'not_supported' 35 36# Wake-on-WiFi test timing constants 37SUSPEND_WAIT_TIME_SECONDS = 10 38RECEIVE_PACKET_WAIT_TIME_SECONDS = 10 39DARK_RESUME_WAIT_TIME_SECONDS = 25 40WAKE_TO_SCAN_PERIOD_SECONDS = 30 41NET_DETECT_SCAN_WAIT_TIME_SECONDS = 15 42WAIT_UP_TIMEOUT_SECONDS = 10 43DISCONNECT_WAIT_TIME_SECONDS = 10 44INTERFACE_DOWN_WAIT_TIME_SECONDS = 10 45 46ConnectTime = namedtuple('ConnectTime', 'state, time') 47 48XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60 49SHILL_XMLRPC_LOG_PATH = '/var/log/shill_xmlrpc_server.log' 50SHILL_BRILLO_XMLRPC_LOG_PATH = '/data/shill_xmlrpc_server.log' 51ANDROID_XMLRPC_LOG_PATH = '/var/log/android_xmlrpc_server.log' 52ANDROID_XMLRPC_SERVER_AUTOTEST_PATH = ( 53 '../../../client/cros/networking/android_xmlrpc_server.py') 54 55def install_android_xmlrpc_server(host): 56 """Install Android XMLRPC server script on |host|. 57 58 @param host: host object representing a remote device. 59 60 """ 61 current_dir = os.path.dirname(os.path.realpath(__file__)) 62 xmlrpc_server_script = os.path.join( 63 current_dir, ANDROID_XMLRPC_SERVER_AUTOTEST_PATH) 64 host.send_file( 65 xmlrpc_server_script, constants.ANDROID_XMLRPC_SERVER_TARGET_DIR) 66 67 68def get_xmlrpc_proxy(host): 69 """Get a shill XMLRPC proxy for |host|. 70 71 The returned object has no particular type. Instead, when you call 72 a method on the object, it marshalls the objects passed as arguments 73 and uses them to make RPCs on the remote server. Thus, you should 74 read shill_xmlrpc_server.py to find out what methods are supported. 75 76 @param host: host object representing a remote device. 77 @return proxy object for remote XMLRPC server. 78 79 """ 80 # Make sure the client library is on the device so that the proxy 81 # code is there when we try to call it. 82 if host.is_client_install_supported: 83 client_at = autotest.Autotest(host) 84 client_at.install() 85 if host.get_os_type() == adb_host.OS_TYPE_BRILLO: 86 xmlrpc_server_command = constants.SHILL_BRILLO_XMLRPC_SERVER_COMMAND 87 log_path = SHILL_BRILLO_XMLRPC_LOG_PATH 88 command_name = constants.SHILL_BRILLO_XMLRPC_SERVER_CLEANUP_PATTERN 89 elif host.get_os_type() == adb_host.OS_TYPE_ANDROID: 90 xmlrpc_server_command = constants.ANDROID_XMLRPC_SERVER_COMMAND 91 log_path = ANDROID_XMLRPC_LOG_PATH 92 command_name = constants.ANDROID_XMLRPC_SERVER_CLEANUP_PATTERN 93 # If there is more than one device attached to the host, use serial to 94 # identify the DUT. 95 if host.adb_serial: 96 xmlrpc_server_command = ( 97 '%s -s %s' % (xmlrpc_server_command, host.adb_serial)) 98 # For android, start the XML RPC server on the accompanying host. 99 host = host.teststation 100 install_android_xmlrpc_server(host) 101 else: 102 xmlrpc_server_command = constants.SHILL_XMLRPC_SERVER_COMMAND 103 log_path = SHILL_XMLRPC_LOG_PATH 104 command_name = constants.SHILL_XMLRPC_SERVER_CLEANUP_PATTERN 105 # Start up the XMLRPC proxy on the client 106 proxy = host.rpc_server_tracker.xmlrpc_connect( 107 xmlrpc_server_command, 108 constants.SHILL_XMLRPC_SERVER_PORT, 109 command_name=command_name, 110 ready_test_name=constants.SHILL_XMLRPC_SERVER_READY_METHOD, 111 timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS, 112 logfile=log_path 113 ) 114 return proxy 115 116 117def _is_conductive(hostname): 118 if utils.host_could_be_in_afe(hostname): 119 conductive = site_utils.get_label_from_afe(hostname.split('.')[0], 120 'conductive:', 121 frontend.AFE()) 122 if conductive and conductive.lower() == 'true': 123 return True 124 return False 125 126 127class WiFiClient(site_linux_system.LinuxSystem): 128 """WiFiClient is a thin layer of logic over a remote DUT in wifitests.""" 129 130 DEFAULT_PING_COUNT = 10 131 COMMAND_PING = 'ping' 132 133 MAX_SERVICE_GONE_TIMEOUT_SECONDS = 60 134 135 # List of interface names we won't consider for use as "the" WiFi interface 136 # on Android hosts. 137 WIFI_IF_BLACKLIST = ['p2p0'] 138 139 UNKNOWN_BOARD_TYPE = 'unknown' 140 141 # DBus device properties. Wireless interfaces should support these. 142 ROAM_THRESHOLD = 'RoamThreshold' 143 WAKE_ON_WIFI_FEATURES = 'WakeOnWiFiFeaturesEnabled' 144 NET_DETECT_SCAN_PERIOD = 'NetDetectScanPeriodSeconds' 145 WAKE_TO_SCAN_PERIOD = 'WakeToScanPeriodSeconds' 146 FORCE_WAKE_TO_SCAN_TIMER = 'ForceWakeToScanTimer' 147 148 CONNECTED_STATES = ['ready', 'portal', 'online'] 149 150 151 @property 152 def machine_id(self): 153 """@return string unique to a particular board/cpu configuration.""" 154 if self._machine_id: 155 return self._machine_id 156 157 uname_result = self.host.run('uname -m', ignore_status=True) 158 kernel_arch = '' 159 if not uname_result.exit_status and uname_result.stdout.find(' ') < 0: 160 kernel_arch = uname_result.stdout.strip() 161 cpu_info = self.host.run('cat /proc/cpuinfo').stdout.splitlines() 162 cpu_count = len(filter(lambda x: x.lower().startswith('bogomips'), 163 cpu_info)) 164 cpu_count_str = '' 165 if cpu_count: 166 cpu_count_str = 'x%d' % cpu_count 167 ghz_value = '' 168 ghz_pattern = re.compile('([0-9.]+GHz)') 169 for line in cpu_info: 170 match = ghz_pattern.search(line) 171 if match is not None: 172 ghz_value = '_' + match.group(1) 173 break 174 175 return '%s_%s%s%s' % (self.board, kernel_arch, ghz_value, cpu_count_str) 176 177 178 @property 179 def powersave_on(self): 180 """@return bool True iff WiFi powersave mode is enabled.""" 181 result = self.host.run("iw dev %s get power_save" % self.wifi_if) 182 output = result.stdout.rstrip() # NB: chop \n 183 # Output should be either "Power save: on" or "Power save: off". 184 find_re = re.compile('([^:]+):\s+(\w+)') 185 find_results = find_re.match(output) 186 if not find_results: 187 raise error.TestFail('Failed to find power_save parameter ' 188 'in iw results.') 189 190 return find_results.group(2) == 'on' 191 192 193 @property 194 def shill(self): 195 """@return shill RPCProxy object.""" 196 return self._shill_proxy 197 198 199 @property 200 def client(self): 201 """Deprecated accessor for the client host. 202 203 The term client is used very loosely in old autotests and this 204 accessor should not be used in new code. Use host() instead. 205 206 @return host object representing a remote DUT. 207 208 """ 209 return self.host 210 211 212 @property 213 def command_ip(self): 214 """@return string path to ip command.""" 215 return self._command_ip 216 217 218 @property 219 def command_iptables(self): 220 """@return string path to iptables command.""" 221 return self._command_iptables 222 223 224 @property 225 def command_ping6(self): 226 """@return string path to ping6 command.""" 227 return self._command_ping6 228 229 230 @property 231 def command_wpa_cli(self): 232 """@return string path to wpa_cli command.""" 233 return self._command_wpa_cli 234 235 236 @property 237 def conductive(self): 238 """@return True if the rig is conductive; False otherwise.""" 239 if self._conductive is None: 240 self._conductive = _is_conductive(self._client_hostname) 241 return self._conductive 242 243 244 @conductive.setter 245 def conductive(self, value): 246 """Set the conductive member to True or False. 247 248 @param value: boolean value to set the conductive member to. 249 """ 250 self._conductive = value 251 252 253 @property 254 def wifi_if(self): 255 """@return string wifi device on machine (e.g. mlan0).""" 256 return self._wifi_if 257 258 259 @property 260 def wifi_mac(self): 261 """@return string MAC address of self.wifi_if.""" 262 return self._interface.mac_address 263 264 265 @property 266 def wifi_ip(self): 267 """@return string IPv4 address of self.wifi_if.""" 268 return self._interface.ipv4_address 269 270 271 @property 272 def wifi_ip_subnet(self): 273 """@return string IPv4 subnet prefix of self.wifi_if.""" 274 return self._interface.ipv4_subnet 275 276 277 @property 278 def wifi_signal_level(self): 279 """Returns the signal level of this DUT's WiFi interface. 280 281 @return int signal level of connected WiFi interface or None (e.g. -67). 282 283 """ 284 return self._interface.signal_level 285 286 287 @staticmethod 288 def assert_bsses_include_ssids(found_bsses, expected_ssids): 289 """Verifies that |found_bsses| includes |expected_ssids|. 290 291 @param found_bsses list of IwBss objects. 292 @param expected_ssids list of string SSIDs. 293 @raise error.TestFail if any element of |expected_ssids| is not found. 294 295 """ 296 for ssid in expected_ssids: 297 if not ssid: 298 continue 299 300 for bss in found_bsses: 301 if bss.ssid == ssid: 302 break 303 else: 304 raise error.TestFail('SSID %s is not in scan results: %r' % 305 (ssid, found_bsses)) 306 307 308 def wifi_noise_level(self, frequency_mhz): 309 """Returns the noise level of this DUT's WiFi interface. 310 311 @param frequency_mhz: frequency at which the noise level should be 312 measured and reported. 313 @return int signal level of connected WiFi interface in dBm (e.g. -67) 314 or None if the value is unavailable. 315 316 """ 317 return self._interface.noise_level(frequency_mhz) 318 319 320 def __init__(self, client_host, result_dir, use_wpa_cli): 321 """ 322 Construct a WiFiClient. 323 324 @param client_host host object representing a remote host. 325 @param result_dir string directory to store test logs/packet caps. 326 @param use_wpa_cli bool True if we want to use |wpa_cli| commands for 327 Android testing. 328 329 """ 330 super(WiFiClient, self).__init__(client_host, 'client', 331 inherit_interfaces=True) 332 self._command_ip = 'ip' 333 self._command_iptables = 'iptables' 334 self._command_ping6 = 'ping6' 335 self._command_wpa_cli = 'wpa_cli' 336 self._machine_id = None 337 self._result_dir = result_dir 338 self._conductive = None 339 self._client_hostname = client_host.hostname 340 341 if self.host.get_os_type() == adb_host.OS_TYPE_ANDROID and use_wpa_cli: 342 # Look up the WiFi device (and its MAC) on the client. 343 devs = self.iw_runner.list_interfaces(desired_if_type='managed') 344 devs = [dev for dev in devs 345 if dev.if_name not in self.WIFI_IF_BLACKLIST] 346 if not devs: 347 raise error.TestFail('No wlan devices found on %s.' % 348 self.host.hostname) 349 350 if len(devs) > 1: 351 logging.warning('Warning, found multiple WiFi devices on ' 352 '%s: %r', self.host.hostname, devs) 353 self._wifi_if = devs[0].if_name 354 self._shill_proxy = wpa_cli_proxy.WpaCliProxy( 355 self.host, self._wifi_if) 356 self._wpa_cli_proxy = self._shill_proxy 357 else: 358 self._shill_proxy = get_xmlrpc_proxy(self.host) 359 interfaces = self._shill_proxy.list_controlled_wifi_interfaces() 360 if not interfaces: 361 logging.debug('No interfaces managed by shill. Rebooting host') 362 self.host.reboot() 363 raise error.TestError('No interfaces managed by shill on %s' % 364 self.host.hostname) 365 self._wifi_if = interfaces[0] 366 self._wpa_cli_proxy = wpa_cli_proxy.WpaCliProxy( 367 self.host, self._wifi_if) 368 self._raise_logging_level() 369 self._interface = interface.Interface(self._wifi_if, host=self.host) 370 logging.debug('WiFi interface is: %r', 371 self._interface.device_description) 372 self._firewall_rules = [] 373 # Turn off powersave mode by default. 374 self.powersave_switch(False) 375 # All tests that use this object assume the interface starts enabled. 376 self.set_device_enabled(self._wifi_if, True) 377 # Invoke the |capabilities| property defined in the parent |Linuxsystem| 378 # to workaround the lazy loading of the capabilities cache and supported 379 # frequency list. This is needed for tests that may need access to these 380 # when the DUT is unreachable (for ex: suspended). 381 self.capabilities 382 383 384 def _assert_method_supported(self, method_name): 385 """Raise a TestNAError if the XMLRPC proxy has no method |method_name|. 386 387 @param method_name: string name of method that should exist on the 388 XMLRPC proxy. 389 390 """ 391 if not self._supports_method(method_name): 392 raise error.TestNAError('%s() is not supported' % method_name) 393 394 395 def _raise_logging_level(self): 396 """Raises logging levels for WiFi on DUT.""" 397 self.host.run('wpa_debug excessive', ignore_status=True) 398 self.host.run('ff_debug --level -5', ignore_status=True) 399 self.host.run('ff_debug +wifi', ignore_status=True) 400 401 402 def is_vht_supported(self): 403 """Returns True if VHT supported; False otherwise""" 404 return self.CAPABILITY_VHT in self.capabilities 405 406 407 def is_5ghz_supported(self): 408 """Returns True if 5Ghz bands are supported; False otherwise.""" 409 return self.CAPABILITY_5GHZ in self.capabilities 410 411 412 def is_ibss_supported(self): 413 """Returns True if IBSS mode is supported; False otherwise.""" 414 return self.CAPABILITY_IBSS in self.capabilities 415 416 417 def is_frequency_supported(self, frequency): 418 """Returns True if the given frequency is supported; False otherwise. 419 420 @param frequency: int Wifi frequency to check if it is supported by 421 DUT. 422 """ 423 return frequency in self.phys_for_frequency 424 425 426 def _supports_method(self, method_name): 427 """Checks if |method_name| is supported on the remote XMLRPC proxy. 428 429 autotest will, for their own reasons, install python files in the 430 autotest client package that correspond the version of the build 431 rather than the version running on the autotest drone. This 432 creates situations where we call methods on the client XMLRPC proxy 433 that don't exist in that version of the code. This detects those 434 situations so that we can degrade more or less gracefully. 435 436 @param method_name: string name of method that should exist on the 437 XMLRPC proxy. 438 @return True if method is available, False otherwise. 439 440 """ 441 # Make no assertions about ADBHost support. We don't use an XMLRPC 442 # proxy with those hosts anyway. 443 supported = (isinstance(self.host, adb_host.ADBHost) or 444 method_name in self._shill_proxy.system.listMethods()) 445 if not supported: 446 logging.warning('%s() is not supported on older images', 447 method_name) 448 return supported 449 450 451 def close(self): 452 """Tear down state associated with the client.""" 453 self.stop_capture() 454 self.powersave_switch(False) 455 self.shill.clean_profiles() 456 super(WiFiClient, self).close() 457 458 459 def firewall_open(self, proto, src): 460 """Opens up firewall to run netperf tests. 461 462 By default, we have a firewall rule for NFQUEUE (see crbug.com/220736). 463 In order to run netperf test, we need to add a new firewall rule BEFORE 464 this NFQUEUE rule in the INPUT chain. 465 466 @param proto a string, test traffic protocol, e.g. udp, tcp. 467 @param src a string, subnet/mask. 468 469 @return a string firewall rule added. 470 471 """ 472 rule = 'INPUT -s %s/32 -p %s -m %s -j ACCEPT' % (src, proto, proto) 473 self.host.run('%s -I %s' % (self._command_iptables, rule)) 474 self._firewall_rules.append(rule) 475 return rule 476 477 478 def firewall_cleanup(self): 479 """Cleans up firewall rules.""" 480 for rule in self._firewall_rules: 481 self.host.run('%s -D %s' % (self._command_iptables, rule)) 482 self._firewall_rules = [] 483 484 485 def sync_host_times(self): 486 """Set time on our DUT to match local time.""" 487 epoch_seconds = time.time() 488 self.shill.sync_time_to(epoch_seconds) 489 490 491 def check_iw_link_value(self, iw_link_key, desired_value): 492 """Assert that the current wireless link property is |desired_value|. 493 494 @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner. 495 @param desired_value string desired value of iw link property. 496 497 """ 498 actual_value = self.get_iw_link_value(iw_link_key) 499 desired_value = str(desired_value) 500 if actual_value != desired_value: 501 raise error.TestFail('Wanted iw link property %s value %s, but ' 502 'got %s instead.' % (iw_link_key, 503 desired_value, 504 actual_value)) 505 506 507 def get_iw_link_value(self, iw_link_key): 508 """Get the current value of a link property for this WiFi interface. 509 510 @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner. 511 512 """ 513 return self.iw_runner.get_link_value(self.wifi_if, iw_link_key) 514 515 516 def powersave_switch(self, turn_on): 517 """Toggle powersave mode for the DUT. 518 519 @param turn_on bool True iff powersave mode should be turned on. 520 521 """ 522 mode = 'off' 523 if turn_on: 524 mode = 'on' 525 self.host.run('iw dev %s set power_save %s' % (self.wifi_if, mode)) 526 527 528 def timed_scan(self, frequencies, ssids, scan_timeout_seconds=10, 529 retry_timeout_seconds=10): 530 """Request timed scan to discover given SSIDs. 531 532 This method will retry for a default of |retry_timeout_seconds| until it 533 is able to successfully kick off a scan. Sometimes, the device on the 534 DUT claims to be busy and rejects our requests. It will raise error 535 if the scan did not complete within |scan_timeout_seconds| or it was 536 not able to discover the given SSIDs. 537 538 @param frequencies list of int WiFi frequencies to scan for. 539 @param ssids list of string ssids to probe request for. 540 @param scan_timeout_seconds: float number of seconds the scan 541 operation not to exceed. 542 @param retry_timeout_seconds: float number of seconds to retry scanning 543 if the interface is busy. This does not retry if certain 544 SSIDs are missing from the results. 545 @return time in seconds took to complete scan request. 546 547 """ 548 start_time = time.time() 549 while time.time() - start_time < retry_timeout_seconds: 550 scan_result = self.iw_runner.timed_scan( 551 self.wifi_if, frequencies=frequencies, ssids=ssids) 552 553 if scan_result is not None: 554 break 555 556 time.sleep(0.5) 557 else: 558 raise error.TestFail('Unable to trigger scan on client.') 559 560 # Verify scan operation completed within given timeout 561 if scan_result.time > scan_timeout_seconds: 562 raise error.TestFail('Scan time %.2fs exceeds the scan timeout' % 563 (scan_result.time)) 564 565 # Verify all ssids are discovered 566 self.assert_bsses_include_ssids(scan_result.bss_list, ssids) 567 568 logging.info('Wifi scan completed in %.2f seconds', scan_result.time) 569 return scan_result.time 570 571 572 def scan(self, frequencies, ssids, timeout_seconds=10, require_match=True): 573 """Request a scan and (optionally) check that requested SSIDs appear in 574 the results. 575 576 This method will retry for a default of |timeout_seconds| until it is 577 able to successfully kick off a scan. Sometimes, the device on the DUT 578 claims to be busy and rejects our requests. 579 580 If |ssids| is non-empty, we will speficially probe for those SSIDs. 581 582 If |require_match| is True, we will verify that every element 583 of |ssids| was found in scan results. 584 585 @param frequencies list of int WiFi frequencies to scan for. 586 @param ssids list of string ssids to probe request for. 587 @param timeout_seconds: float number of seconds to retry scanning 588 if the interface is busy. This does not retry if certain 589 SSIDs are missing from the results. 590 @param require_match: bool True if we must find |ssids|. 591 592 """ 593 start_time = time.time() 594 while time.time() - start_time < timeout_seconds: 595 bss_list = self.iw_runner.scan( 596 self.wifi_if, frequencies=frequencies, ssids=ssids) 597 if bss_list is not None: 598 break 599 600 time.sleep(0.5) 601 else: 602 raise error.TestFail('Unable to trigger scan on client.') 603 604 if require_match: 605 self.assert_bsses_include_ssids(bss_list, ssids) 606 607 608 def wait_for_bsses(self, ssid, num_bss_expected, timeout_seconds=15): 609 """Wait for all BSSes associated with given SSID to be discovered in the 610 scan. 611 612 @param ssid string name of network being queried 613 @param num_bss_expected int number of BSSes expected 614 @param timeout_seconds int seconds to wait for BSSes to be discovered 615 616 """ 617 start_time = time.time() 618 while time.time() - start_time < timeout_seconds: 619 bss_list = self.iw_runner.scan( 620 self.wifi_if, frequencies=[], ssids=[ssid]) 621 622 # Determine number of BSSes found in the scan result. 623 num_bss_found = 0 624 if bss_list is not None: 625 for bss in bss_list: 626 if bss.ssid == ssid: 627 num_bss_found += 1 628 629 # Verify all BSSes are found. 630 if num_bss_found == num_bss_expected: 631 break 632 633 time.sleep(0.5) 634 else: 635 raise error.TestFail('Failed to discover all BSSes.') 636 637 638 def wait_for_service_states(self, ssid, states, timeout_seconds): 639 """Waits for a WiFi service to achieve one of |states|. 640 641 @param ssid string name of network being queried 642 @param states tuple list of states for which the caller is waiting 643 @param timeout_seconds int seconds to wait for a state in |states| 644 645 """ 646 logging.info('Waiting for %s to reach one of %r...', ssid, states) 647 success, state, time = self._shill_proxy.wait_for_service_states( 648 ssid, states, timeout_seconds) 649 logging.info('...ended up in state \'%s\' (%s) after %f seconds.', 650 state, 'success' if success else 'failure', time) 651 return success, state, time 652 653 654 def do_suspend(self, seconds): 655 """Puts the DUT in suspend power state for |seconds| seconds. 656 657 @param seconds: The number of seconds to suspend the device. 658 659 """ 660 logging.info('Suspending DUT for %d seconds...', seconds) 661 self._shill_proxy.do_suspend(seconds) 662 logging.info('...done suspending') 663 664 665 def do_suspend_bg(self, seconds): 666 """Suspend DUT using the power manager - non-blocking. 667 668 @param seconds: The number of seconds to suspend the device. 669 670 """ 671 logging.info('Suspending DUT (in background) for %d seconds...', 672 seconds) 673 self._shill_proxy.do_suspend_bg(seconds) 674 675 676 def clear_supplicant_blacklist(self): 677 """Clear's the AP blacklist on the DUT. 678 679 @return stdout and stderror returns passed from wpa_cli command. 680 681 """ 682 result = self._wpa_cli_proxy.run_wpa_cli_cmd('blacklist clear', 683 check_result=False); 684 logging.info('wpa_cli blacklist clear: out:%r err:%r', result.stdout, 685 result.stderr) 686 return result.stdout, result.stderr 687 688 689 def get_active_wifi_SSIDs(self): 690 """Get a list of visible SSID's around the DUT 691 692 @return list of string SSIDs 693 694 """ 695 self._assert_method_supported('get_active_wifi_SSIDs') 696 return self._shill_proxy.get_active_wifi_SSIDs() 697 698 699 def roam_threshold(self, value): 700 """Get a context manager to temporarily change wpa_supplicant's 701 roaming threshold for the specified interface. 702 703 The correct way to use this method is: 704 705 with client.roam_threshold(40): 706 ... 707 708 @param value: the desired roam threshold for the test. 709 710 @return a context manager for the threshold. 711 712 """ 713 return TemporaryDBusProperty(self._shill_proxy, 714 self.wifi_if, 715 self.ROAM_THRESHOLD, 716 value) 717 718 719 def set_device_enabled(self, wifi_interface, value, 720 fail_on_unsupported=False): 721 """Enable or disable the WiFi device. 722 723 @param wifi_interface: string name of interface being modified. 724 @param enabled: boolean; true if this device should be enabled, 725 false if this device should be disabled. 726 @return True if it worked; False, otherwise. 727 728 """ 729 if fail_on_unsupported: 730 self._assert_method_supported('set_device_enabled') 731 elif not self._supports_method('set_device_enabled'): 732 return False 733 return self._shill_proxy.set_device_enabled(wifi_interface, value) 734 735 736 def add_arp_entry(self, ip_address, mac_address): 737 """Add an ARP entry to the table associated with the WiFi interface. 738 739 @param ip_address: string IP address associated with the new ARP entry. 740 @param mac_address: string MAC address associated with the new ARP 741 entry. 742 743 """ 744 self.host.run('ip neigh add %s lladdr %s dev %s nud perm' % 745 (ip_address, mac_address, self.wifi_if)) 746 747 748 def discover_tdls_link(self, mac_address): 749 """Send a TDLS Discover to |peer_mac_address|. 750 751 @param mac_address: string MAC address associated with the TDLS peer. 752 753 @return bool True if operation initiated successfully, False otherwise. 754 755 """ 756 return self._shill_proxy.discover_tdls_link(self.wifi_if, mac_address) 757 758 759 def establish_tdls_link(self, mac_address): 760 """Establish a TDLS link with |mac_address|. 761 762 @param mac_address: string MAC address associated with the TDLS peer. 763 764 @return bool True if operation initiated successfully, False otherwise. 765 766 """ 767 return self._shill_proxy.establish_tdls_link(self.wifi_if, mac_address) 768 769 770 def query_tdls_link(self, mac_address): 771 """Query a TDLS link with |mac_address|. 772 773 @param mac_address: string MAC address associated with the TDLS peer. 774 775 @return string indicating current TDLS connectivity. 776 777 """ 778 return self._shill_proxy.query_tdls_link(self.wifi_if, mac_address) 779 780 781 def add_wake_packet_source(self, source_ip): 782 """Add |source_ip| as a source that can wake us up with packets. 783 784 @param source_ip: IP address from which to wake upon receipt of packets 785 786 @return True if successful, False otherwise. 787 788 """ 789 return self._shill_proxy.add_wake_packet_source( 790 self.wifi_if, source_ip) 791 792 793 def remove_wake_packet_source(self, source_ip): 794 """Remove |source_ip| as a source that can wake us up with packets. 795 796 @param source_ip: IP address to stop waking on packets from 797 798 @return True if successful, False otherwise. 799 800 """ 801 return self._shill_proxy.remove_wake_packet_source( 802 self.wifi_if, source_ip) 803 804 805 def remove_all_wake_packet_sources(self): 806 """Remove all IPs as sources that can wake us up with packets. 807 808 @return True if successful, False otherwise. 809 810 """ 811 return self._shill_proxy.remove_all_wake_packet_sources(self.wifi_if) 812 813 814 def wake_on_wifi_features(self, features): 815 """Shill supports programming the NIC to wake on special kinds of 816 incoming packets, or on changes to the available APs (disconnect, 817 coming in range of a known SSID). This method allows you to configure 818 what wake-on-WiFi mechanisms are active. It returns a context manager, 819 because this is a system-wide setting and we don't want it to persist 820 across different tests. 821 822 If you enable wake-on-packet, then the IPs registered by 823 add_wake_packet_source will be able to wake the system from suspend. 824 825 The correct way to use this method is: 826 827 with client.wake_on_wifi_features(WAKE_ON_WIFI_DARKCONNECT): 828 ... 829 830 @param features: string from the WAKE_ON_WIFI constants above. 831 832 @return a context manager for the features. 833 834 """ 835 return TemporaryDBusProperty(self._shill_proxy, 836 self.wifi_if, 837 self.WAKE_ON_WIFI_FEATURES, 838 features) 839 840 841 def net_detect_scan_period_seconds(self, period): 842 """Sets the period between net detect scans performed by the NIC to look 843 for whitelisted SSIDs to |period|. This setting only takes effect if the 844 NIC is programmed to wake on SSID. Use as with roam_threshold. 845 846 @param period: integer number of seconds between NIC net detect scans 847 848 @return a context manager for the net detect scan period 849 850 """ 851 return TemporaryDBusProperty(self._shill_proxy, 852 self.wifi_if, 853 self.NET_DETECT_SCAN_PERIOD, 854 period) 855 856 857 def wake_to_scan_period_seconds(self, period): 858 """Sets the period between RTC timer wakeups where the system is woken 859 from suspend to perform scans. This setting only takes effect if the 860 NIC is programmed to wake on SSID. Use as with roam_threshold. 861 862 @param period: integer number of seconds between wake to scan RTC timer 863 wakes. 864 865 @return a context manager for the net detect scan period 866 867 """ 868 return TemporaryDBusProperty(self._shill_proxy, 869 self.wifi_if, 870 self.WAKE_TO_SCAN_PERIOD, 871 period) 872 873 874 def force_wake_to_scan_timer(self, is_forced): 875 """Sets the boolean value determining whether or not to force the use of 876 the wake to scan RTC timer, which wakes the system from suspend 877 periodically to scan for networks. 878 879 @param is_forced: boolean whether or not to force the use of the wake to 880 scan timer 881 882 @return a context manager for the net detect scan period 883 884 """ 885 return TemporaryDBusProperty(self._shill_proxy, 886 self.wifi_if, 887 self.FORCE_WAKE_TO_SCAN_TIMER, 888 is_forced) 889 890 891 def request_roam(self, bssid): 892 """Request that we roam to the specified BSSID. 893 894 Note that this operation assumes that: 895 896 1) We're connected to an SSID for which |bssid| is a member. 897 2) There is a BSS with an appropriate ID in our scan results. 898 899 This method does not check for success of either the command or 900 the roaming operation. 901 902 @param bssid: string MAC address of bss to roam to. 903 904 """ 905 self._wpa_cli_proxy.run_wpa_cli_cmd('roam %s' % bssid, 906 check_result=False); 907 return True 908 909 910 def request_roam_dbus(self, bssid, interface): 911 """Request that we roam to the specified BSSID through dbus. 912 913 Note that this operation assumes that: 914 915 1) We're connected to an SSID for which |bssid| is a member. 916 2) There is a BSS with an appropriate ID in our scan results. 917 918 @param bssid: string MAC address of bss to roam to. 919 920 """ 921 self._assert_method_supported('request_roam_dbus') 922 self._shill_proxy.request_roam_dbus(bssid, interface) 923 924 925 def wait_for_roam(self, bssid, timeout_seconds=10.0): 926 """Wait for a roam to the given |bssid|. 927 928 @param bssid: string bssid to expect a roam to 929 (e.g. '00:11:22:33:44:55'). 930 @param timeout_seconds: float number of seconds to wait for a roam. 931 @return True iff we detect an association to the given |bssid| within 932 |timeout_seconds|. 933 934 """ 935 start_time = time.time() 936 success = False 937 duration = 0.0 938 while time.time() - start_time < timeout_seconds: 939 duration = time.time() - start_time 940 current_bssid = self.iw_runner.get_current_bssid(self.wifi_if) 941 logging.debug('Current BSSID is %s.', current_bssid) 942 if current_bssid == bssid: 943 success = True 944 break 945 time.sleep(0.5) 946 947 logging.debug('%s to %s in %f seconds.', 948 'Roamed ' if success else 'Failed to roam ', 949 bssid, 950 duration) 951 return success 952 953 954 def wait_for_ssid_vanish(self, ssid): 955 """Wait for shill to notice that there are no BSS's for an SSID present. 956 957 Raise a test failing exception if this does not come to pass. 958 959 @param ssid: string SSID of the network to require be missing. 960 961 """ 962 start_time = time.time() 963 while time.time() - start_time < self.MAX_SERVICE_GONE_TIMEOUT_SECONDS: 964 visible_ssids = self.get_active_wifi_SSIDs() 965 logging.info('Got service list: %r', visible_ssids) 966 if ssid not in visible_ssids: 967 return 968 969 self.scan(frequencies=[], ssids=[], timeout_seconds=30) 970 else: 971 raise error.TestFail('shill should mark the BSS as not present') 972 973 974 def reassociate(self, timeout_seconds=10): 975 """Reassociate to the connected network. 976 977 @param timeout_seconds: float number of seconds to wait for operation 978 to complete. 979 980 """ 981 logging.info('Attempt to reassociate') 982 with self.iw_runner.get_event_logger() as logger: 983 logger.start() 984 # Issue reattach command to wpa_supplicant 985 self._wpa_cli_proxy.run_wpa_cli_cmd('reattach'); 986 987 # Wait for the timeout seconds for association to complete 988 time.sleep(timeout_seconds) 989 990 # Stop iw event logger 991 logger.stop() 992 993 # Get association time based on the iw event log 994 reassociate_time = logger.get_reassociation_time() 995 if reassociate_time is None or reassociate_time > timeout_seconds: 996 raise error.TestFail( 997 'Failed to reassociate within given timeout') 998 logging.info('Reassociate time: %.2f seconds', reassociate_time) 999 1000 1001 def wait_for_connection(self, ssid, timeout_seconds=30, freq=None, 1002 ping_ip=None, desired_subnet=None): 1003 """Verifies a connection to network ssid, optionally verifying 1004 frequency, ping connectivity and subnet. 1005 1006 @param ssid string ssid of the network to check. 1007 @param timeout_seconds int number of seconds to wait for 1008 connection on the given frequency. 1009 @param freq int frequency of network to check. 1010 @param ping_ip string ip address to ping for verification. 1011 @param desired_subnet string expected subnet in which client 1012 ip address should reside. 1013 1014 @returns a named tuple of (state, time) 1015 """ 1016 POLLING_INTERVAL_SECONDS = 1.0 1017 start_time = time.time() 1018 duration = lambda: time.time() - start_time 1019 success = False 1020 while duration() < timeout_seconds: 1021 success, state, conn_time = self.wait_for_service_states( 1022 ssid, self.CONNECTED_STATES, 1023 int(math.ceil(timeout_seconds - duration()))) 1024 if not success: 1025 time.sleep(POLLING_INTERVAL_SECONDS) 1026 continue 1027 1028 if freq: 1029 actual_freq = self.get_iw_link_value( 1030 iw_runner.IW_LINK_KEY_FREQUENCY) 1031 if str(freq) != actual_freq: 1032 logging.debug('Waiting for desired frequency %s (got %s).', 1033 freq, actual_freq) 1034 time.sleep(POLLING_INTERVAL_SECONDS) 1035 continue 1036 1037 if desired_subnet: 1038 actual_subnet = self.wifi_ip_subnet 1039 if actual_subnet != desired_subnet: 1040 logging.debug('Waiting for desired subnet %s (got %s).', 1041 desired_subnet, actual_subnet) 1042 time.sleep(POLLING_INTERVAL_SECONDS) 1043 continue 1044 1045 if ping_ip: 1046 ping_config = ping_runner.PingConfig(ping_ip) 1047 self.ping(ping_config) 1048 1049 return ConnectTime(state, conn_time) 1050 1051 freq_error_str = (' on frequency %d Mhz' % freq) if freq else '' 1052 raise error.TestFail( 1053 'Failed to connect to "%s"%s in %f seconds (state=%s)' % 1054 (ssid, freq_error_str, duration(), state)) 1055 1056 1057 @contextmanager 1058 def assert_disconnect_count(self, count): 1059 """Context asserting |count| disconnects for the context lifetime. 1060 1061 Creates an iw logger during the lifetime of the context and asserts 1062 that the client disconnects exactly |count| times. 1063 1064 @param count int the expected number of disconnections. 1065 1066 """ 1067 with self.iw_runner.get_event_logger() as logger: 1068 logger.start() 1069 yield 1070 logger.stop() 1071 if logger.get_disconnect_count() != count: 1072 raise error.TestFail( 1073 'Client disconnected %d times; expected %d' % 1074 (logger.get_disconnect_count(), count)) 1075 1076 1077 def assert_no_disconnects(self): 1078 """Context asserting no disconnects for the context lifetime.""" 1079 return self.assert_disconnect_count(0) 1080 1081 1082 @contextmanager 1083 def assert_disconnect_event(self): 1084 """Context asserting at least one disconnect for the context lifetime. 1085 1086 Creates an iw logger during the lifetime of the context and asserts 1087 that the client disconnects at least one time. 1088 1089 """ 1090 with self.iw_runner.get_event_logger() as logger: 1091 logger.start() 1092 yield 1093 logger.stop() 1094 if logger.get_disconnect_count() == 0: 1095 raise error.TestFail('Client did not disconnect') 1096 1097 1098 def get_num_card_resets(self): 1099 """Get card reset count.""" 1100 reset_msg = 'mwifiex_sdio_card_reset' 1101 result = self.host.run('grep -c %s /var/log/messages' % reset_msg, 1102 ignore_status=True) 1103 if result.exit_status == 1: 1104 return 0 1105 count = int(result.stdout.strip()) 1106 return count 1107 1108 1109 def get_disconnect_reasons(self): 1110 """Get disconnect reason codes.""" 1111 disconnect_reason_msg = "updated DisconnectReason to "; 1112 disconnect_reason_cleared = "clearing DisconnectReason for "; 1113 result = self.host.run('grep -E "(%s|%s)" /var/log/net.log' % 1114 (disconnect_reason_msg, 1115 disconnect_reason_cleared), 1116 ignore_status=True) 1117 if result.exit_status == 1: 1118 return None 1119 1120 lines = result.stdout.strip().split('\n') 1121 disconnect_reasons = [] 1122 disconnect_reason_regex = re.compile(' to (\D?\d+)') 1123 1124 found = False 1125 for line in reversed(lines): 1126 match = disconnect_reason_regex.search(line) 1127 if match is not None: 1128 disconnect_reasons.append(match.group(1)) 1129 found = True 1130 else: 1131 if (found): 1132 break 1133 return list(reversed(disconnect_reasons)) 1134 1135 1136 def release_wifi_if(self): 1137 """Release the control over the wifi interface back to normal operation. 1138 1139 This will give the ownership of the wifi interface back to shill and 1140 wpa_supplicant. 1141 1142 """ 1143 self.set_device_enabled(self._wifi_if, True) 1144 1145 1146 def claim_wifi_if(self): 1147 """Claim the control over the wifi interface from this wifi client. 1148 1149 This claim the ownership of the wifi interface from shill and 1150 wpa_supplicant. The wifi interface should be UP when this call returns. 1151 1152 """ 1153 # Disabling a wifi device in shill will remove that device from 1154 # wpa_supplicant as well. 1155 self.set_device_enabled(self._wifi_if, False) 1156 1157 # Wait for shill to bring down the wifi interface. 1158 is_interface_down = lambda: not self._interface.is_up 1159 client_site_utils.poll_for_condition( 1160 is_interface_down, 1161 timeout=INTERFACE_DOWN_WAIT_TIME_SECONDS, 1162 sleep_interval=0.5, 1163 desc='Timeout waiting for interface to go down.') 1164 # Bring up the wifi interface to allow the test to use the interface. 1165 self.host.run('%s link set %s up' % (self.cmd_ip, self.wifi_if)) 1166 1167 1168 def set_sched_scan(self, enable, fail_on_unsupported=False): 1169 """enable/disable scheduled scan. 1170 1171 @param enable bool flag indicating to enable/disable scheduled scan. 1172 1173 """ 1174 if fail_on_unsupported: 1175 self._assert_method_supported('set_sched_scan') 1176 elif not self._supports_method('set_sched_scan'): 1177 return False 1178 return self._shill_proxy.set_sched_scan(enable) 1179 1180 1181 def check_connected_on_last_resume(self): 1182 """Checks whether the DUT was connected on its last resume. 1183 1184 Checks that the DUT was connected after waking from suspend by parsing 1185 the last instance shill log message that reports shill's connection 1186 status on resume. Fails the test if this log message reports that 1187 the DUT woke up disconnected. 1188 1189 """ 1190 # As of build R43 6913.0.0, the shill log message from the function 1191 # OnAfterResume is called as soon as shill resumes from suspend, and 1192 # will report whether or not shill is connected. The log message will 1193 # take one of the following two forms: 1194 # 1195 # [...] [INFO:wifi.cc(1941)] OnAfterResume: connected 1196 # [...] [INFO:wifi.cc(1941)] OnAfterResume: not connected 1197 # 1198 # where 1941 is an arbitrary PID number. By checking if the last 1199 # instance of this message contains the substring "not connected", we 1200 # can determine whether or not shill was connected on its last resume. 1201 connection_status_log_regex_str = 'INFO:wifi\.cc.*OnAfterResume' 1202 not_connected_substr = 'not connected' 1203 connected_substr = 'connected' 1204 1205 cmd = ('grep -E %s /var/log/net.log | tail -1' % 1206 connection_status_log_regex_str) 1207 connection_status_log = self.host.run(cmd).stdout 1208 if not connection_status_log: 1209 raise error.TestFail('Could not find resume connection status log ' 1210 'message.') 1211 1212 logging.debug('Connection status message:\n%s', connection_status_log) 1213 if not_connected_substr in connection_status_log: 1214 raise error.TestFail('Client was not connected upon waking from ' 1215 'suspend.') 1216 1217 if not connected_substr in connection_status_log: 1218 raise error.TestFail('Last resume log message did not contain ' 1219 'connection status.') 1220 1221 logging.info('Client was connected upon waking from suspend.') 1222 1223 1224 def check_wake_on_wifi_throttled(self): 1225 """ 1226 Checks whether wake on WiFi was throttled on the DUT on the last dark 1227 resume. Check for this by parsing shill logs for a throttling message. 1228 1229 """ 1230 # We are looking for an dark resume error log message indicating that 1231 # wake on WiFi was throttled. This is an example of the error message: 1232 # [...] [ERROR:wake_on_wifi.cc(1304)] OnDarkResume: Too many dark \ 1233 # resumes; disabling wake on WiFi temporarily 1234 dark_resume_log_regex_str = 'ERROR:wake_on_wifi\.cc.*OnDarkResume:.*' 1235 throttled_msg_substr = ('Too many dark resumes; disabling wake on ' 1236 'WiFi temporarily') 1237 1238 cmd = ('grep -E %s /var/log/net.log | tail -1' % 1239 dark_resume_log_regex_str) 1240 last_dark_resume_error_log = self.host.run(cmd).stdout 1241 if not last_dark_resume_error_log: 1242 raise error.TestFail('Could not find a dark resume log message.') 1243 1244 logging.debug('Last dark resume log message:\n%s', 1245 last_dark_resume_error_log) 1246 if not throttled_msg_substr in last_dark_resume_error_log: 1247 raise error.TestFail('Wake on WiFi was not throttled on the last ' 1248 'dark resume.') 1249 1250 logging.info('Wake on WiFi was throttled on the last dark resume.') 1251 1252 1253 def shill_debug_log(self, message): 1254 """Logs a message to the shill log (i.e. /var/log/net.log). This 1255 message will be logged at the DEBUG level. 1256 1257 @param message: the message to be logged. 1258 1259 """ 1260 logging.info(message) 1261 1262 # Skip this command if running on Android, since Android does not 1263 # have a /usr/bin/logger (or an equivalent that takes the same 1264 # parameters as the Linux logger does.) 1265 if not isinstance(self.host, adb_host.ADBHost): 1266 logger_command = ('/usr/bin/logger' 1267 ' --tag shill' 1268 ' --priority daemon.debug' 1269 ' "%s"' % base_utils.sh_escape(message)) 1270 self.host.run(logger_command) 1271 1272 1273 def is_wake_on_wifi_supported(self): 1274 """Returns true iff wake-on-WiFi is supported by the DUT.""" 1275 1276 if (self.shill.get_dbus_property_on_device( 1277 self.wifi_if, self.WAKE_ON_WIFI_FEATURES) == 1278 WAKE_ON_WIFI_NOT_SUPPORTED): 1279 return False 1280 return True 1281 1282 1283class TemporaryDBusProperty: 1284 """Utility class to temporarily change a dbus property for the WiFi device. 1285 1286 Since dbus properties are global and persistent settings, we want 1287 to make sure that we change them back to what they were before the test 1288 started. 1289 1290 """ 1291 1292 def __init__(self, shill_proxy, interface, prop_name, value): 1293 """Construct a TemporaryDBusProperty context manager. 1294 1295 @param shill_proxy: the shill proxy to use to communicate via dbus 1296 @param interface: the name of the interface we are setting the property for 1297 @param prop_name: the name of the property we want to set 1298 @param value: the desired value of the property 1299 1300 """ 1301 self._shill = shill_proxy 1302 self._interface = interface 1303 self._prop_name = prop_name 1304 self._value = value 1305 self._saved_value = None 1306 1307 1308 def __enter__(self): 1309 logging.info('- Setting property %s on device %s', 1310 self._prop_name, self._interface) 1311 1312 self._saved_value = self._shill.get_dbus_property_on_device( 1313 self._interface, self._prop_name) 1314 if self._saved_value is None: 1315 raise error.TestFail('Device or property not found.') 1316 if not self._shill.set_dbus_property_on_device(self._interface, 1317 self._prop_name, 1318 self._value): 1319 raise error.TestFail('Could not set property') 1320 1321 logging.info('- Changed value from %s to %s', 1322 self._saved_value, 1323 self._value) 1324 1325 1326 def __exit__(self, exception, value, traceback): 1327 logging.info('- Resetting property %s', self._prop_name) 1328 1329 if not self._shill.set_dbus_property_on_device(self._interface, 1330 self._prop_name, 1331 self._saved_value): 1332 raise error.TestFail('Could not reset property') 1333