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 141 # TODO(crbug.com/839164): some routers fill their stateful partition 142 # with uncollected metrics. 143 if self.host.path_exists(self._UMA_EVENTS): 144 self.host.run('truncate -s 0 %s' % self._UMA_EVENTS, 145 ignore_status=True) 146 147 # Tear down hostapbr bridge and intermediate functional block 148 # interfaces. Run this even for pcaps, because pcap devices sometimes 149 # are run as APs too. 150 # TODO(crbug.com/1005443): drop the ifb hack when we deploy an AP OS 151 # image that has fixes for crbug.com/960551. 152 result = self.host.run('ls -d /sys/class/net/%s* /sys/class/net/%s*' 153 ' 2>/dev/null' % 154 (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, 155 self.IFB_INTERFACE_PREFIX), 156 ignore_status=True) 157 for path in result.stdout.splitlines(): 158 self.delete_link(path.split('/')[-1]) 159 160 161 @property 162 def phy_list(self): 163 """@return iterable object of PHY descriptions for this system.""" 164 if self._phy_list is None: 165 self._phy_list = self.iw_runner.list_phys() 166 return self._phy_list 167 168 169 def _phy_by_name(self, phy_name): 170 """@return IwPhy for PHY with name |phy_name|, or None.""" 171 for phy in self._phy_list: 172 if phy.name == phy_name: 173 return phy 174 else: 175 return None 176 177 178 def _get_phy_info(self): 179 """Get information about WiFi devices. 180 181 Parse the output of 'iw list' and some of sysfs and return: 182 183 A dict |phys_for_frequency| which maps from each frequency to a 184 list of phys that support that channel. 185 186 A dict |phy_bus_type| which maps from each phy to the bus type for 187 each phy. 188 189 @return phys_for_frequency, phy_bus_type tuple as described. 190 191 """ 192 phys_for_frequency = {} 193 phy_caps = {} 194 phy_list = [] 195 for phy in self.phy_list: 196 phy_list.append(phy.name) 197 for band in phy.bands: 198 for mhz in band.frequencies: 199 if mhz not in phys_for_frequency: 200 phys_for_frequency[mhz] = [phy.name] 201 else: 202 phys_for_frequency[mhz].append(phy.name) 203 204 phy_bus_type = {} 205 for phy in phy_list: 206 phybus = 'unknown' 207 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy) 208 devpath = self.host.run(command).stdout 209 if '/usb' in devpath: 210 phybus = 'usb' 211 elif '/mmc' in devpath: 212 phybus = 'sdio' 213 elif '/pci' in devpath: 214 phybus = 'pci' 215 phy_bus_type[phy] = phybus 216 logging.debug('Got phys for frequency: %r', phys_for_frequency) 217 return phys_for_frequency, phy_bus_type 218 219 220 def _create_bridge_interface(self): 221 """Create a bridge interface.""" 222 self.host.run('%s link add name %s type bridge' % 223 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME)) 224 self.host.run('%s link set dev %s up' % 225 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME)) 226 self._bridge_interface = self.BRIDGE_INTERFACE_NAME 227 228 229 def _create_virtual_ethernet_pair(self): 230 """Create a virtual ethernet pair.""" 231 self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair( 232 interface_ip=None, peer_interface_ip=None, host=self.host) 233 self._virtual_ethernet_pair.setup() 234 235 236 def _get_unique_mac(self): 237 """Get a MAC address that is likely to be unique. 238 239 Generates a MAC address that is a) guaranteed not to be in use 240 on this host, and b) likely to be unique within the test cell. 241 242 @return string MAC address. 243 244 """ 245 # We use SystemRandom to reduce the likelyhood of coupling 246 # across systems. (The default random class might, e.g., seed 247 # itself based on wall-clock time.) 248 sysrand = random.SystemRandom() 249 for tries in range(0, self.MAC_RETRY_LIMIT): 250 mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % ( 251 (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) | 252 self.MAC_BIT_LOCAL, 253 sysrand.getrandbits(8), 254 sysrand.getrandbits(8), 255 sysrand.getrandbits(8), 256 sysrand.getrandbits(8), 257 sysrand.getrandbits(8)) 258 if mac_addr not in self._local_macs_in_use: 259 self._local_macs_in_use.add(mac_addr) 260 return mac_addr 261 else: 262 raise error.TestError('Failed to find a new MAC address') 263 264 265 def _phy_in_use(self, phy_name): 266 """Determine whether or not a PHY is used by an active DEV 267 268 @return bool True iff PHY is in use. 269 """ 270 for net_dev in self._wlanifs_in_use: 271 if net_dev.phy == phy_name: 272 return True 273 return False 274 275 276 def remove_interface(self, interface): 277 """Remove an interface from a WiFi device. 278 279 @param interface string interface to remove (e.g. wlan0). 280 281 """ 282 self.release_interface(interface) 283 self.host.run('%s link set %s down' % (self.cmd_ip, interface)) 284 self.iw_runner.remove_interface(interface) 285 for net_dev in self._interfaces: 286 if net_dev.if_name == interface: 287 self._interfaces.remove(net_dev) 288 break 289 290 291 def delete_link(self, name): 292 """Delete link using the `ip` command. 293 294 @param name string link name. 295 296 """ 297 self.host.run('%s link del %s' % (self.cmd_ip, name), 298 ignore_status=True) 299 300 301 def close(self): 302 """Close global resources held by this system.""" 303 logging.debug('Cleaning up host object for %s', self.role) 304 self._packet_capturer.close() 305 # Release and remove any interfaces that we create. 306 for net_dev in self._wlanifs_in_use: 307 self.release_interface(net_dev.if_name) 308 for net_dev in self._interfaces: 309 if net_dev.inherited: 310 continue 311 self.remove_interface(net_dev.if_name) 312 if self._bridge_interface is not None: 313 self.remove_bridge_interface() 314 if self._virtual_ethernet_pair is not None: 315 self.remove_ethernet_pair_interface() 316 self.host.close() 317 self.host = None 318 319 320 def reboot(self, timeout): 321 """Reboot this system, and restore it to a known-good state. 322 323 @param timeout Maximum seconds to wait for system to return. 324 325 """ 326 self.host.reboot(timeout=timeout, wait=True) 327 self.__setup() 328 329 330 def get_capabilities(self): 331 caps = set() 332 phymap = self.phys_for_frequency 333 if [freq for freq in six.iterkeys(phymap) if freq > 5000]: 334 # The frequencies are expressed in megaherz 335 caps.add(self.CAPABILITY_5GHZ) 336 if [freq for freq in six.iterkeys(phymap) if len(phymap[freq]) > 1]: 337 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND) 338 caps.add(self.CAPABILITY_MULTI_AP) 339 elif len(self.phy_bus_type) > 1: 340 caps.add(self.CAPABILITY_MULTI_AP) 341 for phy in self.phy_list: 342 if ('tdls_mgmt' in phy.commands or 343 'tdls_oper' in phy.commands or 344 'T-DLS' in phy.features): 345 caps.add(self.CAPABILITY_TDLS) 346 if 'authenticate' in phy.commands: 347 caps.add(self.CAPABILITY_SME) 348 if phy.support_vht: 349 caps.add(self.CAPABILITY_VHT) 350 if 'roaming' not in phy.features: 351 caps.add(self.CAPABILITY_SUPPLICANT_ROAMING) 352 if any([iw_runner.DEV_MODE_IBSS in phy.modes 353 for phy in self.phy_list]): 354 caps.add(self.CAPABILITY_IBSS) 355 return caps 356 357 358 def start_capture(self, frequency, 359 width_type=None, snaplen=None, filename=None): 360 """Start a packet capture. 361 362 @param frequency int frequency of channel to capture on. 363 @param width_type object width type from iw_runner. 364 @param snaplen int number of bytes to retain per capture frame. 365 @param filename string filename to write capture to. 366 367 """ 368 if self._packet_capturer.capture_running: 369 self.stop_capture() 370 self._capture_interface = self.get_wlanif(frequency, 'monitor') 371 full_interface = [net_dev for net_dev in self._interfaces 372 if net_dev.if_name == self._capture_interface][0] 373 # If this is the only interface on this phy, we ought to configure 374 # the phy with a channel and a width. Otherwise, inherit the 375 # settings of the phy as they stand. 376 if len([net_dev for net_dev in self._interfaces 377 if net_dev.phy == full_interface.phy]) == 1: 378 self._packet_capturer.configure_raw_monitor( 379 self._capture_interface, frequency, width_type=width_type) 380 else: 381 self.host.run('%s link set %s up' % 382 (self.cmd_ip, self._capture_interface)) 383 384 # Start the capture. 385 if filename: 386 remote_path = os.path.join('/tmp', os.path.basename(filename)) 387 else: 388 remote_path = None 389 self._packet_capturer.start_capture( 390 self._capture_interface, './debug/', snaplen=snaplen, 391 remote_file=remote_path) 392 393 394 def stop_capture(self, save_dir=None, save_filename=None): 395 """Stop a packet capture. 396 397 @param save_dir string path to directory to save pcap files in. 398 @param save_filename string basename of file to save pcap in locally. 399 400 """ 401 if not self._packet_capturer.capture_running: 402 return 403 results = self._packet_capturer.stop_capture( 404 local_save_dir=save_dir, local_pcap_filename=save_filename) 405 self.release_interface(self._capture_interface) 406 self._capture_interface = None 407 return results 408 409 410 def sync_host_times(self): 411 """Set time on our DUT to match local time.""" 412 epoch_seconds = time.time() 413 busybox_format = '%Y%m%d%H%M.%S' 414 busybox_date = datetime.datetime.utcnow().strftime(busybox_format) 415 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' % 416 (epoch_seconds, busybox_date)) 417 418 419 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams): 420 """Get a phy appropriate for a frequency and phytype. 421 422 Return the most appropriate phy interface for operating on the 423 frequency |frequency| in the role indicated by |phytype|. Prefer idle 424 phys to busy phys if any exist. Secondarily, show affinity for phys 425 that use the bus type associated with this phy type. 426 427 @param frequency int WiFi frequency of phy. 428 @param phytype string key of phytype registered at construction time. 429 @param spatial_streams int number of spatial streams required. 430 @return string name of phy to use. 431 432 """ 433 phy_objs = [] 434 for phy_name in self.phys_for_frequency[frequency]: 435 phy_obj = self._phy_by_name(phy_name) 436 num_antennas = min(phy_obj.avail_rx_antennas, 437 phy_obj.avail_tx_antennas) 438 if num_antennas >= spatial_streams: 439 phy_objs.append(phy_obj) 440 elif num_antennas == 0: 441 logging.warning( 442 'Allowing use of %s, which reports zero antennas', phy_name) 443 phy_objs.append(phy_obj) 444 else: 445 logging.debug( 446 'Filtering out %s, which reports only %d antennas', 447 phy_name, num_antennas) 448 449 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use) 450 idle_phy_objs = [phy_obj for phy_obj in phy_objs 451 if phy_obj.name not in busy_phys] 452 phy_objs = idle_phy_objs or phy_objs 453 phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas, 454 phy_obj.avail_tx_antennas), 455 reverse=True) 456 phys = [phy_obj.name for phy_obj in phy_objs] 457 458 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype) 459 preferred_phys = [phy for phy in phys 460 if self.phy_bus_type[phy] == preferred_bus] 461 phys = preferred_phys or phys 462 463 return phys[0] 464 465 466 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as): 467 """Get a WiFi device that supports the given frequency and phytype. 468 469 We simply find or create a suitable DEV. It is left to the 470 caller to actually configure the frequency and bring up the 471 interface. 472 473 @param phytype string type of phy (e.g. 'monitor'). 474 @param spatial_streams int number of spatial streams required. 475 @param frequency int WiFi frequency to support. 476 @param same_phy_as string create the interface on the same phy as this. 477 @return NetDev WiFi device. 478 479 """ 480 if frequency and same_phy_as: 481 raise error.TestError( 482 'Can not combine |frequency| and |same_phy_as|') 483 484 if not (frequency or same_phy_as): 485 raise error.TestError( 486 'Must specify one of |frequency| or |same_phy_as|') 487 488 if spatial_streams is None: 489 spatial_streams = self.MIN_SPATIAL_STREAMS 490 # We don't want to use the 3rd radio on Whirlwind. Reject it if someone 491 # tries to add a test that uses it. 492 elif spatial_streams < self.MIN_SPATIAL_STREAMS and \ 493 self.board == 'whirlwind': 494 raise error.TestError('Requested spatial streams: %d; minimum %d' \ 495 % (spatial_streams, self.MIN_SPATIAL_STREAMS)) 496 497 if same_phy_as: 498 for net_dev in self._interfaces: 499 if net_dev.if_name == same_phy_as: 500 phy = net_dev.phy 501 break 502 else: 503 raise error.TestFail('Unable to find phy for interface %s' % 504 same_phy_as) 505 elif frequency in self.phys_for_frequency: 506 phy = self._get_phy_for_frequency( 507 frequency, phytype, spatial_streams) 508 else: 509 raise error.TestFail('Unable to find phy for frequency %d' % 510 frequency) 511 512 # If we have a suitable unused interface sitting around on this 513 # phy, reuse it. 514 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use): 515 if net_dev.phy == phy and net_dev.if_type == phytype: 516 break 517 else: 518 # Because we can reuse interfaces, we have to iteratively find a 519 # good interface name. 520 name_exists = lambda name: bool([net_dev 521 for net_dev in self._interfaces 522 if net_dev.if_name == name]) 523 if_name = lambda index: '%s%d' % (phytype, index) 524 if_index = len(self._interfaces) 525 while name_exists(if_name(if_index)): 526 if_index += 1 527 net_dev = NetDev(phy=phy, if_name=if_name(if_index), 528 if_type=phytype, inherited=False) 529 self._interfaces.append(net_dev) 530 self.iw_runner.add_interface(phy, net_dev.if_name, phytype) 531 532 # Link must be down to reconfigure MAC address. 533 self.host.run('%s link set dev %s down' % ( 534 self.cmd_ip, net_dev.if_name)) 535 if same_phy_as: 536 self.clone_mac_address(src_dev=same_phy_as, 537 dst_dev=net_dev.if_name) 538 else: 539 self.ensure_unique_mac(net_dev) 540 541 return net_dev 542 543 544 def get_configured_interface(self, phytype, spatial_streams=None, 545 frequency=None, same_phy_as=None): 546 """Get a WiFi device that supports the given frequency and phytype. 547 548 The device's link state will be UP, and (where possible) the device 549 will be configured to operate on |frequency|. 550 551 @param phytype string type of phy (e.g. 'monitor'). 552 @param spatial_streams int number of spatial streams required. 553 @param frequency int WiFi frequency to support. 554 @param same_phy_as string create the interface on the same phy as this. 555 @return string WiFi device. 556 557 """ 558 net_dev = self._get_wlanif( 559 phytype, spatial_streams, frequency, same_phy_as) 560 561 self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name)) 562 563 if frequency: 564 if phytype == 'managed': 565 logging.debug('Skipped setting frequency for DEV %s ' 566 'since managed mode DEVs roam across APs.', 567 net_dev.if_name) 568 elif same_phy_as or self._phy_in_use(net_dev.phy): 569 logging.debug('Skipped setting frequency for DEV %s ' 570 'since PHY %s is already in use', 571 net_dev.if_name, net_dev.phy) 572 else: 573 self.iw_runner.set_freq(net_dev.if_name, frequency) 574 575 self._wlanifs_in_use.append(net_dev) 576 return net_dev.if_name 577 578 579 # TODO(quiche): Deprecate this, in favor of get_configured_interface(). 580 # crbug.com/512169. 581 def get_wlanif(self, frequency, phytype, 582 spatial_streams=None, same_phy_as=None): 583 """Get a WiFi device that supports the given frequency and phytype. 584 585 We simply find or create a suitable DEV. It is left to the 586 caller to actually configure the frequency and bring up the 587 interface. 588 589 @param frequency int WiFi frequency to support. 590 @param phytype string type of phy (e.g. 'monitor'). 591 @param spatial_streams int number of spatial streams required. 592 @param same_phy_as string create the interface on the same phy as this. 593 @return string WiFi device. 594 595 """ 596 net_dev = self._get_wlanif( 597 phytype, spatial_streams, frequency, same_phy_as) 598 self._wlanifs_in_use.append(net_dev) 599 return net_dev.if_name 600 601 602 def ensure_unique_mac(self, net_dev): 603 """Ensure MAC address of |net_dev| meets uniqueness requirements. 604 605 The Linux kernel does not allow multiple APs with the same 606 BSSID on the same PHY (at least, with some drivers). Hence, we 607 want to ensure that the DEVs for a PHY have unique MAC 608 addresses. 609 610 Note that we do not attempt to make the MACs unique across 611 PHYs, because some tests deliberately create such scenarios. 612 613 @param net_dev NetDev to uniquify. 614 615 """ 616 if net_dev.if_type == 'monitor': 617 return 618 619 our_ifname = net_dev.if_name 620 our_phy = net_dev.phy 621 our_mac = interface.Interface(our_ifname, self.host).mac_address 622 sibling_devs = [dev for dev in self._interfaces 623 if (dev.phy == our_phy and 624 dev.if_name != our_ifname and 625 dev.if_type != 'monitor')] 626 sibling_macs = ( 627 interface.Interface(sib_dev.if_name, self.host).mac_address 628 for sib_dev in sibling_devs) 629 if our_mac in sibling_macs: 630 self.configure_interface_mac(our_ifname, 631 self._get_unique_mac()) 632 633 634 def configure_interface_mac(self, wlanif, new_mac): 635 """Change the MAC address for an interface. 636 637 @param wlanif string name of device to reconfigure. 638 @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55') 639 640 """ 641 self.host.run('%s link set %s address %s' % 642 (self.cmd_ip, wlanif, new_mac)) 643 644 645 def clone_mac_address(self, src_dev=None, dst_dev=None): 646 """Copy the MAC address from one interface to another. 647 648 @param src_dev string name of device to copy address from. 649 @param dst_dev string name of device to copy address to. 650 651 """ 652 self.configure_interface_mac( 653 dst_dev, 654 interface.Interface(src_dev, self.host).mac_address) 655 656 657 def release_interface(self, wlanif): 658 """Release a device allocated throuhg get_wlanif(). 659 660 @param wlanif string name of device to release. 661 662 """ 663 for net_dev in self._wlanifs_in_use: 664 if net_dev.if_name == wlanif: 665 self._wlanifs_in_use.remove(net_dev) 666 667 668 def get_bridge_interface(self): 669 """Return the bridge interface, create one if it is not created yet. 670 671 @return string name of bridge interface. 672 """ 673 if self._bridge_interface is None: 674 self._create_bridge_interface() 675 return self._bridge_interface 676 677 678 def remove_bridge_interface(self): 679 """Remove the bridge interface that's been created.""" 680 if self._bridge_interface is not None: 681 self.host.run('%s link delete %s type bridge' % 682 (self.cmd_ip, self._bridge_interface)) 683 self._bridge_interface = None 684 685 686 def add_interface_to_bridge(self, interface): 687 """Add an interface to the bridge interface. 688 689 This will create the bridge interface if it is not created yet. 690 691 @param interface string name of the interface to add to the bridge. 692 """ 693 if self._bridge_interface is None: 694 self._create_bridge_interface() 695 # TODO b:169251326 terms below are set outside of this codebase 696 # and should be updated when possible. ("master" -> "main") 697 self.host.run('%s link set dev %s master %s' % 698 (self.cmd_ip, interface, self._bridge_interface)) 699 700 701 def get_virtual_ethernet_main_interface(self): 702 """Return the main interface of the virtual ethernet pair. 703 704 @return string name of the main interface of the virtual ethernet 705 pair. 706 """ 707 if self._virtual_ethernet_pair is None: 708 self._create_virtual_ethernet_pair() 709 return self._virtual_ethernet_pair.interface_name 710 711 712 def get_virtual_ethernet_peer_interface(self): 713 """Return the peer interface of the virtual ethernet pair. 714 715 @return string name of the peer interface of the virtual ethernet pair. 716 """ 717 if self._virtual_ethernet_pair is None: 718 self._create_virtual_ethernet_pair() 719 return self._virtual_ethernet_pair.peer_interface_name 720 721 722 def remove_ethernet_pair_interface(self): 723 """Remove the virtual ethernet pair that's been created.""" 724 if self._virtual_ethernet_pair is not None: 725 self._virtual_ethernet_pair.teardown() 726 self._virtual_ethernet_pair = None 727 728 729 def require_capabilities(self, requirements): 730 """Require capabilities of this LinuxSystem. 731 732 Check that capabilities in |requirements| exist on this system. 733 Raise an exception to skip but not fail the test if said 734 capabilities are not found. 735 736 @param requirements list of CAPABILITY_* defined above. 737 738 """ 739 missing = [cap for cap in requirements if not cap in self.capabilities] 740 if missing: 741 raise error.TestNAError('%s is missing required capabilites: %r' 742 % (self.role, missing)) 743 744 745 def disable_antennas_except(self, permitted_antennas): 746 """Disable unwanted antennas. 747 748 Disable all antennas except those specified in |permitted_antennas|. 749 Note that one or more of them may remain disabled if the underlying 750 hardware does not support them. 751 752 @param permitted_antennas int bitmask specifying antennas that we should 753 attempt to enable. 754 755 """ 756 for phy in self.phy_list: 757 if not phy.supports_setting_antenna_mask: 758 continue 759 # Determine valid bitmap values based on available antennas. 760 self.iw_runner.set_antenna_bitmap(phy.name, 761 permitted_antennas & phy.avail_tx_antennas, 762 permitted_antennas & phy.avail_rx_antennas) 763 764 765 def enable_all_antennas(self): 766 """Enable all antennas on all phys.""" 767 for phy in self.phy_list: 768 if not phy.supports_setting_antenna_mask: 769 continue 770 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas, 771 phy.avail_rx_antennas) 772 773 774 def ping(self, ping_config): 775 """Ping an IP from this system. 776 777 @param ping_config PingConfig object describing the ping command to run. 778 @return a PingResult object. 779 780 """ 781 logging.info('Pinging from the %s.', self.role) 782 return self._ping_runner.ping(ping_config) 783 784 785 @property 786 def logdir(self): 787 """Return a directory for storing temporary logs. 788 @return string path to temporary log directory. 789 """ 790 return self._logdir 791