blob: 93750f3dff4d5095efd42aeb32ab0251f6694392 [file] [log] [blame]
Derek Beckett63e1c442020-08-11 14:49:47 -07001# Lint as: python2, python3
Paul Stewart2ee7fdf2011-05-19 16:29:23 -07002# 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 Beckett63e1c442020-08-11 14:49:47 -07006from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
Christopher Wiley7c97ded2013-09-24 23:24:33 -070010import datetime
Christopher Wileyf671a5a2013-12-13 15:44:41 -080011import collections
Christopher Wileyf19c28c2013-05-06 15:42:57 -070012import logging
mukesh agrawal5fd90f52015-04-24 16:14:37 -070013import os
mukesh agrawal24a70172015-06-22 10:53:04 -070014import random
Christopher Wiley7c97ded2013-09-24 23:24:33 -070015import time
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070016
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070017from autotest_lib.client.common_lib import error
Christopher Wiley36039222014-12-13 18:27:52 -080018from autotest_lib.client.common_lib.cros import path_utils
Peter Qiu9a63a8b2015-02-03 09:08:16 -080019from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
mukesh agrawal24a70172015-06-22 10:53:04 -070020from autotest_lib.client.common_lib.cros.network import interface
Christopher Wileyf9cb0922013-11-01 09:12:21 -070021from autotest_lib.client.common_lib.cros.network import iw_runner
Christopher Wileyaeef9b52014-03-11 12:24:11 -070022from autotest_lib.client.common_lib.cros.network import ping_runner
Christopher Wileydc7c4622013-07-09 11:44:04 -070023from autotest_lib.server.cros.network import packet_capturer
Derek Beckett63e1c442020-08-11 14:49:47 -070024import six
25from six.moves import range
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070026
Christopher Wileyf671a5a2013-12-13 15:44:41 -080027NetDev = collections.namedtuple('NetDev',
28 ['inherited', 'phy', 'if_name', 'if_type'])
29
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070030class LinuxSystem(object):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070031 """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 Stewart2ee7fdf2011-05-19 16:29:23 -070039 """
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070040
Christopher Wileyf19c28c2013-05-06 15:42:57 -070041 CAPABILITY_5GHZ = '5ghz'
42 CAPABILITY_MULTI_AP = 'multi_ap'
43 CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
Christopher Wiley061f1382013-06-17 18:17:58 -070044 CAPABILITY_IBSS = 'ibss_supported'
Paul Stewart51b0f382013-06-12 09:03:02 -070045 CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
Paul Stewart27ecc5d2013-11-13 16:56:41 -080046 CAPABILITY_TDLS = 'tdls'
Peter Qiu3ef31f02014-10-17 14:25:57 -070047 CAPABILITY_VHT = 'vht'
Matthew Wang8531f622018-05-03 13:50:16 -070048 CAPABILITY_SME = 'sme'
Matthew Wang4d34d422018-12-13 11:16:18 -080049 CAPABILITY_SUPPLICANT_ROAMING = "supplicant_roaming"
Peter Qiu9a63a8b2015-02-03 09:08:16 -080050 BRIDGE_INTERFACE_NAME = 'br0'
Brian Norrisd6500252020-05-13 15:15:47 -070051 HOSTAP_BRIDGE_INTERFACE_PREFIX = 'hostapbr'
Brian Norris7b09bde2019-05-08 13:02:56 -070052 IFB_INTERFACE_PREFIX = 'ifb'
mukesh agrawal3e458912015-06-12 10:40:16 -070053 MIN_SPATIAL_STREAMS = 2
mukesh agrawal24a70172015-06-22 10:53:04 -070054 MAC_BIT_LOCAL = 0x2 # Locally administered.
55 MAC_BIT_MULTICAST = 0x1
56 MAC_RETRY_LIMIT = 1000
Christopher Wileyf19c28c2013-05-06 15:42:57 -070057
Brian Norrisbeb9d352019-10-24 14:21:14 -070058 _UMA_EVENTS = '/var/lib/metrics/uma-events'
Brian Norris9494c0f2019-12-11 16:29:55 -080059 _LOG_PATH_PREFIX = '/tmp/autotest-'
Brian Norrisbeb9d352019-10-24 14:21:14 -070060
Christopher Wileyf19c28c2013-05-06 15:42:57 -070061
62 @property
63 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070064 """@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 Wileyf19c28c2013-05-06 15:42:57 -070069 return self._capabilities
70
71
Peter Qiucb27e012015-03-24 14:34:31 -070072 @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 Wiley408d1812014-01-13 15:27:43 -080081 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070082 self.host = host
83 self.role = role
mukesh agrawal7fd4d092015-06-29 13:50:54 -070084 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 Norris9494c0f2019-12-11 16:29:55 -080095 # 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 agrawal7fd4d092015-06-29 13:50:54 -0700101 # 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 Stewart2ee7fdf2011-05-19 16:29:23 -0700107
Christopher Wiley09ad2062013-09-13 13:34:49 -0700108 self._packet_capturer = packet_capturer.get_packet_capturer(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700109 self.host, host_description=self.role, cmd_ip=self.cmd_ip,
Brian Norris9494c0f2019-12-11 16:29:55 -0800110 cmd_iw=cmd_iw, ignore_failures=True, logdir=self.logdir)
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700111 self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
112 command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700113
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800114 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -0800115 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Roshan Pius9350a8d2016-09-08 16:29:01 -0700116 logging.debug('Current regulatory domain %r',
117 self.iw_runner.get_regulatory_domain())
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800118 self._interfaces = []
119 for interface in self.iw_runner.list_interfaces():
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700120 if self.inherit_interfaces:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800121 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 agrawal24a70172015-06-22 10:53:04 -0700129 self._local_macs_in_use = set()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800130 self._capture_interface = None
Peter Qiucb27e012015-03-24 14:34:31 -0700131 self._board = None
Christopher Wiley80e40922013-10-22 13:14:56 -0700132 # 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 Wileyf19c28c2013-05-06 15:42:57 -0700136 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700137 self._ping_runner = ping_runner.PingRunner(host=self.host)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800138 self._bridge_interface = None
139 self._virtual_ethernet_pair = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700140
Brian Norrisbeb9d352019-10-24 14:21:14 -0700141 # 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 Norrisd6500252020-05-13 15:15:47 -0700147 # 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 Stewart6423bb02012-11-27 17:46:23 -0800160
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800161 @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 agrawal3e458912015-06-12 10:40:16 -0700169 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 Stewart6423bb02012-11-27 17:46:23 -0800178 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700179 """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 Stewart6423bb02012-11-27 17:46:23 -0800191 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800192 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800193 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700194 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800195 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700196 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 Stewart6423bb02012-11-27 17:46:23 -0800203
204 phy_bus_type = {}
205 for phy in phy_list:
206 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700207 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800208 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 Wiley9b406202013-05-06 14:07:49 -0700216 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800217 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700218
Paul Stewart64cc4292011-06-01 10:59:36 -0700219
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800220 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 agrawal24a70172015-06-22 10:53:04 -0700236 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 Beckett63e1c442020-08-11 14:49:47 -0700249 for tries in range(0, self.MAC_RETRY_LIMIT):
mukesh agrawal24a70172015-06-22 10:53:04 -0700250 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 agrawala3423082015-07-20 12:40:18 -0700265 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 Wileyf671a5a2013-12-13 15:44:41 -0800276 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700277 """Remove an interface from a WiFi device.
278
279 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700280
281 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800282 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700283 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700284 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800285 for net_dev in self._interfaces:
286 if net_dev.if_name == interface:
287 self._interfaces.remove(net_dev)
288 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700289
Christopher Wiley061f1382013-06-17 18:17:58 -0700290
Brian Norrisd6500252020-05-13 15:15:47 -0700291 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 Wileyf4bc88b2013-08-29 16:45:15 -0700301 def close(self):
302 """Close global resources held by this system."""
303 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700304 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800305 # 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 Qiu9a63a8b2015-02-03 09:08:16 -0800312 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 Wileyf4bc88b2013-08-29 16:45:15 -0700316 self.host.close()
317 self.host = None
318
319
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700320 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 Wiley061f1382013-06-17 18:17:58 -0700330 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700331 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700332 phymap = self.phys_for_frequency
Derek Beckett63e1c442020-08-11 14:49:47 -0700333 if [freq for freq in six.iterkeys(phymap) if freq > 5000]:
Christopher Wiley061f1382013-06-17 18:17:58 -0700334 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700335 caps.add(self.CAPABILITY_5GHZ)
Derek Beckett63e1c442020-08-11 14:49:47 -0700336 if [freq for freq in six.iterkeys(phymap) if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700337 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
338 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700339 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700340 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800341 for phy in self.phy_list:
Paul Stewartf94151e2015-04-20 20:32:39 -0700342 if ('tdls_mgmt' in phy.commands or
343 'tdls_oper' in phy.commands or
344 'T-DLS' in phy.features):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800345 caps.add(self.CAPABILITY_TDLS)
Matthew Wang8531f622018-05-03 13:50:16 -0700346 if 'authenticate' in phy.commands:
347 caps.add(self.CAPABILITY_SME)
Peter Qiu3ef31f02014-10-17 14:25:57 -0700348 if phy.support_vht:
349 caps.add(self.CAPABILITY_VHT)
Matthew Wang4d34d422018-12-13 11:16:18 -0800350 if 'roaming' not in phy.features:
351 caps.add(self.CAPABILITY_SUPPLICANT_ROAMING)
mukesh agrawal064ea972015-06-26 10:00:53 -0700352 if any([iw_runner.DEV_MODE_IBSS in phy.modes
353 for phy in self.phy_list]):
354 caps.add(self.CAPABILITY_IBSS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700355 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700356
357
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700358 def start_capture(self, frequency,
Jared Pauletti681e4c12019-10-23 18:33:34 -0700359 width_type=None, snaplen=None, filename=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700360 """Start a packet capture.
361
362 @param frequency int frequency of channel to capture on.
Jared Pauletti681e4c12019-10-23 18:33:34 -0700363 @param width_type object width type from iw_runner.
Christopher Wiley618e52b2013-10-14 16:21:07 -0700364 @param snaplen int number of bytes to retain per capture frame.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700365 @param filename string filename to write capture to.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700366
367 """
368 if self._packet_capturer.capture_running:
369 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800370 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 Pauletti681e4c12019-10-23 18:33:34 -0700374 # the phy with a channel and a width. Otherwise, inherit the
375 # settings of the phy as they stand.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800376 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 Pauletti681e4c12019-10-23 18:33:34 -0700379 self._capture_interface, frequency, width_type=width_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800380 else:
381 self.host.run('%s link set %s up' %
382 (self.cmd_ip, self._capture_interface))
383
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700384 # Start the capture.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700385 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 Wiley6823b4f2013-04-22 18:40:24 -0700392
393
Christopher Wiley062a9812013-10-03 15:55:50 -0700394 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 Wiley6823b4f2013-04-22 18:40:24 -0700401 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700402 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700403 results = self._packet_capturer.stop_capture(
404 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800405 self.release_interface(self._capture_interface)
406 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700407 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800408
409
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700410 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 Wileyf671a5a2013-12-13 15:44:41 -0800418
mukesh agrawal43644aa2015-07-06 14:40:22 -0700419 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700420 """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 agrawal43644aa2015-07-06 14:40:22 -0700429 @param spatial_streams int number of spatial streams required.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700430 @return string name of phy to use.
431
Paul Stewart6423bb02012-11-27 17:46:23 -0800432 """
mukesh agrawal43644aa2015-07-06 14:40:22 -0700433 phy_objs = []
434 for phy_name in self.phys_for_frequency[frequency]:
435 phy_obj = self._phy_by_name(phy_name)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700436 num_antennas = min(phy_obj.avail_rx_antennas,
437 phy_obj.avail_tx_antennas)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700438 if num_antennas >= spatial_streams:
439 phy_objs.append(phy_obj)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700440 elif num_antennas == 0:
441 logging.warning(
mukesh agrawal43644aa2015-07-06 14:40:22 -0700442 'Allowing use of %s, which reports zero antennas', phy_name)
443 phy_objs.append(phy_obj)
mukesh agrawal3e458912015-06-12 10:40:16 -0700444 else:
445 logging.debug(
mukesh agrawalbc963c12015-06-19 11:40:03 -0700446 'Filtering out %s, which reports only %d antennas',
mukesh agrawal43644aa2015-07-06 14:40:22 -0700447 phy_name, num_antennas)
Paul Stewart6423bb02012-11-27 17:46:23 -0800448
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800449 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700450 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 Stewart6423bb02012-11-27 17:46:23 -0800457
Christopher Wiley408d1812014-01-13 15:27:43 -0800458 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800459 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 Stewart2ee7fdf2011-05-19 16:29:23 -0700464
465
mukesh agrawala3423082015-07-20 12:40:18 -0700466 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
467 """Get a WiFi device that supports the given frequency and phytype.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700468
mukesh agrawala3423082015-07-20 12:40:18 -0700469 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 Wiley4c6a32c2013-04-24 13:37:51 -0700473 @param phytype string type of phy (e.g. 'monitor').
mukesh agrawal43644aa2015-07-06 14:40:22 -0700474 @param spatial_streams int number of spatial streams required.
mukesh agrawala3423082015-07-20 12:40:18 -0700475 @param frequency int WiFi frequency to support.
Paul Stewart51b0f382013-06-12 09:03:02 -0700476 @param same_phy_as string create the interface on the same phy as this.
mukesh agrawala3423082015-07-20 12:40:18 -0700477 @return NetDev WiFi device.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700478
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700479 """
mukesh agrawala3423082015-07-20 12:40:18 -0700480 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 agrawal43644aa2015-07-06 14:40:22 -0700488 if spatial_streams is None:
489 spatial_streams = self.MIN_SPATIAL_STREAMS
Brian Norris369799e2018-08-03 18:09:43 -0700490 # 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 agrawal43644aa2015-07-06 14:40:22 -0700496
Paul Stewart51b0f382013-06-12 09:03:02 -0700497 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800498 for net_dev in self._interfaces:
499 if net_dev.if_name == same_phy_as:
500 phy = net_dev.phy
501 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700502 else:
503 raise error.TestFail('Unable to find phy for interface %s' %
504 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800505 elif frequency in self.phys_for_frequency:
mukesh agrawal43644aa2015-07-06 14:40:22 -0700506 phy = self._get_phy_for_frequency(
507 frequency, phytype, spatial_streams)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700508 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800509 raise error.TestFail('Unable to find phy for frequency %d' %
510 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700511
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800512 # 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 agrawal24a70172015-06-22 10:53:04 -0700516 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 Stewart2ee7fdf2011-05-19 16:29:23 -0700531
mukesh agrawal24a70172015-06-22 10:53:04 -0700532 # 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 agrawala3423082015-07-20 12:40:18 -0700541 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 Wileyf671a5a2013-12-13 15:44:41 -0800598 self._wlanifs_in_use.append(net_dev)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800599 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800600
601
mukesh agrawal24a70172015-06-22 10:53:04 -0700602 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 Wileyf671a5a2013-12-13 15:44:41 -0800657 def release_interface(self, wlanif):
658 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700659
660 @param wlanif string name of device to release.
661
662 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800663 for net_dev in self._wlanifs_in_use:
664 if net_dev.if_name == wlanif:
665 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700666
667
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800668 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 Beckett7ff6f742020-12-01 11:34:45 -0800695 # TODO b:169251326 terms below are set outside of this codebase
696 # and should be updated when possible. ("master" -> "main")
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800697 self.host.run('%s link set dev %s master %s' %
698 (self.cmd_ip, interface, self._bridge_interface))
699
700
Derek Beckett7ff6f742020-12-01 11:34:45 -0800701 def get_virtual_ethernet_main_interface(self):
702 """Return the main interface of the virtual ethernet pair.
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800703
Derek Beckett7ff6f742020-12-01 11:34:45 -0800704 @return string name of the main interface of the virtual ethernet
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800705 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 Norrisc6801862017-10-06 12:55:37 -0700729 def require_capabilities(self, requirements):
Christopher Wiley9b406202013-05-06 14:07:49 -0700730 """Require capabilities of this LinuxSystem.
731
732 Check that capabilities in |requirements| exist on this system.
Brian Norrisc6801862017-10-06 12:55:37 -0700733 Raise an exception to skip but not fail the test if said
734 capabilities are not found.
Christopher Wiley9b406202013-05-06 14:07:49 -0700735
736 @param requirements list of CAPABILITY_* defined above.
Christopher Wiley9b406202013-05-06 14:07:49 -0700737
738 """
Christopher Wiley9b406202013-05-06 14:07:49 -0700739 missing = [cap for cap in requirements if not cap in self.capabilities]
740 if missing:
Kirtika Ruchandani753498b2018-05-17 14:50:24 -0700741 raise error.TestNAError('%s is missing required capabilites: %r'
742 % (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800743
744
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800745 def disable_antennas_except(self, permitted_antennas):
746 """Disable unwanted antennas.
Peter Qiu2f973252014-02-20 15:30:37 -0800747
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800748 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 Qiu2f973252014-02-20 15:30:37 -0800754
755 """
756 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700757 if not phy.supports_setting_antenna_mask:
758 continue
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800759 # 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 Qiu2f973252014-02-20 15:30:37 -0800763
764
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800765 def enable_all_antennas(self):
766 """Enable all antennas on all phys."""
Peter Qiu2f973252014-02-20 15:30:37 -0800767 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700768 if not phy.supports_setting_antenna_mask:
769 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800770 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
771 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700772
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 Norris9494c0f2019-12-11 16:29:55 -0800783
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