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