Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 1 | # Lint as: python2, python3 |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 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 | |
Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 6 | from __future__ import absolute_import |
| 7 | from __future__ import division |
| 8 | from __future__ import print_function |
| 9 | |
Christopher Wiley | 7c97ded | 2013-09-24 23:24:33 -0700 | [diff] [blame] | 10 | import datetime |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 11 | import collections |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 12 | import logging |
mukesh agrawal | 5fd90f5 | 2015-04-24 16:14:37 -0700 | [diff] [blame] | 13 | import os |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 14 | import random |
Christopher Wiley | 7c97ded | 2013-09-24 23:24:33 -0700 | [diff] [blame] | 15 | import time |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 16 | |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 17 | from autotest_lib.client.common_lib import error |
Christopher Wiley | 3603922 | 2014-12-13 18:27:52 -0800 | [diff] [blame] | 18 | from autotest_lib.client.common_lib.cros import path_utils |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 19 | from autotest_lib.client.common_lib.cros import virtual_ethernet_pair |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 20 | from autotest_lib.client.common_lib.cros.network import interface |
Christopher Wiley | f9cb092 | 2013-11-01 09:12:21 -0700 | [diff] [blame] | 21 | from autotest_lib.client.common_lib.cros.network import iw_runner |
Christopher Wiley | aeef9b5 | 2014-03-11 12:24:11 -0700 | [diff] [blame] | 22 | from autotest_lib.client.common_lib.cros.network import ping_runner |
Christopher Wiley | dc7c462 | 2013-07-09 11:44:04 -0700 | [diff] [blame] | 23 | from autotest_lib.server.cros.network import packet_capturer |
Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 24 | import six |
| 25 | from six.moves import range |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 26 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 27 | NetDev = collections.namedtuple('NetDev', |
| 28 | ['inherited', 'phy', 'if_name', 'if_type']) |
| 29 | |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 30 | class LinuxSystem(object): |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 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 | |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 39 | """ |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 40 | |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 41 | CAPABILITY_5GHZ = '5ghz' |
| 42 | CAPABILITY_MULTI_AP = 'multi_ap' |
| 43 | CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band' |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 44 | CAPABILITY_IBSS = 'ibss_supported' |
Paul Stewart | 51b0f38 | 2013-06-12 09:03:02 -0700 | [diff] [blame] | 45 | CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame' |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 46 | CAPABILITY_TDLS = 'tdls' |
Peter Qiu | 3ef31f0 | 2014-10-17 14:25:57 -0700 | [diff] [blame] | 47 | CAPABILITY_VHT = 'vht' |
Matthew Wang | 8531f62 | 2018-05-03 13:50:16 -0700 | [diff] [blame] | 48 | CAPABILITY_SME = 'sme' |
Matthew Wang | 4d34d42 | 2018-12-13 11:16:18 -0800 | [diff] [blame] | 49 | CAPABILITY_SUPPLICANT_ROAMING = "supplicant_roaming" |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 50 | BRIDGE_INTERFACE_NAME = 'br0' |
Brian Norris | d650025 | 2020-05-13 15:15:47 -0700 | [diff] [blame] | 51 | HOSTAP_BRIDGE_INTERFACE_PREFIX = 'hostapbr' |
Brian Norris | 7b09bde | 2019-05-08 13:02:56 -0700 | [diff] [blame] | 52 | IFB_INTERFACE_PREFIX = 'ifb' |
mukesh agrawal | 3e45891 | 2015-06-12 10:40:16 -0700 | [diff] [blame] | 53 | MIN_SPATIAL_STREAMS = 2 |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 54 | MAC_BIT_LOCAL = 0x2 # Locally administered. |
| 55 | MAC_BIT_MULTICAST = 0x1 |
| 56 | MAC_RETRY_LIMIT = 1000 |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 57 | |
Brian Norris | beb9d35 | 2019-10-24 14:21:14 -0700 | [diff] [blame] | 58 | _UMA_EVENTS = '/var/lib/metrics/uma-events' |
Brian Norris | 9494c0f | 2019-12-11 16:29:55 -0800 | [diff] [blame] | 59 | _LOG_PATH_PREFIX = '/tmp/autotest-' |
Brian Norris | beb9d35 | 2019-10-24 14:21:14 -0700 | [diff] [blame] | 60 | |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 61 | |
| 62 | @property |
| 63 | def capabilities(self): |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 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) |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 69 | return self._capabilities |
| 70 | |
| 71 | |
Peter Qiu | cb27e01 | 2015-03-24 14:34:31 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | 408d181 | 2014-01-13 15:27:43 -0800 | [diff] [blame] | 81 | def __init__(self, host, role, inherit_interfaces=False): |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 82 | self.host = host |
| 83 | self.role = role |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 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 | """ |
Brian Norris | 9494c0f | 2019-12-11 16:29:55 -0800 | [diff] [blame] | 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 | |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 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) |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 107 | |
Christopher Wiley | 09ad206 | 2013-09-13 13:34:49 -0700 | [diff] [blame] | 108 | self._packet_capturer = packet_capturer.get_packet_capturer( |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 109 | self.host, host_description=self.role, cmd_ip=self.cmd_ip, |
Brian Norris | 9494c0f | 2019-12-11 16:29:55 -0800 | [diff] [blame] | 110 | cmd_iw=cmd_iw, ignore_failures=True, logdir=self.logdir) |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 111 | self.iw_runner = iw_runner.IwRunner(remote_host=self.host, |
| 112 | command_iw=cmd_iw) |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 113 | |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 114 | self._phy_list = None |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 115 | self.phys_for_frequency, self.phy_bus_type = self._get_phy_info() |
Roshan Pius | 9350a8d | 2016-09-08 16:29:01 -0700 | [diff] [blame] | 116 | logging.debug('Current regulatory domain %r', |
| 117 | self.iw_runner.get_regulatory_domain()) |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 118 | self._interfaces = [] |
| 119 | for interface in self.iw_runner.list_interfaces(): |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 120 | if self.inherit_interfaces: |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 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 = [] |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 129 | self._local_macs_in_use = set() |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 130 | self._capture_interface = None |
Peter Qiu | cb27e01 | 2015-03-24 14:34:31 -0700 | [diff] [blame] | 131 | self._board = None |
Christopher Wiley | 80e4092 | 2013-10-22 13:14:56 -0700 | [diff] [blame] | 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 |
Christopher Wiley | f19c28c | 2013-05-06 15:42:57 -0700 | [diff] [blame] | 136 | self._capabilities = None |
Christopher Wiley | aeef9b5 | 2014-03-11 12:24:11 -0700 | [diff] [blame] | 137 | self._ping_runner = ping_runner.PingRunner(host=self.host) |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 138 | self._bridge_interface = None |
| 139 | self._virtual_ethernet_pair = None |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 140 | |
Brian Norris | beb9d35 | 2019-10-24 14:21:14 -0700 | [diff] [blame] | 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 | |
Brian Norris | d650025 | 2020-05-13 15:15:47 -0700 | [diff] [blame] | 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 | |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 160 | |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 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 | |
mukesh agrawal | 3e45891 | 2015-06-12 10:40:16 -0700 | [diff] [blame] | 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 | |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 178 | def _get_phy_info(self): |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 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 | |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 191 | """ |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 192 | phys_for_frequency = {} |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 193 | phy_caps = {} |
Christopher Wiley | 7bd2e08 | 2013-10-16 17:40:40 -0700 | [diff] [blame] | 194 | phy_list = [] |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 195 | for phy in self.phy_list: |
Christopher Wiley | 7bd2e08 | 2013-10-16 17:40:40 -0700 | [diff] [blame] | 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) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 203 | |
| 204 | phy_bus_type = {} |
| 205 | for phy in phy_list: |
| 206 | phybus = 'unknown' |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 207 | command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 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 |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 216 | logging.debug('Got phys for frequency: %r', phys_for_frequency) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 217 | return phys_for_frequency, phy_bus_type |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 218 | |
Paul Stewart | 64cc429 | 2011-06-01 10:59:36 -0700 | [diff] [blame] | 219 | |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 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 | |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 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() |
Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 249 | for tries in range(0, self.MAC_RETRY_LIMIT): |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 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 | |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 276 | def remove_interface(self, interface): |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 277 | """Remove an interface from a WiFi device. |
| 278 | |
| 279 | @param interface string interface to remove (e.g. wlan0). |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 280 | |
| 281 | """ |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 282 | self.release_interface(interface) |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 283 | self.host.run('%s link set %s down' % (self.cmd_ip, interface)) |
Christopher Wiley | 7bd2e08 | 2013-10-16 17:40:40 -0700 | [diff] [blame] | 284 | self.iw_runner.remove_interface(interface) |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 285 | for net_dev in self._interfaces: |
| 286 | if net_dev.if_name == interface: |
| 287 | self._interfaces.remove(net_dev) |
| 288 | break |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 289 | |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 290 | |
Brian Norris | d650025 | 2020-05-13 15:15:47 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | f4bc88b | 2013-08-29 16:45:15 -0700 | [diff] [blame] | 301 | def close(self): |
| 302 | """Close global resources held by this system.""" |
| 303 | logging.debug('Cleaning up host object for %s', self.role) |
Christopher Wiley | 618e52b | 2013-10-14 16:21:07 -0700 | [diff] [blame] | 304 | self._packet_capturer.close() |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 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) |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 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() |
Christopher Wiley | f4bc88b | 2013-08-29 16:45:15 -0700 | [diff] [blame] | 316 | self.host.close() |
| 317 | self.host = None |
| 318 | |
| 319 | |
mukesh agrawal | 7fd4d09 | 2015-06-29 13:50:54 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 330 | def get_capabilities(self): |
Christopher Wiley | 16e494f | 2013-06-18 17:31:28 -0700 | [diff] [blame] | 331 | caps = set() |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 332 | phymap = self.phys_for_frequency |
Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 333 | if [freq for freq in six.iterkeys(phymap) if freq > 5000]: |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 334 | # The frequencies are expressed in megaherz |
Christopher Wiley | 16e494f | 2013-06-18 17:31:28 -0700 | [diff] [blame] | 335 | caps.add(self.CAPABILITY_5GHZ) |
Derek Beckett | 63e1c44 | 2020-08-11 14:49:47 -0700 | [diff] [blame] | 336 | if [freq for freq in six.iterkeys(phymap) if len(phymap[freq]) > 1]: |
Christopher Wiley | 16e494f | 2013-06-18 17:31:28 -0700 | [diff] [blame] | 337 | caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND) |
| 338 | caps.add(self.CAPABILITY_MULTI_AP) |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 339 | elif len(self.phy_bus_type) > 1: |
Christopher Wiley | 16e494f | 2013-06-18 17:31:28 -0700 | [diff] [blame] | 340 | caps.add(self.CAPABILITY_MULTI_AP) |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 341 | for phy in self.phy_list: |
Paul Stewart | f94151e | 2015-04-20 20:32:39 -0700 | [diff] [blame] | 342 | if ('tdls_mgmt' in phy.commands or |
| 343 | 'tdls_oper' in phy.commands or |
| 344 | 'T-DLS' in phy.features): |
Paul Stewart | 27ecc5d | 2013-11-13 16:56:41 -0800 | [diff] [blame] | 345 | caps.add(self.CAPABILITY_TDLS) |
Matthew Wang | 8531f62 | 2018-05-03 13:50:16 -0700 | [diff] [blame] | 346 | if 'authenticate' in phy.commands: |
| 347 | caps.add(self.CAPABILITY_SME) |
Peter Qiu | 3ef31f0 | 2014-10-17 14:25:57 -0700 | [diff] [blame] | 348 | if phy.support_vht: |
| 349 | caps.add(self.CAPABILITY_VHT) |
Matthew Wang | 4d34d42 | 2018-12-13 11:16:18 -0800 | [diff] [blame] | 350 | if 'roaming' not in phy.features: |
| 351 | caps.add(self.CAPABILITY_SUPPLICANT_ROAMING) |
mukesh agrawal | 064ea97 | 2015-06-26 10:00:53 -0700 | [diff] [blame] | 352 | if any([iw_runner.DEV_MODE_IBSS in phy.modes |
| 353 | for phy in self.phy_list]): |
| 354 | caps.add(self.CAPABILITY_IBSS) |
Christopher Wiley | 16e494f | 2013-06-18 17:31:28 -0700 | [diff] [blame] | 355 | return caps |
Christopher Wiley | 061f138 | 2013-06-17 18:17:58 -0700 | [diff] [blame] | 356 | |
| 357 | |
mukesh agrawal | 5fd90f5 | 2015-04-24 16:14:37 -0700 | [diff] [blame] | 358 | def start_capture(self, frequency, |
Jared Pauletti | 681e4c1 | 2019-10-23 18:33:34 -0700 | [diff] [blame] | 359 | width_type=None, snaplen=None, filename=None): |
Christopher Wiley | 6823b4f | 2013-04-22 18:40:24 -0700 | [diff] [blame] | 360 | """Start a packet capture. |
| 361 | |
| 362 | @param frequency int frequency of channel to capture on. |
Jared Pauletti | 681e4c1 | 2019-10-23 18:33:34 -0700 | [diff] [blame] | 363 | @param width_type object width type from iw_runner. |
Christopher Wiley | 618e52b | 2013-10-14 16:21:07 -0700 | [diff] [blame] | 364 | @param snaplen int number of bytes to retain per capture frame. |
mukesh agrawal | 5fd90f5 | 2015-04-24 16:14:37 -0700 | [diff] [blame] | 365 | @param filename string filename to write capture to. |
Christopher Wiley | 6823b4f | 2013-04-22 18:40:24 -0700 | [diff] [blame] | 366 | |
| 367 | """ |
| 368 | if self._packet_capturer.capture_running: |
| 369 | self.stop_capture() |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 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 |
Jared Pauletti | 681e4c1 | 2019-10-23 18:33:34 -0700 | [diff] [blame] | 374 | # the phy with a channel and a width. Otherwise, inherit the |
| 375 | # settings of the phy as they stand. |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 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( |
Jared Pauletti | 681e4c1 | 2019-10-23 18:33:34 -0700 | [diff] [blame] | 379 | self._capture_interface, frequency, width_type=width_type) |
Christopher Wiley | f737e02 | 2014-01-23 13:48:00 -0800 | [diff] [blame] | 380 | else: |
| 381 | self.host.run('%s link set %s up' % |
| 382 | (self.cmd_ip, self._capture_interface)) |
| 383 | |
Christopher Wiley | 6823b4f | 2013-04-22 18:40:24 -0700 | [diff] [blame] | 384 | # Start the capture. |
mukesh agrawal | 5fd90f5 | 2015-04-24 16:14:37 -0700 | [diff] [blame] | 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) |
Christopher Wiley | 6823b4f | 2013-04-22 18:40:24 -0700 | [diff] [blame] | 392 | |
| 393 | |
Christopher Wiley | 062a981 | 2013-10-03 15:55:50 -0700 | [diff] [blame] | 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 | """ |
Christopher Wiley | 6823b4f | 2013-04-22 18:40:24 -0700 | [diff] [blame] | 401 | if not self._packet_capturer.capture_running: |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 402 | return |
Christopher Wiley | 618e52b | 2013-10-14 16:21:07 -0700 | [diff] [blame] | 403 | results = self._packet_capturer.stop_capture( |
| 404 | local_save_dir=save_dir, local_pcap_filename=save_filename) |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 405 | self.release_interface(self._capture_interface) |
| 406 | self._capture_interface = None |
Christopher Wiley | 618e52b | 2013-10-14 16:21:07 -0700 | [diff] [blame] | 407 | return results |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 408 | |
| 409 | |
Christopher Wiley | 7c97ded | 2013-09-24 23:24:33 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 418 | |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 419 | def _get_phy_for_frequency(self, frequency, phytype, spatial_streams): |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 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. |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 429 | @param spatial_streams int number of spatial streams required. |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 430 | @return string name of phy to use. |
| 431 | |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 432 | """ |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 433 | phy_objs = [] |
| 434 | for phy_name in self.phys_for_frequency[frequency]: |
| 435 | phy_obj = self._phy_by_name(phy_name) |
mukesh agrawal | bc963c1 | 2015-06-19 11:40:03 -0700 | [diff] [blame] | 436 | num_antennas = min(phy_obj.avail_rx_antennas, |
| 437 | phy_obj.avail_tx_antennas) |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 438 | if num_antennas >= spatial_streams: |
| 439 | phy_objs.append(phy_obj) |
mukesh agrawal | bc963c1 | 2015-06-19 11:40:03 -0700 | [diff] [blame] | 440 | elif num_antennas == 0: |
| 441 | logging.warning( |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 442 | 'Allowing use of %s, which reports zero antennas', phy_name) |
| 443 | phy_objs.append(phy_obj) |
mukesh agrawal | 3e45891 | 2015-06-12 10:40:16 -0700 | [diff] [blame] | 444 | else: |
| 445 | logging.debug( |
mukesh agrawal | bc963c1 | 2015-06-19 11:40:03 -0700 | [diff] [blame] | 446 | 'Filtering out %s, which reports only %d antennas', |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 447 | phy_name, num_antennas) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 448 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 449 | busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use) |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 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] |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 457 | |
Christopher Wiley | 408d181 | 2014-01-13 15:27:43 -0800 | [diff] [blame] | 458 | preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 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] |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 464 | |
| 465 | |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 466 | def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as): |
| 467 | """Get a WiFi device that supports the given frequency and phytype. |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 468 | |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 473 | @param phytype string type of phy (e.g. 'monitor'). |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 474 | @param spatial_streams int number of spatial streams required. |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 475 | @param frequency int WiFi frequency to support. |
Paul Stewart | 51b0f38 | 2013-06-12 09:03:02 -0700 | [diff] [blame] | 476 | @param same_phy_as string create the interface on the same phy as this. |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 477 | @return NetDev WiFi device. |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 478 | |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 479 | """ |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 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 | |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 488 | if spatial_streams is None: |
| 489 | spatial_streams = self.MIN_SPATIAL_STREAMS |
Brian Norris | 369799e | 2018-08-03 18:09:43 -0700 | [diff] [blame] | 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)) |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 496 | |
Paul Stewart | 51b0f38 | 2013-06-12 09:03:02 -0700 | [diff] [blame] | 497 | if same_phy_as: |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 498 | for net_dev in self._interfaces: |
| 499 | if net_dev.if_name == same_phy_as: |
| 500 | phy = net_dev.phy |
| 501 | break |
Paul Stewart | 51b0f38 | 2013-06-12 09:03:02 -0700 | [diff] [blame] | 502 | else: |
| 503 | raise error.TestFail('Unable to find phy for interface %s' % |
| 504 | same_phy_as) |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 505 | elif frequency in self.phys_for_frequency: |
mukesh agrawal | 43644aa | 2015-07-06 14:40:22 -0700 | [diff] [blame] | 506 | phy = self._get_phy_for_frequency( |
| 507 | frequency, phytype, spatial_streams) |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 508 | else: |
Christopher Wiley | 408d181 | 2014-01-13 15:27:43 -0800 | [diff] [blame] | 509 | raise error.TestFail('Unable to find phy for frequency %d' % |
| 510 | frequency) |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 511 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 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: |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 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) |
Paul Stewart | 2ee7fdf | 2011-05-19 16:29:23 -0700 | [diff] [blame] | 531 | |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 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 | |
mukesh agrawal | a342308 | 2015-07-20 12:40:18 -0700 | [diff] [blame] | 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) |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 598 | self._wlanifs_in_use.append(net_dev) |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 599 | return net_dev.if_name |
Paul Stewart | 6423bb0 | 2012-11-27 17:46:23 -0800 | [diff] [blame] | 600 | |
| 601 | |
mukesh agrawal | 24a7017 | 2015-06-22 10:53:04 -0700 | [diff] [blame] | 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 | |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 657 | def release_interface(self, wlanif): |
| 658 | """Release a device allocated throuhg get_wlanif(). |
Christopher Wiley | 4c6a32c | 2013-04-24 13:37:51 -0700 | [diff] [blame] | 659 | |
| 660 | @param wlanif string name of device to release. |
| 661 | |
| 662 | """ |
Christopher Wiley | f671a5a | 2013-12-13 15:44:41 -0800 | [diff] [blame] | 663 | for net_dev in self._wlanifs_in_use: |
| 664 | if net_dev.if_name == wlanif: |
| 665 | self._wlanifs_in_use.remove(net_dev) |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 666 | |
| 667 | |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 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() |
Derek Beckett | 7ff6f74 | 2020-12-01 11:34:45 -0800 | [diff] [blame] | 695 | # TODO b:169251326 terms below are set outside of this codebase |
| 696 | # and should be updated when possible. ("master" -> "main") |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 697 | self.host.run('%s link set dev %s master %s' % |
| 698 | (self.cmd_ip, interface, self._bridge_interface)) |
| 699 | |
| 700 | |
Derek Beckett | 7ff6f74 | 2020-12-01 11:34:45 -0800 | [diff] [blame] | 701 | def get_virtual_ethernet_main_interface(self): |
| 702 | """Return the main interface of the virtual ethernet pair. |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 703 | |
Derek Beckett | 7ff6f74 | 2020-12-01 11:34:45 -0800 | [diff] [blame] | 704 | @return string name of the main interface of the virtual ethernet |
Peter Qiu | 9a63a8b | 2015-02-03 09:08:16 -0800 | [diff] [blame] | 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 | |
Brian Norris | c680186 | 2017-10-06 12:55:37 -0700 | [diff] [blame] | 729 | def require_capabilities(self, requirements): |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 730 | """Require capabilities of this LinuxSystem. |
| 731 | |
| 732 | Check that capabilities in |requirements| exist on this system. |
Brian Norris | c680186 | 2017-10-06 12:55:37 -0700 | [diff] [blame] | 733 | Raise an exception to skip but not fail the test if said |
| 734 | capabilities are not found. |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 735 | |
| 736 | @param requirements list of CAPABILITY_* defined above. |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 737 | |
| 738 | """ |
Christopher Wiley | 9b40620 | 2013-05-06 14:07:49 -0700 | [diff] [blame] | 739 | missing = [cap for cap in requirements if not cap in self.capabilities] |
| 740 | if missing: |
Kirtika Ruchandani | 753498b | 2018-05-17 14:50:24 -0700 | [diff] [blame] | 741 | raise error.TestNAError('%s is missing required capabilites: %r' |
| 742 | % (self.role, missing)) |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 743 | |
| 744 | |
Bindu Mahadev | 67cb772 | 2016-01-05 16:45:00 -0800 | [diff] [blame] | 745 | def disable_antennas_except(self, permitted_antennas): |
| 746 | """Disable unwanted antennas. |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 747 | |
Bindu Mahadev | 67cb772 | 2016-01-05 16:45:00 -0800 | [diff] [blame] | 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. |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 754 | |
| 755 | """ |
| 756 | for phy in self.phy_list: |
Christopher Wiley | faaecd8 | 2014-05-29 10:53:40 -0700 | [diff] [blame] | 757 | if not phy.supports_setting_antenna_mask: |
| 758 | continue |
Bindu Mahadev | 67cb772 | 2016-01-05 16:45:00 -0800 | [diff] [blame] | 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) |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 763 | |
| 764 | |
mukesh agrawal | 4cfb751 | 2015-12-11 15:54:11 -0800 | [diff] [blame] | 765 | def enable_all_antennas(self): |
| 766 | """Enable all antennas on all phys.""" |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 767 | for phy in self.phy_list: |
Christopher Wiley | faaecd8 | 2014-05-29 10:53:40 -0700 | [diff] [blame] | 768 | if not phy.supports_setting_antenna_mask: |
| 769 | continue |
Peter Qiu | 2f97325 | 2014-02-20 15:30:37 -0800 | [diff] [blame] | 770 | self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas, |
| 771 | phy.avail_rx_antennas) |
Christopher Wiley | aeef9b5 | 2014-03-11 12:24:11 -0700 | [diff] [blame] | 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) |
Brian Norris | 9494c0f | 2019-12-11 16:29:55 -0800 | [diff] [blame] | 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 |