• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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