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