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