1#!/usr/bin/env python3
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import importlib
18import logging
19
20ACTS_CONTROLLER_CONFIG_NAME = "Sniffer"
21ACTS_CONTROLLER_REFERENCE_NAME = "sniffers"
22
23
24def create(configs):
25    """Initializes the sniffer structures based on the JSON configuration. The
26    expected keys are:
27
28    Type: A first-level type of sniffer. Planned to be 'local' for sniffers
29        running on the local machine, or 'remote' for sniffers running
30        remotely.
31    SubType: The specific sniffer type to be used.
32    Interface: The WLAN interface used to configure the sniffer.
33    BaseConfigs: A dictionary specifying baseline configurations of the
34        sniffer. Configurations can be overridden when starting a capture.
35        The keys must be one of the Sniffer.CONFIG_KEY_* values.
36    """
37    objs = []
38    for c in configs:
39        sniffer_type = c["Type"]
40        sniffer_subtype = c["SubType"]
41        interface = c["Interface"]
42        base_configs = c["BaseConfigs"]
43        module_name = "acts.controllers.sniffer_lib.{}.{}".format(
44            sniffer_type, sniffer_subtype)
45        module = importlib.import_module(module_name)
46        objs.append(module.Sniffer(interface,
47                                   logging.getLogger(),
48                                   base_configs=base_configs))
49    return objs
50
51
52def destroy(objs):
53    """Destroys the sniffers and terminates any ongoing capture sessions.
54    """
55    for sniffer in objs:
56        try:
57            sniffer.stop_capture()
58        except SnifferError:
59            pass
60
61
62class SnifferError(Exception):
63    """This is the Exception class defined for all errors generated by
64    Sniffer-related modules.
65    """
66    pass
67
68
69class InvalidDataError(Exception):
70    """This exception is thrown when invalid configuration data is passed
71    to a method.
72    """
73    pass
74
75
76class ExecutionError(SnifferError):
77    """This exception is thrown when trying to configure the capture device
78    or when trying to execute the capture operation.
79
80    When this exception is seen, it is possible that the sniffer module is run
81    without sudo (for local sniffers) or keys are out-of-date (for remote
82    sniffers).
83    """
84    pass
85
86
87class InvalidOperationError(SnifferError):
88    """Certain methods may only be accessed when the instance upon which they
89    are invoked is in a certain state. This indicates that the object is not
90    in the correct state for a method to be called.
91    """
92    pass
93
94
95class Sniffer(object):
96    """This class defines an object representing a sniffer.
97
98    The object defines the generic behavior of sniffers - irrespective of how
99    they are implemented, or where they are located: on the local machine or on
100    the remote machine.
101    """
102
103    CONFIG_KEY_CHANNEL = "channel"
104
105    def __init__(self, interface, logger, base_configs=None):
106        """The constructor for the Sniffer. It constructs a sniffer and
107        configures it to be ready for capture.
108
109        Args:
110            interface: A string specifying the interface used to configure the
111                sniffer.
112            logger: ACTS logger object.
113            base_configs: A dictionary containing baseline configurations of the
114                sniffer. These can be overridden when staring a capture. The
115                keys are specified by Sniffer.CONFIG_KEY_*.
116
117        Returns:
118            self: A configured sniffer.
119
120        Raises:
121            InvalidDataError: if the config_path is invalid.
122            NoPermissionError: if an error occurs while configuring the
123                sniffer.
124        """
125        raise NotImplementedError("Base class should not be called directly!")
126
127    def get_descriptor(self):
128        """This function returns a string describing the sniffer. The specific
129        string (and its format) is up to each derived sniffer type.
130
131        Returns:
132            A string describing the sniffer.
133        """
134        raise NotImplementedError("Base class should not be called directly!")
135
136    def get_type(self):
137        """This function returns the type of the sniffer.
138
139        Returns:
140            The type (string) of the sniffer. Corresponds to the 'Type' key of
141            the sniffer configuration.
142        """
143        raise NotImplementedError("Base class should not be called directly!")
144
145    def get_subtype(self):
146        """This function returns the sub-type of the sniffer.
147
148        Returns:
149            The sub-type (string) of the sniffer. Corresponds to the 'SubType'
150            key of the sniffer configuration.
151        """
152        raise NotImplementedError("Base class should not be called directly!")
153
154    def get_interface(self):
155        """This function returns The interface used to configure the sniffer,
156        e.g. 'wlan0'.
157
158        Returns:
159            The interface (string) used to configure the sniffer. Corresponds to
160            the 'Interface' key of the sniffer configuration.
161        """
162        raise NotImplementedError("Base class should not be called directly!")
163
164    def get_capture_file(self):
165        """The sniffer places a capture in the logger directory. This function
166        enables the caller to obtain the path of that capture.
167
168        Returns:
169            The full path of the current or last capture.
170        """
171        raise NotImplementedError("Base class should not be called directly!")
172
173    def start_capture(self,
174                      override_configs=None,
175                      additional_args=None,
176                      duration=None,
177                      packet_count=None):
178        """This function starts a capture which is saved to the specified file
179        path.
180
181        Depending on the type/subtype and configuration of the sniffer the
182        capture may terminate on its own or may require an explicit call to the
183        stop_capture() function.
184
185        This is a non-blocking function so a terminating function must be
186        called - either explicitly or implicitly:
187        - Explicitly: call either stop_capture() or wait_for_capture()
188        - Implicitly: use with a with clause. The wait_for_capture() function
189                      will be called if a duration is specified (i.e. is not
190                      None), otherwise a stop_capture() will be called.
191
192        The capture is saved to a file in the log path of the logger. Use
193        the get_capture_file() to get the full path to the current or most
194        recent capture.
195
196        Args:
197            override_configs: A dictionary which is combined with the
198                base_configs ("BaseConfigs" in the sniffer configuration). The
199                keys (specified by Sniffer.CONFIG_KEY_*) determine the
200                configuration of the sniffer for this specific capture.
201            additional_args: A string specifying additional raw
202                command-line arguments to pass to the underlying sniffer. The
203                interpretation of these flags is sniffer-dependent.
204            duration: An integer specifying the number of seconds over which to
205                capture packets. The sniffer will be terminated after this
206                duration. Used in implicit mode when using a 'with' clause. In
207                explicit control cases may have to be performed using a
208                sleep+stop or as the timeout argument to the wait function.
209            packet_count: An integer specifying the number of packets to capture
210                before terminating. Should be used with duration to guarantee
211                that capture terminates at some point (even if did not capture
212                the specified number of packets).
213
214        Returns:
215            An ActiveCaptureContext process which can be used with a 'with'
216            clause.
217
218        Raises:
219            InvalidDataError: for invalid configurations
220            NoPermissionError: if an error occurs while configuring and running
221                the sniffer.
222        """
223        raise NotImplementedError("Base class should not be called directly!")
224
225    def stop_capture(self):
226        """This function stops a capture and guarantees that the capture is
227        saved to the capture file configured during the start_capture() method.
228        Depending on the type of the sniffer the file may previously contain
229        partial results (e.g. for a local sniffer) or may not exist until the
230        stop_capture() method is executed (e.g. for a remote sniffer).
231
232        Depending on the type/subtype and configuration of the sniffer the
233        capture may terminate on its own without requiring a call to this
234        function. In such a case it is still necessary to call either this
235        function or the wait_for_capture() function to make sure that the
236        capture file is moved to the correct location.
237
238        Raises:
239            NoPermissionError: No permission when trying to stop a capture
240                and save the capture file.
241        """
242        raise NotImplementedError("Base class should not be called directly!")
243
244    def wait_for_capture(self, timeout=None):
245        """This function waits for a capture to terminate and guarantees that
246        the capture is saved to the capture file configured during the
247        start_capture() method. Depending on the type of the sniffer the file
248        may previously contain partial results (e.g. for a local sniffer) or
249        may not exist until the stop_capture() method is executed (e.g. for a
250        remote sniffer).
251
252        Depending on the type/subtype and configuration of the sniffer the
253        capture may terminate on its own without requiring a call to this
254        function. In such a case it is still necessary to call either this
255        function or the stop_capture() function to make sure that the capture
256        file is moved to the correct location.
257
258        Args:
259            timeout: An integer specifying the number of seconds to wait for
260                the capture to terminate on its own. On expiration of the
261                timeout the sniffer is stopped explicitly using the
262                stop_capture() function.
263
264        Raises:
265            NoPermissionError: No permission when trying to stop a capture and
266                save the capture file.
267        """
268        raise NotImplementedError("Base class should not be called directly!")
269
270
271class ActiveCaptureContext(object):
272    """This class defines an object representing an active sniffer capture.
273
274    The object is returned by a Sniffer.start_capture() command and terminates
275    the capture when the 'with' clause exits. It is syntactic sugar for
276    try/finally.
277    """
278
279    _sniffer = None
280    _timeout = None
281
282    def __init__(self, sniffer, timeout=None):
283        self._sniffer = sniffer
284        self._timeout = timeout
285
286    def __enter__(self):
287        pass
288
289    def __exit__(self, type, value, traceback):
290        if self._sniffer is not None:
291            if self._timeout is None:
292                self._sniffer.stop_capture()
293            else:
294                self._sniffer.wait_for_capture(self._timeout)
295        self._sniffer = None
296