Example #1
0
 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"
     )
Example #2
0
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,
        )
Example #3
0
 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
Example #4
0
    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")
Example #5
0
    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()
Example #7
0
    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()
Example #8
0
    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))
Example #9
0
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))
Example #10
0
 def running(self):
     self.playback_task = asyncio_utils.create_task(self.playback(),
                                                    name="client playback")
     self.options = ctx.options
Example #11
0
 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")