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