xref: /aosp_15_r20/external/autotest/server/site_linux_router.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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