648 lines
24 KiB
Python
648 lines
24 KiB
Python
# Lint as: python2, python3
|
|
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import collections
|
|
import logging
|
|
import os
|
|
import re
|
|
from six.moves import map
|
|
from six.moves import range
|
|
|
|
from autotest_lib.client.bin import local_host
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros.network import netblock
|
|
|
|
# A tuple consisting of a readable part number (one of NAME_* below)
|
|
# and a kernel module that provides the driver for this part (e.g. ath9k).
|
|
DeviceDescription = collections.namedtuple('DeviceDescription',
|
|
['name', 'kernel_module'])
|
|
|
|
|
|
# A tuple describing a default route, consisting of an interface name,
|
|
# gateway IP address, and the metric value visible in the routing table.
|
|
DefaultRoute = collections.namedtuple('DefaultRoute', ['interface_name',
|
|
'gateway',
|
|
'metric'])
|
|
|
|
NAME_MARVELL_88W8797_SDIO = 'Marvell 88W8797 SDIO'
|
|
NAME_MARVELL_88W8887_SDIO = 'Marvell 88W8887 SDIO'
|
|
NAME_MARVELL_88W8897_SDIO = 'Marvell 88W8897 SDIO'
|
|
NAME_MARVELL_88W8897_PCIE = 'Marvell 88W8897 PCIE'
|
|
NAME_MARVELL_88W8997_PCIE = 'Marvell 88W8997 PCIE'
|
|
NAME_ATHEROS_AR9280 = 'Atheros AR9280'
|
|
NAME_ATHEROS_AR9382 = 'Atheros AR9382'
|
|
NAME_ATHEROS_AR9462 = 'Atheros AR9462'
|
|
NAME_QUALCOMM_ATHEROS_QCA6174 = 'Qualcomm Atheros QCA6174'
|
|
NAME_QUALCOMM_ATHEROS_QCA6174_SDIO = 'Qualcomm Atheros QCA6174 SDIO'
|
|
NAME_QUALCOMM_WCN3990 = 'Qualcomm WCN3990'
|
|
NAME_INTEL_7260 = 'Intel 7260'
|
|
NAME_INTEL_7265 = 'Intel 7265'
|
|
NAME_INTEL_9000 = 'Intel 9000'
|
|
NAME_INTEL_9260 = 'Intel 9260'
|
|
NAME_INTEL_22260 = 'Intel 22260'
|
|
NAME_INTEL_22560 = 'Intel 22560'
|
|
NAME_BROADCOM_BCM4354_SDIO = 'Broadcom BCM4354 SDIO'
|
|
NAME_BROADCOM_BCM4356_PCIE = 'Broadcom BCM4356 PCIE'
|
|
NAME_BROADCOM_BCM4371_PCIE = 'Broadcom BCM4371 PCIE'
|
|
NAME_REALTEK_8822C_PCIE = 'Realtek 8822C PCIE'
|
|
NAME_UNKNOWN = 'Unknown WiFi Device'
|
|
|
|
DEVICE_INFO_ROOT = '/sys/class/net'
|
|
|
|
DeviceInfo = collections.namedtuple('DeviceInfo', ['vendor', 'device',
|
|
'subsystem',
|
|
'compatible'])
|
|
# Provide default values for parameters.
|
|
DeviceInfo.__new__.__defaults__ = (None, None, None, None)
|
|
|
|
DEVICE_NAME_LOOKUP = {
|
|
DeviceInfo('0x02df', '0x9129'): NAME_MARVELL_88W8797_SDIO,
|
|
DeviceInfo('0x02df', '0x912d'): NAME_MARVELL_88W8897_SDIO,
|
|
DeviceInfo('0x02df', '0x9135'): NAME_MARVELL_88W8887_SDIO,
|
|
DeviceInfo('0x11ab', '0x2b38'): NAME_MARVELL_88W8897_PCIE,
|
|
DeviceInfo('0x1b4b', '0x2b42'): NAME_MARVELL_88W8997_PCIE,
|
|
DeviceInfo('0x168c', '0x002a'): NAME_ATHEROS_AR9280,
|
|
DeviceInfo('0x168c', '0x0030'): NAME_ATHEROS_AR9382,
|
|
DeviceInfo('0x168c', '0x0034'): NAME_ATHEROS_AR9462,
|
|
DeviceInfo('0x168c', '0x003e'): NAME_QUALCOMM_ATHEROS_QCA6174,
|
|
DeviceInfo('0x105b', '0xe09d'): NAME_QUALCOMM_ATHEROS_QCA6174,
|
|
DeviceInfo('0x0271', '0x050a'): NAME_QUALCOMM_ATHEROS_QCA6174_SDIO,
|
|
DeviceInfo('0x8086', '0x08b1'): NAME_INTEL_7260,
|
|
DeviceInfo('0x8086', '0x08b2'): NAME_INTEL_7260,
|
|
DeviceInfo('0x8086', '0x095a'): NAME_INTEL_7265,
|
|
DeviceInfo('0x8086', '0x095b'): NAME_INTEL_7265,
|
|
# Note that Intel 9000 is also Intel 9560 aka Jefferson Peak 2
|
|
DeviceInfo('0x8086', '0x9df0'): NAME_INTEL_9000,
|
|
DeviceInfo('0x8086', '0x31dc'): NAME_INTEL_9000,
|
|
DeviceInfo('0x8086', '0x2526'): NAME_INTEL_9260,
|
|
DeviceInfo('0x8086', '0x2723'): NAME_INTEL_22260,
|
|
# For integrated wifi chips, use device_id and subsystem_id together
|
|
# as an identifier.
|
|
# 0x02f0 is for Quasar on CML, 0x4070 and 0x0074 is for HrP2
|
|
DeviceInfo('0x8086', '0x02f0', subsystem='0x4070'): NAME_INTEL_22560,
|
|
DeviceInfo('0x8086', '0x02f0', subsystem='0x0074'): NAME_INTEL_22560,
|
|
DeviceInfo('0x8086', '0x4df0', subsystem='0x0074'): NAME_INTEL_22560,
|
|
# With the same Quasar, subsystem_id 0x0034 is JfP2
|
|
DeviceInfo('0x8086', '0x02f0', subsystem='0x0034'): NAME_INTEL_9000,
|
|
DeviceInfo('0x02d0', '0x4354'): NAME_BROADCOM_BCM4354_SDIO,
|
|
DeviceInfo('0x14e4', '0x43ec'): NAME_BROADCOM_BCM4356_PCIE,
|
|
DeviceInfo('0x14e4', '0x440d'): NAME_BROADCOM_BCM4371_PCIE,
|
|
DeviceInfo('0x10ec', '0xc822'): NAME_REALTEK_8822C_PCIE,
|
|
|
|
DeviceInfo(compatible='qcom,wcn3990-wifi'): NAME_QUALCOMM_WCN3990,
|
|
}
|
|
|
|
class Interface:
|
|
"""Interace is a class that contains the queriable address properties
|
|
of an network device.
|
|
"""
|
|
ADDRESS_TYPE_MAC = 'link/ether'
|
|
ADDRESS_TYPE_IPV4 = 'inet'
|
|
ADDRESS_TYPE_IPV6 = 'inet6'
|
|
ADDRESS_TYPES = [ ADDRESS_TYPE_MAC, ADDRESS_TYPE_IPV4, ADDRESS_TYPE_IPV6 ]
|
|
|
|
|
|
@staticmethod
|
|
def get_connected_ethernet_interface(ignore_failures=False):
|
|
"""Get an interface object representing a connected ethernet device.
|
|
|
|
Raises an exception if no such interface exists.
|
|
|
|
@param ignore_failures bool function will return None instead of raising
|
|
an exception on failures.
|
|
@return an Interface object except under the conditions described above.
|
|
|
|
"""
|
|
# Assume that ethernet devices are called ethX until proven otherwise.
|
|
for device_name in ['eth%d' % i for i in range(5)]:
|
|
ethernet_if = Interface(device_name)
|
|
if ethernet_if.exists and ethernet_if.ipv4_address:
|
|
return ethernet_if
|
|
|
|
else:
|
|
if ignore_failures:
|
|
return None
|
|
|
|
raise error.TestFail('Failed to find ethernet interface.')
|
|
|
|
|
|
def __init__(self, name, host=None):
|
|
self._name = name
|
|
if host is None:
|
|
self.host = local_host.LocalHost()
|
|
else:
|
|
self.host = host
|
|
self._run = self.host.run
|
|
|
|
|
|
@property
|
|
def name(self):
|
|
"""@return name of the interface (e.g. 'wlan0')."""
|
|
return self._name
|
|
|
|
|
|
@property
|
|
def addresses(self):
|
|
"""@return the addresses (MAC, IP) associated with interface."""
|
|
# "ip addr show %s 2> /dev/null" returns something that looks like:
|
|
#
|
|
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
|
|
# link/ether ac:16:2d:07:51:0f brd ff:ff:ff:ff:ff:ff
|
|
# inet 172.22.73.124/22 brd 172.22.75.255 scope global eth0
|
|
# inet6 2620:0:1000:1b02:ae16:2dff:fe07:510f/64 scope global dynamic
|
|
# valid_lft 2591982sec preferred_lft 604782sec
|
|
# inet6 fe80::ae16:2dff:fe07:510f/64 scope link
|
|
# valid_lft forever preferred_lft forever
|
|
#
|
|
# We extract the second column from any entry for which the first
|
|
# column is an address type we are interested in. For example,
|
|
# for "inet 172.22.73.124/22 ...", we will capture "172.22.73.124/22".
|
|
result = self._run('ip addr show %s 2> /dev/null' % self._name,
|
|
ignore_status=True)
|
|
address_info = result.stdout
|
|
if result.exit_status != 0:
|
|
# The "ip" command will return non-zero if the interface does
|
|
# not exist.
|
|
return {}
|
|
|
|
addresses = {}
|
|
for address_line in address_info.splitlines():
|
|
address_parts = address_line.lstrip().split()
|
|
if len(address_parts) < 2:
|
|
continue
|
|
address_type, address_value = address_parts[:2]
|
|
if address_type in self.ADDRESS_TYPES:
|
|
if address_type not in addresses:
|
|
addresses[address_type] = []
|
|
addresses[address_type].append(address_value)
|
|
return addresses
|
|
|
|
|
|
@property
|
|
def device_path(self):
|
|
"""@return the sysfs path of the interface device"""
|
|
# This assumes that our path separator is the same as the remote host.
|
|
device_path = os.path.join(DEVICE_INFO_ROOT, self._name, 'device')
|
|
if not self.host.path_exists(device_path):
|
|
logging.error('No device information found at %s', device_path)
|
|
return None
|
|
|
|
return device_path
|
|
|
|
|
|
@property
|
|
def wiphy_name(self):
|
|
"""
|
|
@return name of the wiphy (e.g., 'phy0'), if available.
|
|
Otherwise None.
|
|
"""
|
|
readlink_result = self._run('readlink "%s"' %
|
|
os.path.join(DEVICE_INFO_ROOT, self._name, 'phy80211'),
|
|
ignore_status=True)
|
|
if readlink_result.exit_status != 0:
|
|
return None
|
|
|
|
return os.path.basename(readlink_result.stdout.strip())
|
|
|
|
|
|
@property
|
|
def module_name(self):
|
|
"""@return Name of kernel module in use by this interface."""
|
|
module_readlink_result = self._run('readlink "%s"' %
|
|
os.path.join(self.device_path, 'driver', 'module'),
|
|
ignore_status=True)
|
|
if module_readlink_result.exit_status != 0:
|
|
return None
|
|
|
|
return os.path.basename(module_readlink_result.stdout.strip())
|
|
|
|
@property
|
|
def parent_device_name(self):
|
|
"""
|
|
@return Name of device at which wiphy device is present. For example,
|
|
for a wifi NIC present on a PCI bus, this would be the same as
|
|
PCI_SLOT_PATH. """
|
|
path_readlink_result = self._run('readlink "%s"' % self.device_path)
|
|
if path_readlink_result.exit_status != 0:
|
|
return None
|
|
|
|
return os.path.basename(path_readlink_result.stdout.strip())
|
|
|
|
def _get_wifi_device_name(self):
|
|
"""Helper for device_description()."""
|
|
device_path = self.device_path
|
|
if not device_path:
|
|
return None
|
|
|
|
read_file = (lambda path: self._run('cat "%s"' % path).stdout.rstrip()
|
|
if self.host.path_exists(path) else None)
|
|
|
|
# Try to identify using either vendor/product ID, or using device tree
|
|
# "OF_COMPATIBLE_x".
|
|
vendor_id = read_file(os.path.join(device_path, 'vendor'))
|
|
product_id = read_file(os.path.join(device_path, 'device'))
|
|
subsystem_id = read_file(os.path.join(device_path, 'subsystem_device'))
|
|
uevent = read_file(os.path.join(device_path, 'uevent'))
|
|
|
|
# Device tree "compatible".
|
|
for line in uevent.splitlines():
|
|
key, _, value = line.partition('=')
|
|
if re.match('^OF_COMPATIBLE_[0-9]+$', key):
|
|
info = DeviceInfo(compatible=value)
|
|
if info in DEVICE_NAME_LOOKUP:
|
|
return DEVICE_NAME_LOOKUP[info]
|
|
|
|
# {Vendor, Product, Subsystem} ID.
|
|
if subsystem_id is not None:
|
|
info = DeviceInfo(vendor_id, product_id, subsystem=subsystem_id)
|
|
if info in DEVICE_NAME_LOOKUP:
|
|
return DEVICE_NAME_LOOKUP[info]
|
|
|
|
|
|
# {Vendor, Product} ID.
|
|
info = DeviceInfo(vendor_id, product_id)
|
|
if info in DEVICE_NAME_LOOKUP:
|
|
return DEVICE_NAME_LOOKUP[info]
|
|
|
|
return None
|
|
|
|
@property
|
|
def device_description(self):
|
|
"""@return DeviceDescription object for a WiFi interface, or None."""
|
|
if not self.is_wifi_device():
|
|
logging.error('Device description not supported on non-wifi '
|
|
'interface: %s.', self._name)
|
|
return None
|
|
|
|
device_name = self._get_wifi_device_name()
|
|
if not device_name:
|
|
device_name = NAME_UNKNOWN
|
|
logging.error('Device is unknown.')
|
|
else:
|
|
logging.debug('Device is %s', device_name)
|
|
|
|
module_name = self.module_name
|
|
kernel_release = self._run('uname -r').stdout.strip()
|
|
net_drivers_path = '/lib/modules/%s/kernel/drivers/net' % kernel_release
|
|
if module_name is not None and self.host.path_exists(net_drivers_path):
|
|
module_path = self._run('find %s -name %s.ko -printf %%P' % (
|
|
net_drivers_path, module_name)).stdout
|
|
else:
|
|
module_path = 'Unknown (kernel might have modules disabled)'
|
|
return DeviceDescription(device_name, module_path)
|
|
|
|
|
|
@property
|
|
def exists(self):
|
|
"""@return True if this interface exists, False otherwise."""
|
|
# No valid interface has no addresses at all.
|
|
return bool(self.addresses)
|
|
|
|
|
|
|
|
def get_ip_flags(self):
|
|
"""@return List of flags from 'ip addr show'."""
|
|
# "ip addr show %s 2> /dev/null" returns something that looks like:
|
|
#
|
|
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
|
|
# link/ether ac:16:2d:07:51:0f brd ff:ff:ff:ff:ff:ff
|
|
# inet 172.22.73.124/22 brd 172.22.75.255 scope global eth0
|
|
# inet6 2620:0:1000:1b02:ae16:2dff:fe07:510f/64 scope global dynamic
|
|
# valid_lft 2591982sec preferred_lft 604782sec
|
|
# inet6 fe80::ae16:2dff:fe07:510f/64 scope link
|
|
# valid_lft forever preferred_lft forever
|
|
#
|
|
# We only cares about the flags in the first line.
|
|
result = self._run('ip addr show %s 2> /dev/null' % self._name,
|
|
ignore_status=True)
|
|
address_info = result.stdout
|
|
if result.exit_status != 0:
|
|
# The "ip" command will return non-zero if the interface does
|
|
# not exist.
|
|
return []
|
|
status_line = address_info.splitlines()[0]
|
|
flags_str = status_line[status_line.find('<')+1:status_line.find('>')]
|
|
return flags_str.split(',')
|
|
|
|
|
|
@property
|
|
def is_up(self):
|
|
"""@return True if this interface is UP, False otherwise."""
|
|
return 'UP' in self.get_ip_flags()
|
|
|
|
|
|
@property
|
|
def is_lower_up(self):
|
|
"""
|
|
Check if the interface is in LOWER_UP state. This usually means (e.g.,
|
|
for ethernet) a link is detected.
|
|
|
|
@return True if this interface is LOWER_UP, False otherwise."""
|
|
return 'LOWER_UP' in self.get_ip_flags()
|
|
|
|
|
|
def is_link_operational(self):
|
|
"""@return True if RFC 2683 IfOperStatus is UP (i.e., is able to pass
|
|
packets).
|
|
"""
|
|
command = 'ip link show %s' % self._name
|
|
result = self._run(command, ignore_status=True)
|
|
if result.exit_status:
|
|
return False
|
|
return result.stdout.find('state UP') >= 0
|
|
|
|
|
|
@property
|
|
def mac_address(self):
|
|
"""@return the (first) MAC address, e.g., "00:11:22:33:44:55"."""
|
|
return self.addresses.get(self.ADDRESS_TYPE_MAC, [None])[0]
|
|
|
|
|
|
@property
|
|
def ipv4_address_and_prefix(self):
|
|
"""@return the IPv4 address/prefix, e.g., "192.186.0.1/24"."""
|
|
return self.addresses.get(self.ADDRESS_TYPE_IPV4, [None])[0]
|
|
|
|
|
|
@property
|
|
def ipv4_address(self):
|
|
"""@return the (first) IPv4 address, e.g., "192.168.0.1"."""
|
|
netblock_addr = self.netblock
|
|
return netblock_addr.addr if netblock_addr else None
|
|
|
|
|
|
@property
|
|
def ipv4_prefix(self):
|
|
"""@return the IPv4 address prefix e.g., 24."""
|
|
addr = self.netblock
|
|
return addr.prefix_len if addr else None
|
|
|
|
|
|
@property
|
|
def ipv4_subnet(self):
|
|
"""@return string subnet of IPv4 address (e.g. '192.168.0.0')"""
|
|
addr = self.netblock
|
|
return addr.subnet if addr else None
|
|
|
|
|
|
@property
|
|
def ipv4_subnet_mask(self):
|
|
"""@return the IPv4 subnet mask e.g., "255.255.255.0"."""
|
|
addr = self.netblock
|
|
return addr.netmask if addr else None
|
|
|
|
|
|
def is_wifi_device(self):
|
|
"""@return True if iw thinks this is a wifi device."""
|
|
if self._run('iw dev %s info' % self._name,
|
|
ignore_status=True).exit_status:
|
|
logging.debug('%s does not seem to be a wireless device.',
|
|
self._name)
|
|
return False
|
|
return True
|
|
|
|
|
|
@property
|
|
def netblock(self):
|
|
"""Return Netblock object for this interface's IPv4 address.
|
|
|
|
@return Netblock object (or None if no IPv4 address found).
|
|
|
|
"""
|
|
netblock_str = self.ipv4_address_and_prefix
|
|
return netblock.from_addr(netblock_str) if netblock_str else None
|
|
|
|
|
|
@property
|
|
def signal_level(self):
|
|
"""Get the signal level for an interface.
|
|
|
|
This is currently only defined for WiFi interfaces.
|
|
|
|
localhost test # iw dev mlan0 link
|
|
Connected to 04:f0:21:03:7d:b2 (on mlan0)
|
|
SSID: Perf_slvf0_ch36
|
|
freq: 5180
|
|
RX: 699407596 bytes (8165441 packets)
|
|
TX: 58632580 bytes (9923989 packets)
|
|
signal: -54 dBm
|
|
tx bitrate: 130.0 MBit/s MCS 15
|
|
|
|
bss flags:
|
|
dtim period: 2
|
|
beacon int: 100
|
|
|
|
@return signal level in dBm (a negative, integral number).
|
|
|
|
"""
|
|
if not self.is_wifi_device():
|
|
return None
|
|
|
|
result_lines = self._run('iw dev %s link' %
|
|
self._name).stdout.splitlines()
|
|
signal_pattern = re.compile('signal:\s+([-0-9]+)\s+dbm')
|
|
for line in result_lines:
|
|
cleaned = line.strip().lower()
|
|
match = re.search(signal_pattern, cleaned)
|
|
if match is not None:
|
|
return int(match.group(1))
|
|
|
|
logging.error('Failed to find signal level for %s.', self._name)
|
|
return None
|
|
|
|
|
|
@property
|
|
def signal_level_all_chains(self):
|
|
"""Get the signal level for each chain of an interface.
|
|
|
|
This is only defined for WiFi interfaces.
|
|
|
|
localhost test # iw wlan0 station dump
|
|
Station 44:48:c1:af:d7:31 (on wlan0)
|
|
inactive time: 13180 ms
|
|
rx bytes: 46886
|
|
rx packets: 459
|
|
tx bytes: 103159
|
|
tx packets: 745
|
|
tx retries: 17
|
|
tx failed: 0
|
|
beacon loss: 0
|
|
beacon rx: 128
|
|
rx drop misc: 2
|
|
signal: -52 [-52, -53] dBm
|
|
signal avg: 56 dBm
|
|
beacon signal avg: -49 dBm
|
|
tx bitrate: 400.0 MBit/s VHT-MCS 9 40MHz short GI VHT-NSS 2
|
|
rx bitrate: 400.0 MBit/s VHT-MCS 9 40MHz short GI VHT-NSS 2
|
|
authorized: yes
|
|
authenticated: yes
|
|
associated: yes
|
|
preamble: long
|
|
WMM/WME: yes
|
|
MFP: no
|
|
TDLS peer: no
|
|
DTIM period: 1
|
|
beacon interval:100
|
|
short slot time:yes
|
|
connected time: 6874 seconds
|
|
|
|
@return array of signal level information for each antenna in dBm
|
|
(an array of negative, integral numbers e.g. [-67, -60]) or None if
|
|
chain specific data is not provided by the device.
|
|
|
|
"""
|
|
if not self.is_wifi_device():
|
|
return None
|
|
|
|
result_lines = self._run('iw %s station dump' %
|
|
self._name).stdout.splitlines()
|
|
signal_pattern = re.compile('signal:\s+([-0-9]+)\[')
|
|
for line in result_lines:
|
|
cleaned = line.strip().replace(' ', '').lower()
|
|
match = re.search(signal_pattern, cleaned)
|
|
if match is not None:
|
|
signal_levels = cleaned[cleaned.find('[') + 1 :
|
|
cleaned.find(']')].split(',')
|
|
return list(map(int, signal_levels))
|
|
return None
|
|
|
|
|
|
@property
|
|
def mtu(self):
|
|
"""@return the interface configured maximum transmission unit (MTU)."""
|
|
# "ip addr show %s 2> /dev/null" returns something that looks like:
|
|
#
|
|
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
|
|
# link/ether ac:16:2d:07:51:0f brd ff:ff:ff:ff:ff:ff
|
|
# inet 172.22.73.124/22 brd 172.22.75.255 scope global eth0
|
|
# inet6 2620:0:1000:1b02:ae16:2dff:fe07:510f/64 scope global dynamic
|
|
# valid_lft 2591982sec preferred_lft 604782sec
|
|
# inet6 fe80::ae16:2dff:fe07:510f/64 scope link
|
|
# valid_lft forever preferred_lft forever
|
|
#
|
|
# We extract the 'mtu' value (in this example "1500")
|
|
try:
|
|
result = self._run('ip addr show %s 2> /dev/null' % self._name)
|
|
address_info = result.stdout
|
|
except error.CmdError as e:
|
|
# The "ip" command will return non-zero if the interface does
|
|
# not exist.
|
|
return None
|
|
|
|
match = re.search('mtu\s+(\d+)', address_info)
|
|
if not match:
|
|
raise error.TestFail('MTU information is not available.')
|
|
return int(match.group(1))
|
|
|
|
|
|
def noise_level(self, frequency_mhz):
|
|
"""Get the noise level for an interface at a given frequency.
|
|
|
|
This is currently only defined for WiFi interfaces.
|
|
|
|
This only works on some devices because 'iw survey dump' (the method
|
|
used to get the noise) only works on some devices. On other devices,
|
|
this method returns None.
|
|
|
|
@param frequency_mhz: frequency at which the noise level should be
|
|
measured and reported.
|
|
@return noise level in dBm (a negative, integral number) or None.
|
|
|
|
"""
|
|
if not self.is_wifi_device():
|
|
return None
|
|
|
|
# This code has to find the frequency and then find the noise
|
|
# associated with that frequency because 'iw survey dump' output looks
|
|
# like this:
|
|
#
|
|
# localhost test # iw dev mlan0 survey dump
|
|
# ...
|
|
# Survey data from mlan0
|
|
# frequency: 5805 MHz
|
|
# noise: -91 dBm
|
|
# channel active time: 124 ms
|
|
# channel busy time: 1 ms
|
|
# channel receive time: 1 ms
|
|
# channel transmit time: 0 ms
|
|
# Survey data from mlan0
|
|
# frequency: 5825 MHz
|
|
# ...
|
|
|
|
result_lines = self._run('iw dev %s survey dump' %
|
|
self._name).stdout.splitlines()
|
|
my_frequency_pattern = re.compile('frequency:\s*%d mhz' %
|
|
frequency_mhz)
|
|
any_frequency_pattern = re.compile('frequency:\s*\d{4} mhz')
|
|
inside_desired_frequency_block = False
|
|
noise_pattern = re.compile('noise:\s*([-0-9]+)\s+dbm')
|
|
for line in result_lines:
|
|
cleaned = line.strip().lower()
|
|
if my_frequency_pattern.match(cleaned):
|
|
inside_desired_frequency_block = True
|
|
elif inside_desired_frequency_block:
|
|
match = noise_pattern.match(cleaned)
|
|
if match is not None:
|
|
return int(match.group(1))
|
|
if any_frequency_pattern.match(cleaned):
|
|
inside_desired_frequency_block = False
|
|
|
|
logging.error('Failed to find noise level for %s at %d MHz.',
|
|
self._name, frequency_mhz)
|
|
return None
|
|
|
|
|
|
def get_interfaces():
|
|
"""
|
|
Retrieve the list of network interfaces found on the system.
|
|
|
|
@return List of interfaces.
|
|
|
|
"""
|
|
return [Interface(nic.strip()) for nic in os.listdir(DEVICE_INFO_ROOT)]
|
|
|
|
|
|
def get_prioritized_default_route(host=None, interface_name_regex=None):
|
|
"""
|
|
Query a local or remote host for its prioritized default interface
|
|
and route.
|
|
|
|
@param interface_name_regex string regex to filter routes by interface.
|
|
@return DefaultRoute tuple, or None if no default routes are found.
|
|
|
|
"""
|
|
# Build a list of default routes, filtered by interface if requested.
|
|
# Example command output: 'default via 172.23.188.254 dev eth0 metric 2'
|
|
run = host.run if host is not None else utils.run
|
|
output = run('ip route show').stdout
|
|
output_regex_str = 'default\s+via\s+(\S+)\s+dev\s+(\S+)\s+metric\s+(\d+)'
|
|
output_regex = re.compile(output_regex_str)
|
|
defaults = []
|
|
for item in output.splitlines():
|
|
if 'default' not in item:
|
|
continue
|
|
match = output_regex.match(item.strip())
|
|
if match is None:
|
|
raise error.TestFail('Unexpected route output: %s' % item)
|
|
gateway = match.group(1)
|
|
interface_name = match.group(2)
|
|
metric = int(match.group(3))
|
|
if interface_name_regex is not None:
|
|
if re.match(interface_name_regex, interface_name) is None:
|
|
continue
|
|
defaults.append(DefaultRoute(interface_name=interface_name,
|
|
gateway=gateway, metric=metric))
|
|
if not defaults:
|
|
return None
|
|
|
|
# Sort and return the route with the lowest metric value.
|
|
defaults.sort(key=lambda x: x.metric)
|
|
return defaults[0]
|