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