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