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