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