blob: d02c18d40b7fd51c833e56483c89cb09bad5a5b8 [file] [log] [blame]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -07001# 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
Christopher Wiley7c97ded2013-09-24 23:24:33 -07005import datetime
Christopher Wileyf671a5a2013-12-13 15:44:41 -08006import collections
Christopher Wileyf19c28c2013-05-06 15:42:57 -07007import logging
mukesh agrawal5fd90f52015-04-24 16:14:37 -07008import os
mukesh agrawal24a70172015-06-22 10:53:04 -07009import random
Christopher Wiley7c97ded2013-09-24 23:24:33 -070010import time
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070011
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070012from autotest_lib.client.common_lib import error
Christopher Wiley36039222014-12-13 18:27:52 -080013from autotest_lib.client.common_lib.cros import path_utils
Peter Qiu9a63a8b2015-02-03 09:08:16 -080014from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
mukesh agrawal24a70172015-06-22 10:53:04 -070015from autotest_lib.client.common_lib.cros.network import interface
Christopher Wileyf9cb0922013-11-01 09:12:21 -070016from autotest_lib.client.common_lib.cros.network import iw_runner
Christopher Wileyaeef9b52014-03-11 12:24:11 -070017from autotest_lib.client.common_lib.cros.network import ping_runner
Christopher Wileydc7c4622013-07-09 11:44:04 -070018from autotest_lib.server.cros.network import packet_capturer
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070019
Christopher Wileyf671a5a2013-12-13 15:44:41 -080020NetDev = collections.namedtuple('NetDev',
21 ['inherited', 'phy', 'if_name', 'if_type'])
22
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070023class LinuxSystem(object):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070024 """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
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070032 """
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070033
Christopher Wileyf19c28c2013-05-06 15:42:57 -070034 CAPABILITY_5GHZ = '5ghz'
35 CAPABILITY_MULTI_AP = 'multi_ap'
36 CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
Christopher Wiley061f1382013-06-17 18:17:58 -070037 CAPABILITY_IBSS = 'ibss_supported'
Paul Stewart51b0f382013-06-12 09:03:02 -070038 CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
Paul Stewart27ecc5d2013-11-13 16:56:41 -080039 CAPABILITY_TDLS = 'tdls'
Peter Qiu3ef31f02014-10-17 14:25:57 -070040 CAPABILITY_VHT = 'vht'
Matthew Wang8531f622018-05-03 13:50:16 -070041 CAPABILITY_SME = 'sme'
Matthew Wang4d34d422018-12-13 11:16:18 -080042 CAPABILITY_SUPPLICANT_ROAMING = "supplicant_roaming"
Peter Qiu9a63a8b2015-02-03 09:08:16 -080043 BRIDGE_INTERFACE_NAME = 'br0'
Matthew Wang08e868d2018-04-19 12:04:54 -070044 HOSTAP_BRIDGE_INTERFACE_PREFIX = 'hostapbr'
Brian Norris7b09bde2019-05-08 13:02:56 -070045 IFB_INTERFACE_PREFIX = 'ifb'
mukesh agrawal3e458912015-06-12 10:40:16 -070046 MIN_SPATIAL_STREAMS = 2
mukesh agrawal24a70172015-06-22 10:53:04 -070047 MAC_BIT_LOCAL = 0x2 # Locally administered.
48 MAC_BIT_MULTICAST = 0x1
49 MAC_RETRY_LIMIT = 1000
Christopher Wileyf19c28c2013-05-06 15:42:57 -070050
Brian Norrisbeb9d352019-10-24 14:21:14 -070051 _UMA_EVENTS = '/var/lib/metrics/uma-events'
Brian Norris9494c0f2019-12-11 16:29:55 -080052 _LOG_PATH_PREFIX = '/tmp/autotest-'
Brian Norrisbeb9d352019-10-24 14:21:14 -070053
Christopher Wileyf19c28c2013-05-06 15:42:57 -070054
55 @property
56 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070057 """@return iterable object of AP capabilities for this system."""
58 if self._capabilities is None:
59 self._capabilities = self.get_capabilities()
60 logging.info('%s system capabilities: %r',
61 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070062 return self._capabilities
63
64
Peter Qiucb27e012015-03-24 14:34:31 -070065 @property
66 def board(self):
67 """@return string self reported board of this device."""
68 if self._board is None:
69 # Remove 'board:' prefix.
70 self._board = self.host.get_board().split(':')[1]
71 return self._board
72
73
Christopher Wiley408d1812014-01-13 15:27:43 -080074 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070075 self.host = host
76 self.role = role
mukesh agrawal7fd4d092015-06-29 13:50:54 -070077 self.inherit_interfaces = inherit_interfaces
78 self.__setup()
79
80
81 def __setup(self):
82 """Set up this system.
83
84 Can be used either to complete initialization of a LinuxSystem object,
85 or to re-establish a good state after a reboot.
86
87 """
Brian Norris9494c0f2019-12-11 16:29:55 -080088 # hostapd, tcpdump, netperf, etc., may leave behind logs, pcap files,
89 # etc., which can fill up tmpfs. Clear them out now.
90 self.host.run('rm -rf %s*' % self._LOG_PATH_PREFIX)
91 self._logdir = self.host.run('mktemp -d %sXXXXXX' %
92 self._LOG_PATH_PREFIX).stdout.strip()
93
mukesh agrawal7fd4d092015-06-29 13:50:54 -070094 # Command locations.
95 cmd_iw = path_utils.must_be_installed('/usr/sbin/iw', host=self.host)
96 self.cmd_ip = path_utils.must_be_installed('/usr/sbin/ip',
97 host=self.host)
98 self.cmd_readlink = '%s -l' % path_utils.must_be_installed(
99 '/bin/ls', host=self.host)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700100
Christopher Wiley09ad2062013-09-13 13:34:49 -0700101 self._packet_capturer = packet_capturer.get_packet_capturer(
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700102 self.host, host_description=self.role, cmd_ip=self.cmd_ip,
Brian Norris9494c0f2019-12-11 16:29:55 -0800103 cmd_iw=cmd_iw, ignore_failures=True, logdir=self.logdir)
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700104 self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
105 command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700106
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800107 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -0800108 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Roshan Pius9350a8d2016-09-08 16:29:01 -0700109 logging.debug('Current regulatory domain %r',
110 self.iw_runner.get_regulatory_domain())
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800111 self._interfaces = []
Matthew Wang08e868d2018-04-19 12:04:54 -0700112 self._brif_index = 0
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800113 for interface in self.iw_runner.list_interfaces():
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700114 if self.inherit_interfaces:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800115 self._interfaces.append(NetDev(inherited=True,
116 if_name=interface.if_name,
117 if_type=interface.if_type,
118 phy=interface.phy))
119 else:
120 self.iw_runner.remove_interface(interface.if_name)
121
122 self._wlanifs_in_use = []
mukesh agrawal24a70172015-06-22 10:53:04 -0700123 self._local_macs_in_use = set()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800124 self._capture_interface = None
Peter Qiucb27e012015-03-24 14:34:31 -0700125 self._board = None
Christopher Wiley80e40922013-10-22 13:14:56 -0700126 # Some uses of LinuxSystem don't use the interface allocation facility.
127 # Don't force us to remove all the existing interfaces if this facility
128 # is not desired.
129 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -0700130 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700131 self._ping_runner = ping_runner.PingRunner(host=self.host)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800132 self._bridge_interface = None
133 self._virtual_ethernet_pair = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700134
Brian Norrisbeb9d352019-10-24 14:21:14 -0700135 # TODO(crbug.com/839164): some routers fill their stateful partition
136 # with uncollected metrics.
137 if self.host.path_exists(self._UMA_EVENTS):
138 self.host.run('truncate -s 0 %s' % self._UMA_EVENTS,
139 ignore_status=True)
140
Paul Stewart6423bb02012-11-27 17:46:23 -0800141
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800142 @property
143 def phy_list(self):
144 """@return iterable object of PHY descriptions for this system."""
145 if self._phy_list is None:
146 self._phy_list = self.iw_runner.list_phys()
147 return self._phy_list
148
149
mukesh agrawal3e458912015-06-12 10:40:16 -0700150 def _phy_by_name(self, phy_name):
151 """@return IwPhy for PHY with name |phy_name|, or None."""
152 for phy in self._phy_list:
153 if phy.name == phy_name:
154 return phy
155 else:
156 return None
157
158
Paul Stewart6423bb02012-11-27 17:46:23 -0800159 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700160 """Get information about WiFi devices.
161
162 Parse the output of 'iw list' and some of sysfs and return:
163
164 A dict |phys_for_frequency| which maps from each frequency to a
165 list of phys that support that channel.
166
167 A dict |phy_bus_type| which maps from each phy to the bus type for
168 each phy.
169
170 @return phys_for_frequency, phy_bus_type tuple as described.
171
Paul Stewart6423bb02012-11-27 17:46:23 -0800172 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800173 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800174 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700175 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800176 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700177 phy_list.append(phy.name)
178 for band in phy.bands:
179 for mhz in band.frequencies:
180 if mhz not in phys_for_frequency:
181 phys_for_frequency[mhz] = [phy.name]
182 else:
183 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800184
185 phy_bus_type = {}
186 for phy in phy_list:
187 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700188 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800189 devpath = self.host.run(command).stdout
190 if '/usb' in devpath:
191 phybus = 'usb'
192 elif '/mmc' in devpath:
193 phybus = 'sdio'
194 elif '/pci' in devpath:
195 phybus = 'pci'
196 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700197 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800198 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700199
Paul Stewart64cc4292011-06-01 10:59:36 -0700200
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800201 def _create_bridge_interface(self):
202 """Create a bridge interface."""
203 self.host.run('%s link add name %s type bridge' %
204 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
205 self.host.run('%s link set dev %s up' %
206 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
207 self._bridge_interface = self.BRIDGE_INTERFACE_NAME
208
209
210 def _create_virtual_ethernet_pair(self):
211 """Create a virtual ethernet pair."""
212 self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
213 interface_ip=None, peer_interface_ip=None, host=self.host)
214 self._virtual_ethernet_pair.setup()
215
216
mukesh agrawal24a70172015-06-22 10:53:04 -0700217 def _get_unique_mac(self):
218 """Get a MAC address that is likely to be unique.
219
220 Generates a MAC address that is a) guaranteed not to be in use
221 on this host, and b) likely to be unique within the test cell.
222
223 @return string MAC address.
224
225 """
226 # We use SystemRandom to reduce the likelyhood of coupling
227 # across systems. (The default random class might, e.g., seed
228 # itself based on wall-clock time.)
229 sysrand = random.SystemRandom()
230 for tries in xrange(0, self.MAC_RETRY_LIMIT):
231 mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % (
232 (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) |
233 self.MAC_BIT_LOCAL,
234 sysrand.getrandbits(8),
235 sysrand.getrandbits(8),
236 sysrand.getrandbits(8),
237 sysrand.getrandbits(8),
238 sysrand.getrandbits(8))
239 if mac_addr not in self._local_macs_in_use:
240 self._local_macs_in_use.add(mac_addr)
241 return mac_addr
242 else:
243 raise error.TestError('Failed to find a new MAC address')
244
245
mukesh agrawala3423082015-07-20 12:40:18 -0700246 def _phy_in_use(self, phy_name):
247 """Determine whether or not a PHY is used by an active DEV
248
249 @return bool True iff PHY is in use.
250 """
251 for net_dev in self._wlanifs_in_use:
252 if net_dev.phy == phy_name:
253 return True
254 return False
255
256
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800257 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700258 """Remove an interface from a WiFi device.
259
260 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700261
262 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800263 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700264 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700265 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800266 for net_dev in self._interfaces:
267 if net_dev.if_name == interface:
268 self._interfaces.remove(net_dev)
269 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700270
Christopher Wiley061f1382013-06-17 18:17:58 -0700271
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700272 def close(self):
273 """Close global resources held by this system."""
274 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700275 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800276 # Release and remove any interfaces that we create.
277 for net_dev in self._wlanifs_in_use:
278 self.release_interface(net_dev.if_name)
279 for net_dev in self._interfaces:
280 if net_dev.inherited:
281 continue
282 self.remove_interface(net_dev.if_name)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800283 if self._bridge_interface is not None:
284 self.remove_bridge_interface()
285 if self._virtual_ethernet_pair is not None:
286 self.remove_ethernet_pair_interface()
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700287 self.host.close()
288 self.host = None
289
290
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700291 def reboot(self, timeout):
292 """Reboot this system, and restore it to a known-good state.
293
294 @param timeout Maximum seconds to wait for system to return.
295
296 """
297 self.host.reboot(timeout=timeout, wait=True)
298 self.__setup()
299
300
Christopher Wiley061f1382013-06-17 18:17:58 -0700301 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700302 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700303 phymap = self.phys_for_frequency
304 if [freq for freq in phymap.iterkeys() if freq > 5000]:
305 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700306 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700307 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700308 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
309 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700310 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700311 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800312 for phy in self.phy_list:
Paul Stewartf94151e2015-04-20 20:32:39 -0700313 if ('tdls_mgmt' in phy.commands or
314 'tdls_oper' in phy.commands or
315 'T-DLS' in phy.features):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800316 caps.add(self.CAPABILITY_TDLS)
Matthew Wang8531f622018-05-03 13:50:16 -0700317 if 'authenticate' in phy.commands:
318 caps.add(self.CAPABILITY_SME)
Peter Qiu3ef31f02014-10-17 14:25:57 -0700319 if phy.support_vht:
320 caps.add(self.CAPABILITY_VHT)
Matthew Wang4d34d422018-12-13 11:16:18 -0800321 if 'roaming' not in phy.features:
322 caps.add(self.CAPABILITY_SUPPLICANT_ROAMING)
mukesh agrawal064ea972015-06-26 10:00:53 -0700323 if any([iw_runner.DEV_MODE_IBSS in phy.modes
324 for phy in self.phy_list]):
325 caps.add(self.CAPABILITY_IBSS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700326 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700327
328
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700329 def start_capture(self, frequency,
Jared Pauletti681e4c12019-10-23 18:33:34 -0700330 width_type=None, snaplen=None, filename=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700331 """Start a packet capture.
332
333 @param frequency int frequency of channel to capture on.
Jared Pauletti681e4c12019-10-23 18:33:34 -0700334 @param width_type object width type from iw_runner.
Christopher Wiley618e52b2013-10-14 16:21:07 -0700335 @param snaplen int number of bytes to retain per capture frame.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700336 @param filename string filename to write capture to.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700337
338 """
339 if self._packet_capturer.capture_running:
340 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800341 self._capture_interface = self.get_wlanif(frequency, 'monitor')
342 full_interface = [net_dev for net_dev in self._interfaces
343 if net_dev.if_name == self._capture_interface][0]
344 # If this is the only interface on this phy, we ought to configure
Jared Pauletti681e4c12019-10-23 18:33:34 -0700345 # the phy with a channel and a width. Otherwise, inherit the
346 # settings of the phy as they stand.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800347 if len([net_dev for net_dev in self._interfaces
348 if net_dev.phy == full_interface.phy]) == 1:
349 self._packet_capturer.configure_raw_monitor(
Jared Pauletti681e4c12019-10-23 18:33:34 -0700350 self._capture_interface, frequency, width_type=width_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800351 else:
352 self.host.run('%s link set %s up' %
353 (self.cmd_ip, self._capture_interface))
354
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700355 # Start the capture.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700356 if filename:
357 remote_path = os.path.join('/tmp', os.path.basename(filename))
358 else:
359 remote_path = None
360 self._packet_capturer.start_capture(
361 self._capture_interface, './debug/', snaplen=snaplen,
362 remote_file=remote_path)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700363
364
Christopher Wiley062a9812013-10-03 15:55:50 -0700365 def stop_capture(self, save_dir=None, save_filename=None):
366 """Stop a packet capture.
367
368 @param save_dir string path to directory to save pcap files in.
369 @param save_filename string basename of file to save pcap in locally.
370
371 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700372 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700373 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700374 results = self._packet_capturer.stop_capture(
375 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800376 self.release_interface(self._capture_interface)
377 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700378 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800379
380
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700381 def sync_host_times(self):
382 """Set time on our DUT to match local time."""
383 epoch_seconds = time.time()
384 busybox_format = '%Y%m%d%H%M.%S'
385 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
386 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
387 (epoch_seconds, busybox_date))
388
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800389
mukesh agrawal43644aa2015-07-06 14:40:22 -0700390 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700391 """Get a phy appropriate for a frequency and phytype.
392
393 Return the most appropriate phy interface for operating on the
394 frequency |frequency| in the role indicated by |phytype|. Prefer idle
395 phys to busy phys if any exist. Secondarily, show affinity for phys
396 that use the bus type associated with this phy type.
397
398 @param frequency int WiFi frequency of phy.
399 @param phytype string key of phytype registered at construction time.
mukesh agrawal43644aa2015-07-06 14:40:22 -0700400 @param spatial_streams int number of spatial streams required.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700401 @return string name of phy to use.
402
Paul Stewart6423bb02012-11-27 17:46:23 -0800403 """
mukesh agrawal43644aa2015-07-06 14:40:22 -0700404 phy_objs = []
405 for phy_name in self.phys_for_frequency[frequency]:
406 phy_obj = self._phy_by_name(phy_name)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700407 num_antennas = min(phy_obj.avail_rx_antennas,
408 phy_obj.avail_tx_antennas)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700409 if num_antennas >= spatial_streams:
410 phy_objs.append(phy_obj)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700411 elif num_antennas == 0:
412 logging.warning(
mukesh agrawal43644aa2015-07-06 14:40:22 -0700413 'Allowing use of %s, which reports zero antennas', phy_name)
414 phy_objs.append(phy_obj)
mukesh agrawal3e458912015-06-12 10:40:16 -0700415 else:
416 logging.debug(
mukesh agrawalbc963c12015-06-19 11:40:03 -0700417 'Filtering out %s, which reports only %d antennas',
mukesh agrawal43644aa2015-07-06 14:40:22 -0700418 phy_name, num_antennas)
Paul Stewart6423bb02012-11-27 17:46:23 -0800419
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800420 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700421 idle_phy_objs = [phy_obj for phy_obj in phy_objs
422 if phy_obj.name not in busy_phys]
423 phy_objs = idle_phy_objs or phy_objs
424 phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas,
425 phy_obj.avail_tx_antennas),
426 reverse=True)
427 phys = [phy_obj.name for phy_obj in phy_objs]
Paul Stewart6423bb02012-11-27 17:46:23 -0800428
Christopher Wiley408d1812014-01-13 15:27:43 -0800429 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800430 preferred_phys = [phy for phy in phys
431 if self.phy_bus_type[phy] == preferred_bus]
432 phys = preferred_phys or phys
433
434 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700435
436
mukesh agrawala3423082015-07-20 12:40:18 -0700437 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
438 """Get a WiFi device that supports the given frequency and phytype.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700439
mukesh agrawala3423082015-07-20 12:40:18 -0700440 We simply find or create a suitable DEV. It is left to the
441 caller to actually configure the frequency and bring up the
442 interface.
443
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700444 @param phytype string type of phy (e.g. 'monitor').
mukesh agrawal43644aa2015-07-06 14:40:22 -0700445 @param spatial_streams int number of spatial streams required.
mukesh agrawala3423082015-07-20 12:40:18 -0700446 @param frequency int WiFi frequency to support.
Paul Stewart51b0f382013-06-12 09:03:02 -0700447 @param same_phy_as string create the interface on the same phy as this.
mukesh agrawala3423082015-07-20 12:40:18 -0700448 @return NetDev WiFi device.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700449
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700450 """
mukesh agrawala3423082015-07-20 12:40:18 -0700451 if frequency and same_phy_as:
452 raise error.TestError(
453 'Can not combine |frequency| and |same_phy_as|')
454
455 if not (frequency or same_phy_as):
456 raise error.TestError(
457 'Must specify one of |frequency| or |same_phy_as|')
458
mukesh agrawal43644aa2015-07-06 14:40:22 -0700459 if spatial_streams is None:
460 spatial_streams = self.MIN_SPATIAL_STREAMS
Brian Norris369799e2018-08-03 18:09:43 -0700461 # We don't want to use the 3rd radio on Whirlwind. Reject it if someone
462 # tries to add a test that uses it.
463 elif spatial_streams < self.MIN_SPATIAL_STREAMS and \
464 self.board == 'whirlwind':
465 raise error.TestError('Requested spatial streams: %d; minimum %d' \
466 % (spatial_streams, self.MIN_SPATIAL_STREAMS))
mukesh agrawal43644aa2015-07-06 14:40:22 -0700467
Paul Stewart51b0f382013-06-12 09:03:02 -0700468 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800469 for net_dev in self._interfaces:
470 if net_dev.if_name == same_phy_as:
471 phy = net_dev.phy
472 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700473 else:
474 raise error.TestFail('Unable to find phy for interface %s' %
475 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800476 elif frequency in self.phys_for_frequency:
mukesh agrawal43644aa2015-07-06 14:40:22 -0700477 phy = self._get_phy_for_frequency(
478 frequency, phytype, spatial_streams)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700479 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800480 raise error.TestFail('Unable to find phy for frequency %d' %
481 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700482
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800483 # If we have a suitable unused interface sitting around on this
484 # phy, reuse it.
485 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
486 if net_dev.phy == phy and net_dev.if_type == phytype:
mukesh agrawal24a70172015-06-22 10:53:04 -0700487 break
488 else:
489 # Because we can reuse interfaces, we have to iteratively find a
490 # good interface name.
491 name_exists = lambda name: bool([net_dev
492 for net_dev in self._interfaces
493 if net_dev.if_name == name])
494 if_name = lambda index: '%s%d' % (phytype, index)
495 if_index = len(self._interfaces)
496 while name_exists(if_name(if_index)):
497 if_index += 1
498 net_dev = NetDev(phy=phy, if_name=if_name(if_index),
499 if_type=phytype, inherited=False)
500 self._interfaces.append(net_dev)
501 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700502
mukesh agrawal24a70172015-06-22 10:53:04 -0700503 # Link must be down to reconfigure MAC address.
504 self.host.run('%s link set dev %s down' % (
505 self.cmd_ip, net_dev.if_name))
506 if same_phy_as:
507 self.clone_mac_address(src_dev=same_phy_as,
508 dst_dev=net_dev.if_name)
509 else:
510 self.ensure_unique_mac(net_dev)
511
mukesh agrawala3423082015-07-20 12:40:18 -0700512 return net_dev
513
514
Matthew Wang08e868d2018-04-19 12:04:54 -0700515 def get_brif(self):
516 brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
517 self._brif_index)
518 self._brif_index += 1
519 return brif_name
520
521
mukesh agrawala3423082015-07-20 12:40:18 -0700522 def get_configured_interface(self, phytype, spatial_streams=None,
523 frequency=None, same_phy_as=None):
524 """Get a WiFi device that supports the given frequency and phytype.
525
526 The device's link state will be UP, and (where possible) the device
527 will be configured to operate on |frequency|.
528
529 @param phytype string type of phy (e.g. 'monitor').
530 @param spatial_streams int number of spatial streams required.
531 @param frequency int WiFi frequency to support.
532 @param same_phy_as string create the interface on the same phy as this.
533 @return string WiFi device.
534
535 """
536 net_dev = self._get_wlanif(
537 phytype, spatial_streams, frequency, same_phy_as)
538
539 self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name))
540
541 if frequency:
542 if phytype == 'managed':
543 logging.debug('Skipped setting frequency for DEV %s '
544 'since managed mode DEVs roam across APs.',
545 net_dev.if_name)
546 elif same_phy_as or self._phy_in_use(net_dev.phy):
547 logging.debug('Skipped setting frequency for DEV %s '
548 'since PHY %s is already in use',
549 net_dev.if_name, net_dev.phy)
550 else:
551 self.iw_runner.set_freq(net_dev.if_name, frequency)
552
553 self._wlanifs_in_use.append(net_dev)
554 return net_dev.if_name
555
556
557 # TODO(quiche): Deprecate this, in favor of get_configured_interface().
558 # crbug.com/512169.
559 def get_wlanif(self, frequency, phytype,
560 spatial_streams=None, same_phy_as=None):
561 """Get a WiFi device that supports the given frequency and phytype.
562
563 We simply find or create a suitable DEV. It is left to the
564 caller to actually configure the frequency and bring up the
565 interface.
566
567 @param frequency int WiFi frequency to support.
568 @param phytype string type of phy (e.g. 'monitor').
569 @param spatial_streams int number of spatial streams required.
570 @param same_phy_as string create the interface on the same phy as this.
571 @return string WiFi device.
572
573 """
574 net_dev = self._get_wlanif(
575 phytype, spatial_streams, frequency, same_phy_as)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800576 self._wlanifs_in_use.append(net_dev)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800577 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800578
579
mukesh agrawal24a70172015-06-22 10:53:04 -0700580 def ensure_unique_mac(self, net_dev):
581 """Ensure MAC address of |net_dev| meets uniqueness requirements.
582
583 The Linux kernel does not allow multiple APs with the same
584 BSSID on the same PHY (at least, with some drivers). Hence, we
585 want to ensure that the DEVs for a PHY have unique MAC
586 addresses.
587
588 Note that we do not attempt to make the MACs unique across
589 PHYs, because some tests deliberately create such scenarios.
590
591 @param net_dev NetDev to uniquify.
592
593 """
594 if net_dev.if_type == 'monitor':
595 return
596
597 our_ifname = net_dev.if_name
598 our_phy = net_dev.phy
599 our_mac = interface.Interface(our_ifname, self.host).mac_address
600 sibling_devs = [dev for dev in self._interfaces
601 if (dev.phy == our_phy and
602 dev.if_name != our_ifname and
603 dev.if_type != 'monitor')]
604 sibling_macs = (
605 interface.Interface(sib_dev.if_name, self.host).mac_address
606 for sib_dev in sibling_devs)
607 if our_mac in sibling_macs:
608 self.configure_interface_mac(our_ifname,
609 self._get_unique_mac())
610
611
612 def configure_interface_mac(self, wlanif, new_mac):
613 """Change the MAC address for an interface.
614
615 @param wlanif string name of device to reconfigure.
616 @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55')
617
618 """
619 self.host.run('%s link set %s address %s' %
620 (self.cmd_ip, wlanif, new_mac))
621
622
623 def clone_mac_address(self, src_dev=None, dst_dev=None):
624 """Copy the MAC address from one interface to another.
625
626 @param src_dev string name of device to copy address from.
627 @param dst_dev string name of device to copy address to.
628
629 """
630 self.configure_interface_mac(
631 dst_dev,
632 interface.Interface(src_dev, self.host).mac_address)
633
634
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800635 def release_interface(self, wlanif):
636 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700637
638 @param wlanif string name of device to release.
639
640 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800641 for net_dev in self._wlanifs_in_use:
642 if net_dev.if_name == wlanif:
643 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700644
645
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800646 def get_bridge_interface(self):
647 """Return the bridge interface, create one if it is not created yet.
648
649 @return string name of bridge interface.
650 """
651 if self._bridge_interface is None:
652 self._create_bridge_interface()
653 return self._bridge_interface
654
655
656 def remove_bridge_interface(self):
657 """Remove the bridge interface that's been created."""
658 if self._bridge_interface is not None:
659 self.host.run('%s link delete %s type bridge' %
660 (self.cmd_ip, self._bridge_interface))
661 self._bridge_interface = None
662
663
664 def add_interface_to_bridge(self, interface):
665 """Add an interface to the bridge interface.
666
667 This will create the bridge interface if it is not created yet.
668
669 @param interface string name of the interface to add to the bridge.
670 """
671 if self._bridge_interface is None:
672 self._create_bridge_interface()
673 self.host.run('%s link set dev %s master %s' %
674 (self.cmd_ip, interface, self._bridge_interface))
675
676
677 def get_virtual_ethernet_master_interface(self):
678 """Return the master interface of the virtual ethernet pair.
679
680 @return string name of the master interface of the virtual ethernet
681 pair.
682 """
683 if self._virtual_ethernet_pair is None:
684 self._create_virtual_ethernet_pair()
685 return self._virtual_ethernet_pair.interface_name
686
687
688 def get_virtual_ethernet_peer_interface(self):
689 """Return the peer interface of the virtual ethernet pair.
690
691 @return string name of the peer interface of the virtual ethernet pair.
692 """
693 if self._virtual_ethernet_pair is None:
694 self._create_virtual_ethernet_pair()
695 return self._virtual_ethernet_pair.peer_interface_name
696
697
698 def remove_ethernet_pair_interface(self):
699 """Remove the virtual ethernet pair that's been created."""
700 if self._virtual_ethernet_pair is not None:
701 self._virtual_ethernet_pair.teardown()
702 self._virtual_ethernet_pair = None
703
704
Brian Norrisc6801862017-10-06 12:55:37 -0700705 def require_capabilities(self, requirements):
Christopher Wiley9b406202013-05-06 14:07:49 -0700706 """Require capabilities of this LinuxSystem.
707
708 Check that capabilities in |requirements| exist on this system.
Brian Norrisc6801862017-10-06 12:55:37 -0700709 Raise an exception to skip but not fail the test if said
710 capabilities are not found.
Christopher Wiley9b406202013-05-06 14:07:49 -0700711
712 @param requirements list of CAPABILITY_* defined above.
Christopher Wiley9b406202013-05-06 14:07:49 -0700713
714 """
Christopher Wiley9b406202013-05-06 14:07:49 -0700715 missing = [cap for cap in requirements if not cap in self.capabilities]
716 if missing:
Kirtika Ruchandani753498b2018-05-17 14:50:24 -0700717 raise error.TestNAError('%s is missing required capabilites: %r'
718 % (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800719
720
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800721 def disable_antennas_except(self, permitted_antennas):
722 """Disable unwanted antennas.
Peter Qiu2f973252014-02-20 15:30:37 -0800723
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800724 Disable all antennas except those specified in |permitted_antennas|.
725 Note that one or more of them may remain disabled if the underlying
726 hardware does not support them.
727
728 @param permitted_antennas int bitmask specifying antennas that we should
729 attempt to enable.
Peter Qiu2f973252014-02-20 15:30:37 -0800730
731 """
732 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700733 if not phy.supports_setting_antenna_mask:
734 continue
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800735 # Determine valid bitmap values based on available antennas.
736 self.iw_runner.set_antenna_bitmap(phy.name,
737 permitted_antennas & phy.avail_tx_antennas,
738 permitted_antennas & phy.avail_rx_antennas)
Peter Qiu2f973252014-02-20 15:30:37 -0800739
740
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800741 def enable_all_antennas(self):
742 """Enable all antennas on all phys."""
Peter Qiu2f973252014-02-20 15:30:37 -0800743 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700744 if not phy.supports_setting_antenna_mask:
745 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800746 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
747 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700748
749
750 def ping(self, ping_config):
751 """Ping an IP from this system.
752
753 @param ping_config PingConfig object describing the ping command to run.
754 @return a PingResult object.
755
756 """
757 logging.info('Pinging from the %s.', self.role)
758 return self._ping_runner.ping(ping_config)
Brian Norris9494c0f2019-12-11 16:29:55 -0800759
760
761 @property
762 def logdir(self):
763 """Return a directory for storing temporary logs.
764 @return string path to temporary log directory.
765 """
766 return self._logdir