blob: 0c3b156422b40240d0ec2525920409c5e1a6fe51 [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 Wiley7337ff62013-10-03 17:21:46 -070012from autotest_lib.server.cros import wifi_test_utils
Christopher Wileydc7c4622013-07-09 11:44:04 -070013from autotest_lib.server.cros.network import packet_capturer
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070014
Christopher Wileyf671a5a2013-12-13 15:44:41 -080015NetDev = collections.namedtuple('NetDev',
16 ['inherited', 'phy', 'if_name', 'if_type'])
17
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070018class LinuxSystem(object):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070019 """Superclass for test machines running Linux.
20
21 Provides a common point for routines that use the cfg80211 userspace tools
22 to manipulate the wireless stack, regardless of the role they play.
23 Currently the commands shared are the init, which queries for wireless
24 devices, along with start_capture and stop_capture. More commands may
25 migrate from site_linux_router as appropriate to share.
26
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070027 """
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070028
Christopher Wileyf19c28c2013-05-06 15:42:57 -070029 CAPABILITY_5GHZ = '5ghz'
30 CAPABILITY_MULTI_AP = 'multi_ap'
31 CAPABILITY_MULTI_AP_SAME_BAND = 'multi_ap_same_band'
Christopher Wiley061f1382013-06-17 18:17:58 -070032 CAPABILITY_IBSS = 'ibss_supported'
Paul Stewart51b0f382013-06-12 09:03:02 -070033 CAPABILITY_SEND_MANAGEMENT_FRAME = 'send_management_frame'
Paul Stewart27ecc5d2013-11-13 16:56:41 -080034 CAPABILITY_TDLS = 'tdls'
Christopher Wileyf19c28c2013-05-06 15:42:57 -070035
36
37 @property
38 def capabilities(self):
Christopher Wiley061f1382013-06-17 18:17:58 -070039 """@return iterable object of AP capabilities for this system."""
40 if self._capabilities is None:
41 self._capabilities = self.get_capabilities()
42 logging.info('%s system capabilities: %r',
43 self.role, self._capabilities)
Christopher Wileyf19c28c2013-05-06 15:42:57 -070044 return self._capabilities
45
46
Christopher Wiley408d1812014-01-13 15:27:43 -080047 def __init__(self, host, role, inherit_interfaces=False):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070048 # Command locations.
Christopher Wiley7bd2e082013-10-16 17:40:40 -070049 cmd_iw = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080050 host, '/usr/sbin/iw')
Christopher Wiley7337ff62013-10-03 17:21:46 -070051 self.cmd_ip = wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080052 host, '/usr/sbin/ip')
Christopher Wiley7337ff62013-10-03 17:21:46 -070053 self.cmd_readlink = '%s -l' % wifi_test_utils.must_be_installed(
Christopher Wiley408d1812014-01-13 15:27:43 -080054 host, '/bin/ls')
Paul Stewart6423bb02012-11-27 17:46:23 -080055
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070056 self.host = host
57 self.role = role
58
Christopher Wiley09ad2062013-09-13 13:34:49 -070059 self._packet_capturer = packet_capturer.get_packet_capturer(
Christopher Wiley408d1812014-01-13 15:27:43 -080060 self.host, host_description=role, cmd_ip=self.cmd_ip,
61 cmd_iw=cmd_iw, ignore_failures=True)
Christopher Wileyf9cb0922013-11-01 09:12:21 -070062 self.iw_runner = iw_runner.IwRunner(remote_host=host, command_iw=cmd_iw)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070063
Paul Stewart27ecc5d2013-11-13 16:56:41 -080064 self._phy_list = None
Paul Stewart6423bb02012-11-27 17:46:23 -080065 self.phys_for_frequency, self.phy_bus_type = self._get_phy_info()
Christopher Wileyf671a5a2013-12-13 15:44:41 -080066 self._interfaces = []
67 for interface in self.iw_runner.list_interfaces():
68 if inherit_interfaces:
69 self._interfaces.append(NetDev(inherited=True,
70 if_name=interface.if_name,
71 if_type=interface.if_type,
72 phy=interface.phy))
73 else:
74 self.iw_runner.remove_interface(interface.if_name)
75
76 self._wlanifs_in_use = []
77 self._capture_interface = None
Christopher Wiley80e40922013-10-22 13:14:56 -070078 # Some uses of LinuxSystem don't use the interface allocation facility.
79 # Don't force us to remove all the existing interfaces if this facility
80 # is not desired.
81 self._wlanifs_initialized = False
Christopher Wileyf19c28c2013-05-06 15:42:57 -070082 self._capabilities = None
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070083
Paul Stewart6423bb02012-11-27 17:46:23 -080084
Paul Stewart27ecc5d2013-11-13 16:56:41 -080085 @property
86 def phy_list(self):
87 """@return iterable object of PHY descriptions for this system."""
88 if self._phy_list is None:
89 self._phy_list = self.iw_runner.list_phys()
90 return self._phy_list
91
92
Paul Stewart6423bb02012-11-27 17:46:23 -080093 def _get_phy_info(self):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -070094 """Get information about WiFi devices.
95
96 Parse the output of 'iw list' and some of sysfs and return:
97
98 A dict |phys_for_frequency| which maps from each frequency to a
99 list of phys that support that channel.
100
101 A dict |phy_bus_type| which maps from each phy to the bus type for
102 each phy.
103
104 @return phys_for_frequency, phy_bus_type tuple as described.
105
Paul Stewart6423bb02012-11-27 17:46:23 -0800106 """
Paul Stewart6423bb02012-11-27 17:46:23 -0800107 phys_for_frequency = {}
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800108 phy_caps = {}
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700109 phy_list = []
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800110 for phy in self.phy_list:
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700111 phy_list.append(phy.name)
112 for band in phy.bands:
113 for mhz in band.frequencies:
114 if mhz not in phys_for_frequency:
115 phys_for_frequency[mhz] = [phy.name]
116 else:
117 phys_for_frequency[mhz].append(phy.name)
Paul Stewart6423bb02012-11-27 17:46:23 -0800118
119 phy_bus_type = {}
120 for phy in phy_list:
121 phybus = 'unknown'
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700122 command = '%s /sys/class/ieee80211/%s' % (self.cmd_readlink, phy)
Paul Stewart6423bb02012-11-27 17:46:23 -0800123 devpath = self.host.run(command).stdout
124 if '/usb' in devpath:
125 phybus = 'usb'
126 elif '/mmc' in devpath:
127 phybus = 'sdio'
128 elif '/pci' in devpath:
129 phybus = 'pci'
130 phy_bus_type[phy] = phybus
Christopher Wiley9b406202013-05-06 14:07:49 -0700131 logging.debug('Got phys for frequency: %r', phys_for_frequency)
Paul Stewart6423bb02012-11-27 17:46:23 -0800132 return phys_for_frequency, phy_bus_type
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700133
Paul Stewart64cc4292011-06-01 10:59:36 -0700134
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800135 def remove_interface(self, interface):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700136 """Remove an interface from a WiFi device.
137
138 @param interface string interface to remove (e.g. wlan0).
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700139
140 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800141 self.release_interface(interface)
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700142 self.host.run('%s link set %s down' % (self.cmd_ip, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700143 self.iw_runner.remove_interface(interface)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800144 for net_dev in self._interfaces:
145 if net_dev.if_name == interface:
146 self._interfaces.remove(net_dev)
147 break
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700148
Christopher Wiley061f1382013-06-17 18:17:58 -0700149
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700150 def close(self):
151 """Close global resources held by this system."""
152 logging.debug('Cleaning up host object for %s', self.role)
Christopher Wiley618e52b2013-10-14 16:21:07 -0700153 self._packet_capturer.close()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800154 # Release and remove any interfaces that we create.
155 for net_dev in self._wlanifs_in_use:
156 self.release_interface(net_dev.if_name)
157 for net_dev in self._interfaces:
158 if net_dev.inherited:
159 continue
160 self.remove_interface(net_dev.if_name)
Christopher Wileyf4bc88b2013-08-29 16:45:15 -0700161 self.host.close()
162 self.host = None
163
164
Christopher Wiley061f1382013-06-17 18:17:58 -0700165 def get_capabilities(self):
Christopher Wiley16e494f2013-06-18 17:31:28 -0700166 caps = set()
Christopher Wiley061f1382013-06-17 18:17:58 -0700167 phymap = self.phys_for_frequency
168 if [freq for freq in phymap.iterkeys() if freq > 5000]:
169 # The frequencies are expressed in megaherz
Christopher Wiley16e494f2013-06-18 17:31:28 -0700170 caps.add(self.CAPABILITY_5GHZ)
Christopher Wiley061f1382013-06-17 18:17:58 -0700171 if [freq for freq in phymap.iterkeys() if len(phymap[freq]) > 1]:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700172 caps.add(self.CAPABILITY_MULTI_AP_SAME_BAND)
173 caps.add(self.CAPABILITY_MULTI_AP)
Christopher Wiley061f1382013-06-17 18:17:58 -0700174 elif len(self.phy_bus_type) > 1:
Christopher Wiley16e494f2013-06-18 17:31:28 -0700175 caps.add(self.CAPABILITY_MULTI_AP)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800176 for phy in self.phy_list:
177 if 'tdls_mgmt' in phy.commands or 'tdls_oper' in phy.commands:
178 caps.add(self.CAPABILITY_TDLS)
Christopher Wiley16e494f2013-06-18 17:31:28 -0700179 return caps
Christopher Wiley061f1382013-06-17 18:17:58 -0700180
181
Christopher Wiley618e52b2013-10-14 16:21:07 -0700182 def start_capture(self, frequency, ht_type=None, snaplen=None):
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700183 """Start a packet capture.
184
185 @param frequency int frequency of channel to capture on.
186 @param ht_type string one of (None, 'HT20', 'HT40+', 'HT40-').
Christopher Wiley618e52b2013-10-14 16:21:07 -0700187 @param snaplen int number of bytes to retain per capture frame.
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700188
189 """
190 if self._packet_capturer.capture_running:
191 self.stop_capture()
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800192 self._capture_interface = self.get_wlanif(frequency, 'monitor')
193 full_interface = [net_dev for net_dev in self._interfaces
194 if net_dev.if_name == self._capture_interface][0]
195 # If this is the only interface on this phy, we ought to configure
196 # the phy with a channel and ht_type. Otherwise, inherit the settings
197 # of the phy as they stand.
198 if len([net_dev for net_dev in self._interfaces
199 if net_dev.phy == full_interface.phy]) == 1:
200 self._packet_capturer.configure_raw_monitor(
201 self._capture_interface, frequency, ht_type=ht_type)
Christopher Wileyf737e022014-01-23 13:48:00 -0800202 else:
203 self.host.run('%s link set %s up' %
204 (self.cmd_ip, self._capture_interface))
205
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700206 # Start the capture.
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800207 self._packet_capturer.start_capture(self._capture_interface, './debug/',
Christopher Wiley618e52b2013-10-14 16:21:07 -0700208 snaplen=snaplen)
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700209
210
Christopher Wiley062a9812013-10-03 15:55:50 -0700211 def stop_capture(self, save_dir=None, save_filename=None):
212 """Stop a packet capture.
213
214 @param save_dir string path to directory to save pcap files in.
215 @param save_filename string basename of file to save pcap in locally.
216
217 """
Christopher Wiley6823b4f2013-04-22 18:40:24 -0700218 if not self._packet_capturer.capture_running:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700219 return
Christopher Wiley618e52b2013-10-14 16:21:07 -0700220 results = self._packet_capturer.stop_capture(
221 local_save_dir=save_dir, local_pcap_filename=save_filename)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800222 self.release_interface(self._capture_interface)
223 self._capture_interface = None
Christopher Wiley618e52b2013-10-14 16:21:07 -0700224 return results
Paul Stewart6423bb02012-11-27 17:46:23 -0800225
226
Christopher Wiley7c97ded2013-09-24 23:24:33 -0700227 def sync_host_times(self):
228 """Set time on our DUT to match local time."""
229 epoch_seconds = time.time()
230 busybox_format = '%Y%m%d%H%M.%S'
231 busybox_date = datetime.datetime.utcnow().strftime(busybox_format)
232 self.host.run('date -u --set=@%s 2>/dev/null || date -u %s' %
233 (epoch_seconds, busybox_date))
234
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800235
Paul Stewart6423bb02012-11-27 17:46:23 -0800236 def _get_phy_for_frequency(self, frequency, phytype):
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700237 """Get a phy appropriate for a frequency and phytype.
238
239 Return the most appropriate phy interface for operating on the
240 frequency |frequency| in the role indicated by |phytype|. Prefer idle
241 phys to busy phys if any exist. Secondarily, show affinity for phys
242 that use the bus type associated with this phy type.
243
244 @param frequency int WiFi frequency of phy.
245 @param phytype string key of phytype registered at construction time.
246 @return string name of phy to use.
247
Paul Stewart6423bb02012-11-27 17:46:23 -0800248 """
249 phys = self.phys_for_frequency[frequency]
250
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800251 busy_phys = set(net_dev.phy for net_dev in self._wlanifs_in_use)
Paul Stewart6423bb02012-11-27 17:46:23 -0800252 idle_phys = [phy for phy in phys if phy not in busy_phys]
253 phys = idle_phys or phys
254
Christopher Wiley408d1812014-01-13 15:27:43 -0800255 preferred_bus = {'monitor': 'usb', 'managed': 'pci'}.get(phytype)
Paul Stewart6423bb02012-11-27 17:46:23 -0800256 preferred_phys = [phy for phy in phys
257 if self.phy_bus_type[phy] == preferred_bus]
258 phys = preferred_phys or phys
259
260 return phys[0]
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700261
262
Christopher Wiley408d1812014-01-13 15:27:43 -0800263 def get_wlanif(self, frequency, phytype, same_phy_as=None):
264 """Get a WiFi device that supports the given frequency and type.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700265
266 @param frequency int WiFi frequency to support.
267 @param phytype string type of phy (e.g. 'monitor').
Paul Stewart51b0f382013-06-12 09:03:02 -0700268 @param same_phy_as string create the interface on the same phy as this.
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700269 @return string WiFi device.
270
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700271 """
Paul Stewart51b0f382013-06-12 09:03:02 -0700272 if same_phy_as:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800273 for net_dev in self._interfaces:
274 if net_dev.if_name == same_phy_as:
275 phy = net_dev.phy
276 break
Paul Stewart51b0f382013-06-12 09:03:02 -0700277 else:
278 raise error.TestFail('Unable to find phy for interface %s' %
279 same_phy_as)
Paul Stewart6423bb02012-11-27 17:46:23 -0800280 elif frequency in self.phys_for_frequency:
281 phy = self._get_phy_for_frequency(frequency, phytype)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700282 else:
Christopher Wiley408d1812014-01-13 15:27:43 -0800283 raise error.TestFail('Unable to find phy for frequency %d' %
284 frequency)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700285
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800286 # If we have a suitable unused interface sitting around on this
287 # phy, reuse it.
288 for net_dev in set(self._interfaces) - set(self._wlanifs_in_use):
289 if net_dev.phy == phy and net_dev.if_type == phytype:
290 self._wlanifs_in_use.append(net_dev)
291 return net_dev.if_name
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700292
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800293 # Because we can reuse interfaces, we have to iteratively find a good
294 # interface name.
295 name_exists = lambda name: bool([net_dev
296 for net_dev in self._interfaces
297 if net_dev.if_name == name])
298 if_name = lambda index: '%s%d' % (phytype, index)
299 if_index = len(self._interfaces)
300 while name_exists(if_name(if_index)):
301 if_index += 1
302 net_dev = NetDev(phy=phy, if_name=if_name(if_index), if_type=phytype,
303 inherited=False)
304 self._interfaces.append(net_dev)
305 self._wlanifs_in_use.append(net_dev)
306 self.iw_runner.add_interface(phy, net_dev.if_name, phytype)
307 return net_dev.if_name
Paul Stewart6423bb02012-11-27 17:46:23 -0800308
309
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800310 def release_interface(self, wlanif):
311 """Release a device allocated throuhg get_wlanif().
Christopher Wiley4c6a32c2013-04-24 13:37:51 -0700312
313 @param wlanif string name of device to release.
314
315 """
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800316 for net_dev in self._wlanifs_in_use:
317 if net_dev.if_name == wlanif:
318 self._wlanifs_in_use.remove(net_dev)
Christopher Wiley9b406202013-05-06 14:07:49 -0700319
320
321 def require_capabilities(self, requirements, fatal_failure=False):
322 """Require capabilities of this LinuxSystem.
323
324 Check that capabilities in |requirements| exist on this system.
325 Raise and exception to skip but not fail the test if said
326 capabilities are not found. Pass |fatal_failure| to cause this
327 error to become a test failure.
328
329 @param requirements list of CAPABILITY_* defined above.
330 @param fatal_failure bool True iff failures should be fatal.
331
332 """
333 to_be_raised = error.TestNAError
334 if fatal_failure:
335 to_be_raised = error.TestFail
336 missing = [cap for cap in requirements if not cap in self.capabilities]
337 if missing:
338 raise to_be_raised('AP on %s is missing required capabilites: %r' %
339 (self.role, missing))
Peter Qiu2f973252014-02-20 15:30:37 -0800340
341
342 def set_antenna_bitmap(self, tx_bitmap, rx_bitmap):
343 """Setup antenna bitmaps for all the phys.
344
345 @param tx_bitmap int bitmap of allowed antennas to use for TX
346 @param rx_bitmap int bitmap of allowed antennas to use for RX
347
348 """
349 for phy in self.phy_list:
350 self.iw_runner.set_antenna_bitmap(phy.name, tx_bitmap, rx_bitmap)
351
352
353 def set_default_antenna_bitmap(self):
354 """Setup default antenna bitmaps for all the phys."""
355 for phy in self.phy_list:
356 self.iw_runner.set_antenna_bitmap(phy.name, phy.avail_tx_antennas,
357 phy.avail_rx_antennas)