1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import absolute_import 7*9c5db199SXin Lifrom __future__ import division 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Li 10*9c5db199SXin Liimport collections 11*9c5db199SXin Liimport copy 12*9c5db199SXin Liimport logging 13*9c5db199SXin Liimport os 14*9c5db199SXin Liimport random 15*9c5db199SXin Liimport string 16*9c5db199SXin Liimport tempfile 17*9c5db199SXin Liimport time 18*9c5db199SXin Li 19*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 20*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils 21*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import path_utils 22*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import interface 23*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import netblock 24*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import ping_runner 25*9c5db199SXin Lifrom autotest_lib.server import hosts 26*9c5db199SXin Lifrom autotest_lib.server import site_linux_system 27*9c5db199SXin Lifrom autotest_lib.server.cros import dnsname_mangler 28*9c5db199SXin Liimport six 29*9c5db199SXin Lifrom six.moves import range 30*9c5db199SXin Li 31*9c5db199SXin Li 32*9c5db199SXin LiStationInstance = collections.namedtuple('StationInstance', 33*9c5db199SXin Li ['ssid', 'interface', 'dev_type']) 34*9c5db199SXin LiHostapdInstance = collections.namedtuple('HostapdInstance', 35*9c5db199SXin Li ['ssid', 'conf_file', 'log_file', 36*9c5db199SXin Li 'interface', 'config_dict', 37*9c5db199SXin Li 'stderr_log_file', 38*9c5db199SXin Li 'scenario_name']) 39*9c5db199SXin Li 40*9c5db199SXin Li# Send magic packets here, so they can wake up the system but are otherwise 41*9c5db199SXin Li# dropped. 42*9c5db199SXin LiUDP_DISCARD_PORT = 9 43*9c5db199SXin Li 44*9c5db199SXin Lidef build_router_hostname(client_hostname=None, router_hostname=None): 45*9c5db199SXin Li """Build a router hostname from a client hostname. 46*9c5db199SXin Li 47*9c5db199SXin Li @param client_hostname: string hostname of DUT connected to a router. 48*9c5db199SXin Li @param router_hostname: string hostname of router. 49*9c5db199SXin Li @return string hostname of connected router or None if the hostname 50*9c5db199SXin Li cannot be inferred from the client hostname. 51*9c5db199SXin Li 52*9c5db199SXin Li """ 53*9c5db199SXin Li if not router_hostname and not client_hostname: 54*9c5db199SXin Li raise error.TestError('Either client_hostname or router_hostname must ' 55*9c5db199SXin Li 'be specified to build_router_hostname.') 56*9c5db199SXin Li 57*9c5db199SXin Li return dnsname_mangler.get_router_addr(client_hostname, 58*9c5db199SXin Li cmdline_override=router_hostname) 59*9c5db199SXin Li 60*9c5db199SXin Li 61*9c5db199SXin Lidef build_router_proxy(test_name='', client_hostname=None, router_addr=None, 62*9c5db199SXin Li enable_avahi=False): 63*9c5db199SXin Li """Build up a LinuxRouter object. 64*9c5db199SXin Li 65*9c5db199SXin Li Verifies that the remote host responds to ping. 66*9c5db199SXin Li Either client_hostname or router_addr must be specified. 67*9c5db199SXin Li 68*9c5db199SXin Li @param test_name: string name of this test (e.g. 'network_WiFi_TestName'). 69*9c5db199SXin Li @param client_hostname: string hostname of DUT if we're in the lab. 70*9c5db199SXin Li @param router_addr: string DNS/IPv4 address to use for router host object. 71*9c5db199SXin Li @param enable_avahi: boolean True iff avahi should be started on the router. 72*9c5db199SXin Li 73*9c5db199SXin Li @return LinuxRouter or raise error.TestError on failure. 74*9c5db199SXin Li 75*9c5db199SXin Li """ 76*9c5db199SXin Li router_hostname = build_router_hostname(client_hostname=client_hostname, 77*9c5db199SXin Li router_hostname=router_addr) 78*9c5db199SXin Li logging.info('Connecting to router at %s', router_hostname) 79*9c5db199SXin Li ping_helper = ping_runner.PingRunner() 80*9c5db199SXin Li if not ping_helper.simple_ping(router_hostname): 81*9c5db199SXin Li raise error.TestError('Router at %s is not pingable.' % 82*9c5db199SXin Li router_hostname) 83*9c5db199SXin Li 84*9c5db199SXin Li # Use CrosHost for all router hosts and avoid host detection. 85*9c5db199SXin Li # Host detection would use JetstreamHost for Whirlwind routers. 86*9c5db199SXin Li # JetstreamHost assumes ap-daemons are running. 87*9c5db199SXin Li # Testbed routers run the testbed-ap profile with no ap-daemons. 88*9c5db199SXin Li # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection. 89*9c5db199SXin Li return LinuxRouter(hosts.create_host(router_hostname, 90*9c5db199SXin Li host_class=hosts.CrosHost), 91*9c5db199SXin Li test_name, 92*9c5db199SXin Li enable_avahi=enable_avahi) 93*9c5db199SXin Li 94*9c5db199SXin Li 95*9c5db199SXin Liclass LinuxRouter(site_linux_system.LinuxSystem): 96*9c5db199SXin Li """Linux/mac80211-style WiFi Router support for WiFiTest class. 97*9c5db199SXin Li 98*9c5db199SXin Li This class implements test methods/steps that communicate with a 99*9c5db199SXin Li router implemented with Linux/mac80211. The router must 100*9c5db199SXin Li be pre-configured to enable ssh access and have a mac80211-based 101*9c5db199SXin Li wireless device. We also assume hostapd 0.7.x and iw are present 102*9c5db199SXin Li and any necessary modules are pre-loaded. 103*9c5db199SXin Li 104*9c5db199SXin Li """ 105*9c5db199SXin Li 106*9c5db199SXin Li KNOWN_TEST_PREFIX = 'network_WiFi_' 107*9c5db199SXin Li POLLING_INTERVAL_SECONDS = 0.5 108*9c5db199SXin Li STARTUP_TIMEOUT_SECONDS = 30 109*9c5db199SXin Li SUFFIX_LETTERS = string.ascii_lowercase + string.digits 110*9c5db199SXin Li SUBNET_PREFIX_OCTETS = (192, 168) 111*9c5db199SXin Li 112*9c5db199SXin Li HOSTAPD_CONF_FILE_PATTERN = 'hostapd-test-%s.conf' 113*9c5db199SXin Li HOSTAPD_LOG_FILE_PATTERN = 'hostapd-test-%s.log' 114*9c5db199SXin Li HOSTAPD_STDERR_LOG_FILE_PATTERN = 'hostapd-stderr-test-%s.log' 115*9c5db199SXin Li HOSTAPD_CONTROL_INTERFACE_PATTERN = 'hostapd-test-%s.ctrl' 116*9c5db199SXin Li HOSTAPD_DRIVER_NAME = 'nl80211' 117*9c5db199SXin Li 118*9c5db199SXin Li MGMT_FRAME_SENDER_LOG_FILE = 'send_management_frame-test.log' 119*9c5db199SXin Li 120*9c5db199SXin Li PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer' 121*9c5db199SXin Li 122*9c5db199SXin Li _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available' 123*9c5db199SXin Li _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current' 124*9c5db199SXin Li 125*9c5db199SXin Li def get_capabilities(self): 126*9c5db199SXin Li """@return iterable object of AP capabilities for this system.""" 127*9c5db199SXin Li caps = set() 128*9c5db199SXin Li try: 129*9c5db199SXin Li self.cmd_send_management_frame = path_utils.must_be_installed( 130*9c5db199SXin Li '/usr/bin/send_management_frame', host=self.host) 131*9c5db199SXin Li caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) 132*9c5db199SXin Li except error.TestFail: 133*9c5db199SXin Li pass 134*9c5db199SXin Li return super(LinuxRouter, self).get_capabilities().union(caps) 135*9c5db199SXin Li 136*9c5db199SXin Li 137*9c5db199SXin Li @property 138*9c5db199SXin Li def router(self): 139*9c5db199SXin Li """Deprecated. Use self.host instead. 140*9c5db199SXin Li 141*9c5db199SXin Li @return Host object representing the remote router. 142*9c5db199SXin Li 143*9c5db199SXin Li """ 144*9c5db199SXin Li return self.host 145*9c5db199SXin Li 146*9c5db199SXin Li 147*9c5db199SXin Li @property 148*9c5db199SXin Li def wifi_ip(self): 149*9c5db199SXin Li """Simple accessor for the WiFi IP when there is only one AP. 150*9c5db199SXin Li 151*9c5db199SXin Li @return string IP of WiFi interface. 152*9c5db199SXin Li 153*9c5db199SXin Li """ 154*9c5db199SXin Li if len(self.local_servers) != 1: 155*9c5db199SXin Li raise error.TestError('Could not pick a WiFi IP to return.') 156*9c5db199SXin Li 157*9c5db199SXin Li return self.get_wifi_ip(0) 158*9c5db199SXin Li 159*9c5db199SXin Li 160*9c5db199SXin Li def __init__(self, host, test_name, enable_avahi=False, role='router'): 161*9c5db199SXin Li """Build a LinuxRouter. 162*9c5db199SXin Li 163*9c5db199SXin Li @param host Host object representing the remote machine. 164*9c5db199SXin Li @param test_name string name of this test. Used in SSID creation. 165*9c5db199SXin Li @param enable_avahi: boolean True iff avahi should be started on the 166*9c5db199SXin Li router. 167*9c5db199SXin Li @param role string description of host (e.g. router, pcap) 168*9c5db199SXin Li 169*9c5db199SXin Li """ 170*9c5db199SXin Li super(LinuxRouter, self).__init__(host, role) 171*9c5db199SXin Li self._ssid_prefix = test_name 172*9c5db199SXin Li self._enable_avahi = enable_avahi 173*9c5db199SXin Li self.__setup() 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Li def __setup(self): 177*9c5db199SXin Li """Set up this system. 178*9c5db199SXin Li 179*9c5db199SXin Li Can be used either to complete initialization of a LinuxRouter 180*9c5db199SXin Li object, or to re-establish a good state after a reboot. 181*9c5db199SXin Li 182*9c5db199SXin Li """ 183*9c5db199SXin Li self.cmd_dhcpd = '/usr/sbin/dhcpd' 184*9c5db199SXin Li self.cmd_hostapd = path_utils.must_be_installed( 185*9c5db199SXin Li '/usr/sbin/hostapd', host=self.host) 186*9c5db199SXin Li self.cmd_hostapd_cli = path_utils.must_be_installed( 187*9c5db199SXin Li '/usr/sbin/hostapd_cli', host=self.host) 188*9c5db199SXin Li self.cmd_wpa_supplicant = path_utils.must_be_installed( 189*9c5db199SXin Li '/usr/sbin/wpa_supplicant', host=self.host) 190*9c5db199SXin Li self.dhcpd_conf = os.path.join(self.logdir, 'dhcpd.%s.conf') 191*9c5db199SXin Li self.dhcpd_leases = os.path.join(self.logdir, 'dhcpd.leases') 192*9c5db199SXin Li 193*9c5db199SXin Li # Log the most recent message on the router so that we can rebuild the 194*9c5db199SXin Li # suffix relevant to us when debugging failures. 195*9c5db199SXin Li last_log_line = self.host.run('tail -1 /var/log/messages', 196*9c5db199SXin Li ignore_status=True).stdout 197*9c5db199SXin Li # We're trying to get the timestamp from: 198*9c5db199SXin Li # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah 199*9c5db199SXin Li self._log_start_timestamp = last_log_line.strip().partition(' ')[0] 200*9c5db199SXin Li if self._log_start_timestamp: 201*9c5db199SXin Li logging.debug('Will only retrieve logs after %s.', 202*9c5db199SXin Li self._log_start_timestamp) 203*9c5db199SXin Li else: 204*9c5db199SXin Li # If syslog is empty, we just use a wildcard pattern, to grab 205*9c5db199SXin Li # everything. 206*9c5db199SXin Li logging.debug('Empty or corrupt log; will retrieve whole log') 207*9c5db199SXin Li self._log_start_timestamp = '.' 208*9c5db199SXin Li 209*9c5db199SXin Li # hostapd configuration persists throughout the test, subsequent 210*9c5db199SXin Li # 'config' commands only modify it. 211*9c5db199SXin Li if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX): 212*9c5db199SXin Li # Many of our tests start with an uninteresting prefix. 213*9c5db199SXin Li # Remove it so we can have more unique bytes. 214*9c5db199SXin Li self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):] 215*9c5db199SXin Li self._number_unique_ssids = 0 216*9c5db199SXin Li 217*9c5db199SXin Li self._brif_index = 0 218*9c5db199SXin Li 219*9c5db199SXin Li self._total_hostapd_instances = 0 220*9c5db199SXin Li self.local_servers = [] 221*9c5db199SXin Li self.server_address_index = [] 222*9c5db199SXin Li self.hostapd_instances = [] 223*9c5db199SXin Li self.station_instances = [] 224*9c5db199SXin Li self.dhcp_low = 1 225*9c5db199SXin Li self.dhcp_high = 128 226*9c5db199SXin Li 227*9c5db199SXin Li # Kill hostapd and dhcp server if already running. 228*9c5db199SXin Li self._kill_process_instance('hostapd', timeout_seconds=30) 229*9c5db199SXin Li self.stop_dhcp_server(instance=None) 230*9c5db199SXin Li 231*9c5db199SXin Li # Place us in the US by default 232*9c5db199SXin Li self.iw_runner.set_regulatory_domain('US') 233*9c5db199SXin Li 234*9c5db199SXin Li self.enable_all_antennas() 235*9c5db199SXin Li 236*9c5db199SXin Li # Some tests want this functionality, but otherwise, it's a distraction. 237*9c5db199SXin Li if self._enable_avahi: 238*9c5db199SXin Li self.host.run('start avahi', ignore_status=True) 239*9c5db199SXin Li else: 240*9c5db199SXin Li self.host.run('stop avahi', ignore_status=True) 241*9c5db199SXin Li 242*9c5db199SXin Li # Some routers have bad (slow?) random number generators. 243*9c5db199SXin Li self.rng_configure() 244*9c5db199SXin Li 245*9c5db199SXin Li 246*9c5db199SXin Li def close(self): 247*9c5db199SXin Li """Close global resources held by this system.""" 248*9c5db199SXin Li router_log = os.path.join(self.logdir, 'router_log') 249*9c5db199SXin Li self.deconfig() 250*9c5db199SXin Li # dnsmasq and hostapd cause interesting events to go to system logs. 251*9c5db199SXin Li # Retrieve only the suffix of the logs after the timestamp we stored on 252*9c5db199SXin Li # router creation. 253*9c5db199SXin Li self.host.run("sed -n -e '/%s/,$p' /var/log/messages >%s" % 254*9c5db199SXin Li (self._log_start_timestamp, router_log), 255*9c5db199SXin Li ignore_status=True) 256*9c5db199SXin Li self.host.get_file(router_log, 'debug/%s_host_messages' % self.role) 257*9c5db199SXin Li super(LinuxRouter, self).close() 258*9c5db199SXin Li 259*9c5db199SXin Li 260*9c5db199SXin Li def reboot(self, timeout): 261*9c5db199SXin Li """Reboot this router, and restore it to a known-good state. 262*9c5db199SXin Li 263*9c5db199SXin Li @param timeout Maximum seconds to wait for router to return. 264*9c5db199SXin Li 265*9c5db199SXin Li """ 266*9c5db199SXin Li super(LinuxRouter, self).reboot(timeout) 267*9c5db199SXin Li self.__setup() 268*9c5db199SXin Li 269*9c5db199SXin Li 270*9c5db199SXin Li def has_local_server(self): 271*9c5db199SXin Li """@return True iff this router has local servers configured.""" 272*9c5db199SXin Li return bool(self.local_servers) 273*9c5db199SXin Li 274*9c5db199SXin Li 275*9c5db199SXin Li def start_hostapd(self, configuration): 276*9c5db199SXin Li """Start a hostapd instance described by conf. 277*9c5db199SXin Li 278*9c5db199SXin Li @param configuration HostapConfig object. 279*9c5db199SXin Li 280*9c5db199SXin Li """ 281*9c5db199SXin Li # Figure out the correct interface. 282*9c5db199SXin Li interface = self.get_wlanif(configuration.frequency, 'managed', 283*9c5db199SXin Li configuration.min_streams) 284*9c5db199SXin Li phy_name = self.iw_runner.get_interface(interface).phy 285*9c5db199SXin Li 286*9c5db199SXin Li conf_file = os.path.join(self.logdir, 287*9c5db199SXin Li self.HOSTAPD_CONF_FILE_PATTERN % interface) 288*9c5db199SXin Li log_file = os.path.join(self.logdir, 289*9c5db199SXin Li self.HOSTAPD_LOG_FILE_PATTERN % interface) 290*9c5db199SXin Li stderr_log_file = os.path.join(self.logdir, 291*9c5db199SXin Li self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface) 292*9c5db199SXin Li control_interface = os.path.join(self.logdir, 293*9c5db199SXin Li self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface) 294*9c5db199SXin Li hostapd_conf_dict = configuration.generate_dict( 295*9c5db199SXin Li interface, control_interface, 296*9c5db199SXin Li self.build_unique_ssid(suffix=configuration.ssid_suffix)) 297*9c5db199SXin Li logging.debug('hostapd parameters: %r', hostapd_conf_dict) 298*9c5db199SXin Li 299*9c5db199SXin Li # Generate hostapd.conf. 300*9c5db199SXin Li self.router.run("cat <<EOF >%s\n%s\nEOF\n" % 301*9c5db199SXin Li (conf_file, '\n'.join( 302*9c5db199SXin Li "%s=%s" % kv for kv in six.iteritems(hostapd_conf_dict)))) 303*9c5db199SXin Li 304*9c5db199SXin Li # Run hostapd. 305*9c5db199SXin Li logging.info('Starting hostapd on %s(%s) channel=%s...', 306*9c5db199SXin Li interface, phy_name, configuration.channel) 307*9c5db199SXin Li self.router.run('rm %s' % log_file, ignore_status=True) 308*9c5db199SXin Li self.router.run('stop wpasupplicant', ignore_status=True) 309*9c5db199SXin Li start_command = ( 310*9c5db199SXin Li 'OPENSSL_CONF=/etc/ssl/openssl.cnf.compat ' 311*9c5db199SXin Li 'OPENSSL_CHROMIUM_SKIP_TRUSTED_PURPOSE_CHECK=1 ' 312*9c5db199SXin Li '%s -dd -t -K %s > %s 2> %s & echo $!' % ( 313*9c5db199SXin Li self.cmd_hostapd, conf_file, log_file, stderr_log_file)) 314*9c5db199SXin Li pid = int(self.router.run(start_command).stdout.strip()) 315*9c5db199SXin Li self.hostapd_instances.append(HostapdInstance( 316*9c5db199SXin Li hostapd_conf_dict['ssid'], 317*9c5db199SXin Li conf_file, 318*9c5db199SXin Li log_file, 319*9c5db199SXin Li interface, 320*9c5db199SXin Li hostapd_conf_dict.copy(), 321*9c5db199SXin Li stderr_log_file, 322*9c5db199SXin Li configuration.scenario_name)) 323*9c5db199SXin Li 324*9c5db199SXin Li # Wait for confirmation that the router came up. 325*9c5db199SXin Li logging.info('Waiting for hostapd to startup.') 326*9c5db199SXin Li utils.poll_for_condition( 327*9c5db199SXin Li condition=lambda: self._has_hostapd_started(log_file, pid), 328*9c5db199SXin Li exception=error.TestFail('Timed out while waiting for hostapd ' 329*9c5db199SXin Li 'to start.'), 330*9c5db199SXin Li timeout=self.STARTUP_TIMEOUT_SECONDS, 331*9c5db199SXin Li sleep_interval=self.POLLING_INTERVAL_SECONDS) 332*9c5db199SXin Li 333*9c5db199SXin Li if configuration.frag_threshold: 334*9c5db199SXin Li threshold = self.iw_runner.get_fragmentation_threshold(phy_name) 335*9c5db199SXin Li if threshold != configuration.frag_threshold: 336*9c5db199SXin Li raise error.TestNAError('Router does not support setting ' 337*9c5db199SXin Li 'fragmentation threshold') 338*9c5db199SXin Li 339*9c5db199SXin Li 340*9c5db199SXin Li def _has_hostapd_started(self, log_file, pid): 341*9c5db199SXin Li """Determines if hostapd has started. 342*9c5db199SXin Li 343*9c5db199SXin Li @return Whether or not hostapd has started. 344*9c5db199SXin Li @raise error.TestFail if there was a bad config or hostapd terminated. 345*9c5db199SXin Li """ 346*9c5db199SXin Li success = self.router.run( 347*9c5db199SXin Li 'grep "Setup of interface done" %s' % log_file, 348*9c5db199SXin Li ignore_status=True).exit_status == 0 349*9c5db199SXin Li if success: 350*9c5db199SXin Li return True 351*9c5db199SXin Li 352*9c5db199SXin Li # A common failure is an invalid router configuration. 353*9c5db199SXin Li # Detect this and exit early if we see it. 354*9c5db199SXin Li bad_config = self.router.run( 355*9c5db199SXin Li 'grep "Interface initialization failed" %s' % log_file, 356*9c5db199SXin Li ignore_status=True).exit_status == 0 357*9c5db199SXin Li if bad_config: 358*9c5db199SXin Li raise error.TestFail('hostapd failed to initialize AP ' 359*9c5db199SXin Li 'interface.') 360*9c5db199SXin Li 361*9c5db199SXin Li if pid: 362*9c5db199SXin Li early_exit = self.router.run('kill -0 %d' % pid, 363*9c5db199SXin Li ignore_status=True).exit_status 364*9c5db199SXin Li if early_exit: 365*9c5db199SXin Li raise error.TestFail('hostapd process terminated.') 366*9c5db199SXin Li 367*9c5db199SXin Li return False 368*9c5db199SXin Li 369*9c5db199SXin Li 370*9c5db199SXin Li def _kill_process_instance(self, 371*9c5db199SXin Li process, 372*9c5db199SXin Li instance=None, 373*9c5db199SXin Li timeout_seconds=10, 374*9c5db199SXin Li ignore_timeouts=False): 375*9c5db199SXin Li """Kill a process on the router. 376*9c5db199SXin Li 377*9c5db199SXin Li Kills remote program named |process| (optionally only a specific 378*9c5db199SXin Li |instance|). Wait |timeout_seconds| for |process| to die 379*9c5db199SXin Li before returning. If |ignore_timeouts| is False, raise 380*9c5db199SXin Li a TestError on timeouts. 381*9c5db199SXin Li 382*9c5db199SXin Li @param process: string name of process to kill. 383*9c5db199SXin Li @param instance: string fragment of the command line unique to 384*9c5db199SXin Li this instance of the remote process. 385*9c5db199SXin Li @param timeout_seconds: float timeout in seconds to wait. 386*9c5db199SXin Li @param ignore_timeouts: True iff we should ignore failures to 387*9c5db199SXin Li kill processes. 388*9c5db199SXin Li @return True iff the specified process has exited. 389*9c5db199SXin Li 390*9c5db199SXin Li """ 391*9c5db199SXin Li if instance is not None: 392*9c5db199SXin Li search_arg = '-f "^%s.*%s"' % (process, instance) 393*9c5db199SXin Li else: 394*9c5db199SXin Li search_arg = process 395*9c5db199SXin Li 396*9c5db199SXin Li self.host.run('pkill %s' % search_arg, ignore_status=True) 397*9c5db199SXin Li 398*9c5db199SXin Li # Wait for process to die 399*9c5db199SXin Li time.sleep(self.POLLING_INTERVAL_SECONDS) 400*9c5db199SXin Li try: 401*9c5db199SXin Li utils.poll_for_condition( 402*9c5db199SXin Li condition=lambda: self.host.run( 403*9c5db199SXin Li 'pgrep -l %s' % search_arg, 404*9c5db199SXin Li ignore_status=True).exit_status != 0, 405*9c5db199SXin Li timeout=timeout_seconds, 406*9c5db199SXin Li sleep_interval=self.POLLING_INTERVAL_SECONDS) 407*9c5db199SXin Li except utils.TimeoutError: 408*9c5db199SXin Li if ignore_timeouts: 409*9c5db199SXin Li return False 410*9c5db199SXin Li 411*9c5db199SXin Li raise error.TestError( 412*9c5db199SXin Li 'Timed out waiting for %s%s to die' % 413*9c5db199SXin Li (process, 414*9c5db199SXin Li '' if instance is None else ' (instance=%s)' % instance)) 415*9c5db199SXin Li return True 416*9c5db199SXin Li 417*9c5db199SXin Li 418*9c5db199SXin Li def kill_hostapd_instance(self, instance): 419*9c5db199SXin Li """Kills a hostapd instance. 420*9c5db199SXin Li 421*9c5db199SXin Li @param instance HostapdInstance object. 422*9c5db199SXin Li 423*9c5db199SXin Li """ 424*9c5db199SXin Li is_dead = self._kill_process_instance( 425*9c5db199SXin Li self.cmd_hostapd, 426*9c5db199SXin Li instance=instance.conf_file, 427*9c5db199SXin Li timeout_seconds=30, 428*9c5db199SXin Li ignore_timeouts=True) 429*9c5db199SXin Li if instance.scenario_name: 430*9c5db199SXin Li log_identifier = instance.scenario_name 431*9c5db199SXin Li else: 432*9c5db199SXin Li log_identifier = '%d_%s' % ( 433*9c5db199SXin Li self._total_hostapd_instances, instance.interface) 434*9c5db199SXin Li files_to_copy = [(instance.log_file, 435*9c5db199SXin Li 'debug/hostapd_%s_%s.log' % 436*9c5db199SXin Li (self.role, log_identifier)), 437*9c5db199SXin Li (instance.stderr_log_file, 438*9c5db199SXin Li 'debug/hostapd_%s_%s.stderr.log' % 439*9c5db199SXin Li (self.role, log_identifier))] 440*9c5db199SXin Li for remote_file, local_file in files_to_copy: 441*9c5db199SXin Li if self.host.run('ls %s >/dev/null 2>&1' % remote_file, 442*9c5db199SXin Li ignore_status=True).exit_status: 443*9c5db199SXin Li logging.error('Did not collect hostapd log file because ' 444*9c5db199SXin Li 'it was missing.') 445*9c5db199SXin Li else: 446*9c5db199SXin Li self.router.get_file(remote_file, local_file) 447*9c5db199SXin Li self._total_hostapd_instances += 1 448*9c5db199SXin Li if not is_dead: 449*9c5db199SXin Li raise error.TestError('Timed out killing hostapd.') 450*9c5db199SXin Li 451*9c5db199SXin Li 452*9c5db199SXin Li def build_unique_ssid(self, suffix=''): 453*9c5db199SXin Li """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding 454*9c5db199SXin Li the number of APs we've constructed already. 455*9c5db199SXin Li 456*9c5db199SXin Li @param suffix string to append to SSID 457*9c5db199SXin Li 458*9c5db199SXin Li """ 459*9c5db199SXin Li base = len(self.SUFFIX_LETTERS) 460*9c5db199SXin Li number = self._number_unique_ssids 461*9c5db199SXin Li self._number_unique_ssids += 1 462*9c5db199SXin Li unique = '' 463*9c5db199SXin Li while number or not unique: 464*9c5db199SXin Li unique = self.SUFFIX_LETTERS[number % base] + unique 465*9c5db199SXin Li number = number // base 466*9c5db199SXin Li # And salt the SSID so that tests running in adjacent cells are unlikely 467*9c5db199SXin Li # to pick the same SSID and we're resistent to beacons leaking out of 468*9c5db199SXin Li # cells. 469*9c5db199SXin Li salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)]) 470*9c5db199SXin Li return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:] 471*9c5db199SXin Li 472*9c5db199SXin Li 473*9c5db199SXin Li def rng_configure(self): 474*9c5db199SXin Li """Configure the random generator to our liking. 475*9c5db199SXin Li 476*9c5db199SXin Li Some routers (particularly, Gale) seem to have bad Random Number 477*9c5db199SXin Li Generators, such that hostapd can't always generate keys fast enough. 478*9c5db199SXin Li The on-board TPM seems to serve as a better generator, so we try to 479*9c5db199SXin Li switch to that if available. 480*9c5db199SXin Li 481*9c5db199SXin Li Symptoms of a slow RNG: hostapd complains with: 482*9c5db199SXin Li 483*9c5db199SXin Li WPA: Not enough entropy in random pool to proceed - reject first 484*9c5db199SXin Li 4-way handshake 485*9c5db199SXin Li 486*9c5db199SXin Li Ref: 487*9c5db199SXin Li https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854 488*9c5db199SXin Li 489*9c5db199SXin Li Linux devices may have RNG parameters at 490*9c5db199SXin Li /sys/class/misc/hw_random/rng_{available,current}. See: 491*9c5db199SXin Li 492*9c5db199SXin Li https://www.kernel.org/doc/Documentation/hw_random.txt 493*9c5db199SXin Li 494*9c5db199SXin Li """ 495*9c5db199SXin Li 496*9c5db199SXin Li available = self.host.run('cat %s' % self._RNG_AVAILABLE, \ 497*9c5db199SXin Li ignore_status=True).stdout.strip().split(' ') 498*9c5db199SXin Li # System may not have HWRNG support. Just skip this. 499*9c5db199SXin Li if available == "": 500*9c5db199SXin Li return 501*9c5db199SXin Li current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \ 502*9c5db199SXin Li strip() 503*9c5db199SXin Li want_rng = "tpm-rng" 504*9c5db199SXin Li 505*9c5db199SXin Li logging.debug("Available / current RNGs on router: %r / %s", 506*9c5db199SXin Li available, current) 507*9c5db199SXin Li if want_rng in available and want_rng != current: 508*9c5db199SXin Li logging.debug("Switching RNGs: %s -> %s", current, want_rng) 509*9c5db199SXin Li self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT)) 510*9c5db199SXin Li 511*9c5db199SXin Li 512*9c5db199SXin Li def hostap_configure(self, configuration, multi_interface=None): 513*9c5db199SXin Li """Build up a hostapd configuration file and start hostapd. 514*9c5db199SXin Li 515*9c5db199SXin Li Also setup a local server if this router supports them. 516*9c5db199SXin Li 517*9c5db199SXin Li @param configuration HosetapConfig object. 518*9c5db199SXin Li @param multi_interface bool True iff multiple interfaces allowed. 519*9c5db199SXin Li 520*9c5db199SXin Li """ 521*9c5db199SXin Li if multi_interface is None and (self.hostapd_instances or 522*9c5db199SXin Li self.station_instances): 523*9c5db199SXin Li self.deconfig() 524*9c5db199SXin Li if configuration.is_11ac: 525*9c5db199SXin Li router_caps = self.get_capabilities() 526*9c5db199SXin Li if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps: 527*9c5db199SXin Li raise error.TestNAError('Router does not have AC support') 528*9c5db199SXin Li 529*9c5db199SXin Li self.start_hostapd(configuration) 530*9c5db199SXin Li interface = self.hostapd_instances[-1].interface 531*9c5db199SXin Li self.iw_runner.set_tx_power(interface, 'auto') 532*9c5db199SXin Li self.start_local_server(interface, bridge=configuration.bridge) 533*9c5db199SXin Li logging.info('AP configured.') 534*9c5db199SXin Li 535*9c5db199SXin Li 536*9c5db199SXin Li def ibss_configure(self, config): 537*9c5db199SXin Li """Configure a station based AP in IBSS mode. 538*9c5db199SXin Li 539*9c5db199SXin Li Extract relevant configuration objects from |config| despite not 540*9c5db199SXin Li actually being a hostap managed endpoint. 541*9c5db199SXin Li 542*9c5db199SXin Li @param config HostapConfig object. 543*9c5db199SXin Li 544*9c5db199SXin Li """ 545*9c5db199SXin Li if self.station_instances or self.hostapd_instances: 546*9c5db199SXin Li self.deconfig() 547*9c5db199SXin Li interface = self.get_wlanif(config.frequency, 'ibss') 548*9c5db199SXin Li ssid = (config.ssid or 549*9c5db199SXin Li self.build_unique_ssid(suffix=config.ssid_suffix)) 550*9c5db199SXin Li # Connect the station 551*9c5db199SXin Li self.router.run('%s link set %s up' % (self.cmd_ip, interface)) 552*9c5db199SXin Li self.iw_runner.ibss_join(interface, ssid, config.frequency) 553*9c5db199SXin Li # Always start a local server. 554*9c5db199SXin Li self.start_local_server(interface) 555*9c5db199SXin Li # Remember that this interface is up. 556*9c5db199SXin Li self.station_instances.append( 557*9c5db199SXin Li StationInstance(ssid=ssid, interface=interface, 558*9c5db199SXin Li dev_type='ibss')) 559*9c5db199SXin Li 560*9c5db199SXin Li 561*9c5db199SXin Li def local_server_address(self, index): 562*9c5db199SXin Li """Get the local server address for an interface. 563*9c5db199SXin Li 564*9c5db199SXin Li When we multiple local servers, we give them static IP addresses 565*9c5db199SXin Li like 192.168.*.254. 566*9c5db199SXin Li 567*9c5db199SXin Li @param index int describing which local server this is for. 568*9c5db199SXin Li 569*9c5db199SXin Li """ 570*9c5db199SXin Li return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) 571*9c5db199SXin Li 572*9c5db199SXin Li 573*9c5db199SXin Li def local_peer_ip_address(self, index): 574*9c5db199SXin Li """Get the IP address allocated for the peer associated to the AP. 575*9c5db199SXin Li 576*9c5db199SXin Li This address is assigned to a locally associated peer device that 577*9c5db199SXin Li is created for the DUT to perform connectivity tests with. 578*9c5db199SXin Li When we have multiple local servers, we give them static IP addresses 579*9c5db199SXin Li like 192.168.*.253. 580*9c5db199SXin Li 581*9c5db199SXin Li @param index int describing which local server this is for. 582*9c5db199SXin Li 583*9c5db199SXin Li """ 584*9c5db199SXin Li return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) 585*9c5db199SXin Li 586*9c5db199SXin Li def local_bridge_address(self, index): 587*9c5db199SXin Li """Get the bridge address for an interface. 588*9c5db199SXin Li 589*9c5db199SXin Li This address is assigned to a local bridge device. 590*9c5db199SXin Li 591*9c5db199SXin Li @param index int describing which local server this is for. 592*9c5db199SXin Li 593*9c5db199SXin Li """ 594*9c5db199SXin Li return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252)) 595*9c5db199SXin Li 596*9c5db199SXin Li def local_peer_mac_address(self): 597*9c5db199SXin Li """Get the MAC address of the peer interface. 598*9c5db199SXin Li 599*9c5db199SXin Li @return string MAC address of the peer interface. 600*9c5db199SXin Li 601*9c5db199SXin Li """ 602*9c5db199SXin Li iface = interface.Interface(self.station_instances[0].interface, 603*9c5db199SXin Li self.router) 604*9c5db199SXin Li return iface.mac_address 605*9c5db199SXin Li 606*9c5db199SXin Li 607*9c5db199SXin Li def _get_unused_server_address_index(self): 608*9c5db199SXin Li """@return an unused server address index.""" 609*9c5db199SXin Li for address_index in range(0, 256): 610*9c5db199SXin Li if address_index not in self.server_address_index: 611*9c5db199SXin Li return address_index 612*9c5db199SXin Li raise error.TestFail('No available server address index') 613*9c5db199SXin Li 614*9c5db199SXin Li 615*9c5db199SXin Li def change_server_address_index(self, ap_num=0, server_address_index=None): 616*9c5db199SXin Li """Restart the local server with a different server address index. 617*9c5db199SXin Li 618*9c5db199SXin Li This will restart the local server with different gateway IP address 619*9c5db199SXin Li and DHCP address ranges. 620*9c5db199SXin Li 621*9c5db199SXin Li @param ap_num: int hostapd instance number. 622*9c5db199SXin Li @param server_address_index: int server address index. 623*9c5db199SXin Li 624*9c5db199SXin Li """ 625*9c5db199SXin Li interface = self.local_servers[ap_num]['interface']; 626*9c5db199SXin Li # Get an unused server address index if one is not specified, which 627*9c5db199SXin Li # will be different from the one that's currently in used. 628*9c5db199SXin Li if server_address_index is None: 629*9c5db199SXin Li server_address_index = self._get_unused_server_address_index() 630*9c5db199SXin Li 631*9c5db199SXin Li # Restart local server with the new server address index. 632*9c5db199SXin Li self.stop_local_server(self.local_servers[ap_num]) 633*9c5db199SXin Li self.start_local_server(interface, 634*9c5db199SXin Li ap_num=ap_num, 635*9c5db199SXin Li server_address_index=server_address_index) 636*9c5db199SXin Li 637*9c5db199SXin Li 638*9c5db199SXin Li def start_local_server(self, 639*9c5db199SXin Li interface, 640*9c5db199SXin Li ap_num=None, 641*9c5db199SXin Li server_address_index=None, 642*9c5db199SXin Li bridge=None): 643*9c5db199SXin Li """Start a local server on an interface. 644*9c5db199SXin Li 645*9c5db199SXin Li @param interface string (e.g. wlan0) 646*9c5db199SXin Li @param ap_num int the ap instance to start the server for 647*9c5db199SXin Li @param server_address_index int server address index 648*9c5db199SXin Li @param bridge string (e.g. br0) 649*9c5db199SXin Li 650*9c5db199SXin Li """ 651*9c5db199SXin Li logging.info('Starting up local server...') 652*9c5db199SXin Li 653*9c5db199SXin Li if len(self.local_servers) >= 256: 654*9c5db199SXin Li raise error.TestFail('Exhausted available local servers') 655*9c5db199SXin Li 656*9c5db199SXin Li # Get an unused server address index if one is not specified. 657*9c5db199SXin Li # Validate server address index if one is specified. 658*9c5db199SXin Li if server_address_index is None: 659*9c5db199SXin Li server_address_index = self._get_unused_server_address_index() 660*9c5db199SXin Li elif server_address_index in self.server_address_index: 661*9c5db199SXin Li raise error.TestFail('Server address index %d already in used' % 662*9c5db199SXin Li server_address_index) 663*9c5db199SXin Li 664*9c5db199SXin Li server_addr = netblock.from_addr( 665*9c5db199SXin Li self.local_server_address(server_address_index), 666*9c5db199SXin Li prefix_len=24) 667*9c5db199SXin Li 668*9c5db199SXin Li params = {} 669*9c5db199SXin Li params['address_index'] = server_address_index 670*9c5db199SXin Li params['netblock'] = server_addr 671*9c5db199SXin Li params['dhcp_range'] = ' '.join( 672*9c5db199SXin Li (server_addr.get_addr_in_block(1), 673*9c5db199SXin Li server_addr.get_addr_in_block(128))) 674*9c5db199SXin Li params['interface'] = interface 675*9c5db199SXin Li params['bridge'] = bridge 676*9c5db199SXin Li params['ip_params'] = ('%s broadcast %s dev %s' % 677*9c5db199SXin Li (server_addr.netblock, 678*9c5db199SXin Li server_addr.broadcast, 679*9c5db199SXin Li interface)) 680*9c5db199SXin Li if ap_num is None: 681*9c5db199SXin Li self.local_servers.append(params) 682*9c5db199SXin Li else: 683*9c5db199SXin Li self.local_servers.insert(ap_num, params) 684*9c5db199SXin Li self.server_address_index.append(server_address_index) 685*9c5db199SXin Li 686*9c5db199SXin Li self.router.run('%s addr flush %s' % 687*9c5db199SXin Li (self.cmd_ip, interface)) 688*9c5db199SXin Li self.router.run('%s addr add %s' % 689*9c5db199SXin Li (self.cmd_ip, params['ip_params'])) 690*9c5db199SXin Li self.router.run('%s link set %s up' % 691*9c5db199SXin Li (self.cmd_ip, interface)) 692*9c5db199SXin Li if params['bridge']: 693*9c5db199SXin Li bridge_addr = netblock.from_addr( 694*9c5db199SXin Li self.local_bridge_address(server_address_index), 695*9c5db199SXin Li prefix_len=24) 696*9c5db199SXin Li self.router.run("ifconfig %s %s" % 697*9c5db199SXin Li (params['bridge'], bridge_addr.netblock)) 698*9c5db199SXin Li self.start_dhcp_server(interface) 699*9c5db199SXin Li 700*9c5db199SXin Li 701*9c5db199SXin Li def stop_local_server(self, server): 702*9c5db199SXin Li """Stop a local server on the router 703*9c5db199SXin Li 704*9c5db199SXin Li @param server object server configuration parameters. 705*9c5db199SXin Li 706*9c5db199SXin Li """ 707*9c5db199SXin Li self.stop_dhcp_server(server['interface']) 708*9c5db199SXin Li self.router.run("%s addr del %s" % 709*9c5db199SXin Li (self.cmd_ip, server['ip_params']), 710*9c5db199SXin Li ignore_status=True) 711*9c5db199SXin Li self.server_address_index.remove(server['address_index']) 712*9c5db199SXin Li self.local_servers.remove(server) 713*9c5db199SXin Li 714*9c5db199SXin Li 715*9c5db199SXin Li def start_dhcp_server(self, interface): 716*9c5db199SXin Li """Start a dhcp server on an interface. 717*9c5db199SXin Li 718*9c5db199SXin Li @param interface string (e.g. wlan0) 719*9c5db199SXin Li 720*9c5db199SXin Li """ 721*9c5db199SXin Li for server in self.local_servers: 722*9c5db199SXin Li if server['interface'] == interface: 723*9c5db199SXin Li params = server 724*9c5db199SXin Li break 725*9c5db199SXin Li else: 726*9c5db199SXin Li raise error.TestFail('Could not find local server ' 727*9c5db199SXin Li 'to match interface: %r' % interface) 728*9c5db199SXin Li server_addr = params['netblock'] 729*9c5db199SXin Li dhcpd_conf_file = self.dhcpd_conf % interface 730*9c5db199SXin Li dhcp_conf = '\n'.join([ 731*9c5db199SXin Li 'port=0', # disables DNS server 732*9c5db199SXin Li 'bind-interfaces', 733*9c5db199SXin Li 'log-dhcp', 734*9c5db199SXin Li 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1), 735*9c5db199SXin Li server_addr.get_addr_in_block(128))), 736*9c5db199SXin Li 'interface=%s' % (params['bridge'] or params['interface']), 737*9c5db199SXin Li 'dhcp-leasefile=%s' % self.dhcpd_leases]) 738*9c5db199SXin Li self.router.run('cat <<EOF >%s\n%s\nEOF\n' % 739*9c5db199SXin Li (dhcpd_conf_file, dhcp_conf)) 740*9c5db199SXin Li self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file) 741*9c5db199SXin Li 742*9c5db199SXin Li 743*9c5db199SXin Li def stop_dhcp_server(self, instance=None): 744*9c5db199SXin Li """Stop a dhcp server on the router. 745*9c5db199SXin Li 746*9c5db199SXin Li @param instance string instance to kill. 747*9c5db199SXin Li 748*9c5db199SXin Li """ 749*9c5db199SXin Li self._kill_process_instance('dnsmasq', instance=instance) 750*9c5db199SXin Li 751*9c5db199SXin Li 752*9c5db199SXin Li def get_wifi_channel(self, ap_num): 753*9c5db199SXin Li """Return channel of BSS corresponding to |ap_num|. 754*9c5db199SXin Li 755*9c5db199SXin Li @param ap_num int which BSS to get the channel of. 756*9c5db199SXin Li @return int primary channel of BSS. 757*9c5db199SXin Li 758*9c5db199SXin Li """ 759*9c5db199SXin Li instance = self.hostapd_instances[ap_num] 760*9c5db199SXin Li return instance.config_dict['channel'] 761*9c5db199SXin Li 762*9c5db199SXin Li 763*9c5db199SXin Li def get_wifi_ip(self, ap_num): 764*9c5db199SXin Li """Return IP address on the WiFi subnet of a local server on the router. 765*9c5db199SXin Li 766*9c5db199SXin Li If no local servers are configured (e.g. for an RSPro), a TestFail will 767*9c5db199SXin Li be raised. 768*9c5db199SXin Li 769*9c5db199SXin Li @param ap_num int which local server to get an address from. 770*9c5db199SXin Li 771*9c5db199SXin Li """ 772*9c5db199SXin Li if not self.local_servers: 773*9c5db199SXin Li raise error.TestError('No IP address assigned') 774*9c5db199SXin Li 775*9c5db199SXin Li return self.local_servers[ap_num]['netblock'].addr 776*9c5db199SXin Li 777*9c5db199SXin Li 778*9c5db199SXin Li def get_wifi_ip_subnet(self, ap_num): 779*9c5db199SXin Li """Return subnet of WiFi AP instance. 780*9c5db199SXin Li 781*9c5db199SXin Li If no APs are configured a TestError will be raised. 782*9c5db199SXin Li 783*9c5db199SXin Li @param ap_num int which local server to get an address from. 784*9c5db199SXin Li 785*9c5db199SXin Li """ 786*9c5db199SXin Li if not self.local_servers: 787*9c5db199SXin Li raise error.TestError('No APs configured.') 788*9c5db199SXin Li 789*9c5db199SXin Li return self.local_servers[ap_num]['netblock'].subnet 790*9c5db199SXin Li 791*9c5db199SXin Li 792*9c5db199SXin Li def get_hostapd_interface(self, ap_num): 793*9c5db199SXin Li """Get the name of the interface associated with a hostapd instance. 794*9c5db199SXin Li 795*9c5db199SXin Li @param ap_num: int hostapd instance number. 796*9c5db199SXin Li @return string interface name (e.g. 'managed0'). 797*9c5db199SXin Li 798*9c5db199SXin Li """ 799*9c5db199SXin Li if ap_num not in list(range(len(self.hostapd_instances))): 800*9c5db199SXin Li raise error.TestFail('Invalid instance number (%d) with %d ' 801*9c5db199SXin Li 'instances configured.' % 802*9c5db199SXin Li (ap_num, len(self.hostapd_instances))) 803*9c5db199SXin Li 804*9c5db199SXin Li instance = self.hostapd_instances[ap_num] 805*9c5db199SXin Li return instance.interface 806*9c5db199SXin Li 807*9c5db199SXin Li 808*9c5db199SXin Li def get_station_interface(self, instance): 809*9c5db199SXin Li """Get the name of the interface associated with a station. 810*9c5db199SXin Li 811*9c5db199SXin Li @param instance: int station instance number. 812*9c5db199SXin Li @return string interface name (e.g. 'managed0'). 813*9c5db199SXin Li 814*9c5db199SXin Li """ 815*9c5db199SXin Li if instance not in list(range(len(self.station_instances))): 816*9c5db199SXin Li raise error.TestFail('Invalid instance number (%d) with %d ' 817*9c5db199SXin Li 'instances configured.' % 818*9c5db199SXin Li (instance, len(self.station_instances))) 819*9c5db199SXin Li 820*9c5db199SXin Li instance = self.station_instances[instance] 821*9c5db199SXin Li return instance.interface 822*9c5db199SXin Li 823*9c5db199SXin Li 824*9c5db199SXin Li def get_hostapd_mac(self, ap_num): 825*9c5db199SXin Li """Return the MAC address of an AP in the test. 826*9c5db199SXin Li 827*9c5db199SXin Li @param ap_num int index of local server to read the MAC address from. 828*9c5db199SXin Li @return string MAC address like 00:11:22:33:44:55. 829*9c5db199SXin Li 830*9c5db199SXin Li """ 831*9c5db199SXin Li interface_name = self.get_hostapd_interface(ap_num) 832*9c5db199SXin Li ap_interface = interface.Interface(interface_name, self.host) 833*9c5db199SXin Li return ap_interface.mac_address 834*9c5db199SXin Li 835*9c5db199SXin Li 836*9c5db199SXin Li def get_hostapd_phy(self, ap_num): 837*9c5db199SXin Li """Get name of phy for hostapd instance. 838*9c5db199SXin Li 839*9c5db199SXin Li @param ap_num int index of hostapd instance. 840*9c5db199SXin Li @return string phy name of phy corresponding to hostapd's 841*9c5db199SXin Li managed interface. 842*9c5db199SXin Li 843*9c5db199SXin Li """ 844*9c5db199SXin Li interface = self.iw_runner.get_interface( 845*9c5db199SXin Li self.get_hostapd_interface(ap_num)) 846*9c5db199SXin Li return interface.phy 847*9c5db199SXin Li 848*9c5db199SXin Li 849*9c5db199SXin Li def deconfig(self): 850*9c5db199SXin Li """A legacy, deprecated alias for deconfig_aps.""" 851*9c5db199SXin Li self.deconfig_aps() 852*9c5db199SXin Li 853*9c5db199SXin Li 854*9c5db199SXin Li def deconfig_aps(self, instance=None, silent=False): 855*9c5db199SXin Li """De-configure an AP (will also bring wlan down). 856*9c5db199SXin Li 857*9c5db199SXin Li @param instance: int or None. If instance is None, will bring down all 858*9c5db199SXin Li instances of hostapd. 859*9c5db199SXin Li @param silent: True if instances should be brought without de-authing 860*9c5db199SXin Li the DUT. 861*9c5db199SXin Li 862*9c5db199SXin Li """ 863*9c5db199SXin Li if not self.hostapd_instances and not self.station_instances: 864*9c5db199SXin Li return 865*9c5db199SXin Li 866*9c5db199SXin Li if self.hostapd_instances: 867*9c5db199SXin Li local_servers = [] 868*9c5db199SXin Li if instance is not None: 869*9c5db199SXin Li instances = [ self.hostapd_instances.pop(instance) ] 870*9c5db199SXin Li for server in self.local_servers: 871*9c5db199SXin Li if server['interface'] == instances[0].interface: 872*9c5db199SXin Li local_servers = [server] 873*9c5db199SXin Li break 874*9c5db199SXin Li else: 875*9c5db199SXin Li instances = self.hostapd_instances 876*9c5db199SXin Li self.hostapd_instances = [] 877*9c5db199SXin Li local_servers = copy.copy(self.local_servers) 878*9c5db199SXin Li 879*9c5db199SXin Li for instance in instances: 880*9c5db199SXin Li if silent: 881*9c5db199SXin Li # Deconfigure without notifying DUT. Remove the interface 882*9c5db199SXin Li # hostapd uses to send beacon and DEAUTH packets. 883*9c5db199SXin Li self.remove_interface(instance.interface) 884*9c5db199SXin Li 885*9c5db199SXin Li self.kill_hostapd_instance(instance) 886*9c5db199SXin Li self.release_interface(instance.interface) 887*9c5db199SXin Li if self.station_instances: 888*9c5db199SXin Li local_servers = copy.copy(self.local_servers) 889*9c5db199SXin Li instance = self.station_instances.pop() 890*9c5db199SXin Li if instance.dev_type == 'ibss': 891*9c5db199SXin Li self.iw_runner.ibss_leave(instance.interface) 892*9c5db199SXin Li elif instance.dev_type == 'managed': 893*9c5db199SXin Li self._kill_process_instance(self.cmd_wpa_supplicant, 894*9c5db199SXin Li instance=instance.interface) 895*9c5db199SXin Li else: 896*9c5db199SXin Li self.iw_runner.disconnect_station(instance.interface) 897*9c5db199SXin Li self.router.run('%s link set %s down' % 898*9c5db199SXin Li (self.cmd_ip, instance.interface)) 899*9c5db199SXin Li 900*9c5db199SXin Li for server in local_servers: 901*9c5db199SXin Li self.stop_local_server(server) 902*9c5db199SXin Li 903*9c5db199SXin Li for brif in range(self._brif_index): 904*9c5db199SXin Li self.delete_link('%s%d' % 905*9c5db199SXin Li (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif)) 906*9c5db199SXin Li 907*9c5db199SXin Li 908*9c5db199SXin Li def set_ap_interface_down(self, instance=0): 909*9c5db199SXin Li """Bring down the hostapd interface. 910*9c5db199SXin Li 911*9c5db199SXin Li @param instance int router instance number. 912*9c5db199SXin Li 913*9c5db199SXin Li """ 914*9c5db199SXin Li self.host.run('%s link set %s down' % 915*9c5db199SXin Li (self.cmd_ip, self.get_hostapd_interface(instance))) 916*9c5db199SXin Li 917*9c5db199SXin Li 918*9c5db199SXin Li def confirm_pmksa_cache_use(self, instance=0): 919*9c5db199SXin Li """Verify that the PMKSA auth was cached on a hostapd instance. 920*9c5db199SXin Li 921*9c5db199SXin Li @param instance int router instance number. 922*9c5db199SXin Li 923*9c5db199SXin Li """ 924*9c5db199SXin Li log_file = self.hostapd_instances[instance].log_file 925*9c5db199SXin Li pmksa_match = 'PMK from PMKSA cache' 926*9c5db199SXin Li result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), 927*9c5db199SXin Li ignore_status=True) 928*9c5db199SXin Li if result.exit_status: 929*9c5db199SXin Li raise error.TestFail('PMKSA cache was not used in roaming.') 930*9c5db199SXin Li 931*9c5db199SXin Li 932*9c5db199SXin Li def get_ssid(self, instance=None): 933*9c5db199SXin Li """@return string ssid for the network stemming from this router.""" 934*9c5db199SXin Li if instance is None: 935*9c5db199SXin Li instance = 0 936*9c5db199SXin Li if len(self.hostapd_instances) > 1: 937*9c5db199SXin Li raise error.TestFail('No instance of hostapd specified with ' 938*9c5db199SXin Li 'multiple instances present.') 939*9c5db199SXin Li 940*9c5db199SXin Li if self.hostapd_instances: 941*9c5db199SXin Li return self.hostapd_instances[instance].ssid 942*9c5db199SXin Li 943*9c5db199SXin Li if self.station_instances: 944*9c5db199SXin Li return self.station_instances[0].ssid 945*9c5db199SXin Li 946*9c5db199SXin Li raise error.TestFail('Requested ssid of an unconfigured AP.') 947*9c5db199SXin Li 948*9c5db199SXin Li 949*9c5db199SXin Li def deauth_client(self, client_mac): 950*9c5db199SXin Li """Deauthenticates a client described in params. 951*9c5db199SXin Li 952*9c5db199SXin Li @param client_mac string containing the mac address of the client to be 953*9c5db199SXin Li deauthenticated. 954*9c5db199SXin Li 955*9c5db199SXin Li """ 956*9c5db199SXin Li control_if = self.hostapd_instances[-1].config_dict['ctrl_interface'] 957*9c5db199SXin Li self.router.run('%s -p%s deauthenticate %s' % 958*9c5db199SXin Li (self.cmd_hostapd_cli, control_if, client_mac)) 959*9c5db199SXin Li 960*9c5db199SXin Li def send_bss_tm_req(self, client_mac, neighbor_list): 961*9c5db199SXin Li """Send a BSS Transition Management Request to a client. 962*9c5db199SXin Li 963*9c5db199SXin Li @param client_mac string containing the mac address of the client. 964*9c5db199SXin Li @param neighbor_list list of strings containing mac addresses of 965*9c5db199SXin Li candidate APs. 966*9c5db199SXin Li @return string reply received from command 967*9c5db199SXin Li 968*9c5db199SXin Li """ 969*9c5db199SXin Li control_if = self.hostapd_instances[0].config_dict['ctrl_interface'] 970*9c5db199SXin Li command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' % 971*9c5db199SXin Li (self.cmd_hostapd_cli, control_if, client_mac, 972*9c5db199SXin Li ',0,0,0,0 neighbor='.join(neighbor_list))) 973*9c5db199SXin Li ret = self.router.run(command).stdout 974*9c5db199SXin Li return ret.splitlines()[-1] 975*9c5db199SXin Li 976*9c5db199SXin Li def _prep_probe_response_footer(self, footer): 977*9c5db199SXin Li """Write probe response footer temporarily to a local file and copy 978*9c5db199SXin Li over to test router. 979*9c5db199SXin Li 980*9c5db199SXin Li @param footer string containing bytes for the probe response footer. 981*9c5db199SXin Li @raises AutoservRunError: If footer file copy fails. 982*9c5db199SXin Li 983*9c5db199SXin Li """ 984*9c5db199SXin Li with tempfile.NamedTemporaryFile() as fp: 985*9c5db199SXin Li fp.write(footer) 986*9c5db199SXin Li fp.flush() 987*9c5db199SXin Li try: 988*9c5db199SXin Li self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE) 989*9c5db199SXin Li except error.AutoservRunError: 990*9c5db199SXin Li logging.error('failed to copy footer file to AP') 991*9c5db199SXin Li raise 992*9c5db199SXin Li 993*9c5db199SXin Li 994*9c5db199SXin Li def send_management_frame_on_ap(self, frame_type, channel, instance=0): 995*9c5db199SXin Li """Injects a management frame into an active hostapd session. 996*9c5db199SXin Li 997*9c5db199SXin Li @param frame_type string the type of frame to send. 998*9c5db199SXin Li @param channel int targeted channel 999*9c5db199SXin Li @param instance int indicating which hostapd instance to inject into. 1000*9c5db199SXin Li 1001*9c5db199SXin Li """ 1002*9c5db199SXin Li hostap_interface = self.hostapd_instances[instance].interface 1003*9c5db199SXin Li interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) 1004*9c5db199SXin Li self.router.run("%s link set %s up" % (self.cmd_ip, interface)) 1005*9c5db199SXin Li self.router.run('%s -i %s -t %s -c %d' % 1006*9c5db199SXin Li (self.cmd_send_management_frame, interface, frame_type, 1007*9c5db199SXin Li channel)) 1008*9c5db199SXin Li self.release_interface(interface) 1009*9c5db199SXin Li 1010*9c5db199SXin Li 1011*9c5db199SXin Li def send_management_frame(self, interface, frame_type, channel, 1012*9c5db199SXin Li ssid_prefix=None, num_bss=None, 1013*9c5db199SXin Li frame_count=None, delay=None, 1014*9c5db199SXin Li dest_addr=None, probe_resp_footer=None): 1015*9c5db199SXin Li """ 1016*9c5db199SXin Li Injects management frames on specify channel |frequency|. 1017*9c5db199SXin Li 1018*9c5db199SXin Li This function will spawn off a new process to inject specified 1019*9c5db199SXin Li management frames |frame_type| at the specified interface |interface|. 1020*9c5db199SXin Li 1021*9c5db199SXin Li @param interface string interface to inject frames. 1022*9c5db199SXin Li @param frame_type string message type. 1023*9c5db199SXin Li @param channel int targeted channel. 1024*9c5db199SXin Li @param ssid_prefix string SSID prefix. 1025*9c5db199SXin Li @param num_bss int number of BSS. 1026*9c5db199SXin Li @param frame_count int number of frames to send. 1027*9c5db199SXin Li @param delay int milliseconds delay between frames. 1028*9c5db199SXin Li @param dest_addr string destination address (DA) MAC address. 1029*9c5db199SXin Li @param probe_resp_footer string footer for probe response. 1030*9c5db199SXin Li 1031*9c5db199SXin Li @return int PID of the newly created process. 1032*9c5db199SXin Li 1033*9c5db199SXin Li """ 1034*9c5db199SXin Li command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame, 1035*9c5db199SXin Li interface, frame_type, channel) 1036*9c5db199SXin Li if ssid_prefix is not None: 1037*9c5db199SXin Li command += ' -s %s' % (ssid_prefix) 1038*9c5db199SXin Li if num_bss is not None: 1039*9c5db199SXin Li command += ' -b %d' % (num_bss) 1040*9c5db199SXin Li if frame_count is not None: 1041*9c5db199SXin Li command += ' -n %d' % (frame_count) 1042*9c5db199SXin Li if delay is not None: 1043*9c5db199SXin Li command += ' -d %d' % (delay) 1044*9c5db199SXin Li if dest_addr is not None: 1045*9c5db199SXin Li command += ' -a %s' % (dest_addr) 1046*9c5db199SXin Li if probe_resp_footer is not None: 1047*9c5db199SXin Li self._prep_probe_response_footer(footer=probe_resp_footer) 1048*9c5db199SXin Li command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE) 1049*9c5db199SXin Li command += ' > %s 2>&1 & echo $!' % (os.path.join(self.logdir, 1050*9c5db199SXin Li self.MGMT_FRAME_SENDER_LOG_FILE)) 1051*9c5db199SXin Li pid = int(self.router.run(command).stdout) 1052*9c5db199SXin Li return pid 1053*9c5db199SXin Li 1054*9c5db199SXin Li 1055*9c5db199SXin Li def detect_client_deauth(self, client_mac, instance=0): 1056*9c5db199SXin Li """Detects whether hostapd has logged a deauthentication from 1057*9c5db199SXin Li |client_mac|. 1058*9c5db199SXin Li 1059*9c5db199SXin Li @param client_mac string the MAC address of the client to detect. 1060*9c5db199SXin Li @param instance int indicating which hostapd instance to query. 1061*9c5db199SXin Li 1062*9c5db199SXin Li """ 1063*9c5db199SXin Li interface = self.hostapd_instances[instance].interface 1064*9c5db199SXin Li deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) 1065*9c5db199SXin Li log_file = self.hostapd_instances[instance].log_file 1066*9c5db199SXin Li result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), 1067*9c5db199SXin Li ignore_status=True) 1068*9c5db199SXin Li return result.exit_status == 0 1069*9c5db199SXin Li 1070*9c5db199SXin Li 1071*9c5db199SXin Li def detect_client_coexistence_report(self, client_mac, instance=0): 1072*9c5db199SXin Li """Detects whether hostapd has logged an action frame from 1073*9c5db199SXin Li |client_mac| indicating information about 20/40MHz BSS coexistence. 1074*9c5db199SXin Li 1075*9c5db199SXin Li @param client_mac string the MAC address of the client to detect. 1076*9c5db199SXin Li @param instance int indicating which hostapd instance to query. 1077*9c5db199SXin Li 1078*9c5db199SXin Li """ 1079*9c5db199SXin Li coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' 1080*9c5db199SXin Li '.. .. .. .. .. .. .. .. .. .. %s ' 1081*9c5db199SXin Li '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % 1082*9c5db199SXin Li ' '.join(client_mac.split(':'))) 1083*9c5db199SXin Li log_file = self.hostapd_instances[instance].log_file 1084*9c5db199SXin Li result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), 1085*9c5db199SXin Li ignore_status=True) 1086*9c5db199SXin Li return result.exit_status == 0 1087*9c5db199SXin Li 1088*9c5db199SXin Li 1089*9c5db199SXin Li def send_magic_packet(self, dest_ip, dest_mac): 1090*9c5db199SXin Li """Sends a magic packet to the NIC with the given IP and MAC addresses. 1091*9c5db199SXin Li 1092*9c5db199SXin Li @param dest_ip the IP address of the device to send the packet to 1093*9c5db199SXin Li @param dest_mac the hardware MAC address of the device 1094*9c5db199SXin Li 1095*9c5db199SXin Li """ 1096*9c5db199SXin Li # magic packet is 6 0xff bytes followed by the hardware address 1097*9c5db199SXin Li # 16 times 1098*9c5db199SXin Li mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')]) 1099*9c5db199SXin Li magic_packet = '\xff' * 6 + mac_bytes * 16 1100*9c5db199SXin Li 1101*9c5db199SXin Li logging.info('Sending magic packet to %s...', dest_ip) 1102*9c5db199SXin Li self.host.run('python -uc "import socket, sys;' 1103*9c5db199SXin Li 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);' 1104*9c5db199SXin Li 's.sendto(sys.stdin.read(), (\'%s\', %d))"' % 1105*9c5db199SXin Li (dest_ip, UDP_DISCARD_PORT), 1106*9c5db199SXin Li stdin=magic_packet) 1107*9c5db199SXin Li 1108*9c5db199SXin Li 1109*9c5db199SXin Li def setup_bridge_mode_dhcp_server(self): 1110*9c5db199SXin Li """Setup an DHCP server for bridge mode. 1111*9c5db199SXin Li 1112*9c5db199SXin Li Setup an DHCP server on the main interface of the virtual ethernet 1113*9c5db199SXin Li pair, with peer interface connected to the bridge interface. This is 1114*9c5db199SXin Li used for testing APs in bridge mode. 1115*9c5db199SXin Li 1116*9c5db199SXin Li """ 1117*9c5db199SXin Li # Start a local server on main interface of virtual ethernet pair. 1118*9c5db199SXin Li self.start_local_server( 1119*9c5db199SXin Li self.get_virtual_ethernet_main_interface()) 1120*9c5db199SXin Li # Add peer interface to the bridge. 1121*9c5db199SXin Li self.add_interface_to_bridge( 1122*9c5db199SXin Li self.get_virtual_ethernet_peer_interface()) 1123*9c5db199SXin Li 1124*9c5db199SXin Li 1125*9c5db199SXin Li def create_brif(self): 1126*9c5db199SXin Li """Initialize a new bridge interface 1127*9c5db199SXin Li 1128*9c5db199SXin Li @return string bridge interface name 1129*9c5db199SXin Li """ 1130*9c5db199SXin Li brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, 1131*9c5db199SXin Li self._brif_index) 1132*9c5db199SXin Li self._brif_index += 1 1133*9c5db199SXin Li self.host.run('brctl addbr %s' % brif_name) 1134*9c5db199SXin Li return brif_name 1135