blob: cdb9f1215fd6e0f3e3e6d7598630a39a474793a4 [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'
Peter Qiu9a63a8b2015-02-03 09:08:16 -080041 BRIDGE_INTERFACE_NAME = 'br0'
mukesh agrawal3e458912015-06-12 10:40:16 -070042 MIN_SPATIAL_STREAMS = 2
mukesh agrawal24a70172015-06-22 10:53:04 -070043 MAC_BIT_LOCAL = 0x2 # Locally administered.
44 MAC_BIT_MULTICAST = 0x1
45 MAC_RETRY_LIMIT = 1000
Christopher Wileyf19c28c2013-05-06 15:42:57 -070046
47
48 @property
49 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070050 """@return iterable object of AP capabilities for this system."""
51 if self._capabilities is None:
52 self._capabilities = self.get_capabilities()
53 logging.info('%s system capabilities: %r',
54 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070055 return self._capabilities
56
57
Peter Qiucb27e012015-03-24 14:34:31 -070058 @property
59 def board(self):
60 """@return string self reported board of this device."""
61 if self._board is None:
62 # Remove 'board:' prefix.
63 self._board = self.host.get_board().split(':')[1]
64 return self._board
65
66
Christopher Wiley408d1812014-01-13 15:27:43 -080067 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070068 self.host = host
69 self.role = role
mukesh agrawal7fd4d092015-06-29 13:50:54 -070070 self.inherit_interfaces = inherit_interfaces
71 self.__setup()
72
73
74 def __setup(self):
75 """Set up this system.
76
77 Can be used either to complete initialization of a LinuxSystem object,
78 or to re-establish a good state after a reboot.
79
80 """
81 # Command locations.
82 cmd_iw = path_utils.must_be_installed('/usr/sbin/iw', host=self.host)
83 self.cmd_ip = path_utils.must_be_installed('/usr/sbin/ip',
84 host=self.host)
85 self.cmd_readlink = '%s -l' % path_utils.must_be_installed(
86 '/bin/ls', host=self.host)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070087
Christopher Wiley09ad2062013-09-13 13:34:49 -070088 self._packet_capturer = packet_capturer.get_packet_capturer(
mukesh agrawal7fd4d092015-06-29 13:50:54 -070089 self.host, host_description=self.role, cmd_ip=self.cmd_ip,
Christopher Wiley408d1812014-01-13 15:27:43 -080090 cmd_iw=cmd_iw, ignore_failures=True)
mukesh agrawal7fd4d092015-06-29 13:50:54 -070091 self.iw_runner = iw_runner.IwRunner(remote_host=self.host,
92 command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070093
Paul Stewart27ecc5d2013-11-13 16:56:41 -080094 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -080095 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Christopher Wileyf671a5a2013-12-13 15:44:41 -080096 self._interfaces = []
97 for interface in self.iw_runner.list_interfaces():
mukesh agrawal7fd4d092015-06-29 13:50:54 -070098 if self.inherit_interfaces:
Christopher Wileyf671a5a2013-12-13 15:44:41 -080099 self._interfaces.append(NetDev(inherited=True,
100 if_name=interface.if_name,
101 if_type=interface.if_type,
102 phy=interface.phy))
103 else:
104 self.iw_runner.remove_interface(interface.if_name)
105
106 self._wlanifs_in_use = []
mukesh agrawal24a70172015-06-22 10:53:04 -0700107 self._local_macs_in_use = set()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800108 self._capture_interface = None
Peter Qiucb27e012015-03-24 14:34:31 -0700109 self._board = None
Christopher Wiley80e40922013-10-22 13:14:56 -0700110 # Some uses of LinuxSystem don't use the interface allocation facility.
111 # Don't force us to remove all the existing interfaces if this facility
112 # is not desired.
113 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -0700114 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700115 self._ping_runner = ping_runner.PingRunner(host=self.host)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800116 self._bridge_interface = None
117 self._virtual_ethernet_pair = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700118
Paul Stewart6423bb02012-11-27 17:46:23 -0800119
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800120 @property
121 def phy_list(self):
122 """@return iterable object of PHY descriptions for this system."""
123 if self._phy_list is None:
124 self._phy_list = self.iw_runner.list_phys()
125 return self._phy_list
126
127
mukesh agrawal3e458912015-06-12 10:40:16 -0700128 def _phy_by_name(self, phy_name):
129 """@return IwPhy for PHY with name |phy_name|, or None."""
130 for phy in self._phy_list:
131 if phy.name == phy_name:
132 return phy
133 else:
134 return None
135
136
Paul Stewart6423bb02012-11-27 17:46:23 -0800137 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700138 """Get information about WiFi devices.
139
140 Parse the output of 'iw list' and some of sysfs and return:
141
142 A dict |phys_for_frequency| which maps from each frequency to a
143 list of phys that support that channel.
144
145 A dict |phy_bus_type| which maps from each phy to the bus type for
146 each phy.
147
148 @return phys_for_frequency, phy_bus_type tuple as described.
149
Paul Stewart6423bb02012-11-27 17:46:23 -0800150 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800151 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800152 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700153 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800154 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700155 phy_list.append(phy.name)
156 for band in phy.bands:
157 for mhz in band.frequencies:
158 if mhz not in phys_for_frequency:
159 phys_for_frequency[mhz] = [phy.name]
160 else:
161 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800162
163 phy_bus_type = {}
164 for phy in phy_list:
165 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700166 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800167 devpath = self.host.run(command).stdout
168 if '/usb' in devpath:
169 phybus = 'usb'
170 elif '/mmc' in devpath:
171 phybus = 'sdio'
172 elif '/pci' in devpath:
173 phybus = 'pci'
174 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700175 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800176 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700177
Paul Stewart64cc4292011-06-01 10:59:36 -0700178
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800179 def _create_bridge_interface(self):
180 """Create a bridge interface."""
181 self.host.run('%s link add name %s type bridge' %
182 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
183 self.host.run('%s link set dev %s up' %
184 (self.cmd_ip, self.BRIDGE_INTERFACE_NAME))
185 self._bridge_interface = self.BRIDGE_INTERFACE_NAME
186
187
188 def _create_virtual_ethernet_pair(self):
189 """Create a virtual ethernet pair."""
190 self._virtual_ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
191 interface_ip=None, peer_interface_ip=None, host=self.host)
192 self._virtual_ethernet_pair.setup()
193
194
mukesh agrawal24a70172015-06-22 10:53:04 -0700195 def _get_unique_mac(self):
196 """Get a MAC address that is likely to be unique.
197
198 Generates a MAC address that is a) guaranteed not to be in use
199 on this host, and b) likely to be unique within the test cell.
200
201 @return string MAC address.
202
203 """
204 # We use SystemRandom to reduce the likelyhood of coupling
205 # across systems. (The default random class might, e.g., seed
206 # itself based on wall-clock time.)
207 sysrand = random.SystemRandom()
208 for tries in xrange(0, self.MAC_RETRY_LIMIT):
209 mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x' % (
210 (sysrand.getrandbits(8) & ~self.MAC_BIT_MULTICAST) |
211 self.MAC_BIT_LOCAL,
212 sysrand.getrandbits(8),
213 sysrand.getrandbits(8),
214 sysrand.getrandbits(8),
215 sysrand.getrandbits(8),
216 sysrand.getrandbits(8))
217 if mac_addr not in self._local_macs_in_use:
218 self._local_macs_in_use.add(mac_addr)
219 return mac_addr
220 else:
221 raise error.TestError('Failed to find a new MAC address')
222
223
mukesh agrawala3423082015-07-20 12:40:18 -0700224 def _phy_in_use(self, phy_name):
225 """Determine whether or not a PHY is used by an active DEV
226
227 @return bool True iff PHY is in use.
228 """
229 for net_dev in self._wlanifs_in_use:
230 if net_dev.phy == phy_name:
231 return True
232 return False
233
234
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800235 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700236 """Remove an interface from a WiFi device.
237
238 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700239
240 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800241 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700242 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700243 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800244 for net_dev in self._interfaces:
245 if net_dev.if_name == interface:
246 self._interfaces.remove(net_dev)
247 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700248
Christopher Wiley061f1382013-06-17 18:17:58 -0700249
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700250 def close(self):
251 """Close global resources held by this system."""
252 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700253 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800254 # Release and remove any interfaces that we create.
255 for net_dev in self._wlanifs_in_use:
256 self.release_interface(net_dev.if_name)
257 for net_dev in self._interfaces:
258 if net_dev.inherited:
259 continue
260 self.remove_interface(net_dev.if_name)
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800261 if self._bridge_interface is not None:
262 self.remove_bridge_interface()
263 if self._virtual_ethernet_pair is not None:
264 self.remove_ethernet_pair_interface()
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700265 self.host.close()
266 self.host = None
267
268
mukesh agrawal7fd4d092015-06-29 13:50:54 -0700269 def reboot(self, timeout):
270 """Reboot this system, and restore it to a known-good state.
271
272 @param timeout Maximum seconds to wait for system to return.
273
274 """
275 self.host.reboot(timeout=timeout, wait=True)
276 self.__setup()
277
278
Christopher Wiley061f1382013-06-17 18:17:58 -0700279 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700280 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700281 phymap = self.phys_for_frequency
282 if [freq for freq in phymap.iterkeys() if freq > 5000]:
283 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700284 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700285 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700286 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
287 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700288 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700289 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800290 for phy in self.phy_list:
Paul Stewartf94151e2015-04-20 20:32:39 -0700291 if ('tdls_mgmt' in phy.commands or
292 'tdls_oper' in phy.commands or
293 'T-DLS' in phy.features):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800294 caps.add(self.CAPABILITY_TDLS)
Peter Qiu3ef31f02014-10-17 14:25:57 -0700295 if phy.support_vht:
296 caps.add(self.CAPABILITY_VHT)
mukesh agrawal064ea972015-06-26 10:00:53 -0700297 if any([iw_runner.DEV_MODE_IBSS in phy.modes
298 for phy in self.phy_list]):
299 caps.add(self.CAPABILITY_IBSS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700300 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700301
302
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700303 def start_capture(self, frequency,
304 ht_type=None, snaplen=None, filename=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700305 """Start a packet capture.
306
307 @param frequency int frequency of channel to capture on.
308 @param ht_type string one of (None, 'HT20', 'HT40+', 'HT40-').
Christopher Wiley618e52b2013-10-14 16:21:07 -0700309 @param snaplen int number of bytes to retain per capture frame.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700310 @param filename string filename to write capture to.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700311
312 """
313 if self._packet_capturer.capture_running:
314 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800315 self._capture_interface = self.get_wlanif(frequency, 'monitor')
316 full_interface = [net_dev for net_dev in self._interfaces
317 if net_dev.if_name == self._capture_interface][0]
318 # If this is the only interface on this phy, we ought to configure
319 # the phy with a channel and ht_type. Otherwise, inherit the settings
320 # of the phy as they stand.
321 if len([net_dev for net_dev in self._interfaces
322 if net_dev.phy == full_interface.phy]) == 1:
323 self._packet_capturer.configure_raw_monitor(
324 self._capture_interface, frequency, ht_type=ht_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800325 else:
326 self.host.run('%s link set %s up' %
327 (self.cmd_ip, self._capture_interface))
328
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700329 # Start the capture.
mukesh agrawal5fd90f52015-04-24 16:14:37 -0700330 if filename:
331 remote_path = os.path.join('/tmp', os.path.basename(filename))
332 else:
333 remote_path = None
334 self._packet_capturer.start_capture(
335 self._capture_interface, './debug/', snaplen=snaplen,
336 remote_file=remote_path)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700337
338
Christopher Wiley062a9812013-10-03 15:55:50 -0700339 def stop_capture(self, save_dir=None, save_filename=None):
340 """Stop a packet capture.
341
342 @param save_dir string path to directory to save pcap files in.
343 @param save_filename string basename of file to save pcap in locally.
344
345 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700346 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700347 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700348 results = self._packet_capturer.stop_capture(
349 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800350 self.release_interface(self._capture_interface)
351 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700352 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800353
354
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700355 def sync_host_times(self):
356 """Set time on our DUT to match local time."""
357 epoch_seconds = time.time()
358 busybox_format = '%Y%m%d%H%M.%S'
359 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
360 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
361 (epoch_seconds, busybox_date))
362
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800363
mukesh agrawal43644aa2015-07-06 14:40:22 -0700364 def _get_phy_for_frequency(self, frequency, phytype, spatial_streams):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700365 """Get a phy appropriate for a frequency and phytype.
366
367 Return the most appropriate phy interface for operating on the
368 frequency |frequency| in the role indicated by |phytype|. Prefer idle
369 phys to busy phys if any exist. Secondarily, show affinity for phys
370 that use the bus type associated with this phy type.
371
372 @param frequency int WiFi frequency of phy.
373 @param phytype string key of phytype registered at construction time.
mukesh agrawal43644aa2015-07-06 14:40:22 -0700374 @param spatial_streams int number of spatial streams required.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700375 @return string name of phy to use.
376
Paul Stewart6423bb02012-11-27 17:46:23 -0800377 """
mukesh agrawal43644aa2015-07-06 14:40:22 -0700378 phy_objs = []
379 for phy_name in self.phys_for_frequency[frequency]:
380 phy_obj = self._phy_by_name(phy_name)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700381 num_antennas = min(phy_obj.avail_rx_antennas,
382 phy_obj.avail_tx_antennas)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700383 if num_antennas >= spatial_streams:
384 phy_objs.append(phy_obj)
mukesh agrawalbc963c12015-06-19 11:40:03 -0700385 elif num_antennas == 0:
386 logging.warning(
mukesh agrawal43644aa2015-07-06 14:40:22 -0700387 'Allowing use of %s, which reports zero antennas', phy_name)
388 phy_objs.append(phy_obj)
mukesh agrawal3e458912015-06-12 10:40:16 -0700389 else:
390 logging.debug(
mukesh agrawalbc963c12015-06-19 11:40:03 -0700391 'Filtering out %s, which reports only %d antennas',
mukesh agrawal43644aa2015-07-06 14:40:22 -0700392 phy_name, num_antennas)
Paul Stewart6423bb02012-11-27 17:46:23 -0800393
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800394 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
mukesh agrawal43644aa2015-07-06 14:40:22 -0700395 idle_phy_objs = [phy_obj for phy_obj in phy_objs
396 if phy_obj.name not in busy_phys]
397 phy_objs = idle_phy_objs or phy_objs
398 phy_objs.sort(key=lambda phy_obj: min(phy_obj.avail_rx_antennas,
399 phy_obj.avail_tx_antennas),
400 reverse=True)
401 phys = [phy_obj.name for phy_obj in phy_objs]
Paul Stewart6423bb02012-11-27 17:46:23 -0800402
Christopher Wiley408d1812014-01-13 15:27:43 -0800403 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800404 preferred_phys = [phy for phy in phys
405 if self.phy_bus_type[phy] == preferred_bus]
406 phys = preferred_phys or phys
407
408 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700409
410
mukesh agrawala3423082015-07-20 12:40:18 -0700411 def _get_wlanif(self, phytype, spatial_streams, frequency, same_phy_as):
412 """Get a WiFi device that supports the given frequency and phytype.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700413
mukesh agrawala3423082015-07-20 12:40:18 -0700414 We simply find or create a suitable DEV. It is left to the
415 caller to actually configure the frequency and bring up the
416 interface.
417
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700418 @param phytype string type of phy (e.g. 'monitor').
mukesh agrawal43644aa2015-07-06 14:40:22 -0700419 @param spatial_streams int number of spatial streams required.
mukesh agrawala3423082015-07-20 12:40:18 -0700420 @param frequency int WiFi frequency to support.
Paul Stewart51b0f382013-06-12 09:03:02 -0700421 @param same_phy_as string create the interface on the same phy as this.
mukesh agrawala3423082015-07-20 12:40:18 -0700422 @return NetDev WiFi device.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700423
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700424 """
mukesh agrawala3423082015-07-20 12:40:18 -0700425 if frequency and same_phy_as:
426 raise error.TestError(
427 'Can not combine |frequency| and |same_phy_as|')
428
429 if not (frequency or same_phy_as):
430 raise error.TestError(
431 'Must specify one of |frequency| or |same_phy_as|')
432
mukesh agrawal43644aa2015-07-06 14:40:22 -0700433 if spatial_streams is None:
434 spatial_streams = self.MIN_SPATIAL_STREAMS
435
Paul Stewart51b0f382013-06-12 09:03:02 -0700436 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800437 for net_dev in self._interfaces:
438 if net_dev.if_name == same_phy_as:
439 phy = net_dev.phy
440 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700441 else:
442 raise error.TestFail('Unable to find phy for interface %s' %
443 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800444 elif frequency in self.phys_for_frequency:
mukesh agrawal43644aa2015-07-06 14:40:22 -0700445 phy = self._get_phy_for_frequency(
446 frequency, phytype, spatial_streams)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700447 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800448 raise error.TestFail('Unable to find phy for frequency %d' %
449 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700450
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800451 # If we have a suitable unused interface sitting around on this
452 # phy, reuse it.
453 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
454 if net_dev.phy == phy and net_dev.if_type == phytype:
mukesh agrawal24a70172015-06-22 10:53:04 -0700455 break
456 else:
457 # Because we can reuse interfaces, we have to iteratively find a
458 # good interface name.
459 name_exists = lambda name: bool([net_dev
460 for net_dev in self._interfaces
461 if net_dev.if_name == name])
462 if_name = lambda index: '%s%d' % (phytype, index)
463 if_index = len(self._interfaces)
464 while name_exists(if_name(if_index)):
465 if_index += 1
466 net_dev = NetDev(phy=phy, if_name=if_name(if_index),
467 if_type=phytype, inherited=False)
468 self._interfaces.append(net_dev)
469 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700470
mukesh agrawal24a70172015-06-22 10:53:04 -0700471 # Link must be down to reconfigure MAC address.
472 self.host.run('%s link set dev %s down' % (
473 self.cmd_ip, net_dev.if_name))
474 if same_phy_as:
475 self.clone_mac_address(src_dev=same_phy_as,
476 dst_dev=net_dev.if_name)
477 else:
478 self.ensure_unique_mac(net_dev)
479
mukesh agrawala3423082015-07-20 12:40:18 -0700480 return net_dev
481
482
483 def get_configured_interface(self, phytype, spatial_streams=None,
484 frequency=None, same_phy_as=None):
485 """Get a WiFi device that supports the given frequency and phytype.
486
487 The device's link state will be UP, and (where possible) the device
488 will be configured to operate on |frequency|.
489
490 @param phytype string type of phy (e.g. 'monitor').
491 @param spatial_streams int number of spatial streams required.
492 @param frequency int WiFi frequency to support.
493 @param same_phy_as string create the interface on the same phy as this.
494 @return string WiFi device.
495
496 """
497 net_dev = self._get_wlanif(
498 phytype, spatial_streams, frequency, same_phy_as)
499
500 self.host.run('%s link set dev %s up' % (self.cmd_ip, net_dev.if_name))
501
502 if frequency:
503 if phytype == 'managed':
504 logging.debug('Skipped setting frequency for DEV %s '
505 'since managed mode DEVs roam across APs.',
506 net_dev.if_name)
507 elif same_phy_as or self._phy_in_use(net_dev.phy):
508 logging.debug('Skipped setting frequency for DEV %s '
509 'since PHY %s is already in use',
510 net_dev.if_name, net_dev.phy)
511 else:
512 self.iw_runner.set_freq(net_dev.if_name, frequency)
513
514 self._wlanifs_in_use.append(net_dev)
515 return net_dev.if_name
516
517
518 # TODO(quiche): Deprecate this, in favor of get_configured_interface().
519 # crbug.com/512169.
520 def get_wlanif(self, frequency, phytype,
521 spatial_streams=None, same_phy_as=None):
522 """Get a WiFi device that supports the given frequency and phytype.
523
524 We simply find or create a suitable DEV. It is left to the
525 caller to actually configure the frequency and bring up the
526 interface.
527
528 @param frequency int WiFi frequency to support.
529 @param phytype string type of phy (e.g. 'monitor').
530 @param spatial_streams int number of spatial streams required.
531 @param same_phy_as string create the interface on the same phy as this.
532 @return string WiFi device.
533
534 """
535 net_dev = self._get_wlanif(
536 phytype, spatial_streams, frequency, same_phy_as)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800537 self._wlanifs_in_use.append(net_dev)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800538 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800539
540
mukesh agrawal24a70172015-06-22 10:53:04 -0700541 def ensure_unique_mac(self, net_dev):
542 """Ensure MAC address of |net_dev| meets uniqueness requirements.
543
544 The Linux kernel does not allow multiple APs with the same
545 BSSID on the same PHY (at least, with some drivers). Hence, we
546 want to ensure that the DEVs for a PHY have unique MAC
547 addresses.
548
549 Note that we do not attempt to make the MACs unique across
550 PHYs, because some tests deliberately create such scenarios.
551
552 @param net_dev NetDev to uniquify.
553
554 """
555 if net_dev.if_type == 'monitor':
556 return
557
558 our_ifname = net_dev.if_name
559 our_phy = net_dev.phy
560 our_mac = interface.Interface(our_ifname, self.host).mac_address
561 sibling_devs = [dev for dev in self._interfaces
562 if (dev.phy == our_phy and
563 dev.if_name != our_ifname and
564 dev.if_type != 'monitor')]
565 sibling_macs = (
566 interface.Interface(sib_dev.if_name, self.host).mac_address
567 for sib_dev in sibling_devs)
568 if our_mac in sibling_macs:
569 self.configure_interface_mac(our_ifname,
570 self._get_unique_mac())
571
572
573 def configure_interface_mac(self, wlanif, new_mac):
574 """Change the MAC address for an interface.
575
576 @param wlanif string name of device to reconfigure.
577 @param new_mac string MAC address to assign (e.g. '00:11:22:33:44:55')
578
579 """
580 self.host.run('%s link set %s address %s' %
581 (self.cmd_ip, wlanif, new_mac))
582
583
584 def clone_mac_address(self, src_dev=None, dst_dev=None):
585 """Copy the MAC address from one interface to another.
586
587 @param src_dev string name of device to copy address from.
588 @param dst_dev string name of device to copy address to.
589
590 """
591 self.configure_interface_mac(
592 dst_dev,
593 interface.Interface(src_dev, self.host).mac_address)
594
595
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800596 def release_interface(self, wlanif):
597 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700598
599 @param wlanif string name of device to release.
600
601 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800602 for net_dev in self._wlanifs_in_use:
603 if net_dev.if_name == wlanif:
604 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700605
606
Peter Qiu9a63a8b2015-02-03 09:08:16 -0800607 def get_bridge_interface(self):
608 """Return the bridge interface, create one if it is not created yet.
609
610 @return string name of bridge interface.
611 """
612 if self._bridge_interface is None:
613 self._create_bridge_interface()
614 return self._bridge_interface
615
616
617 def remove_bridge_interface(self):
618 """Remove the bridge interface that's been created."""
619 if self._bridge_interface is not None:
620 self.host.run('%s link delete %s type bridge' %
621 (self.cmd_ip, self._bridge_interface))
622 self._bridge_interface = None
623
624
625 def add_interface_to_bridge(self, interface):
626 """Add an interface to the bridge interface.
627
628 This will create the bridge interface if it is not created yet.
629
630 @param interface string name of the interface to add to the bridge.
631 """
632 if self._bridge_interface is None:
633 self._create_bridge_interface()
634 self.host.run('%s link set dev %s master %s' %
635 (self.cmd_ip, interface, self._bridge_interface))
636
637
638 def get_virtual_ethernet_master_interface(self):
639 """Return the master interface of the virtual ethernet pair.
640
641 @return string name of the master interface of the virtual ethernet
642 pair.
643 """
644 if self._virtual_ethernet_pair is None:
645 self._create_virtual_ethernet_pair()
646 return self._virtual_ethernet_pair.interface_name
647
648
649 def get_virtual_ethernet_peer_interface(self):
650 """Return the peer interface of the virtual ethernet pair.
651
652 @return string name of the peer interface of the virtual ethernet pair.
653 """
654 if self._virtual_ethernet_pair is None:
655 self._create_virtual_ethernet_pair()
656 return self._virtual_ethernet_pair.peer_interface_name
657
658
659 def remove_ethernet_pair_interface(self):
660 """Remove the virtual ethernet pair that's been created."""
661 if self._virtual_ethernet_pair is not None:
662 self._virtual_ethernet_pair.teardown()
663 self._virtual_ethernet_pair = None
664
665
Christopher Wiley9b406202013-05-06 14:07:49 -0700666 def require_capabilities(self, requirements, fatal_failure=False):
667 """Require capabilities of this LinuxSystem.
668
669 Check that capabilities in |requirements| exist on this system.
670 Raise and exception to skip but not fail the test if said
671 capabilities are not found. Pass |fatal_failure| to cause this
672 error to become a test failure.
673
674 @param requirements list of CAPABILITY_* defined above.
675 @param fatal_failure bool True iff failures should be fatal.
676
677 """
678 to_be_raised = error.TestNAError
679 if fatal_failure:
680 to_be_raised = error.TestFail
681 missing = [cap for cap in requirements if not cap in self.capabilities]
682 if missing:
683 raise to_be_raised('AP on %s is missing required capabilites: %r' %
684 (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800685
686
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800687 def disable_antennas_except(self, permitted_antennas):
688 """Disable unwanted antennas.
Peter Qiu2f973252014-02-20 15:30:37 -0800689
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800690 Disable all antennas except those specified in |permitted_antennas|.
691 Note that one or more of them may remain disabled if the underlying
692 hardware does not support them.
693
694 @param permitted_antennas int bitmask specifying antennas that we should
695 attempt to enable.
Peter Qiu2f973252014-02-20 15:30:37 -0800696
697 """
698 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700699 if not phy.supports_setting_antenna_mask:
700 continue
Bindu Mahadev67cb7722016-01-05 16:45:00 -0800701 # Determine valid bitmap values based on available antennas.
702 self.iw_runner.set_antenna_bitmap(phy.name,
703 permitted_antennas & phy.avail_tx_antennas,
704 permitted_antennas & phy.avail_rx_antennas)
Peter Qiu2f973252014-02-20 15:30:37 -0800705
706
mukesh agrawal4cfb7512015-12-11 15:54:11 -0800707 def enable_all_antennas(self):
708 """Enable all antennas on all phys."""
Peter Qiu2f973252014-02-20 15:30:37 -0800709 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700710 if not phy.supports_setting_antenna_mask:
711 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800712 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
713 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700714
715
716 def ping(self, ping_config):
717 """Ping an IP from this system.
718
719 @param ping_config PingConfig object describing the ping command to run.
720 @return a PingResult object.
721
722 """
723 logging.info('Pinging from the %s.', self.role)
724 return self._ping_runner.ping(ping_config)