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