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