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 15import collections 16import itertools 17import logging 18import os 19import time 20 21from acts.controllers.ap_lib import hostapd_config 22from acts.controllers.utils_lib.commands import shell 23 24 25class Error(Exception): 26 """An error caused by hostapd.""" 27 28 29class 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 interface_configs = collections.OrderedDict() 182 interface_configs['interface'] = self._interface 183 interface_configs['ctrl_interface'] = self._ctrl_file 184 pairs = ('%s=%s' % (k, v) for k, v in interface_configs.items()) 185 186 packaged_configs = self.config.package_configs() 187 if additional_parameters: 188 packaged_configs.append(additional_parameters) 189 for packaged_config in packaged_configs: 190 config_pairs = ('%s=%s' % (k, v) 191 for k, v in packaged_config.items() 192 if v is not None) 193 pairs = itertools.chain(pairs, config_pairs) 194 195 hostapd_conf = '\n'.join(pairs) 196 197 logging.info('Writing %s' % self._config_file) 198 logging.debug('******************Start*******************') 199 logging.debug('\n%s' % hostapd_conf) 200 logging.debug('*******************End********************') 201 202 self._shell.write_file(self._config_file, hostapd_conf) 203