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