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