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