• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import datetime
6import collections
7import logging
8import os
9import random
10import time
11
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib.cros import path_utils
14from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
15from autotest_lib.client.common_lib.cros.network import interface
16from autotest_lib.client.common_lib.cros.network import iw_runner
17from autotest_lib.client.common_lib.cros.network import ping_runner
18from autotest_lib.server.cros.network import packet_capturer
19
20NetDev = collections.namedtuple('NetDev',
21                                ['inherited', 'phy', 'if_name', 'if_type'])
22
23class 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