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