1# Lint as: python2, python3
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
10import datetime
11import collections
12import logging
13import os
14import random
15import time
16
17from autotest_lib.client.common_lib import error
18from autotest_lib.client.common_lib.cros import path_utils
19from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
20from autotest_lib.client.common_lib.cros.network import interface
21from autotest_lib.client.common_lib.cros.network import iw_runner
22from autotest_lib.client.common_lib.cros.network import ping_runner
23from autotest_lib.server.cros.network import packet_capturer
24import six
25from six.moves import range
26
27NetDev = collections.namedtuple('NetDev',
28                                ['inherited', 'phy', 'if_name', 'if_type'])
29
30class LinuxSystem(object):
31    """Superclass for test machines running Linux.
32
33    Provides a common point for routines that use the cfg80211 userspace tools
34    to manipulate the wireless stack, regardless of the role they play.
35    Currently the commands shared are the init, which queries for wireless
36    devices, along with start_capture and stop_capture.  More commands may
37    migrate from site_linux_router as appropriate to share.
38
39    """
40
41    CAPABILITY_5GHZ = '5ghz'
42    CAPABILITY_MULTI_AP = 'multi_ap'
43    CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
44    CAPABILITY_IBSS = 'ibss_supported'
45    CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
46    CAPABILITY_TDLS = 'tdls'
47    CAPABILITY_VHT = 'vht'
48    CAPABILITY_SME = 'sme'
49    CAPABILITY_SUPPLICANT_ROAMING = "supplicant_roaming"
50    BRIDGE_INTERFACE_NAME = 'br0'
51    HOSTAP_BRIDGE_INTERFACE_PREFIX = 'hostapbr'
52    IFB_INTERFACE_PREFIX = 'ifb'
53    MIN_SPATIAL_STREAMS = 2
54    MAC_BIT_LOCAL = 0x2  # Locally administered.
55    MAC_BIT_MULTICAST = 0x1
56    MAC_RETRY_LIMIT = 1000
57
58    _UMA_EVENTS = '/var/lib/metrics/uma-events'
59    _LOG_PATH_PREFIX = '/tmp/autotest-'
60
61
62    @property
63    def capabilities(self):
64        """@return iterable object of AP capabilities for this system."""
65        if self._capabilities is None:
66            self._capabilities = self.get_capabilities()
67            logging.info('%s system capabilities: %r',
68                         self.role, self._capabilities)
69        return self._capabilities
70
71
72    @property
73    def board(self):
74        """@return string self reported board of this device."""
75        if self._board is None:
76            # Remove 'board:' prefix.
77            self._board = self.host.get_board().split(':')[1]
78        return self._board
79
80
81    def __init__(self, host, role, inherit_interfaces=False):
82        self.host = host
83        self.role = role
84        self.inherit_interfaces = inherit_interfaces
85        self.__setup()
86
87
88    def __setup(self):
89        """Set up this system.
90
91        Can be used either to complete initialization of a LinuxSystem object,
92        or to re-establish a good state after a reboot.
93
94        """
95        # hostapd, tcpdump, netperf, etc., may leave behind logs, pcap files,
96        # etc., which can fill up tmpfs. Clear them out now.
97        self.host.run('rm -rf %s*' % self._LOG_PATH_PREFIX)
98        self._logdir = self.host.run('mktemp -d %sXXXXXX' %
99                self._LOG_PATH_PREFIX).stdout.strip()
100
101        # Command locations.
102        cmd_iw = path_utils.must_be_installed('/usr/sbin/iw', host=self.host)
103        self.cmd_ip = path_utils.must_be_installed('/usr/sbin/ip',
104                                                   host=self.host)
105        self.cmd_readlink = '%s -l' % path_utils.must_be_installed(
106                '/bin/ls', host=self.host)
107
108        self._packet_capturer = packet_capturer.get_packet_capturer(
109                self.host, host_description=self.role, cmd_ip=self.cmd_ip,
110                cmd_iw=cmd_iw, ignore_failures=True, logdir=self.logdir)
111        self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
112                                            command_iw=cmd_iw)
113
114        self._phy_list = None
115        self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
116        logging.debug('Current regulatory domain %r',
117                      self.iw_runner.get_regulatory_domain())
118        self._interfaces = []
119        for interface in self.iw_runner.list_interfaces():
120            if self.inherit_interfaces:
121                self._interfaces.append(NetDev(inherited=True,
122                                               if_name=interface.if_name,
123                                               if_type=interface.if_type,
124                                               phy=interface.phy))
125            else:
126                self.iw_runner.remove_interface(interface.if_name)
127
128        self._wlanifs_in_use = []
129        self._local_macs_in_use = set()
130        self._capture_interface = None
131        self._board = None
132        # Some uses of LinuxSystem don't use the interface allocation facility.
133        # Don't force us to remove all the existing interfaces if this facility
134        # is not desired.
135        self._wlanifs_initialized = False
136        self._capabilities = None
137        self._ping_runner = ping_runner.PingRunner(host=self.host)
138        self._bridge_interface = None
139        self._virtual_ethernet_pair = None
140
141        # TODO(crbug.com/839164): some routers fill their stateful partition
142        # with uncollected metrics.
143        if self.host.path_exists(self._UMA_EVENTS):
144            self.host.run('truncate -s 0 %s' % self._UMA_EVENTS,
145                          ignore_status=True)
146
147        # Tear down hostapbr bridge and intermediate functional block
148        # interfaces. Run this even for pcaps, because pcap devices sometimes
149        # are run as APs too.
150        # TODO(crbug.com/1005443): drop the ifb hack when we deploy an AP OS
151        # image that has fixes for crbug.com/960551.
152        result = self.host.run('ls -d /sys/class/net/%s* /sys/class/net/%s*'
153                               ' 2>/dev/null' %
154                               (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
155                                self.IFB_INTERFACE_PREFIX),
156                               ignore_status=True)
157        for path in result.stdout.splitlines():
158            self.delete_link(path.split('/')[-1])
159
160
161    @property
162    def phy_list(self):
163        """@return iterable object of PHY descriptions for this system."""
164        if self._phy_list is None:
165            self._phy_list = self.iw_runner.list_phys()
166        return self._phy_list
167
168
169    def _phy_by_name(self, phy_name):
170        """@return IwPhy for PHY with name |phy_name|, or None."""
171        for phy in self._phy_list:
172            if phy.name == phy_name:
173                return phy
174        else:
175            return None
176
177
178    def _get_phy_info(self):
179        """Get information about WiFi devices.
180
181        Parse the output of 'iw list' and some of sysfs and return:
182
183        A dict |phys_for_frequency| which maps from each frequency to a
184        list of phys that support that channel.
185
186        A dict |phy_bus_type| which maps from each phy to the bus type for
187        each phy.
188
189        @return phys_for_frequency, phy_bus_type tuple as described.
190
191        """
192        phys_for_frequency = {}
193        phy_caps = {}
194        phy_list = []
195        for phy in self.phy_list:
196            phy_list.append(phy.name)
197            for band in phy.bands:
198                for mhz in band.frequencies:
199                    if mhz not in phys_for_frequency:
200                        phys_for_frequency[mhz] = [phy.name]
201                    else:
202                        phys_for_frequency[mhz].append(phy.name)
203
204        phy_bus_type = {}
205        for phy in phy_list:
206            phybus = 'unknown'
207            command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
208            devpath = self.host.run(command).stdout
209            if '/usb' in devpath:
210                phybus = 'usb'
211            elif '/mmc' in devpath:
212                phybus = 'sdio'
213            elif '/pci' in devpath:
214                phybus = 'pci'
215            phy_bus_type[phy] = phybus
216        logging.debug('Got phys for frequency: %r', phys_for_frequency)
217        return phys_for_frequency, phy_bus_type
218
219
220    def _create_bridge_interface(self):
221        """Create a bridge interface."""
222        self.host.run('%s link add name %s type bridge' %
223                      (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
224        self.host.run('%s link set dev %s up' %
225                      (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
226        self._bridge_interface = self.BRIDGE_INTERFACE_NAME
227
228
229    def _create_virtual_ethernet_pair(self):
230        """Create a virtual ethernet pair."""
231        self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
232                interface_ip=None, peer_interface_ip=None, host=self.host)
233        self._virtual_ethernet_pair.setup()
234
235
236    def _get_unique_mac(self):
237        """Get a MAC address that is likely to be unique.
238
239        Generates a MAC address that is a) guaranteed not to be in use
240        on this host, and b) likely to be unique within the test cell.
241
242        @return string MAC address.
243
244        """
245        # We use SystemRandom to reduce the likelyhood of coupling
246        # across systems. (The default random class might, e.g., seed
247        # itself based on wall-clock time.)
248        sysrand = random.SystemRandom()
249        for tries in range(0, self.MAC_RETRY_LIMIT):
250            mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % (
251                (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) |
252                self.MAC_BIT_LOCAL,
253                sysrand.getrandbits(8),
254                sysrand.getrandbits(8),
255                sysrand.getrandbits(8),
256                sysrand.getrandbits(8),
257                sysrand.getrandbits(8))
258            if mac_addr not in self._local_macs_in_use:
259                self._local_macs_in_use.add(mac_addr)
260                return mac_addr
261        else:
262            raise error.TestError('Failed to find a new MAC address')
263
264
265    def _phy_in_use(self, phy_name):
266        """Determine whether or not a PHY is used by an active DEV
267
268        @return bool True iff PHY is in use.
269        """
270        for net_dev in self._wlanifs_in_use:
271            if net_dev.phy == phy_name:
272                return True
273        return False
274
275
276    def remove_interface(self, interface):
277        """Remove an interface from a WiFi device.
278
279        @param interface string interface to remove (e.g. wlan0).
280
281        """
282        self.release_interface(interface)
283        self.host.run('%s link set %s down' % (self.cmd_ip, interface))
284        self.iw_runner.remove_interface(interface)
285        for net_dev in self._interfaces:
286            if net_dev.if_name == interface:
287                self._interfaces.remove(net_dev)
288                break
289
290
291    def delete_link(self, name):
292        """Delete link using the `ip` command.
293
294        @param name string link name.
295
296        """
297        self.host.run('%s link del %s' % (self.cmd_ip, name),
298                      ignore_status=True)
299
300
301    def close(self):
302        """Close global resources held by this system."""
303        logging.debug('Cleaning up host object for %s', self.role)
304        self._packet_capturer.close()
305        # Release and remove any interfaces that we create.
306        for net_dev in self._wlanifs_in_use:
307            self.release_interface(net_dev.if_name)
308        for net_dev in self._interfaces:
309            if net_dev.inherited:
310                continue
311            self.remove_interface(net_dev.if_name)
312        if self._bridge_interface is not None:
313            self.remove_bridge_interface()
314        if self._virtual_ethernet_pair is not None:
315            self.remove_ethernet_pair_interface()
316        self.host.close()
317        self.host = None
318
319
320    def reboot(self, timeout):
321        """Reboot this system, and restore it to a known-good state.
322
323        @param timeout Maximum seconds to wait for system to return.
324
325        """
326        self.host.reboot(timeout=timeout, wait=True)
327        self.__setup()
328
329
330    def get_capabilities(self):
331        caps = set()
332        phymap = self.phys_for_frequency
333        if [freq for freq in six.iterkeys(phymap) if freq > 5000]:
334            # The frequencies are expressed in megaherz
335            caps.add(self.CAPABILITY_5GHZ)
336        if [freq for freq in six.iterkeys(phymap) if len(phymap[freq]) > 1]:
337            caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
338            caps.add(self.CAPABILITY_MULTI_AP)
339        elif len(self.phy_bus_type) > 1:
340            caps.add(self.CAPABILITY_MULTI_AP)
341        for phy in self.phy_list:
342            if ('tdls_mgmt' in phy.commands or
343                'tdls_oper' in phy.commands or
344                'T-DLS' in phy.features):
345                caps.add(self.CAPABILITY_TDLS)
346            if 'authenticate' in phy.commands:
347                caps.add(self.CAPABILITY_SME)
348            if phy.support_vht:
349                caps.add(self.CAPABILITY_VHT)
350            if 'roaming' not in phy.features:
351                caps.add(self.CAPABILITY_SUPPLICANT_ROAMING)
352        if any([iw_runner.DEV_MODE_IBSS in phy.modes
353                for phy in self.phy_list]):
354            caps.add(self.CAPABILITY_IBSS)
355        return caps
356
357
358    def start_capture(self, frequency,
359                      width_type=None, snaplen=None, filename=None):
360        """Start a packet capture.
361
362        @param frequency int frequency of channel to capture on.
363        @param width_type object width type from iw_runner.
364        @param snaplen int number of bytes to retain per capture frame.
365        @param filename string filename to write capture to.
366
367        """
368        if self._packet_capturer.capture_running:
369            self.stop_capture()
370        self._capture_interface = self.get_wlanif(frequency, 'monitor')
371        full_interface = [net_dev for net_dev in self._interfaces
372                          if net_dev.if_name == self._capture_interface][0]
373        # If this is the only interface on this phy, we ought to configure
374        # the phy with a channel and a width.  Otherwise, inherit the
375        # settings of the phy as they stand.
376        if len([net_dev for net_dev in self._interfaces
377                if net_dev.phy == full_interface.phy]) == 1:
378            self._packet_capturer.configure_raw_monitor(
379                    self._capture_interface, frequency, width_type=width_type)
380        else:
381            self.host.run('%s link set %s up' %
382                          (self.cmd_ip, self._capture_interface))
383
384        # Start the capture.
385        if filename:
386            remote_path = os.path.join('/tmp', os.path.basename(filename))
387        else:
388            remote_path = None
389        self._packet_capturer.start_capture(
390            self._capture_interface, './debug/', snaplen=snaplen,
391            remote_file=remote_path)
392
393
394    def stop_capture(self, save_dir=None, save_filename=None):
395        """Stop a packet capture.
396
397        @param save_dir string path to directory to save pcap files in.
398        @param save_filename string basename of file to save pcap in locally.
399
400        """
401        if not self._packet_capturer.capture_running:
402            return
403        results = self._packet_capturer.stop_capture(
404                local_save_dir=save_dir, local_pcap_filename=save_filename)
405        self.release_interface(self._capture_interface)
406        self._capture_interface = None
407        return results
408
409
410    def sync_host_times(self):
411        """Set time on our DUT to match local time."""
412        epoch_seconds = time.time()
413        busybox_format = '%Y%m%d%H%M.%S'
414        busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
415        self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
416                      (epoch_seconds, busybox_date))
417
418
419    def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
420        """Get a phy appropriate for a frequency and phytype.
421
422        Return the most appropriate phy interface for operating on the
423        frequency |frequency| in the role indicated by |phytype|.  Prefer idle
424        phys to busy phys if any exist.  Secondarily, show affinity for phys
425        that use the bus type associated with this phy type.
426
427        @param frequency int WiFi frequency of phy.
428        @param phytype string key of phytype registered at construction time.
429        @param spatial_streams int number of spatial streams required.
430        @return string name of phy to use.
431
432        """
433        phy_objs = []
434        for phy_name in self.phys_for_frequency[frequency]:
435            phy_obj = self._phy_by_name(phy_name)
436            num_antennas = min(phy_obj.avail_rx_antennas,
437                               phy_obj.avail_tx_antennas)
438            if num_antennas >= spatial_streams:
439                phy_objs.append(phy_obj)
440            elif num_antennas == 0:
441                logging.warning(
442                    'Allowing use of %s, which reports zero antennas', phy_name)
443                phy_objs.append(phy_obj)
444            else:
445                logging.debug(
446                    'Filtering out %s, which reports only %d antennas',
447                    phy_name, num_antennas)
448
449        busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
450        idle_phy_objs = [phy_obj for phy_obj in phy_objs
451                         if phy_obj.name not in busy_phys]
452        phy_objs = idle_phy_objs or phy_objs
453        phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas,
454                                              phy_obj.avail_tx_antennas),
455                      reverse=True)
456        phys = [phy_obj.name for phy_obj in phy_objs]
457
458        preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
459        preferred_phys = [phy for phy in phys
460                          if self.phy_bus_type[phy] == preferred_bus]
461        phys = preferred_phys or phys
462
463        return phys[0]
464
465
466    def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
467        """Get a WiFi device that supports the given frequency and phytype.
468
469        We simply find or create a suitable DEV. It is left to the
470        caller to actually configure the frequency and bring up the
471        interface.
472
473        @param phytype string type of phy (e.g. 'monitor').
474        @param spatial_streams int number of spatial streams required.
475        @param frequency int WiFi frequency to support.
476        @param same_phy_as string create the interface on the same phy as this.
477        @return NetDev WiFi device.
478
479        """
480        if frequency and same_phy_as:
481            raise error.TestError(
482                'Can not combine |frequency| and |same_phy_as|')
483
484        if not (frequency or same_phy_as):
485            raise error.TestError(
486                'Must specify one of |frequency| or |same_phy_as|')
487
488        if spatial_streams is None:
489            spatial_streams = self.MIN_SPATIAL_STREAMS
490        # We don't want to use the 3rd radio on Whirlwind. Reject it if someone
491        # tries to add a test that uses it.
492        elif spatial_streams < self.MIN_SPATIAL_STREAMS and \
493             self.board == 'whirlwind':
494            raise error.TestError('Requested spatial streams: %d; minimum %d' \
495                                  % (spatial_streams, self.MIN_SPATIAL_STREAMS))
496
497        if same_phy_as:
498            for net_dev in self._interfaces:
499                if net_dev.if_name == same_phy_as:
500                    phy = net_dev.phy
501                    break
502            else:
503                raise error.TestFail('Unable to find phy for interface %s' %
504                                     same_phy_as)
505        elif frequency in self.phys_for_frequency:
506            phy = self._get_phy_for_frequency(
507                frequency, phytype, spatial_streams)
508        else:
509            raise error.TestFail('Unable to find phy for frequency %d' %
510                                 frequency)
511
512        # If we have a suitable unused interface sitting around on this
513        # phy, reuse it.
514        for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
515            if net_dev.phy == phy and net_dev.if_type == phytype:
516                break
517        else:
518            # Because we can reuse interfaces, we have to iteratively find a
519            # good interface name.
520            name_exists = lambda name: bool([net_dev
521                                             for net_dev in self._interfaces
522                                             if net_dev.if_name == name])
523            if_name = lambda index: '%s%d' % (phytype, index)
524            if_index = len(self._interfaces)
525            while name_exists(if_name(if_index)):
526                if_index += 1
527            net_dev = NetDev(phy=phy, if_name=if_name(if_index),
528                             if_type=phytype, inherited=False)
529            self._interfaces.append(net_dev)
530            self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
531
532        # Link must be down to reconfigure MAC address.
533        self.host.run('%s link set dev %s down' % (
534            self.cmd_ip, net_dev.if_name))
535        if same_phy_as:
536            self.clone_mac_address(src_dev=same_phy_as,
537                                   dst_dev=net_dev.if_name)
538        else:
539            self.ensure_unique_mac(net_dev)
540
541        return net_dev
542
543
544    def get_configured_interface(self, phytype, spatial_streams=None,
545                                 frequency=None, same_phy_as=None):
546        """Get a WiFi device that supports the given frequency and phytype.
547
548        The device's link state will be UP, and (where possible) the device
549        will be configured to operate on |frequency|.
550
551        @param phytype string type of phy (e.g. 'monitor').
552        @param spatial_streams int number of spatial streams required.
553        @param frequency int WiFi frequency to support.
554        @param same_phy_as string create the interface on the same phy as this.
555        @return string WiFi device.
556
557        """
558        net_dev = self._get_wlanif(
559            phytype, spatial_streams, frequency, same_phy_as)
560
561        self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name))
562
563        if frequency:
564            if phytype == 'managed':
565                logging.debug('Skipped setting frequency for DEV %s '
566                              'since managed mode DEVs roam across APs.',
567                              net_dev.if_name)
568            elif same_phy_as or self._phy_in_use(net_dev.phy):
569                logging.debug('Skipped setting frequency for DEV %s '
570                              'since PHY %s is already in use',
571                              net_dev.if_name, net_dev.phy)
572            else:
573                self.iw_runner.set_freq(net_dev.if_name, frequency)
574
575        self._wlanifs_in_use.append(net_dev)
576        return net_dev.if_name
577
578
579    # TODO(quiche): Deprecate this, in favor of get_configured_interface().
580    # crbug.com/512169.
581    def get_wlanif(self, frequency, phytype,
582                   spatial_streams=None, same_phy_as=None):
583        """Get a WiFi device that supports the given frequency and phytype.
584
585        We simply find or create a suitable DEV. It is left to the
586        caller to actually configure the frequency and bring up the
587        interface.
588
589        @param frequency int WiFi frequency to support.
590        @param phytype string type of phy (e.g. 'monitor').
591        @param spatial_streams int number of spatial streams required.
592        @param same_phy_as string create the interface on the same phy as this.
593        @return string WiFi device.
594
595        """
596        net_dev = self._get_wlanif(
597            phytype, spatial_streams, frequency, same_phy_as)
598        self._wlanifs_in_use.append(net_dev)
599        return net_dev.if_name
600
601
602    def ensure_unique_mac(self, net_dev):
603        """Ensure MAC address of |net_dev| meets uniqueness requirements.
604
605        The Linux kernel does not allow multiple APs with the same
606        BSSID on the same PHY (at least, with some drivers). Hence, we
607        want to ensure that the DEVs for a PHY have unique MAC
608        addresses.
609
610        Note that we do not attempt to make the MACs unique across
611        PHYs, because some tests deliberately create such scenarios.
612
613        @param net_dev NetDev to uniquify.
614
615        """
616        if net_dev.if_type == 'monitor':
617            return
618
619        our_ifname = net_dev.if_name
620        our_phy = net_dev.phy
621        our_mac = interface.Interface(our_ifname, self.host).mac_address
622        sibling_devs = [dev for dev in self._interfaces
623                        if (dev.phy == our_phy and
624                            dev.if_name != our_ifname and
625                            dev.if_type != 'monitor')]
626        sibling_macs = (
627            interface.Interface(sib_dev.if_name, self.host).mac_address
628            for sib_dev in sibling_devs)
629        if our_mac in sibling_macs:
630            self.configure_interface_mac(our_ifname,
631                                         self._get_unique_mac())
632
633
634    def configure_interface_mac(self, wlanif, new_mac):
635        """Change the MAC address for an interface.
636
637        @param wlanif string name of device to reconfigure.
638        @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55')
639
640        """
641        self.host.run('%s link set %s address %s' %
642                      (self.cmd_ip, wlanif, new_mac))
643
644
645    def clone_mac_address(self, src_dev=None, dst_dev=None):
646        """Copy the MAC address from one interface to another.
647
648        @param src_dev string name of device to copy address from.
649        @param dst_dev string name of device to copy address to.
650
651        """
652        self.configure_interface_mac(
653            dst_dev,
654            interface.Interface(src_dev, self.host).mac_address)
655
656
657    def release_interface(self, wlanif):
658        """Release a device allocated throuhg get_wlanif().
659
660        @param wlanif string name of device to release.
661
662        """
663        for net_dev in self._wlanifs_in_use:
664            if net_dev.if_name == wlanif:
665                 self._wlanifs_in_use.remove(net_dev)
666
667
668    def get_bridge_interface(self):
669        """Return the bridge interface, create one if it is not created yet.
670
671        @return string name of bridge interface.
672        """
673        if self._bridge_interface is None:
674            self._create_bridge_interface()
675        return self._bridge_interface
676
677
678    def remove_bridge_interface(self):
679        """Remove the bridge interface that's been created."""
680        if self._bridge_interface is not None:
681            self.host.run('%s link delete %s type bridge' %
682                          (self.cmd_ip, self._bridge_interface))
683        self._bridge_interface = None
684
685
686    def add_interface_to_bridge(self, interface):
687        """Add an interface to the bridge interface.
688
689        This will create the bridge interface if it is not created yet.
690
691        @param interface string name of the interface to add to the bridge.
692        """
693        if self._bridge_interface is None:
694            self._create_bridge_interface()
695        # TODO b:169251326 terms below are set outside of this codebase
696        # and should be updated when possible. ("master" -> "main")
697        self.host.run('%s link set dev %s master %s' %
698                      (self.cmd_ip, interface, self._bridge_interface))
699
700
701    def get_virtual_ethernet_main_interface(self):
702        """Return the main interface of the virtual ethernet pair.
703
704        @return string name of the main interface of the virtual ethernet
705                pair.
706        """
707        if self._virtual_ethernet_pair is None:
708            self._create_virtual_ethernet_pair()
709        return self._virtual_ethernet_pair.interface_name
710
711
712    def get_virtual_ethernet_peer_interface(self):
713        """Return the peer interface of the virtual ethernet pair.
714
715        @return string name of the peer interface of the virtual ethernet pair.
716        """
717        if self._virtual_ethernet_pair is None:
718            self._create_virtual_ethernet_pair()
719        return self._virtual_ethernet_pair.peer_interface_name
720
721
722    def remove_ethernet_pair_interface(self):
723        """Remove the virtual ethernet pair that's been created."""
724        if self._virtual_ethernet_pair is not None:
725            self._virtual_ethernet_pair.teardown()
726        self._virtual_ethernet_pair = None
727
728
729    def require_capabilities(self, requirements):
730        """Require capabilities of this LinuxSystem.
731
732        Check that capabilities in |requirements| exist on this system.
733        Raise an exception to skip but not fail the test if said
734        capabilities are not found.
735
736        @param requirements list of CAPABILITY_* defined above.
737
738        """
739        missing = [cap for cap in requirements if not cap in self.capabilities]
740        if missing:
741            raise error.TestNAError('%s is missing required capabilites: %r'
742                                    % (self.role, missing))
743
744
745    def disable_antennas_except(self, permitted_antennas):
746        """Disable unwanted antennas.
747
748        Disable all antennas except those specified in |permitted_antennas|.
749        Note that one or more of them may remain disabled if the underlying
750        hardware does not support them.
751
752        @param permitted_antennas int bitmask specifying antennas that we should
753        attempt to enable.
754
755        """
756        for phy in self.phy_list:
757            if not phy.supports_setting_antenna_mask:
758                continue
759            # Determine valid bitmap values based on available antennas.
760            self.iw_runner.set_antenna_bitmap(phy.name,
761                permitted_antennas & phy.avail_tx_antennas,
762                permitted_antennas & phy.avail_rx_antennas)
763
764
765    def enable_all_antennas(self):
766        """Enable all antennas on all phys."""
767        for phy in self.phy_list:
768            if not phy.supports_setting_antenna_mask:
769                continue
770            self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
771                                              phy.avail_rx_antennas)
772
773
774    def ping(self, ping_config):
775        """Ping an IP from this system.
776
777        @param ping_config PingConfig object describing the ping command to run.
778        @return a PingResult object.
779
780        """
781        logging.info('Pinging from the %s.', self.role)
782        return self._ping_runner.ping(ping_config)
783
784
785    @property
786    def logdir(self):
787        """Return a directory for storing temporary logs.
788        @return string path to temporary log directory.
789        """
790        return self._logdir
791