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