from dataclasses import dataclass from mitmproxy import flow from mitmproxy import tcp from mitmproxy.connection import Connection from mitmproxy.connection import ConnectionState from mitmproxy.proxy import commands from mitmproxy.proxy import events from mitmproxy.proxy import layer from mitmproxy.proxy.commands import StartHook from mitmproxy.proxy.context import Context from mitmproxy.proxy.events import MessageInjected from mitmproxy.proxy.utils import expect @dataclass class TcpStartHook(StartHook): """ A TCP connection has started. """ flow: tcp.TCPFlow @dataclass class TcpMessageHook(StartHook): """ A TCP connection has received a message. The most recent message will be flow.messages[-1]. The message is user-modifiable. """ flow: tcp.TCPFlow @dataclass class TcpEndHook(StartHook): """ A TCP connection has ended. """ flow: tcp.TCPFlow @dataclass class TcpErrorHook(StartHook): """ A TCP error has occurred. Every TCP flow will receive either a tcp_error or a tcp_end event, but not both. """ flow: tcp.TCPFlow class TcpMessageInjected(MessageInjected[tcp.TCPMessage]): """ The user has injected a custom TCP message. """ class TCPLayer(layer.Layer): """ Simple TCP layer that just relays messages right now. """ flow: tcp.TCPFlow | None def __init__(self, context: Context, ignore: bool = False): super().__init__(context) if ignore: self.flow = None else: self.flow = tcp.TCPFlow(self.context.client, self.context.server, True) @expect(events.Start) def start(self, _) -> layer.CommandGenerator[None]: if self.flow: yield TcpStartHook(self.flow) if self.context.server.timestamp_start is None: err = yield commands.OpenConnection(self.context.server) if err: if self.flow: self.flow.error = flow.Error(str(err)) yield TcpErrorHook(self.flow) yield commands.CloseConnection(self.context.client) self._handle_event = self.done return self._handle_event = self.relay_messages _handle_event = start @expect(events.DataReceived, events.ConnectionClosed, TcpMessageInjected) def relay_messages(self, event: events.Event) -> layer.CommandGenerator[None]: if isinstance(event, TcpMessageInjected): # we just spoof that we received data here and then process that regularly. event = events.DataReceived( self.context.client if event.message.from_client else self.context.server, event.message.content, ) assert isinstance(event, events.ConnectionEvent) from_client = event.connection == self.context.client send_to: Connection if from_client: send_to = self.context.server else: send_to = self.context.client if isinstance(event, events.DataReceived): if self.flow: tcp_message = tcp.TCPMessage(from_client, event.data) self.flow.messages.append(tcp_message) yield TcpMessageHook(self.flow) yield commands.SendData(send_to, tcp_message.content) else: yield commands.SendData(send_to, event.data) elif isinstance(event, events.ConnectionClosed): all_done = not ( (self.context.client.state & ConnectionState.CAN_READ) or (self.context.server.state & ConnectionState.CAN_READ) ) if all_done: self._handle_event = self.done if self.context.server.state is not ConnectionState.CLOSED: yield commands.CloseConnection(self.context.server) if self.context.client.state is not ConnectionState.CLOSED: yield commands.CloseConnection(self.context.client) if self.flow: yield TcpEndHook(self.flow) self.flow.live = False else: yield commands.CloseTcpConnection(send_to, half_close=True) else: raise AssertionError(f"Unexpected event: {event}") @expect(events.DataReceived, events.ConnectionClosed, TcpMessageInjected) def done(self, _) -> layer.CommandGenerator[None]: yield from ()