blob: dbe197b92bc78c51179c09f8396cfbce7d87675a [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'
52
Christopher Wileyf19c28c2013-05-06 15:42:57 -070053
54 @property
55 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070056 """@return iterable object of AP capabilities for this system."""
57 if self._capabilities is None:
58 self._capabilities = self.get_capabilities()
59 logging.info('%s system capabilities: %r',
60 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070061 return self._capabilities
62
63
Peter Qiucb27e012015-03-24 14:34:31 -070064 @property
65 def board(self):
66 """@return string self reported board of this device."""
67 if self._board is None:
68 # Remove 'board:' prefix.
69 self._board = self.host.get_board().split(':')[1]
70 return self._board
71
72
Christopher Wiley408d1812014-01-13 15:27:43 -080073 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070074 self.host = host
75 self.role = role
mukesh agrawal7fd4d092015-06-29 13:50:54 -070076 self.inherit_interfaces = inherit_interfaces
77 self.__setup()
78
79
80 def __setup(self):
81 """Set up this system.
82
83 Can be used either to complete initialization of a LinuxSystem object,
84 or to re-establish a good state after a reboot.
85
86 """
87 # Command locations.
88 cmd_iw = path_utils.must_be_installed('/usr/sbin/iw', host=self.host)
89 self.cmd_ip = path_utils.must_be_installed('/usr/sbin/ip',
90 host=self.host)
91 self.cmd_readlink = '%s -l' % path_utils.must_be_installed(
92 '/bin/ls', host=self.host)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070093
Christopher Wiley09ad2062013-09-13 13:34:49 -070094 self._packet_capturer = packet_capturer.get_packet_capturer(
mukesh agrawal7fd4d092015-06-29 13:50:54 -070095 self.host, host_description=self.role, cmd_ip=self.cmd_ip,
Christopher Wiley408d1812014-01-13 15:27:43 -080096 cmd_iw=cmd_iw, ignore_failures=True)
mukesh agrawal7fd4d092015-06-29 13:50:54 -070097 self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
98 command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070099
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800100 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -0800101 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Roshan Pius9350a8d2016-09-08 16:29:01 -0700102 logging.debug('Current regulatory domain %r',
103 self.iw_runner.get_regulatory_domain())
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800104 self._interfaces = []
Matthew Wang08e868d2018-04-19 12:04:54 -0700105 self._brif_index = 0
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800106 for interface in self.iw_runner.list_interfaces():
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700107 if self.inherit_interfaces:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800108 self._interfaces.append(NetDev(inherited=True,
109 if_name=interface.if_name,
110 if_type=interface.if_type,
111 phy=interface.phy))
112 else:
113 self.iw_runner.remove_interface(interface.if_name)
114
115 self._wlanifs_in_use = []
mukesh agrawal24a70172015-06-22 10:53:04 -0700116 self._local_macs_in_use = set()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800117 self._capture_interface = None
Peter Qiucb27e012015-03-24 14:34:31 -0700118 self._board = None
Christopher Wiley80e40922013-10-22 13:14:56 -0700119 # Some uses of LinuxSystem don't use the interface allocation facility.
120 # Don't force us to remove all the existing interfaces if this facility
121 # is not desired.
122 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -0700123 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700124 self._ping_runner = ping_runner.PingRunner(host=self.host)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800125 self._bridge_interface = None
126 self._virtual_ethernet_pair = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700127
Brian Norrisbeb9d352019-10-24 14:21:14 -0700128 # TODO(crbug.com/839164): some routers fill their stateful partition
129 # with uncollected metrics.
130 if self.host.path_exists(self._UMA_EVENTS):
131 self.host.run('truncate -s 0 %s' % self._UMA_EVENTS,
132 ignore_status=True)
133
Paul Stewart6423bb02012-11-27 17:46:23 -0800134
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800135 @property
136 def phy_list(self):
137 """@return iterable object of PHY descriptions for this system."""
138 if self._phy_list is None:
139 self._phy_list = self.iw_runner.list_phys()
140 return self._phy_list
141
142
mukesh agrawal3e458912015-06-12 10:40:16 -0700143 def _phy_by_name(self, phy_name):
144 """@return IwPhy for PHY with name |phy_name|, or None."""
145 for phy in self._phy_list:
146 if phy.name == phy_name:
147 return phy
148 else:
149 return None
150
151
Paul Stewart6423bb02012-11-27 17:46:23 -0800152 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700153 """Get information about WiFi devices.
154
155 Parse the output of 'iw list' and some of sysfs and return:
156
157 A dict |phys_for_frequency| which maps from each frequency to a
158 list of phys that support that channel.
159
160 A dict |phy_bus_type| which maps from each phy to the bus type for
161 each phy.
162
163 @return phys_for_frequency, phy_bus_type tuple as described.
164
Paul Stewart6423bb02012-11-27 17:46:23 -0800165 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800166 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800167 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700168 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800169 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700170 phy_list.append(phy.name)
171 for band in phy.bands:
172 for mhz in band.frequencies:
173 if mhz not in phys_for_frequency:
174 phys_for_frequency[mhz] = [phy.name]
175 else:
176 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800177
178 phy_bus_type = {}
179 for phy in phy_list:
180 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700181 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800182 devpath = self.host.run(command).stdout
183 if '/usb' in devpath:
184 phybus = 'usb'
185 elif '/mmc' in devpath:
186 phybus = 'sdio'
187 elif '/pci' in devpath:
188 phybus = 'pci'
189 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700190 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800191 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700192
Paul Stewart64cc4292011-06-01 10:59:36 -0700193
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800194 def _create_bridge_interface(self):
195 """Create a bridge interface."""
196 self.host.run('%s link add name %s type bridge' %
197 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
198 self.host.run('%s link set dev %s up' %
199 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
200 self._bridge_interface = self.BRIDGE_INTERFACE_NAME
201
202
203 def _create_virtual_ethernet_pair(self):
204 """Create a virtual ethernet pair."""
205 self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
206 interface_ip=None, peer_interface_ip=None, host=self.host)
207 self._virtual_ethernet_pair.setup()
208
209
mukesh agrawal24a70172015-06-22 10:53:04 -0700210 def _get_unique_mac(self):
211 """Get a MAC address that is likely to be unique.
212
213 Generates a MAC address that is a) guaranteed not to be in use
214 on this host, and b) likely to be unique within the test cell.
215
216 @return string MAC address.
217
218 """
219 # We use SystemRandom to reduce the likelyhood of coupling
220 # across systems. (The default random class might, e.g., seed
221 # itself based on wall-clock time.)
222 sysrand = random.SystemRandom()
223 for tries in xrange(0, self.MAC_RETRY_LIMIT):
224 mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % (
225 (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) |
226 self.MAC_BIT_LOCAL,
227 sysrand.getrandbits(8),
228 sysrand.getrandbits(8),
229 sysrand.getrandbits(8),
230 sysrand.getrandbits(8),
231 sysrand.getrandbits(8))
232 if mac_addr not in self._local_macs_in_use:
233 self._local_macs_in_use.add(mac_addr)
234 return mac_addr
235 else:
236 raise error.TestError('Failed to find a new MAC address')
237
238
mukesh agrawala3423082015-07-20 12:40:18 -0700239 def _phy_in_use(self, phy_name):
240 """Determine whether or not a PHY is used by an active DEV
241
242 @return bool True iff PHY is in use.
243 """
244 for net_dev in self._wlanifs_in_use:
245 if net_dev.phy == phy_name:
246 return True
247 return False
248
249
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800250 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700251 """Remove an interface from a WiFi device.
252
253 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700254
255 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800256 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700257 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700258 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800259 for net_dev in self._interfaces:
260 if net_dev.if_name == interface:
261 self._interfaces.remove(net_dev)
262 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700263
Christopher Wiley061f1382013-06-17 18:17:58 -0700264
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700265 def close(self):
266 """Close global resources held by this system."""
267 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700268 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800269 # Release and remove any interfaces that we create.
270 for net_dev in self._wlanifs_in_use:
271 self.release_interface(net_dev.if_name)
272 for net_dev in self._interfaces:
273 if net_dev.inherited:
274 continue
275 self.remove_interface(net_dev.if_name)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800276 if self._bridge_interface is not None:
277 self.remove_bridge_interface()
278 if self._virtual_ethernet_pair is not None:
279 self.remove_ethernet_pair_interface()
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700280 self.host.close()
281 self.host = None
282
283
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700284 def reboot(self, timeout):
285 """Reboot this system, and restore it to a known-good state.
286
287 @param timeout Maximum seconds to wait for system to return.
288
289 """
290 self.host.reboot(timeout=timeout, wait=True)
291 self.__setup()
292
293
Christopher Wiley061f1382013-06-17 18:17:58 -0700294 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700295 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700296 phymap = self.phys_for_frequency
297 if [freq for freq in phymap.iterkeys() if freq > 5000]:
298 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700299 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700300 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700301 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
302 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700303 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700304 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800305 for phy in self.phy_list:
Paul Stewartf94151e2015-04-20 20:32:39 -0700306 if ('tdls_mgmt' in phy.commands or
307 'tdls_oper' in phy.commands or
308 'T-DLS' in phy.features):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800309 caps.add(self.CAPABILITY_TDLS)
Matthew Wang8531f622018-05-03 13:50:16 -0700310 if 'authenticate' in phy.commands:
311 caps.add(self.CAPABILITY_SME)
Peter Qiu3ef31f02014-10-17 14:25:57 -0700312 if phy.support_vht:
313 caps.add(self.CAPABILITY_VHT)
Matthew Wang4d34d422018-12-13 11:16:18 -0800314 if 'roaming' not in phy.features:
315 caps.add(self.CAPABILITY_SUPPLICANT_ROAMING)
mukesh agrawal064ea972015-06-26 10:00:53 -0700316 if any([iw_runner.DEV_MODE_IBSS in phy.modes
317 for phy in self.phy_list]):
318 caps.add(self.CAPABILITY_IBSS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700319 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700320
321
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700322 def start_capture(self, frequency,
Jared Pauletti681e4c12019-10-23 18:33:34 -0700323 width_type=None, snaplen=None, filename=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700324 """Start a packet capture.
325
326 @param frequency int frequency of channel to capture on.
Jared Pauletti681e4c12019-10-23 18:33:34 -0700327 @param width_type object width type from iw_runner.
Christopher Wiley618e52b2013-10-14 16:21:07 -0700328 @param snaplen int number of bytes to retain per capture frame.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700329 @param filename string filename to write capture to.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700330
331 """
332 if self._packet_capturer.capture_running:
333 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800334 self._capture_interface = self.get_wlanif(frequency, 'monitor')
335 full_interface = [net_dev for net_dev in self._interfaces
336 if net_dev.if_name == self._capture_interface][0]
337 # If this is the only interface on this phy, we ought to configure
Jared Pauletti681e4c12019-10-23 18:33:34 -0700338 # the phy with a channel and a width. Otherwise, inherit the
339 # settings of the phy as they stand.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800340 if len([net_dev for net_dev in self._interfaces
341 if net_dev.phy == full_interface.phy]) == 1:
342 self._packet_capturer.configure_raw_monitor(
Jared Pauletti681e4c12019-10-23 18:33:34 -0700343 self._capture_interface, frequency, width_type=width_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800344 else:
345 self.host.run('%s link set %s up' %
346 (self.cmd_ip, self._capture_interface))
347
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700348 # Start the capture.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700349 if filename:
350 remote_path = os.path.join('/tmp', os.path.basename(filename))
351 else:
352 remote_path = None
353 self._packet_capturer.start_capture(
354 self._capture_interface, './debug/', snaplen=snaplen,
355 remote_file=remote_path)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700356
357
Christopher Wiley062a9812013-10-03 15:55:50 -0700358 def stop_capture(self, save_dir=None, save_filename=None):
359 """Stop a packet capture.
360
361 @param save_dir string path to directory to save pcap files in.
362 @param save_filename string basename of file to save pcap in locally.
363
364 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700365 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700366 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700367 results = self._packet_capturer.stop_capture(
368 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800369 self.release_interface(self._capture_interface)
370 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700371 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800372
373
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700374 def sync_host_times(self):
375 """Set time on our DUT to match local time."""
376 epoch_seconds = time.time()
377 busybox_format = '%Y%m%d%H%M.%S'
378 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
379 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
380 (epoch_seconds, busybox_date))
381
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800382
mukesh agrawal43644aa2015-07-06 14:40:22 -0700383 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700384 """Get a phy appropriate for a frequency and phytype.
385
386 Return the most appropriate phy interface for operating on the
387 frequency |frequency| in the role indicated by |phytype|. Prefer idle
388 phys to busy phys if any exist. Secondarily, show affinity for phys
389 that use the bus type associated with this phy type.
390
391 @param frequency int WiFi frequency of phy.
392 @param phytype string key of phytype registered at construction time.
mukesh agrawal43644aa2015-07-06 14:40:22 -0700393 @param spatial_streams int number of spatial streams required.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700394 @return string name of phy to use.
395
Paul Stewart6423bb02012-11-27 17:46:23 -0800396 """
mukesh agrawal43644aa2015-07-06 14:40:22 -0700397 phy_objs = []
398 for phy_name in self.phys_for_frequency[frequency]:
399 phy_obj = self._phy_by_name(phy_name)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700400 num_antennas = min(phy_obj.avail_rx_antennas,
401 phy_obj.avail_tx_antennas)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700402 if num_antennas >= spatial_streams:
403 phy_objs.append(phy_obj)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700404 elif num_antennas == 0:
405 logging.warning(
mukesh agrawal43644aa2015-07-06 14:40:22 -0700406 'Allowing use of %s, which reports zero antennas', phy_name)
407 phy_objs.append(phy_obj)
mukesh agrawal3e458912015-06-12 10:40:16 -0700408 else:
409 logging.debug(
mukesh agrawalbc963c12015-06-19 11:40:03 -0700410 'Filtering out %s, which reports only %d antennas',
mukesh agrawal43644aa2015-07-06 14:40:22 -0700411 phy_name, num_antennas)
Paul Stewart6423bb02012-11-27 17:46:23 -0800412
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800413 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700414 idle_phy_objs = [phy_obj for phy_obj in phy_objs
415 if phy_obj.name not in busy_phys]
416 phy_objs = idle_phy_objs or phy_objs
417 phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas,
418 phy_obj.avail_tx_antennas),
419 reverse=True)
420 phys = [phy_obj.name for phy_obj in phy_objs]
Paul Stewart6423bb02012-11-27 17:46:23 -0800421
Christopher Wiley408d1812014-01-13 15:27:43 -0800422 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800423 preferred_phys = [phy for phy in phys
424 if self.phy_bus_type[phy] == preferred_bus]
425 phys = preferred_phys or phys
426
427 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700428
429
mukesh agrawala3423082015-07-20 12:40:18 -0700430 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
431 """Get a WiFi device that supports the given frequency and phytype.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700432
mukesh agrawala3423082015-07-20 12:40:18 -0700433 We simply find or create a suitable DEV. It is left to the
434 caller to actually configure the frequency and bring up the
435 interface.
436
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700437 @param phytype string type of phy (e.g. 'monitor').
mukesh agrawal43644aa2015-07-06 14:40:22 -0700438 @param spatial_streams int number of spatial streams required.
mukesh agrawala3423082015-07-20 12:40:18 -0700439 @param frequency int WiFi frequency to support.
Paul Stewart51b0f382013-06-12 09:03:02 -0700440 @param same_phy_as string create the interface on the same phy as this.
mukesh agrawala3423082015-07-20 12:40:18 -0700441 @return NetDev WiFi device.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700442
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700443 """
mukesh agrawala3423082015-07-20 12:40:18 -0700444 if frequency and same_phy_as:
445 raise error.TestError(
446 'Can not combine |frequency| and |same_phy_as|')
447
448 if not (frequency or same_phy_as):
449 raise error.TestError(
450 'Must specify one of |frequency| or |same_phy_as|')
451
mukesh agrawal43644aa2015-07-06 14:40:22 -0700452 if spatial_streams is None:
453 spatial_streams = self.MIN_SPATIAL_STREAMS
Brian Norris369799e2018-08-03 18:09:43 -0700454 # We don't want to use the 3rd radio on Whirlwind. Reject it if someone
455 # tries to add a test that uses it.
456 elif spatial_streams < self.MIN_SPATIAL_STREAMS and \
457 self.board == 'whirlwind':
458 raise error.TestError('Requested spatial streams: %d; minimum %d' \
459 % (spatial_streams, self.MIN_SPATIAL_STREAMS))
mukesh agrawal43644aa2015-07-06 14:40:22 -0700460
Paul Stewart51b0f382013-06-12 09:03:02 -0700461 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800462 for net_dev in self._interfaces:
463 if net_dev.if_name == same_phy_as:
464 phy = net_dev.phy
465 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700466 else:
467 raise error.TestFail('Unable to find phy for interface %s' %
468 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800469 elif frequency in self.phys_for_frequency:
mukesh agrawal43644aa2015-07-06 14:40:22 -0700470 phy = self._get_phy_for_frequency(
471 frequency, phytype, spatial_streams)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700472 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800473 raise error.TestFail('Unable to find phy for frequency %d' %
474 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700475
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800476 # If we have a suitable unused interface sitting around on this
477 # phy, reuse it.
478 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
479 if net_dev.phy == phy and net_dev.if_type == phytype:
mukesh agrawal24a70172015-06-22 10:53:04 -0700480 break
481 else:
482 # Because we can reuse interfaces, we have to iteratively find a
483 # good interface name.
484 name_exists = lambda name: bool([net_dev
485 for net_dev in self._interfaces
486 if net_dev.if_name == name])
487 if_name = lambda index: '%s%d' % (phytype, index)
488 if_index = len(self._interfaces)
489 while name_exists(if_name(if_index)):
490 if_index += 1
491 net_dev = NetDev(phy=phy, if_name=if_name(if_index),
492 if_type=phytype, inherited=False)
493 self._interfaces.append(net_dev)
494 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700495
mukesh agrawal24a70172015-06-22 10:53:04 -0700496 # Link must be down to reconfigure MAC address.
497 self.host.run('%s link set dev %s down' % (
498 self.cmd_ip, net_dev.if_name))
499 if same_phy_as:
500 self.clone_mac_address(src_dev=same_phy_as,
501 dst_dev=net_dev.if_name)
502 else:
503 self.ensure_unique_mac(net_dev)
504
mukesh agrawala3423082015-07-20 12:40:18 -0700505 return net_dev
506
507
Matthew Wang08e868d2018-04-19 12:04:54 -0700508 def get_brif(self):
509 brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
510 self._brif_index)
511 self._brif_index += 1
512 return brif_name
513
514
mukesh agrawala3423082015-07-20 12:40:18 -0700515 def get_configured_interface(self, phytype, spatial_streams=None,
516 frequency=None, same_phy_as=None):
517 """Get a WiFi device that supports the given frequency and phytype.
518
519 The device's link state will be UP, and (where possible) the device
520 will be configured to operate on |frequency|.
521
522 @param phytype string type of phy (e.g. 'monitor').
523 @param spatial_streams int number of spatial streams required.
524 @param frequency int WiFi frequency to support.
525 @param same_phy_as string create the interface on the same phy as this.
526 @return string WiFi device.
527
528 """
529 net_dev = self._get_wlanif(
530 phytype, spatial_streams, frequency, same_phy_as)
531
532 self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name))
533
534 if frequency:
535 if phytype == 'managed':
536 logging.debug('Skipped setting frequency for DEV %s '
537 'since managed mode DEVs roam across APs.',
538 net_dev.if_name)
539 elif same_phy_as or self._phy_in_use(net_dev.phy):
540 logging.debug('Skipped setting frequency for DEV %s '
541 'since PHY %s is already in use',
542 net_dev.if_name, net_dev.phy)
543 else:
544 self.iw_runner.set_freq(net_dev.if_name, frequency)
545
546 self._wlanifs_in_use.append(net_dev)
547 return net_dev.if_name
548
549
550 # TODO(quiche): Deprecate this, in favor of get_configured_interface().
551 # crbug.com/512169.
552 def get_wlanif(self, frequency, phytype,
553 spatial_streams=None, same_phy_as=None):
554 """Get a WiFi device that supports the given frequency and phytype.
555
556 We simply find or create a suitable DEV. It is left to the
557 caller to actually configure the frequency and bring up the
558 interface.
559
560 @param frequency int WiFi frequency to support.
561 @param phytype string type of phy (e.g. 'monitor').
562 @param spatial_streams int number of spatial streams required.
563 @param same_phy_as string create the interface on the same phy as this.
564 @return string WiFi device.
565
566 """
567 net_dev = self._get_wlanif(
568 phytype, spatial_streams, frequency, same_phy_as)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800569 self._wlanifs_in_use.append(net_dev)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800570 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800571
572
mukesh agrawal24a70172015-06-22 10:53:04 -0700573 def ensure_unique_mac(self, net_dev):
574 """Ensure MAC address of |net_dev| meets uniqueness requirements.
575
576 The Linux kernel does not allow multiple APs with the same
577 BSSID on the same PHY (at least, with some drivers). Hence, we
578 want to ensure that the DEVs for a PHY have unique MAC
579 addresses.
580
581 Note that we do not attempt to make the MACs unique across
582 PHYs, because some tests deliberately create such scenarios.
583
584 @param net_dev NetDev to uniquify.
585
586 """
587 if net_dev.if_type == 'monitor':
588 return
589
590 our_ifname = net_dev.if_name
591 our_phy = net_dev.phy
592 our_mac = interface.Interface(our_ifname, self.host).mac_address
593 sibling_devs = [dev for dev in self._interfaces
594 if (dev.phy == our_phy and
595 dev.if_name != our_ifname and
596 dev.if_type != 'monitor')]
597 sibling_macs = (
598 interface.Interface(sib_dev.if_name, self.host).mac_address
599 for sib_dev in sibling_devs)
600 if our_mac in sibling_macs:
601 self.configure_interface_mac(our_ifname,
602 self._get_unique_mac())
603
604
605 def configure_interface_mac(self, wlanif, new_mac):
606 """Change the MAC address for an interface.
607
608 @param wlanif string name of device to reconfigure.
609 @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55')
610
611 """
612 self.host.run('%s link set %s address %s' %
613 (self.cmd_ip, wlanif, new_mac))
614
615
616 def clone_mac_address(self, src_dev=None, dst_dev=None):
617 """Copy the MAC address from one interface to another.
618
619 @param src_dev string name of device to copy address from.
620 @param dst_dev string name of device to copy address to.
621
622 """
623 self.configure_interface_mac(
624 dst_dev,
625 interface.Interface(src_dev, self.host).mac_address)
626
627
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800628 def release_interface(self, wlanif):
629 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700630
631 @param wlanif string name of device to release.
632
633 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800634 for net_dev in self._wlanifs_in_use:
635 if net_dev.if_name == wlanif:
636 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700637
638
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800639 def get_bridge_interface(self):
640 """Return the bridge interface, create one if it is not created yet.
641
642 @return string name of bridge interface.
643 """
644 if self._bridge_interface is None:
645 self._create_bridge_interface()
646 return self._bridge_interface
647
648
649 def remove_bridge_interface(self):
650 """Remove the bridge interface that's been created."""
651 if self._bridge_interface is not None:
652 self.host.run('%s link delete %s type bridge' %
653 (self.cmd_ip, self._bridge_interface))
654 self._bridge_interface = None
655
656
657 def add_interface_to_bridge(self, interface):
658 """Add an interface to the bridge interface.
659
660 This will create the bridge interface if it is not created yet.
661
662 @param interface string name of the interface to add to the bridge.
663 """
664 if self._bridge_interface is None:
665 self._create_bridge_interface()
666 self.host.run('%s link set dev %s master %s' %
667 (self.cmd_ip, interface, self._bridge_interface))
668
669
670 def get_virtual_ethernet_master_interface(self):
671 """Return the master interface of the virtual ethernet pair.
672
673 @return string name of the master interface of the virtual ethernet
674 pair.
675 """
676 if self._virtual_ethernet_pair is None:
677 self._create_virtual_ethernet_pair()
678 return self._virtual_ethernet_pair.interface_name
679
680
681 def get_virtual_ethernet_peer_interface(self):
682 """Return the peer interface of the virtual ethernet pair.
683
684 @return string name of the peer interface of the virtual ethernet pair.
685 """
686 if self._virtual_ethernet_pair is None:
687 self._create_virtual_ethernet_pair()
688 return self._virtual_ethernet_pair.peer_interface_name
689
690
691 def remove_ethernet_pair_interface(self):
692 """Remove the virtual ethernet pair that's been created."""
693 if self._virtual_ethernet_pair is not None:
694 self._virtual_ethernet_pair.teardown()
695 self._virtual_ethernet_pair = None
696
697
Brian Norrisc6801862017-10-06 12:55:37 -0700698 def require_capabilities(self, requirements):
Christopher Wiley9b406202013-05-06 14:07:49 -0700699 """Require capabilities of this LinuxSystem.
700
701 Check that capabilities in |requirements| exist on this system.
Brian Norrisc6801862017-10-06 12:55:37 -0700702 Raise an exception to skip but not fail the test if said
703 capabilities are not found.
Christopher Wiley9b406202013-05-06 14:07:49 -0700704
705 @param requirements list of CAPABILITY_* defined above.
Christopher Wiley9b406202013-05-06 14:07:49 -0700706
707 """
Christopher Wiley9b406202013-05-06 14:07:49 -0700708 missing = [cap for cap in requirements if not cap in self.capabilities]
709 if missing:
Kirtika Ruchandani753498b2018-05-17 14:50:24 -0700710 raise error.TestNAError('%s is missing required capabilites: %r'
711 % (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800712
713
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800714 def disable_antennas_except(self, permitted_antennas):
715 """Disable unwanted antennas.
Peter Qiu2f973252014-02-20 15:30:37 -0800716
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800717 Disable all antennas except those specified in |permitted_antennas|.
718 Note that one or more of them may remain disabled if the underlying
719 hardware does not support them.
720
721 @param permitted_antennas int bitmask specifying antennas that we should
722 attempt to enable.
Peter Qiu2f973252014-02-20 15:30:37 -0800723
724 """
725 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700726 if not phy.supports_setting_antenna_mask:
727 continue
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800728 # Determine valid bitmap values based on available antennas.
729 self.iw_runner.set_antenna_bitmap(phy.name,
730 permitted_antennas & phy.avail_tx_antennas,
731 permitted_antennas & phy.avail_rx_antennas)
Peter Qiu2f973252014-02-20 15:30:37 -0800732
733
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800734 def enable_all_antennas(self):
735 """Enable all antennas on all phys."""
Peter Qiu2f973252014-02-20 15:30:37 -0800736 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700737 if not phy.supports_setting_antenna_mask:
738 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800739 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
740 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700741
742
743 def ping(self, ping_config):
744 """Ping an IP from this system.
745
746 @param ping_config PingConfig object describing the ping command to run.
747 @return a PingResult object.
748
749 """
750 logging.info('Pinging from the %s.', self.role)
751 return self._ping_runner.ping(ping_config)