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