blob: 3739a5554dc4e6c6c59838be8de23c904077f746 [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
Christopher Wiley7c97ded2013-09-24 23:24:33 -07008import time
Christopher Wiley4c6a32c2013-04-24 13:37:51 -07009
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070010from autotest_lib.client.common_lib import error
Christopher Wileyf9cb0922013-11-01 09:12:21 -070011from autotest_lib.client.common_lib.cros.network import iw_runner
Christopher Wileyaeef9b52014-03-11 12:24:11 -070012from autotest_lib.client.common_lib.cros.network import ping_runner
Christopher Wiley7337ff62013-10-03 17:21:46 -070013from autotest_lib.server.cros import wifi_test_utils
Christopher Wileydc7c4622013-07-09 11:44:04 -070014from autotest_lib.server.cros.network import packet_capturer
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070015
Christopher Wileyf671a5a2013-12-13 15:44:41 -080016NetDev = collections.namedtuple('NetDev',
17 ['inherited', 'phy', 'if_name', 'if_type'])
18
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070019class LinuxSystem(object):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070020 """Superclass for test machines running Linux.
21
22 Provides a common point for routines that use the cfg80211 userspace tools
23 to manipulate the wireless stack, regardless of the role they play.
24 Currently the commands shared are the init, which queries for wireless
25 devices, along with start_capture and stop_capture. More commands may
26 migrate from site_linux_router as appropriate to share.
27
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070028 """
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070029
Christopher Wileyf19c28c2013-05-06 15:42:57 -070030 CAPABILITY_5GHZ = '5ghz'
31 CAPABILITY_MULTI_AP = 'multi_ap'
32 CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
Christopher Wiley061f1382013-06-17 18:17:58 -070033 CAPABILITY_IBSS = 'ibss_supported'
Paul Stewart51b0f382013-06-12 09:03:02 -070034 CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
Paul Stewart27ecc5d2013-11-13 16:56:41 -080035 CAPABILITY_TDLS = 'tdls'
Christopher Wileyf19c28c2013-05-06 15:42:57 -070036
37
38 @property
39 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070040 """@return iterable object of AP capabilities for this system."""
41 if self._capabilities is None:
42 self._capabilities = self.get_capabilities()
43 logging.info('%s system capabilities: %r',
44 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070045 return self._capabilities
46
47
Christopher Wiley408d1812014-01-13 15:27:43 -080048 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070049 # Command locations.
Christopher Wiley7bd2e082013-10-16 17:40:40 -070050 cmd_iw = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080051 host, '/usr/sbin/iw')
Christopher Wiley7337ff62013-10-03 17:21:46 -070052 self.cmd_ip = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080053 host, '/usr/sbin/ip')
Christopher Wiley7337ff62013-10-03 17:21:46 -070054 self.cmd_readlink = '%s -l' % wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080055 host, '/bin/ls')
Paul Stewart6423bb02012-11-27 17:46:23 -080056
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070057 self.host = host
58 self.role = role
59
Christopher Wiley09ad2062013-09-13 13:34:49 -070060 self._packet_capturer = packet_capturer.get_packet_capturer(
Christopher Wiley408d1812014-01-13 15:27:43 -080061 self.host, host_description=role, cmd_ip=self.cmd_ip,
62 cmd_iw=cmd_iw, ignore_failures=True)
Christopher Wileyf9cb0922013-11-01 09:12:21 -070063 self.iw_runner = iw_runner.IwRunner(remote_host=host, command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070064
Paul Stewart27ecc5d2013-11-13 16:56:41 -080065 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -080066 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Christopher Wileyf671a5a2013-12-13 15:44:41 -080067 self._interfaces = []
68 for interface in self.iw_runner.list_interfaces():
69 if inherit_interfaces:
70 self._interfaces.append(NetDev(inherited=True,
71 if_name=interface.if_name,
72 if_type=interface.if_type,
73 phy=interface.phy))
74 else:
75 self.iw_runner.remove_interface(interface.if_name)
76
77 self._wlanifs_in_use = []
78 self._capture_interface = None
Christopher Wiley80e40922013-10-22 13:14:56 -070079 # Some uses of LinuxSystem don't use the interface allocation facility.
80 # Don't force us to remove all the existing interfaces if this facility
81 # is not desired.
82 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -070083 self._capabilities = None
Christopher Wileyaeef9b52014-03-11 12:24:11 -070084 self._ping_runner = ping_runner.PingRunner(host=self.host)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070085
Paul Stewart6423bb02012-11-27 17:46:23 -080086
Paul Stewart27ecc5d2013-11-13 16:56:41 -080087 @property
88 def phy_list(self):
89 """@return iterable object of PHY descriptions for this system."""
90 if self._phy_list is None:
91 self._phy_list = self.iw_runner.list_phys()
92 return self._phy_list
93
94
Paul Stewart6423bb02012-11-27 17:46:23 -080095 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070096 """Get information about WiFi devices.
97
98 Parse the output of 'iw list' and some of sysfs and return:
99
100 A dict |phys_for_frequency| which maps from each frequency to a
101 list of phys that support that channel.
102
103 A dict |phy_bus_type| which maps from each phy to the bus type for
104 each phy.
105
106 @return phys_for_frequency, phy_bus_type tuple as described.
107
Paul Stewart6423bb02012-11-27 17:46:23 -0800108 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800109 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800110 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700111 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800112 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700113 phy_list.append(phy.name)
114 for band in phy.bands:
115 for mhz in band.frequencies:
116 if mhz not in phys_for_frequency:
117 phys_for_frequency[mhz] = [phy.name]
118 else:
119 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800120
121 phy_bus_type = {}
122 for phy in phy_list:
123 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700124 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800125 devpath = self.host.run(command).stdout
126 if '/usb' in devpath:
127 phybus = 'usb'
128 elif '/mmc' in devpath:
129 phybus = 'sdio'
130 elif '/pci' in devpath:
131 phybus = 'pci'
132 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700133 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800134 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700135
Paul Stewart64cc4292011-06-01 10:59:36 -0700136
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800137 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700138 """Remove an interface from a WiFi device.
139
140 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700141
142 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800143 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700144 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700145 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800146 for net_dev in self._interfaces:
147 if net_dev.if_name == interface:
148 self._interfaces.remove(net_dev)
149 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700150
Christopher Wiley061f1382013-06-17 18:17:58 -0700151
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700152 def close(self):
153 """Close global resources held by this system."""
154 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700155 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800156 # Release and remove any interfaces that we create.
157 for net_dev in self._wlanifs_in_use:
158 self.release_interface(net_dev.if_name)
159 for net_dev in self._interfaces:
160 if net_dev.inherited:
161 continue
162 self.remove_interface(net_dev.if_name)
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700163 self.host.close()
164 self.host = None
165
166
Christopher Wiley061f1382013-06-17 18:17:58 -0700167 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700168 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700169 phymap = self.phys_for_frequency
170 if [freq for freq in phymap.iterkeys() if freq > 5000]:
171 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700172 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700173 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700174 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
175 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700176 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700177 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800178 for phy in self.phy_list:
179 if 'tdls_mgmt' in phy.commands or 'tdls_oper' in phy.commands:
180 caps.add(self.CAPABILITY_TDLS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700181 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700182
183
Christopher Wiley618e52b2013-10-14 16:21:07 -0700184 def start_capture(self, frequency, ht_type=None, snaplen=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700185 """Start a packet capture.
186
187 @param frequency int frequency of channel to capture on.
188 @param ht_type string one of (None, 'HT20', 'HT40+', 'HT40-').
Christopher Wiley618e52b2013-10-14 16:21:07 -0700189 @param snaplen int number of bytes to retain per capture frame.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700190
191 """
192 if self._packet_capturer.capture_running:
193 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800194 self._capture_interface = self.get_wlanif(frequency, 'monitor')
195 full_interface = [net_dev for net_dev in self._interfaces
196 if net_dev.if_name == self._capture_interface][0]
197 # If this is the only interface on this phy, we ought to configure
198 # the phy with a channel and ht_type. Otherwise, inherit the settings
199 # of the phy as they stand.
200 if len([net_dev for net_dev in self._interfaces
201 if net_dev.phy == full_interface.phy]) == 1:
202 self._packet_capturer.configure_raw_monitor(
203 self._capture_interface, frequency, ht_type=ht_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800204 else:
205 self.host.run('%s link set %s up' %
206 (self.cmd_ip, self._capture_interface))
207
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700208 # Start the capture.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800209 self._packet_capturer.start_capture(self._capture_interface, './debug/',
Christopher Wiley618e52b2013-10-14 16:21:07 -0700210 snaplen=snaplen)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700211
212
Christopher Wiley062a9812013-10-03 15:55:50 -0700213 def stop_capture(self, save_dir=None, save_filename=None):
214 """Stop a packet capture.
215
216 @param save_dir string path to directory to save pcap files in.
217 @param save_filename string basename of file to save pcap in locally.
218
219 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700220 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700221 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700222 results = self._packet_capturer.stop_capture(
223 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800224 self.release_interface(self._capture_interface)
225 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700226 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800227
228
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700229 def sync_host_times(self):
230 """Set time on our DUT to match local time."""
231 epoch_seconds = time.time()
232 busybox_format = '%Y%m%d%H%M.%S'
233 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
234 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
235 (epoch_seconds, busybox_date))
236
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800237
Paul Stewart6423bb02012-11-27 17:46:23 -0800238 def _get_phy_for_frequency(self, frequency, phytype):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700239 """Get a phy appropriate for a frequency and phytype.
240
241 Return the most appropriate phy interface for operating on the
242 frequency |frequency| in the role indicated by |phytype|. Prefer idle
243 phys to busy phys if any exist. Secondarily, show affinity for phys
244 that use the bus type associated with this phy type.
245
246 @param frequency int WiFi frequency of phy.
247 @param phytype string key of phytype registered at construction time.
248 @return string name of phy to use.
249
Paul Stewart6423bb02012-11-27 17:46:23 -0800250 """
251 phys = self.phys_for_frequency[frequency]
252
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800253 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
Paul Stewart6423bb02012-11-27 17:46:23 -0800254 idle_phys = [phy for phy in phys if phy not in busy_phys]
255 phys = idle_phys or phys
256
Christopher Wiley408d1812014-01-13 15:27:43 -0800257 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800258 preferred_phys = [phy for phy in phys
259 if self.phy_bus_type[phy] == preferred_bus]
260 phys = preferred_phys or phys
261
262 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700263
264
Christopher Wiley408d1812014-01-13 15:27:43 -0800265 def get_wlanif(self, frequency, phytype, same_phy_as=None):
266 """Get a WiFi device that supports the given frequency and type.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700267
268 @param frequency int WiFi frequency to support.
269 @param phytype string type of phy (e.g. 'monitor').
Paul Stewart51b0f382013-06-12 09:03:02 -0700270 @param same_phy_as string create the interface on the same phy as this.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700271 @return string WiFi device.
272
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700273 """
Paul Stewart51b0f382013-06-12 09:03:02 -0700274 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800275 for net_dev in self._interfaces:
276 if net_dev.if_name == same_phy_as:
277 phy = net_dev.phy
278 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700279 else:
280 raise error.TestFail('Unable to find phy for interface %s' %
281 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800282 elif frequency in self.phys_for_frequency:
283 phy = self._get_phy_for_frequency(frequency, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700284 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800285 raise error.TestFail('Unable to find phy for frequency %d' %
286 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700287
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800288 # If we have a suitable unused interface sitting around on this
289 # phy, reuse it.
290 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
291 if net_dev.phy == phy and net_dev.if_type == phytype:
292 self._wlanifs_in_use.append(net_dev)
293 return net_dev.if_name
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700294
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800295 # Because we can reuse interfaces, we have to iteratively find a good
296 # interface name.
297 name_exists = lambda name: bool([net_dev
298 for net_dev in self._interfaces
299 if net_dev.if_name == name])
300 if_name = lambda index: '%s%d' % (phytype, index)
301 if_index = len(self._interfaces)
302 while name_exists(if_name(if_index)):
303 if_index += 1
304 net_dev = NetDev(phy=phy, if_name=if_name(if_index), if_type=phytype,
305 inherited=False)
306 self._interfaces.append(net_dev)
307 self._wlanifs_in_use.append(net_dev)
308 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
309 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800310
311
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800312 def release_interface(self, wlanif):
313 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700314
315 @param wlanif string name of device to release.
316
317 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800318 for net_dev in self._wlanifs_in_use:
319 if net_dev.if_name == wlanif:
320 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700321
322
323 def require_capabilities(self, requirements, fatal_failure=False):
324 """Require capabilities of this LinuxSystem.
325
326 Check that capabilities in |requirements| exist on this system.
327 Raise and exception to skip but not fail the test if said
328 capabilities are not found. Pass |fatal_failure| to cause this
329 error to become a test failure.
330
331 @param requirements list of CAPABILITY_* defined above.
332 @param fatal_failure bool True iff failures should be fatal.
333
334 """
335 to_be_raised = error.TestNAError
336 if fatal_failure:
337 to_be_raised = error.TestFail
338 missing = [cap for cap in requirements if not cap in self.capabilities]
339 if missing:
340 raise to_be_raised('AP on %s is missing required capabilites: %r' %
341 (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800342
343
344 def set_antenna_bitmap(self, tx_bitmap, rx_bitmap):
345 """Setup antenna bitmaps for all the phys.
346
347 @param tx_bitmap int bitmap of allowed antennas to use for TX
348 @param rx_bitmap int bitmap of allowed antennas to use for RX
349
350 """
351 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700352 if not phy.supports_setting_antenna_mask:
353 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800354 self.iw_runner.set_antenna_bitmap(phy.name, tx_bitmap, rx_bitmap)
355
356
357 def set_default_antenna_bitmap(self):
358 """Setup default antenna bitmaps for all the phys."""
359 for phy in self.phy_list:
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700360 if not phy.supports_setting_antenna_mask:
361 continue
Peter Qiu2f973252014-02-20 15:30:37 -0800362 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
363 phy.avail_rx_antennas)
Christopher Wileyaeef9b52014-03-11 12:24:11 -0700364
365
366 def ping(self, ping_config):
367 """Ping an IP from this system.
368
369 @param ping_config PingConfig object describing the ping command to run.
370 @return a PingResult object.
371
372 """
373 logging.info('Pinging from the %s.', self.role)
374 return self._ping_runner.ping(ping_config)