blob: 07720b169c1ae1730c76d9c92a6f219123e28657 [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
51
52 @property
53 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070054 """@return iterable object of AP capabilities for this system."""
55 if self._capabilities is None:
56 self._capabilities = self.get_capabilities()
57 logging.info('%s system capabilities: %r',
58 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070059 return self._capabilities
60
61
Peter Qiucb27e012015-03-24 14:34:31 -070062 @property
63 def board(self):
64 """@return string self reported board of this device."""
65 if self._board is None:
66 # Remove 'board:' prefix.
67 self._board = self.host.get_board().split(':')[1]
68 return self._board
69
70
Christopher Wiley408d1812014-01-13 15:27:43 -080071 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070072 self.host = host
73 self.role = role
mukesh agrawal7fd4d092015-06-29 13:50:54 -070074 self.inherit_interfaces = inherit_interfaces
75 self.__setup()
76
77
78 def __setup(self):
79 """Set up this system.
80
81 Can be used either to complete initialization of a LinuxSystem object,
82 or to re-establish a good state after a reboot.
83
84 """
85 # Command locations.
86 cmd_iw = path_utils.must_be_installed('/usr/sbin/iw', host=self.host)
87 self.cmd_ip = path_utils.must_be_installed('/usr/sbin/ip',
88 host=self.host)
89 self.cmd_readlink = '%s -l' % path_utils.must_be_installed(
90 '/bin/ls', host=self.host)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070091
Christopher Wiley09ad2062013-09-13 13:34:49 -070092 self._packet_capturer = packet_capturer.get_packet_capturer(
mukesh agrawal7fd4d092015-06-29 13:50:54 -070093 self.host, host_description=self.role, cmd_ip=self.cmd_ip,
Christopher Wiley408d1812014-01-13 15:27:43 -080094 cmd_iw=cmd_iw, ignore_failures=True)
mukesh agrawal7fd4d092015-06-29 13:50:54 -070095 self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
96 command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070097
Paul Stewart27ecc5d2013-11-13 16:56:41 -080098 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -080099 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Roshan Pius9350a8d2016-09-08 16:29:01 -0700100 logging.debug('Current regulatory domain %r',
101 self.iw_runner.get_regulatory_domain())
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800102 self._interfaces = []
Matthew Wang08e868d2018-04-19 12:04:54 -0700103 self._brif_index = 0
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800104 for interface in self.iw_runner.list_interfaces():
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700105 if self.inherit_interfaces:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800106 self._interfaces.append(NetDev(inherited=True,
107 if_name=interface.if_name,
108 if_type=interface.if_type,
109 phy=interface.phy))
110 else:
111 self.iw_runner.remove_interface(interface.if_name)
112
113 self._wlanifs_in_use = []
mukesh agrawal24a70172015-06-22 10:53:04 -0700114 self._local_macs_in_use = set()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800115 self._capture_interface = None
Peter Qiucb27e012015-03-24 14:34:31 -0700116 self._board = None
Christopher Wiley80e40922013-10-22 13:14:56 -0700117 # Some uses of LinuxSystem don't use the interface allocation facility.
118 # Don't force us to remove all the existing interfaces if this facility
119 # is not desired.
120 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -0700121 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700122 self._ping_runner = ping_runner.PingRunner(host=self.host)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800123 self._bridge_interface = None
124 self._virtual_ethernet_pair = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700125
Paul Stewart6423bb02012-11-27 17:46:23 -0800126
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800127 @property
128 def phy_list(self):
129 """@return iterable object of PHY descriptions for this system."""
130 if self._phy_list is None:
131 self._phy_list = self.iw_runner.list_phys()
132 return self._phy_list
133
134
mukesh agrawal3e458912015-06-12 10:40:16 -0700135 def _phy_by_name(self, phy_name):
136 """@return IwPhy for PHY with name |phy_name|, or None."""
137 for phy in self._phy_list:
138 if phy.name == phy_name:
139 return phy
140 else:
141 return None
142
143
Paul Stewart6423bb02012-11-27 17:46:23 -0800144 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700145 """Get information about WiFi devices.
146
147 Parse the output of 'iw list' and some of sysfs and return:
148
149 A dict |phys_for_frequency| which maps from each frequency to a
150 list of phys that support that channel.
151
152 A dict |phy_bus_type| which maps from each phy to the bus type for
153 each phy.
154
155 @return phys_for_frequency, phy_bus_type tuple as described.
156
Paul Stewart6423bb02012-11-27 17:46:23 -0800157 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800158 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800159 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700160 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800161 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700162 phy_list.append(phy.name)
163 for band in phy.bands:
164 for mhz in band.frequencies:
165 if mhz not in phys_for_frequency:
166 phys_for_frequency[mhz] = [phy.name]
167 else:
168 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800169
170 phy_bus_type = {}
171 for phy in phy_list:
172 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700173 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800174 devpath = self.host.run(command).stdout
175 if '/usb' in devpath:
176 phybus = 'usb'
177 elif '/mmc' in devpath:
178 phybus = 'sdio'
179 elif '/pci' in devpath:
180 phybus = 'pci'
181 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700182 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800183 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700184
Paul Stewart64cc4292011-06-01 10:59:36 -0700185
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800186 def _create_bridge_interface(self):
187 """Create a bridge interface."""
188 self.host.run('%s link add name %s type bridge' %
189 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
190 self.host.run('%s link set dev %s up' %
191 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
192 self._bridge_interface = self.BRIDGE_INTERFACE_NAME
193
194
195 def _create_virtual_ethernet_pair(self):
196 """Create a virtual ethernet pair."""
197 self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
198 interface_ip=None, peer_interface_ip=None, host=self.host)
199 self._virtual_ethernet_pair.setup()
200
201
mukesh agrawal24a70172015-06-22 10:53:04 -0700202 def _get_unique_mac(self):
203 """Get a MAC address that is likely to be unique.
204
205 Generates a MAC address that is a) guaranteed not to be in use
206 on this host, and b) likely to be unique within the test cell.
207
208 @return string MAC address.
209
210 """
211 # We use SystemRandom to reduce the likelyhood of coupling
212 # across systems. (The default random class might, e.g., seed
213 # itself based on wall-clock time.)
214 sysrand = random.SystemRandom()
215 for tries in xrange(0, self.MAC_RETRY_LIMIT):
216 mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % (
217 (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) |
218 self.MAC_BIT_LOCAL,
219 sysrand.getrandbits(8),
220 sysrand.getrandbits(8),
221 sysrand.getrandbits(8),
222 sysrand.getrandbits(8),
223 sysrand.getrandbits(8))
224 if mac_addr not in self._local_macs_in_use:
225 self._local_macs_in_use.add(mac_addr)
226 return mac_addr
227 else:
228 raise error.TestError('Failed to find a new MAC address')
229
230
mukesh agrawala3423082015-07-20 12:40:18 -0700231 def _phy_in_use(self, phy_name):
232 """Determine whether or not a PHY is used by an active DEV
233
234 @return bool True iff PHY is in use.
235 """
236 for net_dev in self._wlanifs_in_use:
237 if net_dev.phy == phy_name:
238 return True
239 return False
240
241
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800242 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700243 """Remove an interface from a WiFi device.
244
245 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700246
247 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800248 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700249 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700250 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800251 for net_dev in self._interfaces:
252 if net_dev.if_name == interface:
253 self._interfaces.remove(net_dev)
254 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700255
Christopher Wiley061f1382013-06-17 18:17:58 -0700256
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700257 def close(self):
258 """Close global resources held by this system."""
259 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700260 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800261 # Release and remove any interfaces that we create.
262 for net_dev in self._wlanifs_in_use:
263 self.release_interface(net_dev.if_name)
264 for net_dev in self._interfaces:
265 if net_dev.inherited:
266 continue
267 self.remove_interface(net_dev.if_name)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800268 if self._bridge_interface is not None:
269 self.remove_bridge_interface()
270 if self._virtual_ethernet_pair is not None:
271 self.remove_ethernet_pair_interface()
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700272 self.host.close()
273 self.host = None
274
275
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700276 def reboot(self, timeout):
277 """Reboot this system, and restore it to a known-good state.
278
279 @param timeout Maximum seconds to wait for system to return.
280
281 """
282 self.host.reboot(timeout=timeout, wait=True)
283 self.__setup()
284
285
Christopher Wiley061f1382013-06-17 18:17:58 -0700286 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700287 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700288 phymap = self.phys_for_frequency
289 if [freq for freq in phymap.iterkeys() if freq > 5000]:
290 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700291 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700292 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700293 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
294 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700295 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700296 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800297 for phy in self.phy_list:
Paul Stewartf94151e2015-04-20 20:32:39 -0700298 if ('tdls_mgmt' in phy.commands or
299 'tdls_oper' in phy.commands or
300 'T-DLS' in phy.features):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800301 caps.add(self.CAPABILITY_TDLS)
Matthew Wang8531f622018-05-03 13:50:16 -0700302 if 'authenticate' in phy.commands:
303 caps.add(self.CAPABILITY_SME)
Peter Qiu3ef31f02014-10-17 14:25:57 -0700304 if phy.support_vht:
305 caps.add(self.CAPABILITY_VHT)
Matthew Wang4d34d422018-12-13 11:16:18 -0800306 if 'roaming' not in phy.features:
307 caps.add(self.CAPABILITY_SUPPLICANT_ROAMING)
mukesh agrawal064ea972015-06-26 10:00:53 -0700308 if any([iw_runner.DEV_MODE_IBSS in phy.modes
309 for phy in self.phy_list]):
310 caps.add(self.CAPABILITY_IBSS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700311 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700312
313
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700314 def start_capture(self, frequency,
315 ht_type=None, snaplen=None, filename=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700316 """Start a packet capture.
317
318 @param frequency int frequency of channel to capture on.
319 @param ht_type string one of (None, 'HT20', 'HT40+', 'HT40-').
Christopher Wiley618e52b2013-10-14 16:21:07 -0700320 @param snaplen int number of bytes to retain per capture frame.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700321 @param filename string filename to write capture to.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700322
323 """
324 if self._packet_capturer.capture_running:
325 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800326 self._capture_interface = self.get_wlanif(frequency, 'monitor')
327 full_interface = [net_dev for net_dev in self._interfaces
328 if net_dev.if_name == self._capture_interface][0]
329 # If this is the only interface on this phy, we ought to configure
330 # the phy with a channel and ht_type. Otherwise, inherit the settings
331 # of the phy as they stand.
332 if len([net_dev for net_dev in self._interfaces
333 if net_dev.phy == full_interface.phy]) == 1:
334 self._packet_capturer.configure_raw_monitor(
335 self._capture_interface, frequency, ht_type=ht_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800336 else:
337 self.host.run('%s link set %s up' %
338 (self.cmd_ip, self._capture_interface))
339
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700340 # Start the capture.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700341 if filename:
342 remote_path = os.path.join('/tmp', os.path.basename(filename))
343 else:
344 remote_path = None
345 self._packet_capturer.start_capture(
346 self._capture_interface, './debug/', snaplen=snaplen,
347 remote_file=remote_path)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700348
349
Christopher Wiley062a9812013-10-03 15:55:50 -0700350 def stop_capture(self, save_dir=None, save_filename=None):
351 """Stop a packet capture.
352
353 @param save_dir string path to directory to save pcap files in.
354 @param save_filename string basename of file to save pcap in locally.
355
356 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700357 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700358 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700359 results = self._packet_capturer.stop_capture(
360 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800361 self.release_interface(self._capture_interface)
362 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700363 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800364
365
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700366 def sync_host_times(self):
367 """Set time on our DUT to match local time."""
368 epoch_seconds = time.time()
369 busybox_format = '%Y%m%d%H%M.%S'
370 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
371 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
372 (epoch_seconds, busybox_date))
373
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800374
mukesh agrawal43644aa2015-07-06 14:40:22 -0700375 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700376 """Get a phy appropriate for a frequency and phytype.
377
378 Return the most appropriate phy interface for operating on the
379 frequency |frequency| in the role indicated by |phytype|. Prefer idle
380 phys to busy phys if any exist. Secondarily, show affinity for phys
381 that use the bus type associated with this phy type.
382
383 @param frequency int WiFi frequency of phy.
384 @param phytype string key of phytype registered at construction time.
mukesh agrawal43644aa2015-07-06 14:40:22 -0700385 @param spatial_streams int number of spatial streams required.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700386 @return string name of phy to use.
387
Paul Stewart6423bb02012-11-27 17:46:23 -0800388 """
mukesh agrawal43644aa2015-07-06 14:40:22 -0700389 phy_objs = []
390 for phy_name in self.phys_for_frequency[frequency]:
391 phy_obj = self._phy_by_name(phy_name)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700392 num_antennas = min(phy_obj.avail_rx_antennas,
393 phy_obj.avail_tx_antennas)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700394 if num_antennas >= spatial_streams:
395 phy_objs.append(phy_obj)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700396 elif num_antennas == 0:
397 logging.warning(
mukesh agrawal43644aa2015-07-06 14:40:22 -0700398 'Allowing use of %s, which reports zero antennas', phy_name)
399 phy_objs.append(phy_obj)
mukesh agrawal3e458912015-06-12 10:40:16 -0700400 else:
401 logging.debug(
mukesh agrawalbc963c12015-06-19 11:40:03 -0700402 'Filtering out %s, which reports only %d antennas',
mukesh agrawal43644aa2015-07-06 14:40:22 -0700403 phy_name, num_antennas)
Paul Stewart6423bb02012-11-27 17:46:23 -0800404
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800405 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700406 idle_phy_objs = [phy_obj for phy_obj in phy_objs
407 if phy_obj.name not in busy_phys]
408 phy_objs = idle_phy_objs or phy_objs
409 phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas,
410 phy_obj.avail_tx_antennas),
411 reverse=True)
412 phys = [phy_obj.name for phy_obj in phy_objs]
Paul Stewart6423bb02012-11-27 17:46:23 -0800413
Christopher Wiley408d1812014-01-13 15:27:43 -0800414 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800415 preferred_phys = [phy for phy in phys
416 if self.phy_bus_type[phy] == preferred_bus]
417 phys = preferred_phys or phys
418
419 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700420
421
mukesh agrawala3423082015-07-20 12:40:18 -0700422 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
423 """Get a WiFi device that supports the given frequency and phytype.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700424
mukesh agrawala3423082015-07-20 12:40:18 -0700425 We simply find or create a suitable DEV. It is left to the
426 caller to actually configure the frequency and bring up the
427 interface.
428
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700429 @param phytype string type of phy (e.g. 'monitor').
mukesh agrawal43644aa2015-07-06 14:40:22 -0700430 @param spatial_streams int number of spatial streams required.
mukesh agrawala3423082015-07-20 12:40:18 -0700431 @param frequency int WiFi frequency to support.
Paul Stewart51b0f382013-06-12 09:03:02 -0700432 @param same_phy_as string create the interface on the same phy as this.
mukesh agrawala3423082015-07-20 12:40:18 -0700433 @return NetDev WiFi device.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700434
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700435 """
mukesh agrawala3423082015-07-20 12:40:18 -0700436 if frequency and same_phy_as:
437 raise error.TestError(
438 'Can not combine |frequency| and |same_phy_as|')
439
440 if not (frequency or same_phy_as):
441 raise error.TestError(
442 'Must specify one of |frequency| or |same_phy_as|')
443
mukesh agrawal43644aa2015-07-06 14:40:22 -0700444 if spatial_streams is None:
445 spatial_streams = self.MIN_SPATIAL_STREAMS
Brian Norris369799e2018-08-03 18:09:43 -0700446 # We don't want to use the 3rd radio on Whirlwind. Reject it if someone
447 # tries to add a test that uses it.
448 elif spatial_streams < self.MIN_SPATIAL_STREAMS and \
449 self.board == 'whirlwind':
450 raise error.TestError('Requested spatial streams: %d; minimum %d' \
451 % (spatial_streams, self.MIN_SPATIAL_STREAMS))
mukesh agrawal43644aa2015-07-06 14:40:22 -0700452
Paul Stewart51b0f382013-06-12 09:03:02 -0700453 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800454 for net_dev in self._interfaces:
455 if net_dev.if_name == same_phy_as:
456 phy = net_dev.phy
457 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700458 else:
459 raise error.TestFail('Unable to find phy for interface %s' %
460 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800461 elif frequency in self.phys_for_frequency:
mukesh agrawal43644aa2015-07-06 14:40:22 -0700462 phy = self._get_phy_for_frequency(
463 frequency, phytype, spatial_streams)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700464 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800465 raise error.TestFail('Unable to find phy for frequency %d' %
466 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700467
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800468 # If we have a suitable unused interface sitting around on this
469 # phy, reuse it.
470 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
471 if net_dev.phy == phy and net_dev.if_type == phytype:
mukesh agrawal24a70172015-06-22 10:53:04 -0700472 break
473 else:
474 # Because we can reuse interfaces, we have to iteratively find a
475 # good interface name.
476 name_exists = lambda name: bool([net_dev
477 for net_dev in self._interfaces
478 if net_dev.if_name == name])
479 if_name = lambda index: '%s%d' % (phytype, index)
480 if_index = len(self._interfaces)
481 while name_exists(if_name(if_index)):
482 if_index += 1
483 net_dev = NetDev(phy=phy, if_name=if_name(if_index),
484 if_type=phytype, inherited=False)
485 self._interfaces.append(net_dev)
486 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700487
mukesh agrawal24a70172015-06-22 10:53:04 -0700488 # Link must be down to reconfigure MAC address.
489 self.host.run('%s link set dev %s down' % (
490 self.cmd_ip, net_dev.if_name))
491 if same_phy_as:
492 self.clone_mac_address(src_dev=same_phy_as,
493 dst_dev=net_dev.if_name)
494 else:
495 self.ensure_unique_mac(net_dev)
496
mukesh agrawala3423082015-07-20 12:40:18 -0700497 return net_dev
498
499
Matthew Wang08e868d2018-04-19 12:04:54 -0700500 def get_brif(self):
501 brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX,
502 self._brif_index)
503 self._brif_index += 1
504 return brif_name
505
506
mukesh agrawala3423082015-07-20 12:40:18 -0700507 def get_configured_interface(self, phytype, spatial_streams=None,
508 frequency=None, same_phy_as=None):
509 """Get a WiFi device that supports the given frequency and phytype.
510
511 The device's link state will be UP, and (where possible) the device
512 will be configured to operate on |frequency|.
513
514 @param phytype string type of phy (e.g. 'monitor').
515 @param spatial_streams int number of spatial streams required.
516 @param frequency int WiFi frequency to support.
517 @param same_phy_as string create the interface on the same phy as this.
518 @return string WiFi device.
519
520 """
521 net_dev = self._get_wlanif(
522 phytype, spatial_streams, frequency, same_phy_as)
523
524 self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name))
525
526 if frequency:
527 if phytype == 'managed':
528 logging.debug('Skipped setting frequency for DEV %s '
529 'since managed mode DEVs roam across APs.',
530 net_dev.if_name)
531 elif same_phy_as or self._phy_in_use(net_dev.phy):
532 logging.debug('Skipped setting frequency for DEV %s '
533 'since PHY %s is already in use',
534 net_dev.if_name, net_dev.phy)
535 else:
536 self.iw_runner.set_freq(net_dev.if_name, frequency)
537
538 self._wlanifs_in_use.append(net_dev)
539 return net_dev.if_name
540
541
542 # TODO(quiche): Deprecate this, in favor of get_configured_interface().
543 # crbug.com/512169.
544 def get_wlanif(self, frequency, phytype,
545 spatial_streams=None, same_phy_as=None):
546 """Get a WiFi device that supports the given frequency and phytype.
547
548 We simply find or create a suitable DEV. It is left to the
549 caller to actually configure the frequency and bring up the
550 interface.
551
552 @param frequency int WiFi frequency to support.
553 @param phytype string type of phy (e.g. 'monitor').
554 @param spatial_streams int number of spatial streams required.
555 @param same_phy_as string create the interface on the same phy as this.
556 @return string WiFi device.
557
558 """
559 net_dev = self._get_wlanif(
560 phytype, spatial_streams, frequency, same_phy_as)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800561 self._wlanifs_in_use.append(net_dev)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800562 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800563
564
mukesh agrawal24a70172015-06-22 10:53:04 -0700565 def ensure_unique_mac(self, net_dev):
566 """Ensure MAC address of |net_dev| meets uniqueness requirements.
567
568 The Linux kernel does not allow multiple APs with the same
569 BSSID on the same PHY (at least, with some drivers). Hence, we
570 want to ensure that the DEVs for a PHY have unique MAC
571 addresses.
572
573 Note that we do not attempt to make the MACs unique across
574 PHYs, because some tests deliberately create such scenarios.
575
576 @param net_dev NetDev to uniquify.
577
578 """
579 if net_dev.if_type == 'monitor':
580 return
581
582 our_ifname = net_dev.if_name
583 our_phy = net_dev.phy
584 our_mac = interface.Interface(our_ifname, self.host).mac_address
585 sibling_devs = [dev for dev in self._interfaces
586 if (dev.phy == our_phy and
587 dev.if_name != our_ifname and
588 dev.if_type != 'monitor')]
589 sibling_macs = (
590 interface.Interface(sib_dev.if_name, self.host).mac_address
591 for sib_dev in sibling_devs)
592 if our_mac in sibling_macs:
593 self.configure_interface_mac(our_ifname,
594 self._get_unique_mac())
595
596
597 def configure_interface_mac(self, wlanif, new_mac):
598 """Change the MAC address for an interface.
599
600 @param wlanif string name of device to reconfigure.
601 @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55')
602
603 """
604 self.host.run('%s link set %s address %s' %
605 (self.cmd_ip, wlanif, new_mac))
606
607
608 def clone_mac_address(self, src_dev=None, dst_dev=None):
609 """Copy the MAC address from one interface to another.
610
611 @param src_dev string name of device to copy address from.
612 @param dst_dev string name of device to copy address to.
613
614 """
615 self.configure_interface_mac(
616 dst_dev,
617 interface.Interface(src_dev, self.host).mac_address)
618
619
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800620 def release_interface(self, wlanif):
621 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700622
623 @param wlanif string name of device to release.
624
625 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800626 for net_dev in self._wlanifs_in_use:
627 if net_dev.if_name == wlanif:
628 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700629
630
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800631 def get_bridge_interface(self):
632 """Return the bridge interface, create one if it is not created yet.
633
634 @return string name of bridge interface.
635 """
636 if self._bridge_interface is None:
637 self._create_bridge_interface()
638 return self._bridge_interface
639
640
641 def remove_bridge_interface(self):
642 """Remove the bridge interface that's been created."""
643 if self._bridge_interface is not None:
644 self.host.run('%s link delete %s type bridge' %
645 (self.cmd_ip, self._bridge_interface))
646 self._bridge_interface = None
647
648
649 def add_interface_to_bridge(self, interface):
650 """Add an interface to the bridge interface.
651
652 This will create the bridge interface if it is not created yet.
653
654 @param interface string name of the interface to add to the bridge.
655 """
656 if self._bridge_interface is None:
657 self._create_bridge_interface()
658 self.host.run('%s link set dev %s master %s' %
659 (self.cmd_ip, interface, self._bridge_interface))
660
661
662 def get_virtual_ethernet_master_interface(self):
663 """Return the master interface of the virtual ethernet pair.
664
665 @return string name of the master interface of the virtual ethernet
666 pair.
667 """
668 if self._virtual_ethernet_pair is None:
669 self._create_virtual_ethernet_pair()
670 return self._virtual_ethernet_pair.interface_name
671
672
673 def get_virtual_ethernet_peer_interface(self):
674 """Return the peer interface of the virtual ethernet pair.
675
676 @return string name of the peer interface of the virtual ethernet pair.
677 """
678 if self._virtual_ethernet_pair is None:
679 self._create_virtual_ethernet_pair()
680 return self._virtual_ethernet_pair.peer_interface_name
681
682
683 def remove_ethernet_pair_interface(self):
684 """Remove the virtual ethernet pair that's been created."""
685 if self._virtual_ethernet_pair is not None:
686 self._virtual_ethernet_pair.teardown()
687 self._virtual_ethernet_pair = None
688
689
Brian Norrisc6801862017-10-06 12:55:37 -0700690 def require_capabilities(self, requirements):
Christopher Wiley9b406202013-05-06 14:07:49 -0700691 """Require capabilities of this LinuxSystem.
692
693 Check that capabilities in |requirements| exist on this system.
Brian Norrisc6801862017-10-06 12:55:37 -0700694 Raise an exception to skip but not fail the test if said
695 capabilities are not found.
Christopher Wiley9b406202013-05-06 14:07:49 -0700696
697 @param requirements list of CAPABILITY_* defined above.
Christopher Wiley9b406202013-05-06 14:07:49 -0700698
699 """
Christopher Wiley9b406202013-05-06 14:07:49 -0700700 missing = [cap for cap in requirements if not cap in self.capabilities]
701 if missing:
Kirtika Ruchandani753498b2018-05-17 14:50:24 -0700702 raise error.TestNAError('%s is missing required capabilites: %r'
703 % (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800704
705
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800706 def disable_antennas_except(self, permitted_antennas):
707 """Disable unwanted antennas.
Peter Qiu2f973252014-02-20 15:30:37 -0800708
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800709 Disable all antennas except those specified in |permitted_antennas|.
710 Note that one or more of them may remain disabled if the underlying
711 hardware does not support them.
712
713 @param permitted_antennas int bitmask specifying antennas that we should
714 attempt to enable.
Peter Qiu2f973252014-02-20 15:30:37 -0800715
716 """
717 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700718 if not phy.supports_setting_antenna_mask:
719 continue
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800720 # Determine valid bitmap values based on available antennas.
721 self.iw_runner.set_antenna_bitmap(phy.name,
722 permitted_antennas & phy.avail_tx_antennas,
723 permitted_antennas & phy.avail_rx_antennas)
Peter Qiu2f973252014-02-20 15:30:37 -0800724
725
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800726 def enable_all_antennas(self):
727 """Enable all antennas on all phys."""
Peter Qiu2f973252014-02-20 15:30:37 -0800728 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700729 if not phy.supports_setting_antenna_mask:
730 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800731 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
732 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700733
734
735 def ping(self, ping_config):
736 """Ping an IP from this system.
737
738 @param ping_config PingConfig object describing the ping command to run.
739 @return a PingResult object.
740
741 """
742 logging.info('Pinging from the %s.', self.role)
743 return self._ping_runner.ping(ping_config)