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