• 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            self._scan_for_errors(True)
138            time.sleep(0.1)
139
140    def _scan_for_errors(self, should_be_up):
141        """Scans the radvd log for any errors.
142
143        Args:
144            should_be_up: If true then radvd program is expected to be alive.
145                          If it is found not alive while this is true an error
146                          is thrown.
147
148        Raises:
149            Error: Raised when a radvd error is found.
150        """
151        # Store this so that all other errors have priority.
152        is_dead = not self.is_alive()
153
154        exited_prematurely = self._shell.search_file('Exiting', self._log_file)
155        if exited_prematurely:
156            raise Error('Radvd exited prematurely.', self)
157        if should_be_up and is_dead:
158            raise Error('Radvd failed to start', self)
159
160    def _write_configs(self, config):
161        """Writes the configs to the radvd config file.
162
163        Args:
164            config: a RadvdConfig object.
165        """
166        self._shell.delete_file(self._config_file)
167        conf = config.package_configs()
168        lines = ['interface %s {' % self._interface]
169        for (interface_option_key,
170             interface_option) in conf['interface_options'].items():
171            lines.append('\t%s %s;' %
172                         (str(interface_option_key), str(interface_option)))
173        lines.append('\tprefix %s' % conf['prefix'])
174        lines.append('\t{')
175        for prefix_option in conf['prefix_options'].items():
176            lines.append('\t\t%s;' % ' '.join(map(str, prefix_option)))
177        lines.append('\t};')
178        if conf['clients']:
179            lines.append('\tclients')
180            lines.append('\t{')
181            for client in conf['clients']:
182                lines.append('\t\t%s;' % client)
183            lines.append('\t};')
184        if conf['route']:
185            lines.append('\troute %s {' % conf['route'])
186            for route_option in conf['route_options'].items():
187                lines.append('\t\t%s;' % ' '.join(map(str, route_option)))
188            lines.append('\t};')
189        if conf['rdnss']:
190            lines.append('\tRDNSS %s {' %
191                         ' '.join([str(elem) for elem in conf['rdnss']]))
192            for rdnss_option in conf['rdnss_options'].items():
193                lines.append('\t\t%s;' % ' '.join(map(str, rdnss_option)))
194            lines.append('\t};')
195        lines.append('};')
196        output_config = '\n'.join(lines)
197        logging.info('Writing %s' % self._config_file)
198        logging.debug('******************Start*******************')
199        logging.debug('\n%s' % output_config)
200        logging.debug('*******************End********************')
201
202        self._shell.write_file(self._config_file, output_config)
203