|  | # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import collections | 
|  | import copy | 
|  | import logging | 
|  | import os | 
|  | import random | 
|  | import string | 
|  | import tempfile | 
|  | import time | 
|  |  | 
|  | from autotest_lib.client.common_lib import error | 
|  | from autotest_lib.client.common_lib import utils | 
|  | from autotest_lib.client.common_lib.cros import path_utils | 
|  | from autotest_lib.client.common_lib.cros.network import interface | 
|  | from autotest_lib.client.common_lib.cros.network import netblock | 
|  | from autotest_lib.client.common_lib.cros.network import ping_runner | 
|  | from autotest_lib.server import hosts | 
|  | from autotest_lib.server import site_linux_system | 
|  | from autotest_lib.server.cros import dnsname_mangler | 
|  |  | 
|  |  | 
|  | StationInstance = collections.namedtuple('StationInstance', | 
|  | ['ssid', 'interface', 'dev_type']) | 
|  | HostapdInstance = collections.namedtuple('HostapdInstance', | 
|  | ['ssid', 'conf_file', 'log_file', | 
|  | 'interface', 'config_dict', | 
|  | 'stderr_log_file', | 
|  | 'scenario_name']) | 
|  |  | 
|  | # Send magic packets here, so they can wake up the system but are otherwise | 
|  | # dropped. | 
|  | UDP_DISCARD_PORT = 9 | 
|  |  | 
|  | def build_router_hostname(client_hostname=None, router_hostname=None): | 
|  | """Build a router hostname from a client hostname. | 
|  |  | 
|  | @param client_hostname: string hostname of DUT connected to a router. | 
|  | @param router_hostname: string hostname of router. | 
|  | @return string hostname of connected router or None if the hostname | 
|  | cannot be inferred from the client hostname. | 
|  |  | 
|  | """ | 
|  | if not router_hostname and not client_hostname: | 
|  | raise error.TestError('Either client_hostname or router_hostname must ' | 
|  | 'be specified to build_router_hostname.') | 
|  |  | 
|  | return dnsname_mangler.get_router_addr(client_hostname, | 
|  | cmdline_override=router_hostname) | 
|  |  | 
|  |  | 
|  | def build_router_proxy(test_name='', client_hostname=None, router_addr=None, | 
|  | enable_avahi=False): | 
|  | """Build up a LinuxRouter object. | 
|  |  | 
|  | Verifies that the remote host responds to ping. | 
|  | Either client_hostname or router_addr must be specified. | 
|  |  | 
|  | @param test_name: string name of this test (e.g. 'network_WiFi_TestName'). | 
|  | @param client_hostname: string hostname of DUT if we're in the lab. | 
|  | @param router_addr: string DNS/IPv4 address to use for router host object. | 
|  | @param enable_avahi: boolean True iff avahi should be started on the router. | 
|  |  | 
|  | @return LinuxRouter or raise error.TestError on failure. | 
|  |  | 
|  | """ | 
|  | router_hostname = build_router_hostname(client_hostname=client_hostname, | 
|  | router_hostname=router_addr) | 
|  | logging.info('Connecting to router at %s', router_hostname) | 
|  | ping_helper = ping_runner.PingRunner() | 
|  | if not ping_helper.simple_ping(router_hostname): | 
|  | raise error.TestError('Router at %s is not pingable.' % | 
|  | router_hostname) | 
|  |  | 
|  | # Use CrosHost for all router hosts and avoid host detection. | 
|  | # Host detection would use JetstreamHost for Whirlwind routers. | 
|  | # JetstreamHost assumes ap-daemons are running. | 
|  | # Testbed routers run the testbed-ap profile with no ap-daemons. | 
|  | # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection. | 
|  | return LinuxRouter(hosts.create_host(router_hostname, | 
|  | host_class=hosts.CrosHost), | 
|  | test_name, | 
|  | enable_avahi=enable_avahi) | 
|  |  | 
|  |  | 
|  | class LinuxRouter(site_linux_system.LinuxSystem): | 
|  | """Linux/mac80211-style WiFi Router support for WiFiTest class. | 
|  |  | 
|  | This class implements test methods/steps that communicate with a | 
|  | router implemented with Linux/mac80211.  The router must | 
|  | be pre-configured to enable ssh access and have a mac80211-based | 
|  | wireless device.  We also assume hostapd 0.7.x and iw are present | 
|  | and any necessary modules are pre-loaded. | 
|  |  | 
|  | """ | 
|  |  | 
|  | KNOWN_TEST_PREFIX = 'network_WiFi_' | 
|  | POLLING_INTERVAL_SECONDS = 0.5 | 
|  | STARTUP_TIMEOUT_SECONDS = 30 | 
|  | SUFFIX_LETTERS = string.ascii_lowercase + string.digits | 
|  | SUBNET_PREFIX_OCTETS = (192, 168) | 
|  |  | 
|  | HOSTAPD_CONF_FILE_PATTERN = 'hostapd-test-%s.conf' | 
|  | HOSTAPD_LOG_FILE_PATTERN = 'hostapd-test-%s.log' | 
|  | HOSTAPD_STDERR_LOG_FILE_PATTERN = 'hostapd-stderr-test-%s.log' | 
|  | HOSTAPD_CONTROL_INTERFACE_PATTERN = 'hostapd-test-%s.ctrl' | 
|  | HOSTAPD_DRIVER_NAME = 'nl80211' | 
|  |  | 
|  | MGMT_FRAME_SENDER_LOG_FILE = 'send_management_frame-test.log' | 
|  |  | 
|  | PROBE_RESPONSE_FOOTER_FILE = '/tmp/autotest-probe_response_footer' | 
|  |  | 
|  | _RNG_AVAILABLE = '/sys/class/misc/hw_random/rng_available' | 
|  | _RNG_CURRENT = '/sys/class/misc/hw_random/rng_current' | 
|  |  | 
|  | def get_capabilities(self): | 
|  | """@return iterable object of AP capabilities for this system.""" | 
|  | caps = set() | 
|  | try: | 
|  | self.cmd_send_management_frame = path_utils.must_be_installed( | 
|  | '/usr/bin/send_management_frame', host=self.host) | 
|  | caps.add(self.CAPABILITY_SEND_MANAGEMENT_FRAME) | 
|  | except error.TestFail: | 
|  | pass | 
|  | return super(LinuxRouter, self).get_capabilities().union(caps) | 
|  |  | 
|  |  | 
|  | @property | 
|  | def router(self): | 
|  | """Deprecated.  Use self.host instead. | 
|  |  | 
|  | @return Host object representing the remote router. | 
|  |  | 
|  | """ | 
|  | return self.host | 
|  |  | 
|  |  | 
|  | @property | 
|  | def wifi_ip(self): | 
|  | """Simple accessor for the WiFi IP when there is only one AP. | 
|  |  | 
|  | @return string IP of WiFi interface. | 
|  |  | 
|  | """ | 
|  | if len(self.local_servers) != 1: | 
|  | raise error.TestError('Could not pick a WiFi IP to return.') | 
|  |  | 
|  | return self.get_wifi_ip(0) | 
|  |  | 
|  |  | 
|  | def __init__(self, host, test_name, enable_avahi=False, role='router'): | 
|  | """Build a LinuxRouter. | 
|  |  | 
|  | @param host Host object representing the remote machine. | 
|  | @param test_name string name of this test.  Used in SSID creation. | 
|  | @param enable_avahi: boolean True iff avahi should be started on the | 
|  | router. | 
|  | @param role string description of host (e.g. router, pcap) | 
|  |  | 
|  | """ | 
|  | super(LinuxRouter, self).__init__(host, role) | 
|  | self._ssid_prefix = test_name | 
|  | self._enable_avahi = enable_avahi | 
|  | self.__setup() | 
|  |  | 
|  |  | 
|  | def __setup(self): | 
|  | """Set up this system. | 
|  |  | 
|  | Can be used either to complete initialization of a LinuxRouter | 
|  | object, or to re-establish a good state after a reboot. | 
|  |  | 
|  | """ | 
|  | self.cmd_dhcpd = '/usr/sbin/dhcpd' | 
|  | self.cmd_hostapd = path_utils.must_be_installed( | 
|  | '/usr/sbin/hostapd', host=self.host) | 
|  | self.cmd_hostapd_cli = path_utils.must_be_installed( | 
|  | '/usr/sbin/hostapd_cli', host=self.host) | 
|  | self.cmd_wpa_supplicant = path_utils.must_be_installed( | 
|  | '/usr/sbin/wpa_supplicant', host=self.host) | 
|  | self.dhcpd_conf = os.path.join(self.logdir, 'dhcpd.%s.conf') | 
|  | self.dhcpd_leases = os.path.join(self.logdir, 'dhcpd.leases') | 
|  |  | 
|  | # Log the most recent message on the router so that we can rebuild the | 
|  | # suffix relevant to us when debugging failures. | 
|  | last_log_line = self.host.run('tail -1 /var/log/messages', | 
|  | ignore_status=True).stdout | 
|  | # We're trying to get the timestamp from: | 
|  | # 2014-07-23T17:29:34.961056+00:00 localhost kernel: blah blah blah | 
|  | self._log_start_timestamp = last_log_line.strip().partition(' ')[0] | 
|  | if self._log_start_timestamp: | 
|  | logging.debug('Will only retrieve logs after %s.', | 
|  | self._log_start_timestamp) | 
|  | else: | 
|  | # If syslog is empty, we just use a wildcard pattern, to grab | 
|  | # everything. | 
|  | logging.debug('Empty or corrupt log; will retrieve whole log') | 
|  | self._log_start_timestamp = '.' | 
|  |  | 
|  | # hostapd configuration persists throughout the test, subsequent | 
|  | # 'config' commands only modify it. | 
|  | if self._ssid_prefix.startswith(self.KNOWN_TEST_PREFIX): | 
|  | # Many of our tests start with an uninteresting prefix. | 
|  | # Remove it so we can have more unique bytes. | 
|  | self._ssid_prefix = self._ssid_prefix[len(self.KNOWN_TEST_PREFIX):] | 
|  | self._number_unique_ssids = 0 | 
|  |  | 
|  | self._brif_index = 0 | 
|  |  | 
|  | self._total_hostapd_instances = 0 | 
|  | self.local_servers = [] | 
|  | self.server_address_index = [] | 
|  | self.hostapd_instances = [] | 
|  | self.station_instances = [] | 
|  | self.dhcp_low = 1 | 
|  | self.dhcp_high = 128 | 
|  |  | 
|  | # Kill hostapd and dhcp server if already running. | 
|  | self._kill_process_instance('hostapd', timeout_seconds=30) | 
|  | self.stop_dhcp_server(instance=None) | 
|  |  | 
|  | # Place us in the US by default | 
|  | self.iw_runner.set_regulatory_domain('US') | 
|  |  | 
|  | self.enable_all_antennas() | 
|  |  | 
|  | # Some tests want this functionality, but otherwise, it's a distraction. | 
|  | if self._enable_avahi: | 
|  | self.host.run('start avahi', ignore_status=True) | 
|  | else: | 
|  | self.host.run('stop avahi', ignore_status=True) | 
|  |  | 
|  | # Some routers have bad (slow?) random number generators. | 
|  | self.rng_configure() | 
|  |  | 
|  |  | 
|  | def close(self): | 
|  | """Close global resources held by this system.""" | 
|  | router_log = os.path.join(self.logdir, 'router_log') | 
|  | self.deconfig() | 
|  | # dnsmasq and hostapd cause interesting events to go to system logs. | 
|  | # Retrieve only the suffix of the logs after the timestamp we stored on | 
|  | # router creation. | 
|  | self.host.run("sed -n -e '/%s/,$p' /var/log/messages >%s" % | 
|  | (self._log_start_timestamp, router_log), | 
|  | ignore_status=True) | 
|  | self.host.get_file(router_log, 'debug/%s_host_messages' % self.role) | 
|  | super(LinuxRouter, self).close() | 
|  |  | 
|  |  | 
|  | def reboot(self, timeout): | 
|  | """Reboot this router, and restore it to a known-good state. | 
|  |  | 
|  | @param timeout Maximum seconds to wait for router to return. | 
|  |  | 
|  | """ | 
|  | super(LinuxRouter, self).reboot(timeout) | 
|  | self.__setup() | 
|  |  | 
|  |  | 
|  | def has_local_server(self): | 
|  | """@return True iff this router has local servers configured.""" | 
|  | return bool(self.local_servers) | 
|  |  | 
|  |  | 
|  | def start_hostapd(self, configuration): | 
|  | """Start a hostapd instance described by conf. | 
|  |  | 
|  | @param configuration HostapConfig object. | 
|  |  | 
|  | """ | 
|  | # Figure out the correct interface. | 
|  | interface = self.get_wlanif(configuration.frequency, 'managed', | 
|  | configuration.min_streams) | 
|  | phy_name = self.iw_runner.get_interface(interface).phy | 
|  |  | 
|  | conf_file = os.path.join(self.logdir, | 
|  | self.HOSTAPD_CONF_FILE_PATTERN % interface) | 
|  | log_file = os.path.join(self.logdir, | 
|  | self.HOSTAPD_LOG_FILE_PATTERN % interface) | 
|  | stderr_log_file = os.path.join(self.logdir, | 
|  | self.HOSTAPD_STDERR_LOG_FILE_PATTERN % interface) | 
|  | control_interface = os.path.join(self.logdir, | 
|  | self.HOSTAPD_CONTROL_INTERFACE_PATTERN % interface) | 
|  | hostapd_conf_dict = configuration.generate_dict( | 
|  | interface, control_interface, | 
|  | self.build_unique_ssid(suffix=configuration.ssid_suffix)) | 
|  | logging.debug('hostapd parameters: %r', hostapd_conf_dict) | 
|  |  | 
|  | # Generate hostapd.conf. | 
|  | self.router.run("cat <<EOF >%s\n%s\nEOF\n" % | 
|  | (conf_file, '\n'.join( | 
|  | "%s=%s" % kv for kv in hostapd_conf_dict.iteritems()))) | 
|  |  | 
|  | # Run hostapd. | 
|  | logging.info('Starting hostapd on %s(%s) channel=%s...', | 
|  | interface, phy_name, configuration.channel) | 
|  | self.router.run('rm %s' % log_file, ignore_status=True) | 
|  | self.router.run('stop wpasupplicant', ignore_status=True) | 
|  | start_command = '%s -dd -t -K %s > %s 2> %s & echo $!' % ( | 
|  | self.cmd_hostapd, conf_file, log_file, stderr_log_file) | 
|  | pid = int(self.router.run(start_command).stdout.strip()) | 
|  | self.hostapd_instances.append(HostapdInstance( | 
|  | hostapd_conf_dict['ssid'], | 
|  | conf_file, | 
|  | log_file, | 
|  | interface, | 
|  | hostapd_conf_dict.copy(), | 
|  | stderr_log_file, | 
|  | configuration.scenario_name)) | 
|  |  | 
|  | # Wait for confirmation that the router came up. | 
|  | logging.info('Waiting for hostapd to startup.') | 
|  | utils.poll_for_condition( | 
|  | condition=lambda: self._has_hostapd_started(log_file, pid), | 
|  | exception=error.TestFail('Timed out while waiting for hostapd ' | 
|  | 'to start.'), | 
|  | timeout=self.STARTUP_TIMEOUT_SECONDS, | 
|  | sleep_interval=self.POLLING_INTERVAL_SECONDS) | 
|  |  | 
|  | if configuration.frag_threshold: | 
|  | threshold = self.iw_runner.get_fragmentation_threshold(phy_name) | 
|  | if threshold != configuration.frag_threshold: | 
|  | raise error.TestNAError('Router does not support setting ' | 
|  | 'fragmentation threshold') | 
|  |  | 
|  |  | 
|  | def _has_hostapd_started(self, log_file, pid): | 
|  | """Determines if hostapd has started. | 
|  |  | 
|  | @return Whether or not hostapd has started. | 
|  | @raise error.TestFail if there was a bad config or hostapd terminated. | 
|  | """ | 
|  | success = self.router.run( | 
|  | 'grep "Setup of interface done" %s' % log_file, | 
|  | ignore_status=True).exit_status == 0 | 
|  | if success: | 
|  | return True | 
|  |  | 
|  | # A common failure is an invalid router configuration. | 
|  | # Detect this and exit early if we see it. | 
|  | bad_config = self.router.run( | 
|  | 'grep "Interface initialization failed" %s' % log_file, | 
|  | ignore_status=True).exit_status == 0 | 
|  | if bad_config: | 
|  | raise error.TestFail('hostapd failed to initialize AP ' | 
|  | 'interface.') | 
|  |  | 
|  | if pid: | 
|  | early_exit = self.router.run('kill -0 %d' % pid, | 
|  | ignore_status=True).exit_status | 
|  | if early_exit: | 
|  | raise error.TestFail('hostapd process terminated.') | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | def _kill_process_instance(self, | 
|  | process, | 
|  | instance=None, | 
|  | timeout_seconds=10, | 
|  | ignore_timeouts=False): | 
|  | """Kill a process on the router. | 
|  |  | 
|  | Kills remote program named |process| (optionally only a specific | 
|  | |instance|).  Wait |timeout_seconds| for |process| to die | 
|  | before returning.  If |ignore_timeouts| is False, raise | 
|  | a TestError on timeouts. | 
|  |  | 
|  | @param process: string name of process to kill. | 
|  | @param instance: string fragment of the command line unique to | 
|  | this instance of the remote process. | 
|  | @param timeout_seconds: float timeout in seconds to wait. | 
|  | @param ignore_timeouts: True iff we should ignore failures to | 
|  | kill processes. | 
|  | @return True iff the specified process has exited. | 
|  |  | 
|  | """ | 
|  | if instance is not None: | 
|  | search_arg = '-f "^%s.*%s"' % (process, instance) | 
|  | else: | 
|  | search_arg = process | 
|  |  | 
|  | self.host.run('pkill %s' % search_arg, ignore_status=True) | 
|  |  | 
|  | # Wait for process to die | 
|  | time.sleep(self.POLLING_INTERVAL_SECONDS) | 
|  | try: | 
|  | utils.poll_for_condition( | 
|  | condition=lambda: self.host.run( | 
|  | 'pgrep -l %s' % search_arg, | 
|  | ignore_status=True).exit_status != 0, | 
|  | timeout=timeout_seconds, | 
|  | sleep_interval=self.POLLING_INTERVAL_SECONDS) | 
|  | except utils.TimeoutError: | 
|  | if ignore_timeouts: | 
|  | return False | 
|  |  | 
|  | raise error.TestError( | 
|  | 'Timed out waiting for %s%s to die' % | 
|  | (process, | 
|  | '' if instance is None else ' (instance=%s)' % instance)) | 
|  | return True | 
|  |  | 
|  |  | 
|  | def kill_hostapd_instance(self, instance): | 
|  | """Kills a hostapd instance. | 
|  |  | 
|  | @param instance HostapdInstance object. | 
|  |  | 
|  | """ | 
|  | is_dead = self._kill_process_instance( | 
|  | self.cmd_hostapd, | 
|  | instance=instance.conf_file, | 
|  | timeout_seconds=30, | 
|  | ignore_timeouts=True) | 
|  | if instance.scenario_name: | 
|  | log_identifier = instance.scenario_name | 
|  | else: | 
|  | log_identifier = '%d_%s' % ( | 
|  | self._total_hostapd_instances, instance.interface) | 
|  | files_to_copy = [(instance.log_file, | 
|  | 'debug/hostapd_%s_%s.log' % | 
|  | (self.role, log_identifier)), | 
|  | (instance.stderr_log_file, | 
|  | 'debug/hostapd_%s_%s.stderr.log' % | 
|  | (self.role, log_identifier))] | 
|  | for remote_file, local_file in files_to_copy: | 
|  | if self.host.run('ls %s >/dev/null 2>&1' % remote_file, | 
|  | ignore_status=True).exit_status: | 
|  | logging.error('Did not collect hostapd log file because ' | 
|  | 'it was missing.') | 
|  | else: | 
|  | self.router.get_file(remote_file, local_file) | 
|  | self._total_hostapd_instances += 1 | 
|  | if not is_dead: | 
|  | raise error.TestError('Timed out killing hostapd.') | 
|  |  | 
|  |  | 
|  | def build_unique_ssid(self, suffix=''): | 
|  | """ Build our unique token by base-<len(self.SUFFIX_LETTERS)> encoding | 
|  | the number of APs we've constructed already. | 
|  |  | 
|  | @param suffix string to append to SSID | 
|  |  | 
|  | """ | 
|  | base = len(self.SUFFIX_LETTERS) | 
|  | number = self._number_unique_ssids | 
|  | self._number_unique_ssids += 1 | 
|  | unique = '' | 
|  | while number or not unique: | 
|  | unique = self.SUFFIX_LETTERS[number % base] + unique | 
|  | number = number / base | 
|  | # And salt the SSID so that tests running in adjacent cells are unlikely | 
|  | # to pick the same SSID and we're resistent to beacons leaking out of | 
|  | # cells. | 
|  | salt = ''.join([random.choice(self.SUFFIX_LETTERS) for x in range(5)]) | 
|  | return '_'.join([self._ssid_prefix, unique, salt, suffix])[-32:] | 
|  |  | 
|  |  | 
|  | def rng_configure(self): | 
|  | """Configure the random generator to our liking. | 
|  |  | 
|  | Some routers (particularly, Gale) seem to have bad Random Number | 
|  | Generators, such that hostapd can't always generate keys fast enough. | 
|  | The on-board TPM seems to serve as a better generator, so we try to | 
|  | switch to that if available. | 
|  |  | 
|  | Symptoms of a slow RNG: hostapd complains with: | 
|  |  | 
|  | WPA: Not enough entropy in random pool to proceed - reject first | 
|  | 4-way handshake | 
|  |  | 
|  | Ref: | 
|  | https://chromium.googlesource.com/chromiumos/third_party/hostap/+/7ea51f728bb7/src/ap/wpa_auth.c#1854 | 
|  |  | 
|  | Linux devices may have RNG parameters at | 
|  | /sys/class/misc/hw_random/rng_{available,current}. See: | 
|  |  | 
|  | https://www.kernel.org/doc/Documentation/hw_random.txt | 
|  |  | 
|  | """ | 
|  |  | 
|  | available = self.host.run('cat %s' % self._RNG_AVAILABLE, \ | 
|  | ignore_status=True).stdout.strip().split(' ') | 
|  | # System may not have HWRNG support. Just skip this. | 
|  | if available == "": | 
|  | return | 
|  | current = self.host.run('cat %s' % self._RNG_CURRENT).stdout. \ | 
|  | strip() | 
|  | want_rng = "tpm-rng" | 
|  |  | 
|  | logging.debug("Available / current RNGs on router: %r / %s", | 
|  | available, current) | 
|  | if want_rng in available and want_rng != current: | 
|  | logging.debug("Switching RNGs: %s -> %s", current, want_rng) | 
|  | self.host.run('echo -n "%s" > %s' % (want_rng, self._RNG_CURRENT)) | 
|  |  | 
|  |  | 
|  | def hostap_configure(self, configuration, multi_interface=None): | 
|  | """Build up a hostapd configuration file and start hostapd. | 
|  |  | 
|  | Also setup a local server if this router supports them. | 
|  |  | 
|  | @param configuration HosetapConfig object. | 
|  | @param multi_interface bool True iff multiple interfaces allowed. | 
|  |  | 
|  | """ | 
|  | if multi_interface is None and (self.hostapd_instances or | 
|  | self.station_instances): | 
|  | self.deconfig() | 
|  | if configuration.is_11ac: | 
|  | router_caps = self.get_capabilities() | 
|  | if site_linux_system.LinuxSystem.CAPABILITY_VHT not in router_caps: | 
|  | raise error.TestNAError('Router does not have AC support') | 
|  |  | 
|  | self.start_hostapd(configuration) | 
|  | interface = self.hostapd_instances[-1].interface | 
|  | self.iw_runner.set_tx_power(interface, 'auto') | 
|  | self.start_local_server(interface, bridge=configuration.bridge) | 
|  | logging.info('AP configured.') | 
|  |  | 
|  |  | 
|  | def ibss_configure(self, config): | 
|  | """Configure a station based AP in IBSS mode. | 
|  |  | 
|  | Extract relevant configuration objects from |config| despite not | 
|  | actually being a hostap managed endpoint. | 
|  |  | 
|  | @param config HostapConfig object. | 
|  |  | 
|  | """ | 
|  | if self.station_instances or self.hostapd_instances: | 
|  | self.deconfig() | 
|  | interface = self.get_wlanif(config.frequency, 'ibss') | 
|  | ssid = (config.ssid or | 
|  | self.build_unique_ssid(suffix=config.ssid_suffix)) | 
|  | # Connect the station | 
|  | self.router.run('%s link set %s up' % (self.cmd_ip, interface)) | 
|  | self.iw_runner.ibss_join(interface, ssid, config.frequency) | 
|  | # Always start a local server. | 
|  | self.start_local_server(interface) | 
|  | # Remember that this interface is up. | 
|  | self.station_instances.append( | 
|  | StationInstance(ssid=ssid, interface=interface, | 
|  | dev_type='ibss')) | 
|  |  | 
|  |  | 
|  | def local_server_address(self, index): | 
|  | """Get the local server address for an interface. | 
|  |  | 
|  | When we multiple local servers, we give them static IP addresses | 
|  | like 192.168.*.254. | 
|  |  | 
|  | @param index int describing which local server this is for. | 
|  |  | 
|  | """ | 
|  | return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 254)) | 
|  |  | 
|  |  | 
|  | def local_peer_ip_address(self, index): | 
|  | """Get the IP address allocated for the peer associated to the AP. | 
|  |  | 
|  | This address is assigned to a locally associated peer device that | 
|  | is created for the DUT to perform connectivity tests with. | 
|  | When we have multiple local servers, we give them static IP addresses | 
|  | like 192.168.*.253. | 
|  |  | 
|  | @param index int describing which local server this is for. | 
|  |  | 
|  | """ | 
|  | return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253)) | 
|  |  | 
|  | def local_bridge_address(self, index): | 
|  | """Get the bridge address for an interface. | 
|  |  | 
|  | This address is assigned to a local bridge device. | 
|  |  | 
|  | @param index int describing which local server this is for. | 
|  |  | 
|  | """ | 
|  | return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252)) | 
|  |  | 
|  | def local_peer_mac_address(self): | 
|  | """Get the MAC address of the peer interface. | 
|  |  | 
|  | @return string MAC address of the peer interface. | 
|  |  | 
|  | """ | 
|  | iface = interface.Interface(self.station_instances[0].interface, | 
|  | self.router) | 
|  | return iface.mac_address | 
|  |  | 
|  |  | 
|  | def _get_unused_server_address_index(self): | 
|  | """@return an unused server address index.""" | 
|  | for address_index in range(0, 256): | 
|  | if address_index not in self.server_address_index: | 
|  | return address_index | 
|  | raise error.TestFail('No available server address index') | 
|  |  | 
|  |  | 
|  | def change_server_address_index(self, ap_num=0, server_address_index=None): | 
|  | """Restart the local server with a different server address index. | 
|  |  | 
|  | This will restart the local server with different gateway IP address | 
|  | and DHCP address ranges. | 
|  |  | 
|  | @param ap_num: int hostapd instance number. | 
|  | @param server_address_index: int server address index. | 
|  |  | 
|  | """ | 
|  | interface = self.local_servers[ap_num]['interface']; | 
|  | # Get an unused server address index if one is not specified, which | 
|  | # will be different from the one that's currently in used. | 
|  | if server_address_index is None: | 
|  | server_address_index = self._get_unused_server_address_index() | 
|  |  | 
|  | # Restart local server with the new server address index. | 
|  | self.stop_local_server(self.local_servers[ap_num]) | 
|  | self.start_local_server(interface, | 
|  | ap_num=ap_num, | 
|  | server_address_index=server_address_index) | 
|  |  | 
|  |  | 
|  | def start_local_server(self, | 
|  | interface, | 
|  | ap_num=None, | 
|  | server_address_index=None, | 
|  | bridge=None): | 
|  | """Start a local server on an interface. | 
|  |  | 
|  | @param interface string (e.g. wlan0) | 
|  | @param ap_num int the ap instance to start the server for | 
|  | @param server_address_index int server address index | 
|  | @param bridge string (e.g. br0) | 
|  |  | 
|  | """ | 
|  | logging.info('Starting up local server...') | 
|  |  | 
|  | if len(self.local_servers) >= 256: | 
|  | raise error.TestFail('Exhausted available local servers') | 
|  |  | 
|  | # Get an unused server address index if one is not specified. | 
|  | # Validate server address index if one is specified. | 
|  | if server_address_index is None: | 
|  | server_address_index = self._get_unused_server_address_index() | 
|  | elif server_address_index in self.server_address_index: | 
|  | raise error.TestFail('Server address index %d already in used' % | 
|  | server_address_index) | 
|  |  | 
|  | server_addr = netblock.from_addr( | 
|  | self.local_server_address(server_address_index), | 
|  | prefix_len=24) | 
|  |  | 
|  | params = {} | 
|  | params['address_index'] = server_address_index | 
|  | params['netblock'] = server_addr | 
|  | params['dhcp_range'] = ' '.join( | 
|  | (server_addr.get_addr_in_block(1), | 
|  | server_addr.get_addr_in_block(128))) | 
|  | params['interface'] = interface | 
|  | params['bridge'] = bridge | 
|  | params['ip_params'] = ('%s broadcast %s dev %s' % | 
|  | (server_addr.netblock, | 
|  | server_addr.broadcast, | 
|  | interface)) | 
|  | if ap_num is None: | 
|  | self.local_servers.append(params) | 
|  | else: | 
|  | self.local_servers.insert(ap_num, params) | 
|  | self.server_address_index.append(server_address_index) | 
|  |  | 
|  | self.router.run('%s addr flush %s' % | 
|  | (self.cmd_ip, interface)) | 
|  | self.router.run('%s addr add %s' % | 
|  | (self.cmd_ip, params['ip_params'])) | 
|  | self.router.run('%s link set %s up' % | 
|  | (self.cmd_ip, interface)) | 
|  | if params['bridge']: | 
|  | bridge_addr = netblock.from_addr( | 
|  | self.local_bridge_address(server_address_index), | 
|  | prefix_len=24) | 
|  | self.router.run("ifconfig %s %s" % | 
|  | (params['bridge'], bridge_addr.netblock)) | 
|  | self.start_dhcp_server(interface) | 
|  |  | 
|  |  | 
|  | def stop_local_server(self, server): | 
|  | """Stop a local server on the router | 
|  |  | 
|  | @param server object server configuration parameters. | 
|  |  | 
|  | """ | 
|  | self.stop_dhcp_server(server['interface']) | 
|  | self.router.run("%s addr del %s" % | 
|  | (self.cmd_ip, server['ip_params']), | 
|  | ignore_status=True) | 
|  | self.server_address_index.remove(server['address_index']) | 
|  | self.local_servers.remove(server) | 
|  |  | 
|  |  | 
|  | def start_dhcp_server(self, interface): | 
|  | """Start a dhcp server on an interface. | 
|  |  | 
|  | @param interface string (e.g. wlan0) | 
|  |  | 
|  | """ | 
|  | for server in self.local_servers: | 
|  | if server['interface'] == interface: | 
|  | params = server | 
|  | break | 
|  | else: | 
|  | raise error.TestFail('Could not find local server ' | 
|  | 'to match interface: %r' % interface) | 
|  | server_addr = params['netblock'] | 
|  | dhcpd_conf_file = self.dhcpd_conf % interface | 
|  | dhcp_conf = '\n'.join([ | 
|  | 'port=0',  # disables DNS server | 
|  | 'bind-interfaces', | 
|  | 'log-dhcp', | 
|  | 'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1), | 
|  | server_addr.get_addr_in_block(128))), | 
|  | 'interface=%s' % (params['bridge'] or params['interface']), | 
|  | 'dhcp-leasefile=%s' % self.dhcpd_leases]) | 
|  | self.router.run('cat <<EOF >%s\n%s\nEOF\n' % | 
|  | (dhcpd_conf_file, dhcp_conf)) | 
|  | self.router.run('dnsmasq --conf-file=%s' % dhcpd_conf_file) | 
|  |  | 
|  |  | 
|  | def stop_dhcp_server(self, instance=None): | 
|  | """Stop a dhcp server on the router. | 
|  |  | 
|  | @param instance string instance to kill. | 
|  |  | 
|  | """ | 
|  | self._kill_process_instance('dnsmasq', instance=instance) | 
|  |  | 
|  |  | 
|  | def get_wifi_channel(self, ap_num): | 
|  | """Return channel of BSS corresponding to |ap_num|. | 
|  |  | 
|  | @param ap_num int which BSS to get the channel of. | 
|  | @return int primary channel of BSS. | 
|  |  | 
|  | """ | 
|  | instance = self.hostapd_instances[ap_num] | 
|  | return instance.config_dict['channel'] | 
|  |  | 
|  |  | 
|  | def get_wifi_ip(self, ap_num): | 
|  | """Return IP address on the WiFi subnet of a local server on the router. | 
|  |  | 
|  | If no local servers are configured (e.g. for an RSPro), a TestFail will | 
|  | be raised. | 
|  |  | 
|  | @param ap_num int which local server to get an address from. | 
|  |  | 
|  | """ | 
|  | if not self.local_servers: | 
|  | raise error.TestError('No IP address assigned') | 
|  |  | 
|  | return self.local_servers[ap_num]['netblock'].addr | 
|  |  | 
|  |  | 
|  | def get_wifi_ip_subnet(self, ap_num): | 
|  | """Return subnet of WiFi AP instance. | 
|  |  | 
|  | If no APs are configured a TestError will be raised. | 
|  |  | 
|  | @param ap_num int which local server to get an address from. | 
|  |  | 
|  | """ | 
|  | if not self.local_servers: | 
|  | raise error.TestError('No APs configured.') | 
|  |  | 
|  | return self.local_servers[ap_num]['netblock'].subnet | 
|  |  | 
|  |  | 
|  | def get_hostapd_interface(self, ap_num): | 
|  | """Get the name of the interface associated with a hostapd instance. | 
|  |  | 
|  | @param ap_num: int hostapd instance number. | 
|  | @return string interface name (e.g. 'managed0'). | 
|  |  | 
|  | """ | 
|  | if ap_num not in range(len(self.hostapd_instances)): | 
|  | raise error.TestFail('Invalid instance number (%d) with %d ' | 
|  | 'instances configured.' % | 
|  | (ap_num, len(self.hostapd_instances))) | 
|  |  | 
|  | instance = self.hostapd_instances[ap_num] | 
|  | return instance.interface | 
|  |  | 
|  |  | 
|  | def get_station_interface(self, instance): | 
|  | """Get the name of the interface associated with a station. | 
|  |  | 
|  | @param instance: int station instance number. | 
|  | @return string interface name (e.g. 'managed0'). | 
|  |  | 
|  | """ | 
|  | if instance not in range(len(self.station_instances)): | 
|  | raise error.TestFail('Invalid instance number (%d) with %d ' | 
|  | 'instances configured.' % | 
|  | (instance, len(self.station_instances))) | 
|  |  | 
|  | instance = self.station_instances[instance] | 
|  | return instance.interface | 
|  |  | 
|  |  | 
|  | def get_hostapd_mac(self, ap_num): | 
|  | """Return the MAC address of an AP in the test. | 
|  |  | 
|  | @param ap_num int index of local server to read the MAC address from. | 
|  | @return string MAC address like 00:11:22:33:44:55. | 
|  |  | 
|  | """ | 
|  | interface_name = self.get_hostapd_interface(ap_num) | 
|  | ap_interface = interface.Interface(interface_name, self.host) | 
|  | return ap_interface.mac_address | 
|  |  | 
|  |  | 
|  | def get_hostapd_phy(self, ap_num): | 
|  | """Get name of phy for hostapd instance. | 
|  |  | 
|  | @param ap_num int index of hostapd instance. | 
|  | @return string phy name of phy corresponding to hostapd's | 
|  | managed interface. | 
|  |  | 
|  | """ | 
|  | interface = self.iw_runner.get_interface( | 
|  | self.get_hostapd_interface(ap_num)) | 
|  | return interface.phy | 
|  |  | 
|  |  | 
|  | def deconfig(self): | 
|  | """A legacy, deprecated alias for deconfig_aps.""" | 
|  | self.deconfig_aps() | 
|  |  | 
|  |  | 
|  | def deconfig_aps(self, instance=None, silent=False): | 
|  | """De-configure an AP (will also bring wlan down). | 
|  |  | 
|  | @param instance: int or None.  If instance is None, will bring down all | 
|  | instances of hostapd. | 
|  | @param silent: True if instances should be brought without de-authing | 
|  | the DUT. | 
|  |  | 
|  | """ | 
|  | if not self.hostapd_instances and not self.station_instances: | 
|  | return | 
|  |  | 
|  | if self.hostapd_instances: | 
|  | local_servers = [] | 
|  | if instance is not None: | 
|  | instances = [ self.hostapd_instances.pop(instance) ] | 
|  | for server in self.local_servers: | 
|  | if server['interface'] == instances[0].interface: | 
|  | local_servers = [server] | 
|  | break | 
|  | else: | 
|  | instances = self.hostapd_instances | 
|  | self.hostapd_instances = [] | 
|  | local_servers = copy.copy(self.local_servers) | 
|  |  | 
|  | for instance in instances: | 
|  | if silent: | 
|  | # Deconfigure without notifying DUT.  Remove the interface | 
|  | # hostapd uses to send beacon and DEAUTH packets. | 
|  | self.remove_interface(instance.interface) | 
|  |  | 
|  | self.kill_hostapd_instance(instance) | 
|  | self.release_interface(instance.interface) | 
|  | if self.station_instances: | 
|  | local_servers = copy.copy(self.local_servers) | 
|  | instance = self.station_instances.pop() | 
|  | if instance.dev_type == 'ibss': | 
|  | self.iw_runner.ibss_leave(instance.interface) | 
|  | elif instance.dev_type == 'managed': | 
|  | self._kill_process_instance(self.cmd_wpa_supplicant, | 
|  | instance=instance.interface) | 
|  | else: | 
|  | self.iw_runner.disconnect_station(instance.interface) | 
|  | self.router.run('%s link set %s down' % | 
|  | (self.cmd_ip, instance.interface)) | 
|  |  | 
|  | for server in local_servers: | 
|  | self.stop_local_server(server) | 
|  |  | 
|  | for brif in range(self._brif_index): | 
|  | self.delete_link('%s%d' % | 
|  | (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, brif)) | 
|  |  | 
|  |  | 
|  | def set_ap_interface_down(self, instance=0): | 
|  | """Bring down the hostapd interface. | 
|  |  | 
|  | @param instance int router instance number. | 
|  |  | 
|  | """ | 
|  | self.host.run('%s link set %s down' % | 
|  | (self.cmd_ip, self.get_hostapd_interface(instance))) | 
|  |  | 
|  |  | 
|  | def confirm_pmksa_cache_use(self, instance=0): | 
|  | """Verify that the PMKSA auth was cached on a hostapd instance. | 
|  |  | 
|  | @param instance int router instance number. | 
|  |  | 
|  | """ | 
|  | log_file = self.hostapd_instances[instance].log_file | 
|  | pmksa_match = 'PMK from PMKSA cache' | 
|  | result = self.router.run('grep -q "%s" %s' % (pmksa_match, log_file), | 
|  | ignore_status=True) | 
|  | if result.exit_status: | 
|  | raise error.TestFail('PMKSA cache was not used in roaming.') | 
|  |  | 
|  |  | 
|  | def get_ssid(self, instance=None): | 
|  | """@return string ssid for the network stemming from this router.""" | 
|  | if instance is None: | 
|  | instance = 0 | 
|  | if len(self.hostapd_instances) > 1: | 
|  | raise error.TestFail('No instance of hostapd specified with ' | 
|  | 'multiple instances present.') | 
|  |  | 
|  | if self.hostapd_instances: | 
|  | return self.hostapd_instances[instance].ssid | 
|  |  | 
|  | if self.station_instances: | 
|  | return self.station_instances[0].ssid | 
|  |  | 
|  | raise error.TestFail('Requested ssid of an unconfigured AP.') | 
|  |  | 
|  |  | 
|  | def deauth_client(self, client_mac): | 
|  | """Deauthenticates a client described in params. | 
|  |  | 
|  | @param client_mac string containing the mac address of the client to be | 
|  | deauthenticated. | 
|  |  | 
|  | """ | 
|  | control_if = self.hostapd_instances[-1].config_dict['ctrl_interface'] | 
|  | self.router.run('%s -p%s deauthenticate %s' % | 
|  | (self.cmd_hostapd_cli, control_if, client_mac)) | 
|  |  | 
|  | def send_bss_tm_req(self, client_mac, neighbor_list): | 
|  | """Send a BSS Transition Management Request to a client. | 
|  |  | 
|  | @param client_mac string containing the mac address of the client. | 
|  | @param neighbor_list list of strings containing mac addresses of | 
|  | candidate APs. | 
|  | @return string reply received from command | 
|  |  | 
|  | """ | 
|  | control_if = self.hostapd_instances[0].config_dict['ctrl_interface'] | 
|  | command = ('%s -p%s BSS_TM_REQ %s neighbor=%s,0,0,0,0 pref=1' % | 
|  | (self.cmd_hostapd_cli, control_if, client_mac, | 
|  | ',0,0,0,0 neighbor='.join(neighbor_list))) | 
|  | ret = self.router.run(command).stdout | 
|  | return ret.splitlines()[-1] | 
|  |  | 
|  | def _prep_probe_response_footer(self, footer): | 
|  | """Write probe response footer temporarily to a local file and copy | 
|  | over to test router. | 
|  |  | 
|  | @param footer string containing bytes for the probe response footer. | 
|  | @raises AutoservRunError: If footer file copy fails. | 
|  |  | 
|  | """ | 
|  | with tempfile.NamedTemporaryFile() as fp: | 
|  | fp.write(footer) | 
|  | fp.flush() | 
|  | try: | 
|  | self.host.send_file(fp.name, self.PROBE_RESPONSE_FOOTER_FILE) | 
|  | except error.AutoservRunError: | 
|  | logging.error('failed to copy footer file to AP') | 
|  | raise | 
|  |  | 
|  |  | 
|  | def send_management_frame_on_ap(self, frame_type, channel, instance=0): | 
|  | """Injects a management frame into an active hostapd session. | 
|  |  | 
|  | @param frame_type string the type of frame to send. | 
|  | @param channel int targeted channel | 
|  | @param instance int indicating which hostapd instance to inject into. | 
|  |  | 
|  | """ | 
|  | hostap_interface = self.hostapd_instances[instance].interface | 
|  | interface = self.get_wlanif(0, 'monitor', same_phy_as=hostap_interface) | 
|  | self.router.run("%s link set %s up" % (self.cmd_ip, interface)) | 
|  | self.router.run('%s -i %s -t %s -c %d' % | 
|  | (self.cmd_send_management_frame, interface, frame_type, | 
|  | channel)) | 
|  | self.release_interface(interface) | 
|  |  | 
|  |  | 
|  | def send_management_frame(self, interface, frame_type, channel, | 
|  | ssid_prefix=None, num_bss=None, | 
|  | frame_count=None, delay=None, | 
|  | dest_addr=None, probe_resp_footer=None): | 
|  | """ | 
|  | Injects management frames on specify channel |frequency|. | 
|  |  | 
|  | This function will spawn off a new process to inject specified | 
|  | management frames |frame_type| at the specified interface |interface|. | 
|  |  | 
|  | @param interface string interface to inject frames. | 
|  | @param frame_type string message type. | 
|  | @param channel int targeted channel. | 
|  | @param ssid_prefix string SSID prefix. | 
|  | @param num_bss int number of BSS. | 
|  | @param frame_count int number of frames to send. | 
|  | @param delay int milliseconds delay between frames. | 
|  | @param dest_addr string destination address (DA) MAC address. | 
|  | @param probe_resp_footer string footer for probe response. | 
|  |  | 
|  | @return int PID of the newly created process. | 
|  |  | 
|  | """ | 
|  | command = '%s -i %s -t %s -c %d' % (self.cmd_send_management_frame, | 
|  | interface, frame_type, channel) | 
|  | if ssid_prefix is not None: | 
|  | command += ' -s %s' % (ssid_prefix) | 
|  | if num_bss is not None: | 
|  | command += ' -b %d' % (num_bss) | 
|  | if frame_count is not None: | 
|  | command += ' -n %d' % (frame_count) | 
|  | if delay is not None: | 
|  | command += ' -d %d' % (delay) | 
|  | if dest_addr is not None: | 
|  | command += ' -a %s' % (dest_addr) | 
|  | if probe_resp_footer is not None: | 
|  | self._prep_probe_response_footer(footer=probe_resp_footer) | 
|  | command += ' -f %s' % (self.PROBE_RESPONSE_FOOTER_FILE) | 
|  | command += ' > %s 2>&1 & echo $!' % (os.path.join(self.logdir, | 
|  | self.MGMT_FRAME_SENDER_LOG_FILE)) | 
|  | pid = int(self.router.run(command).stdout) | 
|  | return pid | 
|  |  | 
|  |  | 
|  | def detect_client_deauth(self, client_mac, instance=0): | 
|  | """Detects whether hostapd has logged a deauthentication from | 
|  | |client_mac|. | 
|  |  | 
|  | @param client_mac string the MAC address of the client to detect. | 
|  | @param instance int indicating which hostapd instance to query. | 
|  |  | 
|  | """ | 
|  | interface = self.hostapd_instances[instance].interface | 
|  | deauth_msg = "%s: deauthentication: STA=%s" % (interface, client_mac) | 
|  | log_file = self.hostapd_instances[instance].log_file | 
|  | result = self.router.run("grep -qi '%s' %s" % (deauth_msg, log_file), | 
|  | ignore_status=True) | 
|  | return result.exit_status == 0 | 
|  |  | 
|  |  | 
|  | def detect_client_coexistence_report(self, client_mac, instance=0): | 
|  | """Detects whether hostapd has logged an action frame from | 
|  | |client_mac| indicating information about 20/40MHz BSS coexistence. | 
|  |  | 
|  | @param client_mac string the MAC address of the client to detect. | 
|  | @param instance int indicating which hostapd instance to query. | 
|  |  | 
|  | """ | 
|  | coex_msg = ('nl80211: MLME event frame - hexdump(len=.*): ' | 
|  | '.. .. .. .. .. .. .. .. .. .. %s ' | 
|  | '.. .. .. .. .. .. .. .. 04 00.*48 01 ..' % | 
|  | ' '.join(client_mac.split(':'))) | 
|  | log_file = self.hostapd_instances[instance].log_file | 
|  | result = self.router.run("grep -qi '%s' %s" % (coex_msg, log_file), | 
|  | ignore_status=True) | 
|  | return result.exit_status == 0 | 
|  |  | 
|  |  | 
|  | def send_magic_packet(self, dest_ip, dest_mac): | 
|  | """Sends a magic packet to the NIC with the given IP and MAC addresses. | 
|  |  | 
|  | @param dest_ip the IP address of the device to send the packet to | 
|  | @param dest_mac the hardware MAC address of the device | 
|  |  | 
|  | """ | 
|  | # magic packet is 6 0xff bytes followed by the hardware address | 
|  | # 16 times | 
|  | mac_bytes = ''.join([chr(int(b, 16)) for b in dest_mac.split(':')]) | 
|  | magic_packet = '\xff' * 6 + mac_bytes * 16 | 
|  |  | 
|  | logging.info('Sending magic packet to %s...', dest_ip) | 
|  | self.host.run('python -uc "import socket, sys;' | 
|  | 's = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);' | 
|  | 's.sendto(sys.stdin.read(), (\'%s\', %d))"' % | 
|  | (dest_ip, UDP_DISCARD_PORT), | 
|  | stdin=magic_packet) | 
|  |  | 
|  |  | 
|  | def setup_bridge_mode_dhcp_server(self): | 
|  | """Setup an DHCP server for bridge mode. | 
|  |  | 
|  | Setup an DHCP server on the master interface of the virtual ethernet | 
|  | pair, with peer interface connected to the bridge interface. This is | 
|  | used for testing APs in bridge mode. | 
|  |  | 
|  | """ | 
|  | # Start a local server on master interface of virtual ethernet pair. | 
|  | self.start_local_server( | 
|  | self.get_virtual_ethernet_master_interface()) | 
|  | # Add peer interface to the bridge. | 
|  | self.add_interface_to_bridge( | 
|  | self.get_virtual_ethernet_peer_interface()) | 
|  |  | 
|  |  | 
|  | def create_brif(self): | 
|  | """Initialize a new bridge interface | 
|  |  | 
|  | @return string bridge interface name | 
|  | """ | 
|  | brif_name = '%s%d' % (self.HOSTAP_BRIDGE_INTERFACE_PREFIX, | 
|  | self._brif_index) | 
|  | self._brif_index += 1 | 
|  | self.host.run('brctl addbr %s' % brif_name) | 
|  | return brif_name |