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