• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#   Copyright 2020 - 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 logging
16import shlex
17import tempfile
18import time
19
20from acts.controllers.utils_lib.commands import shell
21from acts.libs.proc import job
22
23
24class Error(Exception):
25    """An error caused by radvd."""
26
27
28class Radvd(object):
29    """Manages the radvd program.
30
31    https://en.wikipedia.org/wiki/Radvd
32    This implements the Router Advertisement Daemon of IPv6 router addresses
33    and IPv6 routing prefixes using the Neighbor Discovery Protocol.
34
35    Attributes:
36        config: The radvd configuration that is being used.
37    """
38    def __init__(self, runner, interface, working_dir=None, radvd_binary=None):
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            radvd_binary: Location of the radvd binary
46        """
47        if not radvd_binary:
48            logging.debug('No radvd binary specified.  '
49                          'Assuming radvd is in the path.')
50            radvd_binary = 'radvd'
51        else:
52            logging.debug('Using radvd binary located at %s' % radvd_binary)
53        if working_dir is None and runner == job.run:
54            working_dir = tempfile.gettempdir()
55        else:
56            working_dir = '/tmp'
57        self._radvd_binary = radvd_binary
58        self._runner = runner
59        self._interface = interface
60        self._working_dir = working_dir
61        self.config = None
62        self._shell = shell.ShellCommand(runner, working_dir)
63        self._log_file = '%s/radvd-%s.log' % (working_dir, self._interface)
64        self._config_file = '%s/radvd-%s.conf' % (working_dir, self._interface)
65        self._pid_file = '%s/radvd-%s.pid' % (working_dir, self._interface)
66        self._ps_identifier = '%s.*%s' % (self._radvd_binary,
67                                          self._config_file)
68
69    def start(self, config, timeout=60):
70        """Starts radvd
71
72        Starts the radvd daemon and runs it in the background.
73
74        Args:
75            config: Configs to start the radvd with.
76            timeout: Time to wait for radvd  to come up.
77
78        Returns:
79            True if the daemon could be started. Note that the daemon can still
80            start and not work. Invalid configurations can take a long amount
81            of time to be produced, and because the daemon runs indefinitely
82            it's impossible to wait on. If you need to check if configs are ok
83            then periodic checks to is_running and logs should be used.
84        """
85        if self.is_alive():
86            self.stop()
87
88        self.config = config
89
90        self._shell.delete_file(self._log_file)
91        self._shell.delete_file(self._config_file)
92        self._write_configs(self.config)
93
94        radvd_command = '%s -C %s -p %s -m logfile -d 5 -l %s' % (
95            self._radvd_binary, shlex.quote(self._config_file),
96            shlex.quote(self._pid_file), self._log_file)
97        job_str = '%s > "%s" 2>&1' % (radvd_command, self._log_file)
98        self._runner.run_async(job_str)
99
100        try:
101            self._wait_for_process(timeout=timeout)
102        except Error:
103            self.stop()
104            raise
105
106    def stop(self):
107        """Kills the daemon if it is running."""
108        self._shell.kill(self._ps_identifier)
109
110    def is_alive(self):
111        """
112        Returns:
113            True if the daemon is running.
114        """
115        return self._shell.is_alive(self._ps_identifier)
116
117    def pull_logs(self):
118        """Pulls the log files from where radvd is running.
119
120        Returns:
121            A string of the radvd logs.
122        """
123        # TODO: Auto pulling of logs when stop is called.
124        return self._shell.read_file(self._log_file)
125
126    def _wait_for_process(self, timeout=60):
127        """Waits for the process to come up.
128
129        Waits until the radvd process is found running, or there is
130        a timeout. If the program never comes up then the log file
131        will be scanned for errors.
132
133        Raises: See _scan_for_errors
134        """
135        start_time = time.time()
136        while time.time() - start_time < timeout and not self.is_alive():
137            time.sleep(0.1)
138            self._scan_for_errors(False)
139        self._scan_for_errors(True)
140
141    def _scan_for_errors(self, should_be_up):
142        """Scans the radvd log for any errors.
143
144        Args:
145            should_be_up: If true then radvd program is expected to be alive.
146                          If it is found not alive while this is true an error
147                          is thrown.
148
149        Raises:
150            Error: Raised when a radvd error is found.
151        """
152        # Store this so that all other errors have priority.
153        is_dead = not self.is_alive()
154
155        exited_prematurely = self._shell.search_file('Exiting', self._log_file)
156        if exited_prematurely:
157            raise Error('Radvd exited prematurely.', self)
158        if should_be_up and is_dead:
159            raise Error('Radvd failed to start', self)
160
161    def _write_configs(self, config):
162        """Writes the configs to the radvd config file.
163
164        Args:
165            config: a RadvdConfig object.
166        """
167        self._shell.delete_file(self._config_file)
168        conf = config.package_configs()
169        lines = ['interface %s {' % self._interface]
170        for (interface_option_key,
171             interface_option) in conf['interface_options'].items():
172            lines.append('\t%s %s;' %
173                         (str(interface_option_key), str(interface_option)))
174        lines.append('\tprefix %s' % conf['prefix'])
175        lines.append('\t{')
176        for prefix_option in conf['prefix_options'].items():
177            lines.append('\t\t%s;' % ' '.join(map(str, prefix_option)))
178        lines.append('\t};')
179        if conf['clients']:
180            lines.append('\tclients')
181            lines.append('\t{')
182            for client in conf['clients']:
183                lines.append('\t\t%s;' % client)
184            lines.append('\t};')
185        if conf['route']:
186            lines.append('\troute %s {' % conf['route'])
187            for route_option in conf['route_options'].items():
188                lines.append('\t\t%s;' % ' '.join(map(str, route_option)))
189            lines.append('\t};')
190        if conf['rdnss']:
191            lines.append('\tRDNSS %s {' %
192                         ' '.join([str(elem) for elem in conf['rdnss']]))
193            for rdnss_option in conf['rdnss_options'].items():
194                lines.append('\t\t%s;' % ' '.join(map(str, rdnss_option)))
195            lines.append('\t};')
196        lines.append('};')
197        output_config = '\n'.join(lines)
198        logging.info('Writing %s' % self._config_file)
199        logging.debug('******************Start*******************')
200        logging.debug('\n%s' % output_config)
201        logging.debug('*******************End********************')
202
203        self._shell.write_file(self._config_file, output_config)
204