Home | History | Annotate | Download | only in ap_lib
      1 #   Copyright 2016 - The Android Open Source Project
      2 #
      3 #   Licensed under the Apache License, Version 2.0 (the "License");
      4 #   you may not use this file except in compliance with the License.
      5 #   You may obtain a copy of the License at
      6 #
      7 #       http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 #   Unless required by applicable law or agreed to in writing, software
     10 #   distributed under the License is distributed on an "AS IS" BASIS,
     11 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 #   See the License for the specific language governing permissions and
     13 #   limitations under the License.
     14 
     15 import collections
     16 import itertools
     17 import logging
     18 import os
     19 import time
     20 
     21 from acts.controllers.ap_lib import hostapd_config
     22 from acts.controllers.utils_lib.commands import shell
     23 
     24 
     25 class Error(Exception):
     26     """An error caused by hostapd."""
     27 
     28 
     29 class Hostapd(object):
     30     """Manages the hostapd program.
     31 
     32     Attributes:
     33         config: The hostapd configuration that is being used.
     34     """
     35 
     36     PROGRAM_FILE = '/usr/sbin/hostapd'
     37 
     38     def __init__(self, runner, interface, working_dir='/tmp'):
     39         """
     40         Args:
     41             runner: Object that has run_async and run methods for executing
     42                     shell commands (e.g. connection.SshConnection)
     43             interface: string, The name of the interface to use (eg. wlan0).
     44             working_dir: The directory to work out of.
     45         """
     46         self._runner = runner
     47         self._interface = interface
     48         self._working_dir = working_dir
     49         self.config = None
     50         self._shell = shell.ShellCommand(runner, working_dir)
     51         self._log_file = 'hostapd-%s.log' % self._interface
     52         self._ctrl_file = 'hostapd-%s.ctrl' % self._interface
     53         self._config_file = 'hostapd-%s.conf' % self._interface
     54         self._identifier = '%s.*%s' % (self.PROGRAM_FILE, self._config_file)
     55 
     56     def start(self, config, timeout=60, additional_parameters=None):
     57         """Starts hostapd
     58 
     59         Starts the hostapd daemon and runs it in the background.
     60 
     61         Args:
     62             config: Configs to start the hostapd with.
     63             timeout: Time to wait for DHCP server to come up.
     64             additional_parameters: A dictionary of parameters that can sent
     65                                    directly into the hostapd config file.  This
     66                                    can be used for debugging and or adding one
     67                                    off parameters into the config.
     68 
     69         Returns:
     70             True if the daemon could be started. Note that the daemon can still
     71             start and not work. Invalid configurations can take a long amount
     72             of time to be produced, and because the daemon runs indefinitely
     73             it's impossible to wait on. If you need to check if configs are ok
     74             then periodic checks to is_running and logs should be used.
     75         """
     76         if self.is_alive():
     77             self.stop()
     78 
     79         self.config = config
     80 
     81         self._shell.delete_file(self._ctrl_file)
     82         self._shell.delete_file(self._log_file)
     83         self._shell.delete_file(self._config_file)
     84         self._write_configs(additional_parameters=additional_parameters)
     85 
     86         hostapd_command = '%s -dd -t "%s"' % (self.PROGRAM_FILE,
     87                                               self._config_file)
     88         base_command = 'cd "%s"; %s' % (self._working_dir, hostapd_command)
     89         job_str = '%s > "%s" 2>&1' % (base_command, self._log_file)
     90         self._runner.run_async(job_str)
     91 
     92         try:
     93             self._wait_for_process(timeout=timeout)
     94             self._wait_for_interface(timeout=timeout)
     95         except:
     96             self.stop()
     97             raise
     98 
     99     def stop(self):
    100         """Kills the daemon if it is running."""
    101         self._shell.kill(self._identifier)
    102 
    103     def is_alive(self):
    104         """
    105         Returns:
    106             True if the daemon is running.
    107         """
    108         return self._shell.is_alive(self._identifier)
    109 
    110     def pull_logs(self):
    111         """Pulls the log files from where hostapd is running.
    112 
    113         Returns:
    114             A string of the hostapd logs.
    115         """
    116         # TODO: Auto pulling of logs when stop is called.
    117         return self._shell.read_file(self._log_file)
    118 
    119     def _wait_for_process(self, timeout=60):
    120         """Waits for the process to come up.
    121 
    122         Waits until the hostapd process is found running, or there is
    123         a timeout. If the program never comes up then the log file
    124         will be scanned for errors.
    125 
    126         Raises: See _scan_for_errors
    127         """
    128         start_time = time.time()
    129         while time.time() - start_time < timeout and not self.is_alive():
    130             self._scan_for_errors(False)
    131             time.sleep(0.1)
    132 
    133     def _wait_for_interface(self, timeout=60):
    134         """Waits for hostapd to report that the interface is up.
    135 
    136         Waits until hostapd says the interface has been brought up or an
    137         error occurs.
    138 
    139         Raises: see _scan_for_errors
    140         """
    141         start_time = time.time()
    142         while time.time() - start_time < timeout:
    143             success = self._shell.search_file('Setup of interface done',
    144                                               self._log_file)
    145             if success:
    146                 return
    147 
    148             self._scan_for_errors(True)
    149 
    150     def _scan_for_errors(self, should_be_up):
    151         """Scans the hostapd log for any errors.
    152 
    153         Args:
    154             should_be_up: If true then hostapd program is expected to be alive.
    155                           If it is found not alive while this is true an error
    156                           is thrown.
    157 
    158         Raises:
    159             Error: Raised when a hostapd error is found.
    160         """
    161         # Store this so that all other errors have priority.
    162         is_dead = not self.is_alive()
    163 
    164         bad_config = self._shell.search_file('Interface initialization failed',
    165                                              self._log_file)
    166         if bad_config:
    167             raise Error('Interface failed to start', self)
    168 
    169         bad_config = self._shell.search_file(
    170             "Interface %s wasn't started" % self._interface, self._log_file)
    171         if bad_config:
    172             raise Error('Interface failed to start', self)
    173 
    174         if should_be_up and is_dead:
    175             raise Error('Hostapd failed to start', self)
    176 
    177     def _write_configs(self, additional_parameters=None):
    178         """Writes the configs to the hostapd config file."""
    179         self._shell.delete_file(self._config_file)
    180 
    181         our_configs = collections.OrderedDict()
    182         our_configs['interface'] = self._interface
    183         our_configs['ctrl_interface'] = self._ctrl_file
    184         packaged_configs = self.config.package_configs()
    185         if additional_parameters:
    186             packaged_configs.append(additional_parameters)
    187 
    188         pairs = ('%s=%s' % (k, v) for k, v in our_configs.items())
    189         for packaged_config in packaged_configs:
    190             config_pairs = ('%s=%s' % (k, v)
    191                             for k, v in packaged_config.items())
    192             pairs = itertools.chain(pairs, config_pairs)
    193 
    194         hostapd_conf = '\n'.join(pairs)
    195 
    196         logging.info('Writing %s' % self._config_file)
    197         logging.debug('******************Start*******************')
    198         logging.debug('\n%s' % hostapd_conf)
    199         logging.debug('*******************End********************')
    200 
    201         self._shell.write_file(self._config_file, hostapd_conf)
    202