blob: 4b1bd777eff001808413056c38aa6dbb8053ce05 [file] [log] [blame]
Kris Rambish38521312013-10-16 23:39:51 -07001# Copyright (c) 2013 The Chromium 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
5import collections
Christopher Wiley99e9c4a2013-12-09 11:16:52 -08006import logging
Dane Pollock47566ae2017-05-02 13:47:55 -07007import operator
Christopher Wiley7bd2e082013-10-16 17:40:40 -07008import re
Kris Rambish38521312013-10-16 23:39:51 -07009
Christopher Wiley99e9c4a2013-12-09 11:16:52 -080010from autotest_lib.client.common_lib import error
Christopher Wileyf9cb0922013-11-01 09:12:21 -070011from autotest_lib.client.common_lib import utils
Peter Qiu1e310902014-03-04 12:51:22 -080012from autotest_lib.client.common_lib.cros.network import iw_event_logger
Christopher Wileyf9cb0922013-11-01 09:12:21 -070013
mukesh agrawal385c10a2015-04-28 13:43:39 -070014# These must mirror the values in 'iw list' output.
15CHAN_FLAG_DISABLED = 'disabled'
16CHAN_FLAG_NO_IR = 'no IR'
17CHAN_FLAG_PASSIVE_SCAN = 'passive scan'
18CHAN_FLAG_RADAR_DETECT = 'radar detection'
mukesh agrawal064ea972015-06-26 10:00:53 -070019DEV_MODE_AP = 'AP'
20DEV_MODE_IBSS = 'IBSS'
21DEV_MODE_MONITOR = 'monitor'
Dane Pollock47566ae2017-05-02 13:47:55 -070022DEV_MODE_MESH_POINT = 'mesh point'
23DEV_MODE_STATION = 'managed'
24SUPPORTED_DEV_MODES = (DEV_MODE_AP, DEV_MODE_IBSS, DEV_MODE_MONITOR,
25 DEV_MODE_MESH_POINT, DEV_MODE_STATION)
Christopher Wileyf9cb0922013-11-01 09:12:21 -070026
Kris Rambish38521312013-10-16 23:39:51 -070027HT20 = 'HT20'
28HT40_ABOVE = 'HT40+'
29HT40_BELOW = 'HT40-'
30
bmahadevc3d2c3b2013-10-28 18:46:09 -070031SECURITY_OPEN = 'open'
32SECURITY_WEP = 'wep'
33SECURITY_WPA = 'wpa'
34SECURITY_WPA2 = 'wpa2'
mukesh agrawal064ea972015-06-26 10:00:53 -070035# Mixed mode security is WPA2/WPA
bmahadevc3d2c3b2013-10-28 18:46:09 -070036SECURITY_MIXED = 'mixed'
37
Kris Rambish38521312013-10-16 23:39:51 -070038# Table of lookups between the output of item 'secondary channel offset:' from
39# iw <device> scan to constants.
40
41HT_TABLE = {'no secondary': HT20,
42 'above': HT40_ABOVE,
43 'below': HT40_BELOW}
44
mukesh agrawal385c10a2015-04-28 13:43:39 -070045IwBand = collections.namedtuple(
46 'Band', ['num', 'frequencies', 'frequency_flags', 'mcs_indices'])
bmahadevc3d2c3b2013-10-28 18:46:09 -070047IwBss = collections.namedtuple('IwBss', ['bss', 'frequency', 'ssid', 'security',
Kris Rambishc122cf42015-06-03 14:04:38 -070048 'ht', 'signal'])
Christopher Wileyf671a5a2013-12-13 15:44:41 -080049IwNetDev = collections.namedtuple('IwNetDev', ['phy', 'if_name', 'if_type'])
Peter Qiu6f5752e2014-02-14 15:36:53 -080050IwTimedScan = collections.namedtuple('IwTimedScan', ['time', 'bss_list'])
Christopher Wileyf671a5a2013-12-13 15:44:41 -080051
Paul Stewart27ecc5d2013-11-13 16:56:41 -080052# The fields for IwPhy are as follows:
53# name: string name of the phy, such as "phy0"
54# bands: list of IwBand objects.
55# modes: List of strings containing interface modes supported, such as "AP".
Paul Stewartd4ee8cc2015-04-20 20:27:03 -070056# commands: List of strings containing nl80211 commands supported, such as
Paul Stewart27ecc5d2013-11-13 16:56:41 -080057# "authenticate".
Paul Stewartd4ee8cc2015-04-20 20:27:03 -070058# features: List of strings containing nl80211 features supported, such as
59# "T-DLS".
mukesh agrawal2b758ee2014-01-27 18:03:03 -080060# max_scan_ssids: Maximum number of SSIDs which can be scanned at once.
61IwPhy = collections.namedtuple(
Paul Stewartd4ee8cc2015-04-20 20:27:03 -070062 'Phy', ['name', 'bands', 'modes', 'commands', 'features',
63 'max_scan_ssids', 'avail_tx_antennas', 'avail_rx_antennas',
Peter Qiu3ef31f02014-10-17 14:25:57 -070064 'supports_setting_antenna_mask', 'support_vht'])
Kris Rambish38521312013-10-16 23:39:51 -070065
66DEFAULT_COMMAND_IW = 'iw'
67
Roshan Piusa5523932015-10-23 17:00:16 -070068# Redirect stderr to stdout on Cros since adb commands cannot distinguish them
69# on Brillo.
70IW_TIME_COMMAND_FORMAT = '(time -p %s) 2>&1'
71IW_TIME_COMMAND_OUTPUT_START = 'real'
Peter Qiu6f5752e2014-02-14 15:36:53 -080072
Christopher Wiley99e9c4a2013-12-09 11:16:52 -080073IW_LINK_KEY_BEACON_INTERVAL = 'beacon int'
74IW_LINK_KEY_DTIM_PERIOD = 'dtim period'
75IW_LINK_KEY_FREQUENCY = 'freq'
Dane Pollock47566ae2017-05-02 13:47:55 -070076IW_LINK_KEY_SIGNAL = 'signal'
vikram hirehal690ca482017-05-22 12:36:52 -070077IW_LINK_KEY_RX_BITRATE = 'rx bitrate'
Zachary Marcusddb1ec52018-05-23 17:18:19 -070078IW_LINK_KEY_RX_DROPS = 'rx drop misc'
79IW_LINK_KEY_RX_PACKETS = 'rx packets'
vikram hirehal690ca482017-05-22 12:36:52 -070080IW_LINK_KEY_TX_BITRATE = 'tx bitrate'
Zachary Marcusddb1ec52018-05-23 17:18:19 -070081IW_LINK_KEY_TX_FAILURES = 'tx failed'
82IW_LINK_KEY_TX_PACKETS = 'tx packets'
83IW_LINK_KEY_TX_RETRIES = 'tx retries'
Peter Qiu1e310902014-03-04 12:51:22 -080084IW_LOCAL_EVENT_LOG_FILE = './debug/iw_event_%d.log'
Christopher Wiley99e9c4a2013-12-09 11:16:52 -080085
86
Dane Pollock47566ae2017-05-02 13:47:55 -070087def _get_all_link_keys(link_information):
88 """Parses link or station dump output for link key value pairs.
89
90 Link or station dump information is in the format below:
91
92 Connected to 74:e5:43:10:4f:c0 (on wlan0)
93 SSID: PMKSACaching_4m9p5_ch1
94 freq: 5220
95 RX: 5370 bytes (37 packets)
96 TX: 3604 bytes (15 packets)
97 signal: -59 dBm
98 tx bitrate: 13.0 MBit/s MCS 1
99
100 bss flags: short-slot-time
101 dtim period: 5
102 beacon int: 100
103
104 @param link_information: string containing the raw link or station dump
105 information as reported by iw. Note that this parsing assumes a single
106 entry, in the case of multiple entries (e.g. listing stations from an
107 AP, or listing mesh peers), the entries must be split on a per
108 peer/client basis before this parsing operation.
109 @return a dictionary containing all the link key/value pairs.
110
111 """
112 link_key_value_pairs = {}
113 keyval_regex = re.compile(r'^\s+(.*):\s+(.*)$')
114 for link_key in link_information.splitlines()[1:]:
115 match = re.search(keyval_regex, link_key)
116 if match:
117 # Station dumps can contain blank lines.
118 link_key_value_pairs[match.group(1)] = match.group(2)
119 return link_key_value_pairs
120
121
122def _extract_bssid(link_information, interface_name, station_dump=False):
123 """Get the BSSID that |interface_name| is associated with.
124
125 See doc for _get_all_link_keys() for expected format of the station or link
126 information entry.
127
128 @param link_information: string containing the raw link or station dump
129 information as reported by iw. Note that this parsing assumes a single
130 entry, in the case of multiple entries (e.g. listing stations from an AP
131 or listing mesh peers), the entries must be split on a per peer/client
132 basis before this parsing operation.
133 @param interface_name: string name of interface (e.g. 'wlan0').
134 @param station_dump: boolean indicator of whether the link information is
135 from a 'station dump' query. If False, it is assumed the string is from
136 a 'link' query.
137 @return string bssid of the current association, or None if no matching
138 association information is found.
139
140 """
141 # We're looking for a line like this when parsing the output of a 'link'
142 # query:
143 # Connected to 04:f0:21:03:7d:bb (on wlan0)
144 # We're looking for a line like this when parsing the output of a
145 # 'station dump' query:
146 # Station 04:f0:21:03:7d:bb (on mesh-5000mhz)
147 identifier = 'Station' if station_dump else 'Connected to'
148 search_re = r'%s ([0-9a-fA-F:]{17}) \(on %s\)' % (identifier,
149 interface_name)
150 match = re.match(search_re, link_information)
151 if match is None:
152 return None
153 return match.group(1)
154
155
Kris Rambish38521312013-10-16 23:39:51 -0700156class IwRunner(object):
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700157 """Defines an interface to the 'iw' command."""
Kris Rambish38521312013-10-16 23:39:51 -0700158
159
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700160 def __init__(self, remote_host=None, command_iw=DEFAULT_COMMAND_IW):
161 self._run = utils.run
Peter Qiu1e310902014-03-04 12:51:22 -0800162 self._host = remote_host
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700163 if remote_host:
164 self._run = remote_host.run
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700165 self._command_iw = command_iw
Peter Qiu1e310902014-03-04 12:51:22 -0800166 self._log_id = 0
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700167
168
Christopher Wiley5689d362014-01-07 15:21:25 -0800169 def _parse_scan_results(self, output):
170 """Parse the output of the 'scan' and 'scan dump' commands.
171
Kris Rambishc122cf42015-06-03 14:04:38 -0700172 Here is an example of what a single network would look like for
173 the input parameter. Some fields have been removed in this example:
174 BSS 00:11:22:33:44:55(on wlan0)
175 freq: 2447
176 beacon interval: 100 TUs
177 signal: -46.00 dBm
178 Information elements from Probe Response frame:
179 SSID: my_open_network
180 Extended supported rates: 24.0 36.0 48.0 54.0
181 HT capabilities:
182 Capabilities: 0x0c
183 HT20
184 HT operation:
185 * primary channel: 8
186 * secondary channel offset: no secondary
187 * STA channel width: 20 MHz
188 RSN: * Version: 1
189 * Group cipher: CCMP
190 * Pairwise ciphers: CCMP
191 * Authentication suites: PSK
192 * Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000)
193
Christopher Wiley5689d362014-01-07 15:21:25 -0800194 @param output: string command output.
195
196 @returns a list of IwBss namedtuples; None if the scan fails
197
198 """
199 bss = None
200 frequency = None
201 ssid = None
202 ht = None
Kris Rambishc122cf42015-06-03 14:04:38 -0700203 signal = None
Christopher Wiley5689d362014-01-07 15:21:25 -0800204 security = None
205 supported_securities = []
206 bss_list = []
207 for line in output.splitlines():
208 line = line.strip()
Paul Stewarta618b122014-02-14 10:28:05 -0800209 bss_match = re.match('BSS ([0-9a-f:]+)', line)
210 if bss_match:
Christopher Wiley5689d362014-01-07 15:21:25 -0800211 if bss != None:
212 security = self.determine_security(supported_securities)
Kris Rambishc122cf42015-06-03 14:04:38 -0700213 iwbss = IwBss(bss, frequency, ssid, security, ht, signal)
Christopher Wiley5689d362014-01-07 15:21:25 -0800214 bss_list.append(iwbss)
215 bss = frequency = ssid = security = ht = None
216 supported_securities = []
Paul Stewarta618b122014-02-14 10:28:05 -0800217 bss = bss_match.group(1)
Christopher Wiley5689d362014-01-07 15:21:25 -0800218 if line.startswith('freq:'):
219 frequency = int(line.split()[1])
Kris Rambishc122cf42015-06-03 14:04:38 -0700220 if line.startswith('signal:'):
221 signal = float(line.split()[1])
Christopher Wiley9fd3eeb2015-01-30 14:04:21 -0800222 if line.startswith('SSID: '):
223 _, ssid = line.split(': ', 1)
Christopher Wiley5689d362014-01-07 15:21:25 -0800224 if line.startswith('* secondary channel offset'):
225 ht = HT_TABLE[line.split(':')[1].strip()]
226 if line.startswith('WPA'):
227 supported_securities.append(SECURITY_WPA)
228 if line.startswith('RSN'):
229 supported_securities.append(SECURITY_WPA2)
230 security = self.determine_security(supported_securities)
Kris Rambishc122cf42015-06-03 14:04:38 -0700231 bss_list.append(IwBss(bss, frequency, ssid, security, ht, signal))
Christopher Wiley5689d362014-01-07 15:21:25 -0800232 return bss_list
233
234
Roshan Piusa5523932015-10-23 17:00:16 -0700235 def _parse_scan_time(self, output):
236 """
237 Parse the scan time in seconds from the output of the 'time -p "scan"'
238 command.
239
240 'time -p' Command output format is below:
241 real 0.01
242 user 0.01
243 sys 0.00
244
245 @param output: string command output.
246
247 @returns float time in seconds.
248
249 """
250 output_lines = output.splitlines()
251 for line_num, line in enumerate(output_lines):
252 line = line.strip()
253 if (line.startswith(IW_TIME_COMMAND_OUTPUT_START) and
254 output_lines[line_num + 1].startswith('user') and
255 output_lines[line_num + 2].startswith('sys')):
256 return float(line.split()[1])
257 raise error.TestFail('Could not parse scan time.')
258
259
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700260 def add_interface(self, phy, interface, interface_type):
261 """
262 Add an interface to a WiFi PHY.
263
264 @param phy: string name of PHY to add an interface to.
265 @param interface: string name of interface to add.
266 @param interface_type: string type of interface to add (e.g. 'monitor').
267
268 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700269 self._run('%s phy %s interface add %s type %s' %
270 (self._command_iw, phy, interface, interface_type))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700271
272
273 def disconnect_station(self, interface):
274 """
275 Disconnect a STA from a network.
276
277 @param interface: string name of interface to disconnect.
278
279 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700280 self._run('%s dev %s disconnect' % (self._command_iw, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700281
282
Christopher Wiley142554e2014-07-07 15:59:28 -0700283 def get_current_bssid(self, interface_name):
284 """Get the BSSID that |interface_name| is associated with.
285
286 @param interface_name: string name of interface (e.g. 'wlan0').
287 @return string bssid of our current association, or None.
288
289 """
290 result = self._run('%s dev %s link' %
291 (self._command_iw, interface_name),
292 ignore_status=True)
293 if result.exit_status:
294 # See comment in get_link_value.
295 return None
296
Dane Pollock47566ae2017-05-02 13:47:55 -0700297 return _extract_bssid(result.stdout, interface_name)
Christopher Wiley142554e2014-07-07 15:59:28 -0700298
299
Christopher Wileya3effac2014-02-05 11:16:11 -0800300 def get_interface(self, interface_name):
301 """Get full information about an interface given an interface name.
302
303 @param interface_name: string name of interface (e.g. 'wlan0').
304 @return IwNetDev tuple.
305
306 """
307 matching_interfaces = [iw_if for iw_if in self.list_interfaces()
308 if iw_if.if_name == interface_name]
309 if len(matching_interfaces) != 1:
310 raise error.TestFail('Could not find interface named %s' %
311 interface_name)
312
313 return matching_interfaces[0]
314
315
Peter Qiu5ae96ef2014-09-30 10:02:46 -0700316 def get_link_value(self, interface, iw_link_key):
Christopher Wiley99e9c4a2013-12-09 11:16:52 -0800317 """Get the value of a link property for |interface|.
318
Dane Pollock47566ae2017-05-02 13:47:55 -0700319 Checks the link using iw, and parses the result to return a link key.
Christopher Wiley99e9c4a2013-12-09 11:16:52 -0800320
321 @param iw_link_key: string one of IW_LINK_KEY_* defined above.
322 @param interface: string desired value of iw link property.
Dane Pollock47566ae2017-05-02 13:47:55 -0700323 @return string containing the corresponding link property value, None
324 if there was a parsing error or the iw command failed.
Christopher Wiley99e9c4a2013-12-09 11:16:52 -0800325
326 """
327 result = self._run('%s dev %s link' % (self._command_iw, interface),
Peter Qiu5ae96ef2014-09-30 10:02:46 -0700328 ignore_status=True)
Christopher Wiley99e9c4a2013-12-09 11:16:52 -0800329 if result.exit_status:
330 # When roaming, there is a period of time for mac80211 based drivers
331 # when the driver is 'associated' with an SSID but not a particular
332 # BSS. This causes iw to return an error code (-2) when attempting
333 # to retrieve information specific to the BSS. This does not happen
334 # in mwifiex drivers.
335 return None
Dane Pollock47566ae2017-05-02 13:47:55 -0700336 actual_value = _get_all_link_keys(result.stdout).get(iw_link_key)
337 if actual_value is not None:
338 logging.info('Found iw link key %s with value %s.',
339 iw_link_key, actual_value)
Christopher Wiley99e9c4a2013-12-09 11:16:52 -0800340 return actual_value
341
342
Dane Pollock47566ae2017-05-02 13:47:55 -0700343 def get_station_dump(self, interface):
344 """Gets information about connected peers.
345
346 Returns information about the currently connected peers. When the host
347 is in station mode, it returns a single entry, with information about
348 the link to the AP it is currently connected to. If the host is in mesh
349 or AP mode, it can return multiple entries, one for each connected
350 station, or mesh peer.
351
352 @param interface: string name of interface to get peer information
353 from.
354 @return a list of dictionaries with link information about each
355 connected peer (ordered by peer mac address).
356
357 """
358 result = self._run('%s dev %s station dump' %
359 (self._command_iw, interface))
360 parts = re.split(r'^Station ', result.stdout, flags=re.MULTILINE)[1:]
361 peer_list_raw = ['Station ' + x for x in parts]
362 parsed_peer_info = []
Zachary Marcusddb1ec52018-05-23 17:18:19 -0700363
Dane Pollock47566ae2017-05-02 13:47:55 -0700364 for peer in peer_list_raw:
365 peer_link_keys = _get_all_link_keys(peer)
Zachary Marcusddb1ec52018-05-23 17:18:19 -0700366 rssi_str = peer_link_keys.get(IW_LINK_KEY_SIGNAL, '0')
Dane Pollock47566ae2017-05-02 13:47:55 -0700367 rssi_int = int(rssi_str.split()[0])
Zachary Marcusddb1ec52018-05-23 17:18:19 -0700368
369 tx_bitrate = peer_link_keys.get(IW_LINK_KEY_TX_BITRATE, '0')
370 tx_failures = int(peer_link_keys.get(IW_LINK_KEY_TX_FAILURES, 0))
371 tx_packets = int(peer_link_keys.get(IW_LINK_KEY_TX_PACKETS, 0))
372 tx_retries = int(peer_link_keys.get(IW_LINK_KEY_TX_RETRIES, 0))
373
374 rx_bitrate = peer_link_keys.get(IW_LINK_KEY_RX_BITRATE, '0')
375 rx_drops = int(peer_link_keys.get(IW_LINK_KEY_RX_DROPS, 0))
376 rx_packets = int(peer_link_keys.get(IW_LINK_KEY_RX_PACKETS, 0))
377
Dane Pollock47566ae2017-05-02 13:47:55 -0700378 mac = _extract_bssid(link_information=peer,
379 interface_name=interface,
380 station_dump=True)
Zachary Marcusddb1ec52018-05-23 17:18:19 -0700381
382 # If any of these are missing, they will be None
383 peer_info = {'rssi_int': rssi_int,
384 'rssi_str': rssi_str,
385 'tx_bitrate': tx_bitrate,
386 'tx_failures': tx_failures,
387 'tx_packets': tx_packets,
388 'tx_retries': tx_retries,
389 'rx_bitrate': rx_bitrate,
390 'rx_drops': rx_drops,
391 'rx_packets': rx_packets,
392 'mac': mac}
393
394 # don't evaluate if tx_packets 0
395 if tx_packets:
396 peer_info['tx_retry_rate'] = tx_retries / float(tx_packets)
397 peer_info['tx_failure_rate'] = tx_failures / float(tx_packets)
398
399 # don't evaluate if rx_packets is 0
400 if rx_packets:
401 peer_info['rx_drop_rate'] = rx_drops / float(rx_packets)
402
403 parsed_peer_info.append(peer_info)
Dane Pollock47566ae2017-05-02 13:47:55 -0700404 return sorted(parsed_peer_info, key=operator.itemgetter('mac'))
405
406
407 def get_operating_mode(self, interface):
408 """Gets the operating mode for |interface|.
409
410 @param interface: string name of interface to get peer information
411 about.
412
413 @return string one of DEV_MODE_* defined above, or None if no mode is
414 found, or if an unsupported mode is found.
415
416 """
417 ret = self._run('%s dev %s info' % (self._command_iw, interface))
418 mode_regex = r'^\s*type (.*)$'
419 match = re.search(mode_regex, ret.stdout, re.MULTILINE)
420 if match:
421 operating_mode = match.group(1)
422 if operating_mode in SUPPORTED_DEV_MODES:
423 return operating_mode
424 logging.warning(
425 'Unsupported operating mode %s found for interface: %s. '
426 'Supported modes: %s', operating_mode, interface,
427 SUPPORTED_DEV_MODES)
428 return None
429
430
431 def get_radio_config(self, interface):
432 """Gets the channel information of a specfic interface using iw.
433
434 @param interface: string name of interface to get radio information
435 from.
436
437 @return dictionary containing the channel information.
438
439 """
440 channel_config = {}
441 ret = self._run('%s dev %s info' % (self._command_iw, interface))
442 channel_config_regex = (r'^\s*channel ([0-9]+) \(([0-9]+) MHz\), '
443 'width: ([2,4,8]0) MHz, center1: ([0-9]+) MHz')
444 match = re.search(channel_config_regex, ret.stdout, re.MULTILINE)
445
446 if match:
447 channel_config['number'] = int(match.group(1))
448 channel_config['freq'] = int(match.group(2))
449 channel_config['width'] = int(match.group(3))
450 channel_config['center1_freq'] = int(match.group(4))
451
452 return channel_config
453
454
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700455 def ibss_join(self, interface, ssid, frequency):
456 """
457 Join a WiFi interface to an IBSS.
458
459 @param interface: string name of interface to join to the IBSS.
460 @param ssid: string SSID of IBSS to join.
461 @param frequency: int frequency of IBSS in Mhz.
462
463 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700464 self._run('%s dev %s ibss join %s %d' %
465 (self._command_iw, interface, ssid, frequency))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700466
467
468 def ibss_leave(self, interface):
469 """
470 Leave an IBSS.
471
472 @param interface: string name of interface to remove from the IBSS.
473
474 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700475 self._run('%s dev %s ibss leave' % (self._command_iw, interface))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700476
477
Christopher Wiley604a3502014-01-13 17:23:47 -0800478 def list_interfaces(self, desired_if_type=None):
479 """List WiFi related interfaces on this system.
480
481 @param desired_if_type: string type of interface to filter
482 our returned list of interfaces for (e.g. 'managed').
483
484 @return list of IwNetDev tuples.
485
486 """
Filipe Brandenburgerfe0694c2015-07-14 10:46:02 -0700487
488 # Parse output in the following format:
489 #
490 # $ adb shell iw dev
491 # phy#0
492 # Unnamed/non-netdev interface
493 # wdev 0x2
494 # addr aa:bb:cc:dd:ee:ff
495 # type P2P-device
496 # Interface wlan0
497 # ifindex 4
498 # wdev 0x1
499 # addr aa:bb:cc:dd:ee:ff
500 # ssid Whatever
501 # type managed
502
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700503 output = self._run('%s dev' % self._command_iw).stdout
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700504 interfaces = []
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800505 phy = None
506 if_name = None
507 if_type = None
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700508 for line in output.splitlines():
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800509 m = re.match('phy#([0-9]+)', line)
510 if m:
511 phy = 'phy%d' % int(m.group(1))
Filipe Brandenburgerfe0694c2015-07-14 10:46:02 -0700512 if_name = None
513 if_type = None
514 continue
515 if not phy:
516 continue
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700517 m = re.match('[\s]*Interface (.*)', line)
518 if m:
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800519 if_name = m.group(1)
Filipe Brandenburgerfe0694c2015-07-14 10:46:02 -0700520 continue
521 if not if_name:
522 continue
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800523 # Common values for type are 'managed', 'monitor', and 'IBSS'.
524 m = re.match('[\s]*type ([a-zA-Z]+)', line)
525 if m:
526 if_type = m.group(1)
Christopher Wileyf671a5a2013-12-13 15:44:41 -0800527 interfaces.append(IwNetDev(phy=phy, if_name=if_name,
528 if_type=if_type))
529 # One phy may have many interfaces, so don't reset it.
Filipe Brandenburgerfe0694c2015-07-14 10:46:02 -0700530 if_name = None
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700531
Christopher Wiley604a3502014-01-13 17:23:47 -0800532 if desired_if_type:
533 interfaces = [interface for interface in interfaces
534 if interface.if_type == desired_if_type]
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700535 return interfaces
536
537
538 def list_phys(self):
539 """
540 List WiFi PHYs on the given host.
541
542 @return list of IwPhy tuples.
543
544 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700545 output = self._run('%s list' % self._command_iw).stdout
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800546
547 pending_phy_name = None
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700548 current_band = None
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800549 current_section = None
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700550 all_phys = []
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800551
552 def add_pending_phy():
553 """Add the pending phy into |all_phys|."""
554 bands = tuple(IwBand(band.num,
555 tuple(band.frequencies),
mukesh agrawal385c10a2015-04-28 13:43:39 -0700556 dict(band.frequency_flags),
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800557 tuple(band.mcs_indices))
558 for band in pending_phy_bands)
559 new_phy = IwPhy(pending_phy_name,
560 bands,
561 tuple(pending_phy_modes),
562 tuple(pending_phy_commands),
Paul Stewartd4ee8cc2015-04-20 20:27:03 -0700563 tuple(pending_phy_features),
Peter Qiu2f973252014-02-20 15:30:37 -0800564 pending_phy_max_scan_ssids,
565 pending_phy_tx_antennas,
Christopher Wileyfaaecd82014-05-29 10:53:40 -0700566 pending_phy_rx_antennas,
Peter Qiu3ef31f02014-10-17 14:25:57 -0700567 pending_phy_tx_antennas and pending_phy_rx_antennas,
568 pending_phy_support_vht)
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800569 all_phys.append(new_phy)
570
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700571 for line in output.splitlines():
572 match_phy = re.search('Wiphy (.*)', line)
573 if match_phy:
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800574 if pending_phy_name:
575 add_pending_phy()
576 pending_phy_name = match_phy.group(1)
577 pending_phy_bands = []
578 pending_phy_modes = []
579 pending_phy_commands = []
Paul Stewartd4ee8cc2015-04-20 20:27:03 -0700580 pending_phy_features = []
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800581 pending_phy_max_scan_ssids = None
Peter Qiu2f973252014-02-20 15:30:37 -0800582 pending_phy_tx_antennas = 0
583 pending_phy_rx_antennas = 0
Peter Qiu3ef31f02014-10-17 14:25:57 -0700584 pending_phy_support_vht = False
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700585 continue
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800586
Paul Stewartd4a6e8c2014-01-06 08:00:17 -0800587 match_section = re.match('\s*(\w.*):\s*$', line)
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800588 if match_section:
589 current_section = match_section.group(1)
590 match_band = re.match('Band (\d+)', current_section)
591 if match_band:
592 current_band = IwBand(num=int(match_band.group(1)),
593 frequencies=[],
mukesh agrawal385c10a2015-04-28 13:43:39 -0700594 frequency_flags={},
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800595 mcs_indices=[])
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800596 pending_phy_bands.append(current_band)
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700597 continue
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800598
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800599 # Check for max_scan_ssids. This isn't a section, but it
600 # also isn't within a section.
601 match_max_scan_ssids = re.match('\s*max # scan SSIDs: (\d+)',
602 line)
603 if match_max_scan_ssids and pending_phy_name:
604 pending_phy_max_scan_ssids = int(
605 match_max_scan_ssids.group(1))
606 continue
607
608 if (current_section == 'Supported interface modes' and
609 pending_phy_name):
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800610 mode_match = re.search('\* (\w+)', line)
611 if mode_match:
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800612 pending_phy_modes.append(mode_match.group(1))
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800613 continue
614
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800615 if current_section == 'Supported commands' and pending_phy_name:
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800616 command_match = re.search('\* (\w+)', line)
617 if command_match:
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800618 pending_phy_commands.append(command_match.group(1))
Paul Stewart27ecc5d2013-11-13 16:56:41 -0800619 continue
620
Paul Stewartd18a22a2014-12-10 23:55:00 -0800621 if (current_section is not None and
622 current_section.startswith('VHT Capabilities') and
623 pending_phy_name):
Peter Qiu3ef31f02014-10-17 14:25:57 -0700624 pending_phy_support_vht = True
625 continue
626
Peter Qiu2f973252014-02-20 15:30:37 -0800627 match_avail_antennas = re.match('\s*Available Antennas: TX (\S+)'
628 ' RX (\S+)', line)
629 if match_avail_antennas and pending_phy_name:
630 pending_phy_tx_antennas = int(
631 match_avail_antennas.group(1), 16)
632 pending_phy_rx_antennas = int(
633 match_avail_antennas.group(2), 16)
634 continue
635
Paul Stewartd4ee8cc2015-04-20 20:27:03 -0700636 match_device_support = re.match('\s*Device supports (.*)\.', line)
637 if match_device_support and pending_phy_name:
638 pending_phy_features.append(match_device_support.group(1))
639 continue
640
mukesh agrawal2b758ee2014-01-27 18:03:03 -0800641 if not all([current_band, pending_phy_name,
642 line.startswith('\t')]):
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700643 continue
644
mukesh agrawal385c10a2015-04-28 13:43:39 -0700645 # E.g.
646 # * 2412 MHz [1] (20.0 dBm)
647 # * 2467 MHz [12] (20.0 dBm) (passive scan)
648 # * 2472 MHz [13] (disabled)
649 # * 5260 MHz [52] (19.0 dBm) (no IR, radar detection)
650 match_chan_info = re.search(
651 r'(?P<frequency>\d+) MHz'
652 r' (?P<chan_num>\[\d+\])'
653 r'(?: \((?P<tx_power_limit>[0-9.]+ dBm)\))?'
654 r'(?: \((?P<flags>[a-zA-Z, ]+)\))?', line)
655 if match_chan_info:
656 frequency = int(match_chan_info.group('frequency'))
657 current_band.frequencies.append(frequency)
658 flags_string = match_chan_info.group('flags')
659 if flags_string:
660 current_band.frequency_flags[frequency] = frozenset(
661 flags_string.split(','))
662 else:
663 # Populate the dict with an empty set, to make
664 # things uniform for client code.
665 current_band.frequency_flags[frequency] = frozenset()
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700666 continue
667
668 # re_mcs needs to match something like:
669 # HT TX/RX MCS rate indexes supported: 0-15, 32
670 if re.search('HT TX/RX MCS rate indexes supported: ', line):
671 rate_string = line.split(':')[1].strip()
672 for piece in rate_string.split(','):
673 if piece.find('-') > 0:
674 # Must be a range like ' 0-15'
675 begin, end = piece.split('-')
676 for index in range(int(begin), int(end) + 1):
677 current_band.mcs_indices.append(index)
678 else:
679 # Must be a single rate like '32 '
680 current_band.mcs_indices.append(int(piece))
Paul Stewart89589772014-02-07 08:38:10 -0800681 if pending_phy_name:
682 add_pending_phy()
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700683 return all_phys
684
685
686 def remove_interface(self, interface, ignore_status=False):
687 """
688 Remove a WiFi interface from a PHY.
689
690 @param interface: string name of interface (e.g. mon0)
691 @param ignore_status: boolean True iff we should ignore failures
692 to remove the interface.
693
694 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700695 self._run('%s dev %s del' % (self._command_iw, interface),
696 ignore_status=ignore_status)
Kris Rambish38521312013-10-16 23:39:51 -0700697
698
bmahadevc3d2c3b2013-10-28 18:46:09 -0700699 def determine_security(self, supported_securities):
700 """Determines security from the given list of supported securities.
701
702 @param supported_securities: list of supported securities from scan
703
704 """
705 if not supported_securities:
706 security = SECURITY_OPEN
707 elif len(supported_securities) == 1:
708 security = supported_securities[0]
709 else:
710 security = SECURITY_MIXED
711 return security
712
713
Christopher Wiley4b01f862013-11-01 09:56:03 -0700714 def scan(self, interface, frequencies=(), ssids=()):
Kris Rambish38521312013-10-16 23:39:51 -0700715 """Performs a scan.
716
717 @param interface: the interface to run the iw command against
Christopher Wiley4b01f862013-11-01 09:56:03 -0700718 @param frequencies: list of int frequencies in Mhz to scan.
719 @param ssids: list of string SSIDs to send probe requests for.
Kris Rambish38521312013-10-16 23:39:51 -0700720
Christopher Wiley5689d362014-01-07 15:21:25 -0800721 @returns a list of IwBss namedtuples; None if the scan fails
Kris Rambish38521312013-10-16 23:39:51 -0700722
723 """
Peter Qiu6f5752e2014-02-14 15:36:53 -0800724 scan_result = self.timed_scan(interface, frequencies, ssids)
725 if scan_result is None:
726 return None
727 return scan_result.bss_list
728
729
730 def timed_scan(self, interface, frequencies=(), ssids=()):
731 """Performs a timed scan.
732
733 @param interface: the interface to run the iw command against
734 @param frequencies: list of int frequencies in Mhz to scan.
735 @param ssids: list of string SSIDs to send probe requests for.
736
737 @returns a IwTimedScan namedtuple; None if the scan fails
738
739 """
Christopher Wiley4b01f862013-11-01 09:56:03 -0700740 freq_param = ''
741 if frequencies:
742 freq_param = ' freq %s' % ' '.join(map(str, frequencies))
743 ssid_param = ''
744 if ssids:
745 ssid_param = ' ssid "%s"' % '" "'.join(ssids)
746
Roshan Piusa5523932015-10-23 17:00:16 -0700747 iw_command = '%s dev %s scan%s%s' % (self._command_iw,
748 interface, freq_param, ssid_param)
749 command = IW_TIME_COMMAND_FORMAT % iw_command
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700750 scan = self._run(command, ignore_status=True)
Jason Abele6d455492013-11-01 13:01:22 -0700751 if scan.exit_status != 0:
Kris Rambish38521312013-10-16 23:39:51 -0700752 # The device was busy
Kris Rambish299dad52014-01-14 20:29:49 -0800753 logging.debug('scan exit_status: %d', scan.exit_status)
754 return None
Peter Qiuc4beba02014-03-24 14:46:24 -0700755 if not scan.stdout:
Roshan Piusa5523932015-10-23 17:00:16 -0700756 raise error.TestFail('Missing scan parse time')
757
758 if scan.stdout.startswith(IW_TIME_COMMAND_OUTPUT_START):
Peter Qiuc4beba02014-03-24 14:46:24 -0700759 logging.debug('Empty scan result')
760 bss_list = []
761 else:
762 bss_list = self._parse_scan_results(scan.stdout)
Roshan Piusa5523932015-10-23 17:00:16 -0700763 scan_time = self._parse_scan_time(scan.stdout)
Peter Qiu6f5752e2014-02-14 15:36:53 -0800764 return IwTimedScan(scan_time, bss_list)
Kris Rambish38521312013-10-16 23:39:51 -0700765
Kris Rambish38521312013-10-16 23:39:51 -0700766
Christopher Wiley5689d362014-01-07 15:21:25 -0800767 def scan_dump(self, interface):
768 """Dump the contents of the scan cache.
769
770 Note that this does not trigger a scan. Instead, it returns
771 the kernel's idea of what BSS's are currently visible.
772
773 @param interface: the interface to run the iw command against
774
775 @returns a list of IwBss namedtuples; None if the scan fails
776
777 """
778 result = self._run('%s dev %s scan dump' % (self._command_iw,
779 interface))
780 return self._parse_scan_results(result.stdout)
Kris Rambish38521312013-10-16 23:39:51 -0700781
782
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700783 def set_tx_power(self, interface, power):
784 """
785 Set the transmission power for an interface.
786
787 @param interface: string name of interface to set Tx power on.
788 @param power: string power parameter. (e.g. 'auto').
789
790 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700791 self._run('%s dev %s set txpower %s' %
792 (self._command_iw, interface, power))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700793
794
Peter Qiuc4beba02014-03-24 14:46:24 -0700795 def set_freq(self, interface, freq):
796 """
797 Set the frequency for an interface.
798
799 @param interface: string name of interface to set frequency on.
800 @param freq: int frequency
801
802 """
803 self._run('%s dev %s set freq %d' %
804 (self._command_iw, interface, freq))
805
806
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700807 def set_regulatory_domain(self, domain_string):
808 """
Paul Stewart24406442014-02-13 10:23:49 -0800809 Set the regulatory domain of the current machine. Note that
810 the regulatory change happens asynchronously to the exit of
811 this function.
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700812
813 @param domain_string: string regulatory domain name (e.g. 'US').
814
815 """
Christopher Wileyf9cb0922013-11-01 09:12:21 -0700816 self._run('%s reg set %s' % (self._command_iw, domain_string))
Christopher Wiley7bd2e082013-10-16 17:40:40 -0700817
818
Paul Stewart24406442014-02-13 10:23:49 -0800819 def get_regulatory_domain(self):
820 """
821 Get the regulatory domain of the current machine.
822
823 @returns a string containing the 2-letter regulatory domain name
824 (e.g. 'US').
825
826 """
827 output = self._run('%s reg get' % self._command_iw).stdout
Brian Norris8d1a0762018-08-28 19:15:59 -0700828 m = re.search('^country (..):', output, re.MULTILINE)
Paul Stewart24406442014-02-13 10:23:49 -0800829 if not m:
830 return None
831 return m.group(1)
832
833
Kris Rambish6b8a9552015-06-09 11:02:59 -0700834 def wait_for_scan_result(self, interface, bsses=(), ssids=(),
835 timeout_seconds=30, wait_for_all=False):
836 """Returns a list of IWBSS objects for given list of bsses or ssids.
837
838 This method will scan for a given timeout and return all of the networks
839 that have a matching ssid or bss. If wait_for_all is true and all
840 networks are not found within the given timeout an empty list will
841 be returned.
Kris Rambish38521312013-10-16 23:39:51 -0700842
843 @param interface: which interface to run iw against
Kris Rambish6b8a9552015-06-09 11:02:59 -0700844 @param bsses: a list of BSS strings
845 @param ssids: a list of ssid strings
Kris Rambish38521312013-10-16 23:39:51 -0700846 @param timeout_seconds: the amount of time to wait in seconds
Kris Rambish6b8a9552015-06-09 11:02:59 -0700847 @param wait_for_all: True to wait for all listed bsses or ssids; False
848 to return if any of the networks were found
Kris Rambish38521312013-10-16 23:39:51 -0700849
Kris Rambishfc81be72014-06-03 12:44:17 -0700850 @returns a list of IwBss collections that contain the given bss or ssid;
851 if the scan is empty or returns an error code None is returned.
Kris Rambish38521312013-10-16 23:39:51 -0700852
853 """
Zachary Marcusfe67b652018-04-11 15:22:53 -0700854
Kris Rambish299dad52014-01-14 20:29:49 -0800855 logging.info('Performing a scan with a max timeout of %d seconds.',
856 timeout_seconds)
Kris Rambish6b8a9552015-06-09 11:02:59 -0700857
Zachary Marcusfe67b652018-04-11 15:22:53 -0700858 # If the in-progress scan takes more than 30 seconds to
859 # complete it will most likely never complete; abort.
860 # See crbug.com/309148
harpreet23370612018-11-16 17:22:19 -0800861 scan_results = list()
862 try:
863 scan_results = utils.poll_for_condition(
864 condition=lambda: self.scan(interface),
865 timeout=timeout_seconds,
866 sleep_interval=5, # to allow in-progress scans to complete
867 desc='Timed out getting IWBSSes that match desired')
868 except utils.TimeoutError as e:
869 pass
Kris Rambishfc81be72014-06-03 12:44:17 -0700870
Zachary Marcusfe67b652018-04-11 15:22:53 -0700871 if not scan_results: # empty list or None
Kris Rambishfc81be72014-06-03 12:44:17 -0700872 return None
Zachary Marcusfe67b652018-04-11 15:22:53 -0700873
874 # get all IWBSSes from the scan that match any of the desired
875 # ssids or bsses passed in
876 matching_iwbsses = filter(
877 lambda iwbss: iwbss.ssid in ssids or iwbss.bss in bsses,
878 scan_results)
879 if wait_for_all:
880 found_bsses = [iwbss.bss for iwbss in matching_iwbsses]
881 found_ssids = [iwbss.ssid for iwbss in matching_iwbsses]
882 # if an expected bss or ssid was not found, and it was required
883 # by the caller that all expected be found, return empty list
884 if any(bss not in found_bsses for bss in bsses) or any(
885 ssid not in found_ssids for ssid in ssids):
886 return list()
887 return list(matching_iwbsses)
Paul Stewart6ddeba72013-11-18 10:08:23 -0800888
889
890 def wait_for_link(self, interface, timeout_seconds=10):
891 """Waits until a link completes on |interface|.
892
893 @param interface: which interface to run iw against.
894 @param timeout_seconds: the amount of time to wait in seconds.
895
896 @returns True if link was established before the timeout.
897
898 """
Zachary Marcusfe67b652018-04-11 15:22:53 -0700899 return utils.poll_for_condition(
900 # gets link results from running dev command, then assumes the
901 # link is completed if 'Not connected' is absent from stdout
902 condition=lambda: 'Not connected' not in self._run(
903 '%s dev %s link' % (self._command_iw, interface)).stdout,
904 timeout=timeout_seconds,
905 sleep_interval=1,
906 desc='Wait until a link completes on |interface|')
Peter Qiu2f973252014-02-20 15:30:37 -0800907
908
909 def set_antenna_bitmap(self, phy, tx_bitmap, rx_bitmap):
910 """Set antenna chain mask on given phy (radio).
911
912 This function will set the antennas allowed to use for TX and
913 RX on the |phy| based on the |tx_bitmap| and |rx_bitmap|.
914 This command is only allowed when the interfaces on the phy are down.
915
916 @param phy: phy name
917 @param tx_bitmap: bitmap of allowed antennas to use for TX
918 @param rx_bitmap: bitmap of allowed antennas to use for RX
919
920 """
921 command = '%s phy %s set antenna %d %d' % (self._command_iw, phy,
922 tx_bitmap, rx_bitmap)
923 self._run(command)
Peter Qiu1e310902014-03-04 12:51:22 -0800924
925
926 def get_event_logger(self):
927 """Create and return a IwEventLogger object.
928
929 @returns a IwEventLogger object.
930
931 """
932 local_file = IW_LOCAL_EVENT_LOG_FILE % (self._log_id)
933 self._log_id += 1
934 return iw_event_logger.IwEventLogger(self._host, self._command_iw,
935 local_file)
Kris Rambishb9b46852014-12-19 15:29:58 -0800936
937
938 def vht_supported(self):
939 """Returns True if VHT is supported; False otherwise."""
940 result = self._run('%s list' % self._command_iw).stdout
941 if 'VHT Capabilities' in result:
942 return True
943 return False
Roshan Pius2c5f99b2015-10-20 18:45:19 -0700944
945
946 def frequency_supported(self, frequency):
947 """Returns True if the given frequency is supported; False otherwise.
948
949 @param frequency: int Wifi frequency to check if it is supported by
950 DUT.
951 """
952 phys = self.list_phys()
953 for phy in phys:
954 for band in phy.bands:
955 if frequency in band.frequencies:
956 return True
957 return False
Edward Hill7d411222017-10-02 17:26:56 -0600958
959
960 def get_fragmentation_threshold(self, phy):
961 """Returns the fragmentation threshold for |phy|.
962
963 @param phy: phy name
964 """
965 ret = self._run('%s phy %s info' % (self._command_iw, phy))
966 frag_regex = r'^\s+Fragmentation threshold:\s+([0-9]+)$'
967 match = re.search(frag_regex, ret.stdout, re.MULTILINE)
968
969 if match:
970 return int(match.group(1))
971
972 return None