def log(self, message: str, level: str = "info") -> None: x = log.LogEntry(self.log_prefix + message, level) x.reply = controller.DummyReply() # type: ignore asyncio_utils.create_task( self.master.addons.handle_lifecycle("log", x), name="ProxyConnectionHandler.log" )
def test_closed_loop(): # Crude test for line coverage. # This should eventually go, see the description in asyncio_utils.create_task for details. asyncio_utils.create_task( ttask(), name="ttask", ) with pytest.raises(RuntimeError): asyncio_utils.create_task( ttask(), name="ttask", ignore_closed_loop=False, )
async def playback(self): while True: self.inflight = await self.queue.get() try: h = ReplayHandler(self.inflight, self.options) if ctx.options.client_replay_concurrency == -1: asyncio_utils.create_task(h.replay(), name="client playback awaiting response") else: await h.replay() except Exception: ctx.log(f"Client replay has crashed!\n{traceback.format_exc()}", "error") self.queue.task_done() self.inflight = None
def server_event(self, event: events.Event) -> None: self.timeout_watchdog.register_activity() try: layer_commands = self.layer.handle_event(event) for command in layer_commands: if isinstance(command, commands.OpenConnection): assert command.connection not in self.transports handler = asyncio_utils.create_task( self.open_connection(command), name= f"server connection manager {command.connection.address}", client=self.client.peername, ) self.transports[command.connection] = ConnectionIO( handler=handler) elif isinstance( command, commands.ConnectionCommand ) and command.connection not in self.transports: return # The connection has already been closed. elif isinstance(command, commands.SendData): writer = self.transports[command.connection].writer assert writer writer.write(command.data) elif isinstance(command, commands.CloseConnection): self.close_connection(command.connection, command.half_close) elif isinstance(command, commands.GetSocket): writer = self.transports[command.connection].writer assert writer socket = writer.get_extra_info("socket") self.server_event( events.GetSocketCompleted(command, socket)) elif isinstance(command, commands.StartHook): asyncio_utils.create_task( self.hook_task(command), name=f"handle_hook({command.name})", client=self.client.peername, ) elif isinstance(command, commands.Log): self.log(command.message, command.level) else: raise RuntimeError(f"Unexpected command: {command}") except Exception: self.log(f"mitmproxy has crashed!\n{traceback.format_exc()}", level="error")
async def handle_client(self) -> None: watch = asyncio_utils.create_task( self.timeout_watchdog.watch(), name="timeout watchdog", client=self.client.peername, ) if not watch: return # this should not be needed, see asyncio_utils.create_task self.log("client connect") await self.handle_hook(server_hooks.ClientConnectedHook(self.client)) if self.client.error: self.log("client kill connection") writer = self.transports.pop(self.client).writer assert writer writer.close() else: handler = asyncio_utils.create_task( self.handle_connection(self.client), name=f"client connection handler", client=self.client.peername, ) if not handler: return # this should not be needed, see asyncio_utils.create_task self.transports[self.client].handler = handler self.server_event(events.Start()) await asyncio.wait([handler]) watch.cancel() self.log("client disconnect") self.client.timestamp_end = time.time() await self.handle_hook(server_hooks.ClientDisconnectedHook(self.client) ) if self.transports: self.log("closing transports...", "debug") for io in self.transports.values(): if io.handler: asyncio_utils.cancel_task(io.handler, "client disconnected") await asyncio.wait( [x.handler for x in self.transports.values() if x.handler]) self.log("transports closed!", "debug")
async def test_simple(): task = asyncio_utils.create_task(ttask(), name="ttask", client=("127.0.0.1", 42313)) assert asyncio_utils.task_repr(task) == "127.0.0.1:42313: ttask (age: 0s)" await asyncio.sleep(0) assert "newname" in asyncio_utils.task_repr(task) asyncio_utils.cancel_task(task, "bye") await asyncio.sleep(0) assert task.cancelled()
def __init__(self, path: str, reload: bool) -> None: self.name = "scriptmanager:" + path self.path = path self.fullpath = os.path.expanduser(path.strip("'\" ")) self.ns = None self.is_running = False if not os.path.isfile(self.fullpath): raise exceptions.OptionsError('No such script') self.reloadtask = None if reload: self.reloadtask = asyncio_utils.create_task( self.watcher(), name=f"script watcher for {path}", ) else: self.loadscript()
async def open_connection(self, command: commands.OpenConnection) -> None: if not command.connection.address: self.log(f"Cannot open connection, no hostname given.") self.server_event(events.OpenConnectionCompleted(command, f"Cannot open connection, no hostname given.")) return hook_data = server_hooks.ServerConnectionHookData( client=self.client, server=command.connection ) await self.handle_hook(server_hooks.ServerConnectHook(hook_data)) if command.connection.error: self.log(f"server connection to {human.format_address(command.connection.address)} killed before connect.") self.server_event(events.OpenConnectionCompleted(command, "Connection killed.")) return async with self.max_conns[command.connection.address]: try: command.connection.timestamp_start = time.time() reader, writer = await asyncio.open_connection(*command.connection.address) except (IOError, asyncio.CancelledError) as e: err = str(e) if not err: # str(CancelledError()) returns empty string. err = "connection cancelled" self.log(f"error establishing server connection: {err}") command.connection.error = err self.server_event(events.OpenConnectionCompleted(command, err)) if isinstance(e, asyncio.CancelledError): # From https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError: # > In almost all situations the exception must be re-raised. # It is not really defined what almost means here, but we play safe. raise else: command.connection.timestamp_tcp_setup = time.time() command.connection.state = ConnectionState.OPEN command.connection.peername = writer.get_extra_info('peername') command.connection.sockname = writer.get_extra_info('sockname') self.transports[command.connection].reader = reader self.transports[command.connection].writer = writer assert command.connection.peername if command.connection.address[0] != command.connection.peername[0]: addr = f"{command.connection.address[0]} ({human.format_address(command.connection.peername)})" else: addr = human.format_address(command.connection.address) self.log(f"server connect {addr}") connected_hook = asyncio_utils.create_task( self.handle_hook(server_hooks.ServerConnectedHook(hook_data)), name=f"handle_hook(server_connected) {addr}", client=self.client.peername, ) if not connected_hook: return # this should not be needed, see asyncio_utils.create_task self.server_event(events.OpenConnectionCompleted(command, None)) # during connection opening, this function is the designated handler that can be cancelled. # once we have a connection, we do want the teardown here to happen in any case, so we # reassign the handler to .handle_connection and then clean up here once that is done. new_handler = asyncio_utils.create_task( self.handle_connection(command.connection), name=f"server connection handler for {addr}", client=self.client.peername, ) if not new_handler: return # this should not be needed, see asyncio_utils.create_task self.transports[command.connection].handler = new_handler await asyncio.wait([new_handler]) self.log(f"server disconnect {addr}") command.connection.timestamp_end = time.time() await connected_hook # wait here for this so that closed always comes after connected. await self.handle_hook(server_hooks.ServerDisconnectedHook(hook_data))
class ConnectionHandler(metaclass=abc.ABCMeta): transports: typing.MutableMapping[Connection, ConnectionIO] timeout_watchdog: TimeoutWatchdog client: Client max_conns: typing.DefaultDict[Address, asyncio.Semaphore] layer: layer.Layer def __init__(self, context: Context) -> None: self.client = context.client self.transports = {} self.max_conns = collections.defaultdict(lambda: asyncio.Semaphore(5)) # Ask for the first layer right away. # In a reverse proxy scenario, this is necessary as we would otherwise hang # on protocols that start with a server greeting. self.layer = layer.NextLayer(context, ask_on_start=True) self.timeout_watchdog = TimeoutWatchdog(self.on_timeout) async def handle_client(self) -> None: watch = asyncio_utils.create_task( self.timeout_watchdog.watch(), name="timeout watchdog", client=self.client.peername, ) if not watch: return # this should not be needed, see asyncio_utils.create_task self.log("client connect") await self.handle_hook(server_hooks.ClientConnectedHook(self.client)) if self.client.error: self.log("client kill connection") writer = self.transports.pop(self.client).writer assert writer writer.close() else: handler = asyncio_utils.create_task( self.handle_connection(self.client), name=f"client connection handler", client=self.client.peername, ) if not handler: return # this should not be needed, see asyncio_utils.create_task self.transports[self.client].handler = handler self.server_event(events.Start()) await asyncio.wait([handler]) watch.cancel() self.log("client disconnect") self.client.timestamp_end = time.time() await self.handle_hook(server_hooks.ClientDisconnectedHook(self.client) ) if self.transports: self.log("closing transports...", "debug") for io in self.transports.values(): if io.handler: asyncio_utils.cancel_task(io.handler, "client disconnected") await asyncio.wait( [x.handler for x in self.transports.values() if x.handler]) self.log("transports closed!", "debug") async def open_connection(self, command: commands.OpenConnection) -> None: if not command.connection.address: self.log(f"Cannot open connection, no hostname given.") self.server_event( events.OpenConnectionCompleted( command, f"Cannot open connection, no hostname given.")) return hook_data = server_hooks.ServerConnectionHookData( client=self.client, server=command.connection) await self.handle_hook(server_hooks.ServerConnectHook(hook_data)) if err := command.connection.error: self.log( f"server connection to {human.format_address(command.connection.address)} killed before connect: {err}" ) self.server_event( events.OpenConnectionCompleted(command, f"Connection killed: {err}")) return async with self.max_conns[command.connection.address]: try: command.connection.timestamp_start = time.time() reader, writer = await asyncio.open_connection( *command.connection.address) except (IOError, asyncio.CancelledError) as e: err = str(e) if not err: # str(CancelledError()) returns empty string. err = "connection cancelled" self.log(f"error establishing server connection: {err}") command.connection.error = err self.server_event(events.OpenConnectionCompleted(command, err)) if isinstance(e, asyncio.CancelledError): # From https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError: # > In almost all situations the exception must be re-raised. # It is not really defined what almost means here, but we play safe. raise else: command.connection.timestamp_tcp_setup = time.time() command.connection.state = ConnectionState.OPEN command.connection.peername = writer.get_extra_info('peername') command.connection.sockname = writer.get_extra_info('sockname') self.transports[command.connection].reader = reader self.transports[command.connection].writer = writer assert command.connection.peername if command.connection.address[ 0] != command.connection.peername[0]: addr = f"{command.connection.address[0]} ({human.format_address(command.connection.peername)})" else: addr = human.format_address(command.connection.address) self.log(f"server connect {addr}") connected_hook = asyncio_utils.create_task( self.handle_hook( server_hooks.ServerConnectedHook(hook_data)), name=f"handle_hook(server_connected) {addr}", client=self.client.peername, ) if not connected_hook: return # this should not be needed, see asyncio_utils.create_task self.server_event(events.OpenConnectionCompleted( command, None)) # during connection opening, this function is the designated handler that can be cancelled. # once we have a connection, we do want the teardown here to happen in any case, so we # reassign the handler to .handle_connection and then clean up here once that is done. new_handler = asyncio_utils.create_task( self.handle_connection(command.connection), name=f"server connection handler for {addr}", client=self.client.peername, ) if not new_handler: return # this should not be needed, see asyncio_utils.create_task self.transports[command.connection].handler = new_handler await asyncio.wait([new_handler]) self.log(f"server disconnect {addr}") command.connection.timestamp_end = time.time() await connected_hook # wait here for this so that closed always comes after connected. await self.handle_hook( server_hooks.ServerDisconnectedHook(hook_data))
def running(self): self.playback_task = asyncio_utils.create_task(self.playback(), name="client playback") self.options = ctx.options
def log(self, message: str, level: str = "info") -> None: x = log.LogEntry(self.log_prefix + message, level) asyncio_utils.create_task(self.master.addons.handle_lifecycle( log.AddLogHook(x)), name="ProxyConnectionHandler.log")