Files
baijiahao_data_crawl/venv/Lib/site-packages/mitmproxy/proxy/tunnel.py

202 lines
8.1 KiB
Python
Raw Normal View History

2025-12-25 11:16:59 +08:00
import time
from enum import auto
from enum import Enum
from typing import Union
from mitmproxy import connection
from mitmproxy.proxy import commands
from mitmproxy.proxy import context
from mitmproxy.proxy import events
from mitmproxy.proxy import layer
from mitmproxy.proxy.layer import Layer
class TunnelState(Enum):
INACTIVE = auto()
ESTABLISHING = auto()
OPEN = auto()
CLOSED = auto()
class TunnelLayer(layer.Layer):
"""
A specialized layer that simplifies the implementation of tunneling protocols such as SOCKS, upstream HTTP proxies,
or TLS.
"""
child_layer: layer.Layer
tunnel_connection: connection.Connection
"""The 'outer' connection which provides the tunnel protocol I/O"""
conn: connection.Connection
"""The 'inner' connection which provides data I/O"""
tunnel_state: TunnelState = TunnelState.INACTIVE
command_to_reply_to: commands.OpenConnection | None = None
_event_queue: list[events.Event]
"""
If the connection already exists when we receive the start event,
we buffer commands until we have established the tunnel.
"""
def __init__(
self,
context: context.Context,
tunnel_connection: connection.Connection,
conn: connection.Connection,
):
super().__init__(context)
self.tunnel_connection = tunnel_connection
self.conn = conn
self.child_layer = layer.NextLayer(self.context)
self._event_queue = []
def __repr__(self):
return f"{type(self).__name__}({self.tunnel_state.name.lower()})"
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:
if isinstance(event, events.Start):
if self.tunnel_connection.state is not connection.ConnectionState.CLOSED:
# we might be in the interesting state here where the connection is already half-closed,
# for example because next_layer buffered events and the client disconnected in the meantime.
# we still expect a close event to arrive, so we carry on here as normal for now.
self.tunnel_state = TunnelState.ESTABLISHING
yield from self.start_handshake()
yield from self.event_to_child(event)
elif (
isinstance(event, events.ConnectionEvent)
and event.connection == self.tunnel_connection
):
if isinstance(event, events.DataReceived):
if self.tunnel_state is TunnelState.ESTABLISHING:
done, err = yield from self.receive_handshake_data(event.data)
if done:
if self.conn != self.tunnel_connection:
self.conn.state = connection.ConnectionState.OPEN
self.conn.timestamp_start = time.time()
if err:
if self.conn != self.tunnel_connection:
self.conn.state = connection.ConnectionState.CLOSED
self.conn.timestamp_start = time.time()
yield from self.on_handshake_error(err)
if done or err:
yield from self._handshake_finished(err)
else:
yield from self.receive_data(event.data)
elif isinstance(event, events.ConnectionClosed):
if self.conn != self.tunnel_connection:
self.conn.state &= ~connection.ConnectionState.CAN_READ
self.conn.timestamp_end = time.time()
if self.tunnel_state is TunnelState.OPEN:
yield from self.receive_close()
elif self.tunnel_state is TunnelState.ESTABLISHING:
err = "connection closed"
yield from self.on_handshake_error(err)
yield from self._handshake_finished(err)
self.tunnel_state = TunnelState.CLOSED
else: # pragma: no cover
raise AssertionError(f"Unexpected event: {event}")
else:
yield from self.event_to_child(event)
def _handshake_finished(self, err: str | None) -> layer.CommandGenerator[None]:
if err:
self.tunnel_state = TunnelState.CLOSED
else:
self.tunnel_state = TunnelState.OPEN
if self.command_to_reply_to:
yield from self.event_to_child(
events.OpenConnectionCompleted(self.command_to_reply_to, err)
)
self.command_to_reply_to = None
else:
for evt in self._event_queue:
yield from self.event_to_child(evt)
self._event_queue.clear()
def _handle_command(
self, command: commands.Command
) -> layer.CommandGenerator[None]:
if (
isinstance(command, commands.ConnectionCommand)
and command.connection == self.conn
):
if isinstance(command, commands.SendData):
yield from self.send_data(command.data)
elif isinstance(command, commands.CloseConnection):
if self.conn != self.tunnel_connection:
self.conn.state &= ~connection.ConnectionState.CAN_WRITE
command.connection = self.tunnel_connection
yield from self.send_close(command)
elif isinstance(command, commands.OpenConnection):
# create our own OpenConnection command object that blocks here.
self.command_to_reply_to = command
self.tunnel_state = TunnelState.ESTABLISHING
err = yield commands.OpenConnection(self.tunnel_connection)
if err:
yield from self.event_to_child(
events.OpenConnectionCompleted(command, err)
)
self.tunnel_state = TunnelState.CLOSED
else:
yield from self.start_handshake()
else: # pragma: no cover
raise AssertionError(f"Unexpected command: {command}")
else:
yield command
def event_to_child(self, event: events.Event) -> layer.CommandGenerator[None]:
if (
self.tunnel_state is TunnelState.ESTABLISHING
and not self.command_to_reply_to
):
self._event_queue.append(event)
return
for command in self.child_layer.handle_event(event):
yield from self._handle_command(command)
def start_handshake(self) -> layer.CommandGenerator[None]:
yield from self._handle_event(events.DataReceived(self.tunnel_connection, b""))
def receive_handshake_data(
self, data: bytes
) -> layer.CommandGenerator[tuple[bool, str | None]]:
"""returns a (done, err) tuple"""
yield from ()
return True, None
def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
"""Called if either receive_handshake_data returns an error or we receive a close during handshake."""
yield commands.CloseConnection(self.tunnel_connection)
def receive_data(self, data: bytes) -> layer.CommandGenerator[None]:
yield from self.event_to_child(events.DataReceived(self.conn, data))
def receive_close(self) -> layer.CommandGenerator[None]:
yield from self.event_to_child(events.ConnectionClosed(self.conn))
def send_data(self, data: bytes) -> layer.CommandGenerator[None]:
yield commands.SendData(self.tunnel_connection, data)
def send_close(
self, command: commands.CloseConnection
) -> layer.CommandGenerator[None]:
yield command
class LayerStack:
def __init__(self) -> None:
self._stack: list[Layer] = []
def __getitem__(self, item: int) -> Layer:
return self._stack.__getitem__(item)
def __truediv__(self, other: Union[Layer, "LayerStack"]) -> "LayerStack":
if isinstance(other, Layer):
if self._stack:
self._stack[-1].child_layer = other # type: ignore
self._stack.append(other)
else:
if self._stack:
self._stack[-1].child_layer = other[0] # type: ignore
self._stack.extend(other._stack)
return self