1# Copyright (c) 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import collections 6import copy 7import logging 8import operator 9import re 10import time 11 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.common_lib import utils 14from autotest_lib.client.common_lib.cros.network import iw_event_logger 15 16# These must mirror the values in 'iw list' output. 17CHAN_FLAG_DISABLED = 'disabled' 18CHAN_FLAG_NO_IR = 'no IR' 19CHAN_FLAG_PASSIVE_SCAN = 'passive scan' 20CHAN_FLAG_RADAR_DETECT = 'radar detection' 21DEV_MODE_AP = 'AP' 22DEV_MODE_IBSS = 'IBSS' 23DEV_MODE_MONITOR = 'monitor' 24DEV_MODE_MESH_POINT = 'mesh point' 25DEV_MODE_STATION = 'managed' 26SUPPORTED_DEV_MODES = (DEV_MODE_AP, DEV_MODE_IBSS, DEV_MODE_MONITOR, 27 DEV_MODE_MESH_POINT, DEV_MODE_STATION) 28 29HT20 = 'HT20' 30HT40_ABOVE = 'HT40+' 31HT40_BELOW = 'HT40-' 32 33SECURITY_OPEN = 'open' 34SECURITY_WEP = 'wep' 35SECURITY_WPA = 'wpa' 36SECURITY_WPA2 = 'wpa2' 37# Mixed mode security is WPA2/WPA 38SECURITY_MIXED = 'mixed' 39 40# Table of lookups between the output of item 'secondary channel offset:' from 41# iw <device> scan to constants. 42 43HT_TABLE = {'no secondary': HT20, 44 'above': HT40_ABOVE, 45 'below': HT40_BELOW} 46 47IwBand = collections.namedtuple( 48 'Band', ['num', 'frequencies', 'frequency_flags', 'mcs_indices']) 49IwBss = collections.namedtuple('IwBss', ['bss', 'frequency', 'ssid', 'security', 50 'ht', 'signal']) 51IwNetDev = collections.namedtuple('IwNetDev', ['phy', 'if_name', 'if_type']) 52IwTimedScan = collections.namedtuple('IwTimedScan', ['time', 'bss_list']) 53 54# The fields for IwPhy are as follows: 55# name: string name of the phy, such as "phy0" 56# bands: list of IwBand objects. 57# modes: List of strings containing interface modes supported, such as "AP". 58# commands: List of strings containing nl80211 commands supported, such as 59# "authenticate". 60# features: List of strings containing nl80211 features supported, such as 61# "T-DLS". 62# max_scan_ssids: Maximum number of SSIDs which can be scanned at once. 63IwPhy = collections.namedtuple( 64 'Phy', ['name', 'bands', 'modes', 'commands', 'features', 65 'max_scan_ssids', 'avail_tx_antennas', 'avail_rx_antennas', 66 'supports_setting_antenna_mask', 'support_vht']) 67 68DEFAULT_COMMAND_IW = 'iw' 69 70# Redirect stderr to stdout on Cros since adb commands cannot distinguish them 71# on Brillo. 72IW_TIME_COMMAND_FORMAT = '(time -p %s) 2>&1' 73IW_TIME_COMMAND_OUTPUT_START = 'real' 74 75IW_LINK_KEY_BEACON_INTERVAL = 'beacon int' 76IW_LINK_KEY_DTIM_PERIOD = 'dtim period' 77IW_LINK_KEY_FREQUENCY = 'freq' 78IW_LINK_KEY_SIGNAL = 'signal' 79IW_LINK_KEY_RX_BITRATE = 'rx bitrate' 80IW_LINK_KEY_TX_BITRATE = 'tx bitrate' 81IW_LOCAL_EVENT_LOG_FILE = './debug/iw_event_%d.log' 82 83 84def _get_all_link_keys(link_information): 85 """Parses link or station dump output for link key value pairs. 86 87 Link or station dump information is in the format below: 88 89 Connected to 74:e5:43:10:4f:c0 (on wlan0) 90 SSID: PMKSACaching_4m9p5_ch1 91 freq: 5220 92 RX: 5370 bytes (37 packets) 93 TX: 3604 bytes (15 packets) 94 signal: -59 dBm 95 tx bitrate: 13.0 MBit/s MCS 1 96 97 bss flags: short-slot-time 98 dtim period: 5 99 beacon int: 100 100 101 @param link_information: string containing the raw link or station dump 102 information as reported by iw. Note that this parsing assumes a single 103 entry, in the case of multiple entries (e.g. listing stations from an 104 AP, or listing mesh peers), the entries must be split on a per 105 peer/client basis before this parsing operation. 106 @return a dictionary containing all the link key/value pairs. 107 108 """ 109 link_key_value_pairs = {} 110 keyval_regex = re.compile(r'^\s+(.*):\s+(.*)$') 111 for link_key in link_information.splitlines()[1:]: 112 match = re.search(keyval_regex, link_key) 113 if match: 114 # Station dumps can contain blank lines. 115 link_key_value_pairs[match.group(1)] = match.group(2) 116 return link_key_value_pairs 117 118 119def _extract_bssid(link_information, interface_name, station_dump=False): 120 """Get the BSSID that |interface_name| is associated with. 121 122 See doc for _get_all_link_keys() for expected format of the station or link 123 information entry. 124 125 @param link_information: string containing the raw link or station dump 126 information as reported by iw. Note that this parsing assumes a single 127 entry, in the case of multiple entries (e.g. listing stations from an AP 128 or listing mesh peers), the entries must be split on a per peer/client 129 basis before this parsing operation. 130 @param interface_name: string name of interface (e.g. 'wlan0'). 131 @param station_dump: boolean indicator of whether the link information is 132 from a 'station dump' query. If False, it is assumed the string is from 133 a 'link' query. 134 @return string bssid of the current association, or None if no matching 135 association information is found. 136 137 """ 138 # We're looking for a line like this when parsing the output of a 'link' 139 # query: 140 # Connected to 04:f0:21:03:7d:bb (on wlan0) 141 # We're looking for a line like this when parsing the output of a 142 # 'station dump' query: 143 # Station 04:f0:21:03:7d:bb (on mesh-5000mhz) 144 identifier = 'Station' if station_dump else 'Connected to' 145 search_re = r'%s ([0-9a-fA-F:]{17}) \(on %s\)' % (identifier, 146 interface_name) 147 match = re.match(search_re, link_information) 148 if match is None: 149 return None 150 return match.group(1) 151 152 153class IwRunner(object): 154 """Defines an interface to the 'iw' command.""" 155 156 157 def __init__(self, remote_host=None, command_iw=DEFAULT_COMMAND_IW): 158 self._run = utils.run 159 self._host = remote_host 160 if remote_host: 161 self._run = remote_host.run 162 self._command_iw = command_iw 163 self._log_id = 0 164 165 166 def _parse_scan_results(self, output): 167 """Parse the output of the 'scan' and 'scan dump' commands. 168 169 Here is an example of what a single network would look like for 170 the input parameter. Some fields have been removed in this example: 171 BSS 00:11:22:33:44:55(on wlan0) 172 freq: 2447 173 beacon interval: 100 TUs 174 signal: -46.00 dBm 175 Information elements from Probe Response frame: 176 SSID: my_open_network 177 Extended supported rates: 24.0 36.0 48.0 54.0 178 HT capabilities: 179 Capabilities: 0x0c 180 HT20 181 HT operation: 182 * primary channel: 8 183 * secondary channel offset: no secondary 184 * STA channel width: 20 MHz 185 RSN: * Version: 1 186 * Group cipher: CCMP 187 * Pairwise ciphers: CCMP 188 * Authentication suites: PSK 189 * Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000) 190 191 @param output: string command output. 192 193 @returns a list of IwBss namedtuples; None if the scan fails 194 195 """ 196 bss = None 197 frequency = None 198 ssid = None 199 ht = None 200 signal = None 201 security = None 202 supported_securities = [] 203 bss_list = [] 204 for line in output.splitlines(): 205 line = line.strip() 206 bss_match = re.match('BSS ([0-9a-f:]+)', line) 207 if bss_match: 208 if bss != None: 209 security = self.determine_security(supported_securities) 210 iwbss = IwBss(bss, frequency, ssid, security, ht, signal) 211 bss_list.append(iwbss) 212 bss = frequency = ssid = security = ht = None 213 supported_securities = [] 214 bss = bss_match.group(1) 215 if line.startswith('freq:'): 216 frequency = int(line.split()[1]) 217 if line.startswith('signal:'): 218 signal = float(line.split()[1]) 219 if line.startswith('SSID: '): 220 _, ssid = line.split(': ', 1) 221 if line.startswith('* secondary channel offset'): 222 ht = HT_TABLE[line.split(':')[1].strip()] 223 if line.startswith('WPA'): 224 supported_securities.append(SECURITY_WPA) 225 if line.startswith('RSN'): 226 supported_securities.append(SECURITY_WPA2) 227 security = self.determine_security(supported_securities) 228 bss_list.append(IwBss(bss, frequency, ssid, security, ht, signal)) 229 return bss_list 230 231 232 def _parse_scan_time(self, output): 233 """ 234 Parse the scan time in seconds from the output of the 'time -p "scan"' 235 command. 236 237 'time -p' Command output format is below: 238 real 0.01 239 user 0.01 240 sys 0.00 241 242 @param output: string command output. 243 244 @returns float time in seconds. 245 246 """ 247 output_lines = output.splitlines() 248 for line_num, line in enumerate(output_lines): 249 line = line.strip() 250 if (line.startswith(IW_TIME_COMMAND_OUTPUT_START) and 251 output_lines[line_num + 1].startswith('user') and 252 output_lines[line_num + 2].startswith('sys')): 253 return float(line.split()[1]) 254 raise error.TestFail('Could not parse scan time.') 255 256 257 def add_interface(self, phy, interface, interface_type): 258 """ 259 Add an interface to a WiFi PHY. 260 261 @param phy: string name of PHY to add an interface to. 262 @param interface: string name of interface to add. 263 @param interface_type: string type of interface to add (e.g. 'monitor'). 264 265 """ 266 self._run('%s phy %s interface add %s type %s' % 267 (self._command_iw, phy, interface, interface_type)) 268 269 270 def disconnect_station(self, interface): 271 """ 272 Disconnect a STA from a network. 273 274 @param interface: string name of interface to disconnect. 275 276 """ 277 self._run('%s dev %s disconnect' % (self._command_iw, interface)) 278 279 280 def get_current_bssid(self, interface_name): 281 """Get the BSSID that |interface_name| is associated with. 282 283 @param interface_name: string name of interface (e.g. 'wlan0'). 284 @return string bssid of our current association, or None. 285 286 """ 287 result = self._run('%s dev %s link' % 288 (self._command_iw, interface_name), 289 ignore_status=True) 290 if result.exit_status: 291 # See comment in get_link_value. 292 return None 293 294 return _extract_bssid(result.stdout, interface_name) 295 296 297 def get_interface(self, interface_name): 298 """Get full information about an interface given an interface name. 299 300 @param interface_name: string name of interface (e.g. 'wlan0'). 301 @return IwNetDev tuple. 302 303 """ 304 matching_interfaces = [iw_if for iw_if in self.list_interfaces() 305 if iw_if.if_name == interface_name] 306 if len(matching_interfaces) != 1: 307 raise error.TestFail('Could not find interface named %s' % 308 interface_name) 309 310 return matching_interfaces[0] 311 312 313 def get_link_value(self, interface, iw_link_key): 314 """Get the value of a link property for |interface|. 315 316 Checks the link using iw, and parses the result to return a link key. 317 318 @param iw_link_key: string one of IW_LINK_KEY_* defined above. 319 @param interface: string desired value of iw link property. 320 @return string containing the corresponding link property value, None 321 if there was a parsing error or the iw command failed. 322 323 """ 324 result = self._run('%s dev %s link' % (self._command_iw, interface), 325 ignore_status=True) 326 if result.exit_status: 327 # When roaming, there is a period of time for mac80211 based drivers 328 # when the driver is 'associated' with an SSID but not a particular 329 # BSS. This causes iw to return an error code (-2) when attempting 330 # to retrieve information specific to the BSS. This does not happen 331 # in mwifiex drivers. 332 return None 333 actual_value = _get_all_link_keys(result.stdout).get(iw_link_key) 334 if actual_value is not None: 335 logging.info('Found iw link key %s with value %s.', 336 iw_link_key, actual_value) 337 return actual_value 338 339 340 def get_station_dump(self, interface): 341 """Gets information about connected peers. 342 343 Returns information about the currently connected peers. When the host 344 is in station mode, it returns a single entry, with information about 345 the link to the AP it is currently connected to. If the host is in mesh 346 or AP mode, it can return multiple entries, one for each connected 347 station, or mesh peer. 348 349 @param interface: string name of interface to get peer information 350 from. 351 @return a list of dictionaries with link information about each 352 connected peer (ordered by peer mac address). 353 354 """ 355 result = self._run('%s dev %s station dump' % 356 (self._command_iw, interface)) 357 parts = re.split(r'^Station ', result.stdout, flags=re.MULTILINE)[1:] 358 peer_list_raw = ['Station ' + x for x in parts] 359 parsed_peer_info = [] 360 for peer in peer_list_raw: 361 peer_link_keys = _get_all_link_keys(peer) 362 rssi_str = peer_link_keys[IW_LINK_KEY_SIGNAL] 363 tx_bitrate = peer_link_keys.get(IW_LINK_KEY_TX_BITRATE) 364 # Station may not have rx_bitrate in station dump 365 rx_bitrate = peer_link_keys.get(IW_LINK_KEY_RX_BITRATE) 366 rssi_int = int(rssi_str.split()[0]) 367 mac = _extract_bssid(link_information=peer, 368 interface_name=interface, 369 station_dump=True) 370 parsed_peer_info.append({'rssi_int': rssi_int, 371 'rssi_str': rssi_str, 372 'tx_bitrate': tx_bitrate, 373 'rx_bitrate': rx_bitrate, 374 'mac': mac}) 375 return sorted(parsed_peer_info, key=operator.itemgetter('mac')) 376 377 378 def get_operating_mode(self, interface): 379 """Gets the operating mode for |interface|. 380 381 @param interface: string name of interface to get peer information 382 about. 383 384 @return string one of DEV_MODE_* defined above, or None if no mode is 385 found, or if an unsupported mode is found. 386 387 """ 388 ret = self._run('%s dev %s info' % (self._command_iw, interface)) 389 mode_regex = r'^\s*type (.*)$' 390 match = re.search(mode_regex, ret.stdout, re.MULTILINE) 391 if match: 392 operating_mode = match.group(1) 393 if operating_mode in SUPPORTED_DEV_MODES: 394 return operating_mode 395 logging.warning( 396 'Unsupported operating mode %s found for interface: %s. ' 397 'Supported modes: %s', operating_mode, interface, 398 SUPPORTED_DEV_MODES) 399 return None 400 401 402 def get_radio_config(self, interface): 403 """Gets the channel information of a specfic interface using iw. 404 405 @param interface: string name of interface to get radio information 406 from. 407 408 @return dictionary containing the channel information. 409 410 """ 411 channel_config = {} 412 ret = self._run('%s dev %s info' % (self._command_iw, interface)) 413 channel_config_regex = (r'^\s*channel ([0-9]+) \(([0-9]+) MHz\), ' 414 'width: ([2,4,8]0) MHz, center1: ([0-9]+) MHz') 415 match = re.search(channel_config_regex, ret.stdout, re.MULTILINE) 416 417 if match: 418 channel_config['number'] = int(match.group(1)) 419 channel_config['freq'] = int(match.group(2)) 420 channel_config['width'] = int(match.group(3)) 421 channel_config['center1_freq'] = int(match.group(4)) 422 423 return channel_config 424 425 426 def ibss_join(self, interface, ssid, frequency): 427 """ 428 Join a WiFi interface to an IBSS. 429 430 @param interface: string name of interface to join to the IBSS. 431 @param ssid: string SSID of IBSS to join. 432 @param frequency: int frequency of IBSS in Mhz. 433 434 """ 435 self._run('%s dev %s ibss join %s %d' % 436 (self._command_iw, interface, ssid, frequency)) 437 438 439 def ibss_leave(self, interface): 440 """ 441 Leave an IBSS. 442 443 @param interface: string name of interface to remove from the IBSS. 444 445 """ 446 self._run('%s dev %s ibss leave' % (self._command_iw, interface)) 447 448 449 def list_interfaces(self, desired_if_type=None): 450 """List WiFi related interfaces on this system. 451 452 @param desired_if_type: string type of interface to filter 453 our returned list of interfaces for (e.g. 'managed'). 454 455 @return list of IwNetDev tuples. 456 457 """ 458 459 # Parse output in the following format: 460 # 461 # $ adb shell iw dev 462 # phy#0 463 # Unnamed/non-netdev interface 464 # wdev 0x2 465 # addr aa:bb:cc:dd:ee:ff 466 # type P2P-device 467 # Interface wlan0 468 # ifindex 4 469 # wdev 0x1 470 # addr aa:bb:cc:dd:ee:ff 471 # ssid Whatever 472 # type managed 473 474 output = self._run('%s dev' % self._command_iw).stdout 475 interfaces = [] 476 phy = None 477 if_name = None 478 if_type = None 479 for line in output.splitlines(): 480 m = re.match('phy#([0-9]+)', line) 481 if m: 482 phy = 'phy%d' % int(m.group(1)) 483 if_name = None 484 if_type = None 485 continue 486 if not phy: 487 continue 488 m = re.match('[\s]*Interface (.*)', line) 489 if m: 490 if_name = m.group(1) 491 continue 492 if not if_name: 493 continue 494 # Common values for type are 'managed', 'monitor', and 'IBSS'. 495 m = re.match('[\s]*type ([a-zA-Z]+)', line) 496 if m: 497 if_type = m.group(1) 498 interfaces.append(IwNetDev(phy=phy, if_name=if_name, 499 if_type=if_type)) 500 # One phy may have many interfaces, so don't reset it. 501 if_name = None 502 503 if desired_if_type: 504 interfaces = [interface for interface in interfaces 505 if interface.if_type == desired_if_type] 506 return interfaces 507 508 509 def list_phys(self): 510 """ 511 List WiFi PHYs on the given host. 512 513 @return list of IwPhy tuples. 514 515 """ 516 output = self._run('%s list' % self._command_iw).stdout 517 518 pending_phy_name = None 519 current_band = None 520 current_section = None 521 all_phys = [] 522 523 def add_pending_phy(): 524 """Add the pending phy into |all_phys|.""" 525 bands = tuple(IwBand(band.num, 526 tuple(band.frequencies), 527 dict(band.frequency_flags), 528 tuple(band.mcs_indices)) 529 for band in pending_phy_bands) 530 new_phy = IwPhy(pending_phy_name, 531 bands, 532 tuple(pending_phy_modes), 533 tuple(pending_phy_commands), 534 tuple(pending_phy_features), 535 pending_phy_max_scan_ssids, 536 pending_phy_tx_antennas, 537 pending_phy_rx_antennas, 538 pending_phy_tx_antennas and pending_phy_rx_antennas, 539 pending_phy_support_vht) 540 all_phys.append(new_phy) 541 542 for line in output.splitlines(): 543 match_phy = re.search('Wiphy (.*)', line) 544 if match_phy: 545 if pending_phy_name: 546 add_pending_phy() 547 pending_phy_name = match_phy.group(1) 548 pending_phy_bands = [] 549 pending_phy_modes = [] 550 pending_phy_commands = [] 551 pending_phy_features = [] 552 pending_phy_max_scan_ssids = None 553 pending_phy_tx_antennas = 0 554 pending_phy_rx_antennas = 0 555 pending_phy_support_vht = False 556 continue 557 558 match_section = re.match('\s*(\w.*):\s*$', line) 559 if match_section: 560 current_section = match_section.group(1) 561 match_band = re.match('Band (\d+)', current_section) 562 if match_band: 563 current_band = IwBand(num=int(match_band.group(1)), 564 frequencies=[], 565 frequency_flags={}, 566 mcs_indices=[]) 567 pending_phy_bands.append(current_band) 568 continue 569 570 # Check for max_scan_ssids. This isn't a section, but it 571 # also isn't within a section. 572 match_max_scan_ssids = re.match('\s*max # scan SSIDs: (\d+)', 573 line) 574 if match_max_scan_ssids and pending_phy_name: 575 pending_phy_max_scan_ssids = int( 576 match_max_scan_ssids.group(1)) 577 continue 578 579 if (current_section == 'Supported interface modes' and 580 pending_phy_name): 581 mode_match = re.search('\* (\w+)', line) 582 if mode_match: 583 pending_phy_modes.append(mode_match.group(1)) 584 continue 585 586 if current_section == 'Supported commands' and pending_phy_name: 587 command_match = re.search('\* (\w+)', line) 588 if command_match: 589 pending_phy_commands.append(command_match.group(1)) 590 continue 591 592 if (current_section is not None and 593 current_section.startswith('VHT Capabilities') and 594 pending_phy_name): 595 pending_phy_support_vht = True 596 continue 597 598 match_avail_antennas = re.match('\s*Available Antennas: TX (\S+)' 599 ' RX (\S+)', line) 600 if match_avail_antennas and pending_phy_name: 601 pending_phy_tx_antennas = int( 602 match_avail_antennas.group(1), 16) 603 pending_phy_rx_antennas = int( 604 match_avail_antennas.group(2), 16) 605 continue 606 607 match_device_support = re.match('\s*Device supports (.*)\.', line) 608 if match_device_support and pending_phy_name: 609 pending_phy_features.append(match_device_support.group(1)) 610 continue 611 612 if not all([current_band, pending_phy_name, 613 line.startswith('\t')]): 614 continue 615 616 # E.g. 617 # * 2412 MHz [1] (20.0 dBm) 618 # * 2467 MHz [12] (20.0 dBm) (passive scan) 619 # * 2472 MHz [13] (disabled) 620 # * 5260 MHz [52] (19.0 dBm) (no IR, radar detection) 621 match_chan_info = re.search( 622 r'(?P<frequency>\d+) MHz' 623 r' (?P<chan_num>\[\d+\])' 624 r'(?: \((?P<tx_power_limit>[0-9.]+ dBm)\))?' 625 r'(?: \((?P<flags>[a-zA-Z, ]+)\))?', line) 626 if match_chan_info: 627 frequency = int(match_chan_info.group('frequency')) 628 current_band.frequencies.append(frequency) 629 flags_string = match_chan_info.group('flags') 630 if flags_string: 631 current_band.frequency_flags[frequency] = frozenset( 632 flags_string.split(',')) 633 else: 634 # Populate the dict with an empty set, to make 635 # things uniform for client code. 636 current_band.frequency_flags[frequency] = frozenset() 637 continue 638 639 # re_mcs needs to match something like: 640 # HT TX/RX MCS rate indexes supported: 0-15, 32 641 if re.search('HT TX/RX MCS rate indexes supported: ', line): 642 rate_string = line.split(':')[1].strip() 643 for piece in rate_string.split(','): 644 if piece.find('-') > 0: 645 # Must be a range like ' 0-15' 646 begin, end = piece.split('-') 647 for index in range(int(begin), int(end) + 1): 648 current_band.mcs_indices.append(index) 649 else: 650 # Must be a single rate like '32 ' 651 current_band.mcs_indices.append(int(piece)) 652 if pending_phy_name: 653 add_pending_phy() 654 return all_phys 655 656 657 def remove_interface(self, interface, ignore_status=False): 658 """ 659 Remove a WiFi interface from a PHY. 660 661 @param interface: string name of interface (e.g. mon0) 662 @param ignore_status: boolean True iff we should ignore failures 663 to remove the interface. 664 665 """ 666 self._run('%s dev %s del' % (self._command_iw, interface), 667 ignore_status=ignore_status) 668 669 670 def determine_security(self, supported_securities): 671 """Determines security from the given list of supported securities. 672 673 @param supported_securities: list of supported securities from scan 674 675 """ 676 if not supported_securities: 677 security = SECURITY_OPEN 678 elif len(supported_securities) == 1: 679 security = supported_securities[0] 680 else: 681 security = SECURITY_MIXED 682 return security 683 684 685 def scan(self, interface, frequencies=(), ssids=()): 686 """Performs a scan. 687 688 @param interface: the interface to run the iw command against 689 @param frequencies: list of int frequencies in Mhz to scan. 690 @param ssids: list of string SSIDs to send probe requests for. 691 692 @returns a list of IwBss namedtuples; None if the scan fails 693 694 """ 695 scan_result = self.timed_scan(interface, frequencies, ssids) 696 if scan_result is None: 697 return None 698 return scan_result.bss_list 699 700 701 def timed_scan(self, interface, frequencies=(), ssids=()): 702 """Performs a timed scan. 703 704 @param interface: the interface to run the iw command against 705 @param frequencies: list of int frequencies in Mhz to scan. 706 @param ssids: list of string SSIDs to send probe requests for. 707 708 @returns a IwTimedScan namedtuple; None if the scan fails 709 710 """ 711 freq_param = '' 712 if frequencies: 713 freq_param = ' freq %s' % ' '.join(map(str, frequencies)) 714 ssid_param = '' 715 if ssids: 716 ssid_param = ' ssid "%s"' % '" "'.join(ssids) 717 718 iw_command = '%s dev %s scan%s%s' % (self._command_iw, 719 interface, freq_param, ssid_param) 720 command = IW_TIME_COMMAND_FORMAT % iw_command 721 scan = self._run(command, ignore_status=True) 722 if scan.exit_status != 0: 723 # The device was busy 724 logging.debug('scan exit_status: %d', scan.exit_status) 725 return None 726 if not scan.stdout: 727 raise error.TestFail('Missing scan parse time') 728 729 if scan.stdout.startswith(IW_TIME_COMMAND_OUTPUT_START): 730 logging.debug('Empty scan result') 731 bss_list = [] 732 else: 733 bss_list = self._parse_scan_results(scan.stdout) 734 scan_time = self._parse_scan_time(scan.stdout) 735 return IwTimedScan(scan_time, bss_list) 736 737 738 def scan_dump(self, interface): 739 """Dump the contents of the scan cache. 740 741 Note that this does not trigger a scan. Instead, it returns 742 the kernel's idea of what BSS's are currently visible. 743 744 @param interface: the interface to run the iw command against 745 746 @returns a list of IwBss namedtuples; None if the scan fails 747 748 """ 749 result = self._run('%s dev %s scan dump' % (self._command_iw, 750 interface)) 751 return self._parse_scan_results(result.stdout) 752 753 754 def set_tx_power(self, interface, power): 755 """ 756 Set the transmission power for an interface. 757 758 @param interface: string name of interface to set Tx power on. 759 @param power: string power parameter. (e.g. 'auto'). 760 761 """ 762 self._run('%s dev %s set txpower %s' % 763 (self._command_iw, interface, power)) 764 765 766 def set_freq(self, interface, freq): 767 """ 768 Set the frequency for an interface. 769 770 @param interface: string name of interface to set frequency on. 771 @param freq: int frequency 772 773 """ 774 self._run('%s dev %s set freq %d' % 775 (self._command_iw, interface, freq)) 776 777 778 def set_regulatory_domain(self, domain_string): 779 """ 780 Set the regulatory domain of the current machine. Note that 781 the regulatory change happens asynchronously to the exit of 782 this function. 783 784 @param domain_string: string regulatory domain name (e.g. 'US'). 785 786 """ 787 self._run('%s reg set %s' % (self._command_iw, domain_string)) 788 789 790 def get_regulatory_domain(self): 791 """ 792 Get the regulatory domain of the current machine. 793 794 @returns a string containing the 2-letter regulatory domain name 795 (e.g. 'US'). 796 797 """ 798 output = self._run('%s reg get' % self._command_iw).stdout 799 m = re.match('^country (..):', output) 800 if not m: 801 return None 802 return m.group(1) 803 804 805 def wait_for_scan_result(self, interface, bsses=(), ssids=(), 806 timeout_seconds=30, wait_for_all=False): 807 """Returns a list of IWBSS objects for given list of bsses or ssids. 808 809 This method will scan for a given timeout and return all of the networks 810 that have a matching ssid or bss. If wait_for_all is true and all 811 networks are not found within the given timeout an empty list will 812 be returned. 813 814 @param interface: which interface to run iw against 815 @param bsses: a list of BSS strings 816 @param ssids: a list of ssid strings 817 @param timeout_seconds: the amount of time to wait in seconds 818 @param wait_for_all: True to wait for all listed bsses or ssids; False 819 to return if any of the networks were found 820 821 @returns a list of IwBss collections that contain the given bss or ssid; 822 if the scan is empty or returns an error code None is returned. 823 824 """ 825 start_time = time.time() 826 scan_failure_attempts = 0 827 logging.info('Performing a scan with a max timeout of %d seconds.', 828 timeout_seconds) 829 remaining_bsses = copy.copy(bsses) 830 remaining_ssids = copy.copy(ssids) 831 while time.time() - start_time < timeout_seconds: 832 scan_results = self.scan(interface) 833 if scan_results is None or len(scan_results) == 0: 834 scan_failure_attempts += 1 835 # Allow in-progress scan to complete 836 time.sleep(5) 837 # If the in-progress scan takes more than 30 seconds to 838 # complete it will most likely never complete; abort. 839 # See crbug.com/309148. 840 if scan_failure_attempts > 5: 841 logging.error('Scan failed to run, see debug log for ' 842 'error code.') 843 return None 844 continue 845 scan_failure_attempts = 0 846 matching_iwbsses = set() 847 for iwbss in scan_results: 848 if iwbss.bss in bsses and len(remaining_bsses) > 0: 849 remaining_bsses.remove(iwbss.bss) 850 matching_iwbsses.add(iwbss) 851 if iwbss.ssid in ssids and len(remaining_ssids) > 0: 852 remaining_ssids.remove(iwbss.ssid) 853 matching_iwbsses.add(iwbss) 854 if wait_for_all: 855 if len(remaining_bsses) == 0 and len(remaining_ssids) == 0: 856 return list(matching_iwbsses) 857 else: 858 if len(matching_iwbsses) > 0: 859 return list(matching_iwbsses) 860 861 862 if scan_failure_attempts > 0: 863 return None 864 # The SSID wasn't found, but the device is fine. 865 return list() 866 867 868 def wait_for_link(self, interface, timeout_seconds=10): 869 """Waits until a link completes on |interface|. 870 871 @param interface: which interface to run iw against. 872 @param timeout_seconds: the amount of time to wait in seconds. 873 874 @returns True if link was established before the timeout. 875 876 """ 877 start_time = time.time() 878 while time.time() - start_time < timeout_seconds: 879 link_results = self._run('%s dev %s link' % 880 (self._command_iw, interface)) 881 if 'Not connected' not in link_results.stdout: 882 return True 883 time.sleep(1) 884 return False 885 886 887 def set_antenna_bitmap(self, phy, tx_bitmap, rx_bitmap): 888 """Set antenna chain mask on given phy (radio). 889 890 This function will set the antennas allowed to use for TX and 891 RX on the |phy| based on the |tx_bitmap| and |rx_bitmap|. 892 This command is only allowed when the interfaces on the phy are down. 893 894 @param phy: phy name 895 @param tx_bitmap: bitmap of allowed antennas to use for TX 896 @param rx_bitmap: bitmap of allowed antennas to use for RX 897 898 """ 899 command = '%s phy %s set antenna %d %d' % (self._command_iw, phy, 900 tx_bitmap, rx_bitmap) 901 self._run(command) 902 903 904 def get_event_logger(self): 905 """Create and return a IwEventLogger object. 906 907 @returns a IwEventLogger object. 908 909 """ 910 local_file = IW_LOCAL_EVENT_LOG_FILE % (self._log_id) 911 self._log_id += 1 912 return iw_event_logger.IwEventLogger(self._host, self._command_iw, 913 local_file) 914 915 916 def vht_supported(self): 917 """Returns True if VHT is supported; False otherwise.""" 918 result = self._run('%s list' % self._command_iw).stdout 919 if 'VHT Capabilities' in result: 920 return True 921 return False 922 923 924 def frequency_supported(self, frequency): 925 """Returns True if the given frequency is supported; False otherwise. 926 927 @param frequency: int Wifi frequency to check if it is supported by 928 DUT. 929 """ 930 phys = self.list_phys() 931 for phy in phys: 932 for band in phy.bands: 933 if frequency in band.frequencies: 934 return True 935 return False 936 937 938 def get_fragmentation_threshold(self, phy): 939 """Returns the fragmentation threshold for |phy|. 940 941 @param phy: phy name 942 """ 943 ret = self._run('%s phy %s info' % (self._command_iw, phy)) 944 frag_regex = r'^\s+Fragmentation threshold:\s+([0-9]+)$' 945 match = re.search(frag_regex, ret.stdout, re.MULTILINE) 946 947 if match: 948 return int(match.group(1)) 949 950 return None 951