Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_raw_layers.py434 linesDownload Raw Back to quic
1"""2This module contains the proxy layers for raw QUIC proxying.3This is used if we want to speak QUIC, but we do not want to do HTTP.4"""5 6from __future__ import annotations7 8import time9 10from aioquic.quic.connection import QuicErrorCode11from aioquic.quic.connection import stream_is_client_initiated12from aioquic.quic.connection import stream_is_unidirectional13 14from ._commands import CloseQuicConnection15from ._commands import ResetQuicStream16from ._commands import SendQuicStreamData17from ._commands import StopSendingQuicStream18from ._events import QuicConnectionClosed19from ._events import QuicStreamDataReceived20from ._events import QuicStreamEvent21from ._events import QuicStreamReset22from mitmproxy import connection23from mitmproxy.connection import Connection24from mitmproxy.proxy import commands25from mitmproxy.proxy import context26from mitmproxy.proxy import events27from mitmproxy.proxy import layer28from mitmproxy.proxy import tunnel29from mitmproxy.proxy.layers.tcp import TCPLayer30from mitmproxy.proxy.layers.udp import UDPLayer31 32 33class QuicStreamNextLayer(layer.NextLayer):34    """`NextLayer` variant that callbacks `QuicStreamLayer` after layer decision."""35 36    def __init__(37        self,38        context: context.Context,39        stream: QuicStreamLayer,40        ask_on_start: bool = False,41    ) -> None:42        super().__init__(context, ask_on_start)43        self._stream = stream44        self._layer: layer.Layer | None = None45 46    @property  # type: ignore47    def layer(self) -> layer.Layer | None:  # type: ignore48        return self._layer49 50    @layer.setter51    def layer(self, value: layer.Layer | None) -> None:52        self._layer = value53        if self._layer:54            self._stream.refresh_metadata()55 56 57class QuicStreamLayer(layer.Layer):58    """59    Layer for QUIC streams.60    Serves as a marker for NextLayer and keeps track of the connection states.61    """62 63    client: connection.Client64    """Virtual client connection for this stream. Use this in QuicRawLayer instead of `context.client`."""65    server: connection.Server66    """Virtual server connection for this stream. Use this in QuicRawLayer instead of `context.server`."""67    child_layer: layer.Layer68    """The stream's child layer."""69 70    def __init__(71        self, context: context.Context, force_raw: bool, stream_id: int72    ) -> None:73        # we mustn't reuse the client from the QUIC connection, as the state and protocol differs74        self.client = context.client = context.client.copy()75        self.client.transport_protocol = "tcp"76        self.client.state = connection.ConnectionState.OPEN77 78        # unidirectional client streams are not fully open, set the appropriate state79        if stream_is_unidirectional(stream_id):80            self.client.state = (81                connection.ConnectionState.CAN_READ82                if stream_is_client_initiated(stream_id)83                else connection.ConnectionState.CAN_WRITE84            )85        self._client_stream_id = stream_id86 87        # start with a closed server88        self.server = context.server = connection.Server(89            address=context.server.address,90            transport_protocol="tcp",91        )92        self._server_stream_id: int | None = None93 94        super().__init__(context)95        self.child_layer = (96            TCPLayer(context) if force_raw else QuicStreamNextLayer(context, self)97        )98        self.refresh_metadata()99 100        # we don't handle any events, pass everything to the child layer101        self.handle_event = self.child_layer.handle_event  # type: ignore102        self._handle_event = self.child_layer._handle_event  # type: ignore103 104    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:105        raise AssertionError  # pragma: no cover106 107    def open_server_stream(self, server_stream_id) -> None:108        assert self._server_stream_id is None109        self._server_stream_id = server_stream_id110        self.server.timestamp_start = time.time()111        self.server.state = (112            (113                connection.ConnectionState.CAN_WRITE114                if stream_is_client_initiated(server_stream_id)115                else connection.ConnectionState.CAN_READ116            )117            if stream_is_unidirectional(server_stream_id)118            else connection.ConnectionState.OPEN119        )120        self.refresh_metadata()121 122    def refresh_metadata(self) -> None:123        # find the first transport layer124        child_layer: layer.Layer | None = self.child_layer125        while True:126            if isinstance(child_layer, layer.NextLayer):127                child_layer = child_layer.layer128            elif isinstance(child_layer, tunnel.TunnelLayer):129                child_layer = child_layer.child_layer130            else:131                break  # pragma: no cover132        if isinstance(child_layer, (UDPLayer, TCPLayer)) and child_layer.flow:133            child_layer.flow.metadata["quic_is_unidirectional"] = (134                stream_is_unidirectional(self._client_stream_id)135            )136            child_layer.flow.metadata["quic_initiator"] = (137                "client"138                if stream_is_client_initiated(self._client_stream_id)139                else "server"140            )141            child_layer.flow.metadata["quic_stream_id_client"] = self._client_stream_id142            child_layer.flow.metadata["quic_stream_id_server"] = self._server_stream_id143 144    def stream_id(self, client: bool) -> int | None:145        return self._client_stream_id if client else self._server_stream_id146 147 148class RawQuicLayer(layer.Layer):149    """150    This layer is responsible for de-multiplexing QUIC streams into an individual layer stack per stream.151    """152 153    force_raw: bool154    """Indicates whether traffic should be treated as raw TCP/UDP without further protocol detection."""155    datagram_layer: layer.Layer156    """157  The layer that is handling datagrams over QUIC. It's like a child_layer, but with a forked context.158  Instead of having a datagram-equivalent for all `QuicStream*` classes, we use `SendData` and `DataReceived` instead.159  There is also no need for another `NextLayer` marker, as a missing `QuicStreamLayer` implies UDP,160  and the connection state is the same as the one of the underlying QUIC connection.161  """162    client_stream_ids: dict[int, QuicStreamLayer]163    """Maps stream IDs from the client connection to stream layers."""164    server_stream_ids: dict[int, QuicStreamLayer]165    """Maps stream IDs from the server connection to stream layers."""166    connections: dict[connection.Connection, layer.Layer]167    """Maps connections to layers."""168    command_sources: dict[commands.Command, layer.Layer]169    """Keeps track of blocking commands and wakeup requests."""170    next_stream_id: list[int]171    """List containing the next stream ID for all four is_unidirectional/is_client combinations."""172 173    def __init__(self, context: context.Context, force_raw: bool = False) -> None:174        super().__init__(context)175        self.force_raw = force_raw176        self.datagram_layer = (177            UDPLayer(self.context.fork())178            if force_raw179            else layer.NextLayer(self.context.fork())180        )181        self.client_stream_ids = {}182        self.server_stream_ids = {}183        self.connections = {184            context.client: self.datagram_layer,185            context.server: self.datagram_layer,186        }187        self.command_sources = {}188        self.next_stream_id = [0, 1, 2, 3]189 190    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:191        # we treat the datagram layer as child layer, so forward Start192        if isinstance(event, events.Start):193            if self.context.server.timestamp_start is None:194                err = yield commands.OpenConnection(self.context.server)195                if err:196                    yield commands.CloseConnection(self.context.client)197                    self._handle_event = self.done  # type: ignore198                    return199            yield from self.event_to_child(self.datagram_layer, event)200 201        # properly forward completion events based on their command202        elif isinstance(event, events.CommandCompleted):203            yield from self.event_to_child(204                self.command_sources.pop(event.command), event205            )206 207        # route injected messages based on their connections (prefer client, fallback to server)208        elif isinstance(event, events.MessageInjected):209            if event.flow.client_conn in self.connections:210                yield from self.event_to_child(211                    self.connections[event.flow.client_conn], event212                )213            elif event.flow.server_conn in self.connections:214                yield from self.event_to_child(215                    self.connections[event.flow.server_conn], event216                )217            else:218                raise AssertionError(f"Flow not associated: {event.flow!r}")219 220        # handle stream events targeting this context221        elif isinstance(event, QuicStreamEvent) and (222            event.connection is self.context.client223            or event.connection is self.context.server224        ):225            from_client = event.connection is self.context.client226 227            # fetch or create the layer228            stream_ids = (229                self.client_stream_ids if from_client else self.server_stream_ids230            )231            if event.stream_id in stream_ids:232                stream_layer = stream_ids[event.stream_id]233            else:234                # ensure we haven't just forgotten to register the ID235                assert stream_is_client_initiated(event.stream_id) == from_client236 237                # for server-initiated streams we need to open the client as well238                if from_client:239                    client_stream_id = event.stream_id240                    server_stream_id = None241                else:242                    client_stream_id = self.get_next_available_stream_id(243                        is_client=False,244                        is_unidirectional=stream_is_unidirectional(event.stream_id),245                    )246                    server_stream_id = event.stream_id247 248                # create, register and start the layer249                stream_layer = QuicStreamLayer(250                    self.context.fork(),251                    force_raw=self.force_raw,252                    stream_id=client_stream_id,253                )254                self.client_stream_ids[client_stream_id] = stream_layer255                if server_stream_id is not None:256                    stream_layer.open_server_stream(server_stream_id)257                    self.server_stream_ids[server_stream_id] = stream_layer258                self.connections[stream_layer.client] = stream_layer259                self.connections[stream_layer.server] = stream_layer260                yield from self.event_to_child(stream_layer, events.Start())261 262            # forward data and close events263            conn: Connection = (264                stream_layer.client if from_client else stream_layer.server265            )266            if isinstance(event, QuicStreamDataReceived):267                if event.data:268                    yield from self.event_to_child(269                        stream_layer, events.DataReceived(conn, event.data)270                    )271                if event.end_stream:272                    yield from self.close_stream_layer(stream_layer, from_client)273            elif isinstance(event, QuicStreamReset):274                # preserve stream resets275                for command in self.close_stream_layer(stream_layer, from_client):276                    if (277                        isinstance(command, SendQuicStreamData)278                        and command.stream_id == stream_layer.stream_id(not from_client)279                        and command.end_stream280                        and not command.data281                    ):282                        yield ResetQuicStream(283                            command.connection, command.stream_id, event.error_code284                        )285                    else:286                        yield command287            else:288                raise AssertionError(f"Unexpected stream event: {event!r}")289 290        # handle close events that target this context291        elif isinstance(event, QuicConnectionClosed) and (292            event.connection is self.context.client293            or event.connection is self.context.server294        ):295            from_client = event.connection is self.context.client296            other_conn = self.context.server if from_client else self.context.client297 298            # be done if both connections are closed299            if other_conn.connected:300                yield CloseQuicConnection(301                    other_conn, event.error_code, event.frame_type, event.reason_phrase302                )303            else:304                self._handle_event = self.done  # type: ignore305 306            # always forward to the datagram layer and swallow `CloseConnection` commands307            for command in self.event_to_child(self.datagram_layer, event):308                if (309                    not isinstance(command, commands.CloseConnection)310                    or command.connection is not other_conn311                ):312                    yield command313 314            # forward to either the client or server connection of stream layers and swallow empty stream end315            for conn, child_layer in self.connections.items():316                if isinstance(child_layer, QuicStreamLayer) and (317                    (conn is child_layer.client)318                    if from_client319                    else (conn is child_layer.server)320                ):321                    conn.state &= ~connection.ConnectionState.CAN_WRITE322                    for command in self.close_stream_layer(child_layer, from_client):323                        if not isinstance(command, SendQuicStreamData) or command.data:324                            yield command325 326        # all other connection events are routed to their corresponding layer327        elif isinstance(event, events.ConnectionEvent):328            yield from self.event_to_child(self.connections[event.connection], event)329 330        else:331            raise AssertionError(f"Unexpected event: {event!r}")332 333    def close_stream_layer(334        self, stream_layer: QuicStreamLayer, client: bool335    ) -> layer.CommandGenerator[None]:336        """Closes the incoming part of a connection."""337 338        conn = stream_layer.client if client else stream_layer.server339        conn.state &= ~connection.ConnectionState.CAN_READ340        assert conn.timestamp_start is not None341        if conn.timestamp_end is None:342            conn.timestamp_end = time.time()343            yield from self.event_to_child(stream_layer, events.ConnectionClosed(conn))344 345    def event_to_child(346        self, child_layer: layer.Layer, event: events.Event347    ) -> layer.CommandGenerator[None]:348        """Forwards events to child layers and translates commands."""349 350        for command in child_layer.handle_event(event):351            # intercept commands for streams connections352            if (353                isinstance(child_layer, QuicStreamLayer)354                and isinstance(command, commands.ConnectionCommand)355                and (356                    command.connection is child_layer.client357                    or command.connection is child_layer.server358                )359            ):360                # get the target connection and stream ID361                to_client = command.connection is child_layer.client362                quic_conn = self.context.client if to_client else self.context.server363                stream_id = child_layer.stream_id(to_client)364 365                # write data and check CloseConnection wasn't called before366                if isinstance(command, commands.SendData):367                    assert stream_id is not None368                    if command.connection.state & connection.ConnectionState.CAN_WRITE:369                        yield SendQuicStreamData(quic_conn, stream_id, command.data)370 371                # send a FIN and optionally also a STOP frame372                elif isinstance(command, commands.CloseConnection):373                    assert stream_id is not None374                    if command.connection.state & connection.ConnectionState.CAN_WRITE:375                        command.connection.state &= (376                            ~connection.ConnectionState.CAN_WRITE377                        )378                        yield SendQuicStreamData(379                            quic_conn, stream_id, b"", end_stream=True380                        )381                    # XXX: Use `command.connection.state & connection.ConnectionState.CAN_READ` instead?382                    only_close_our_half = (383                        isinstance(command, commands.CloseTcpConnection)384                        and command.half_close385                    )386                    if not only_close_our_half:387                        if stream_is_client_initiated(388                            stream_id389                        ) == to_client or not stream_is_unidirectional(stream_id):390                            yield StopSendingQuicStream(391                                quic_conn, stream_id, QuicErrorCode.NO_ERROR392                            )393                        yield from self.close_stream_layer(child_layer, to_client)394 395                # open server connections by reserving the next stream ID396                elif isinstance(command, commands.OpenConnection):397                    assert not to_client398                    assert stream_id is None399                    client_stream_id = child_layer.stream_id(client=True)400                    assert client_stream_id is not None401                    stream_id = self.get_next_available_stream_id(402                        is_client=True,403                        is_unidirectional=stream_is_unidirectional(client_stream_id),404                    )405                    child_layer.open_server_stream(stream_id)406                    self.server_stream_ids[stream_id] = child_layer407                    yield from self.event_to_child(408                        child_layer, events.OpenConnectionCompleted(command, None)409                    )410 411                else:412                    raise AssertionError(413                        f"Unexpected stream connection command: {command!r}"414                    )415 416            # remember blocking and wakeup commands417            else:418                if command.blocking or isinstance(command, commands.RequestWakeup):419                    self.command_sources[command] = child_layer420                if isinstance(command, commands.OpenConnection):421                    self.connections[command.connection] = child_layer422                yield command423 424    def get_next_available_stream_id(425        self, is_client: bool, is_unidirectional: bool = False426    ) -> int:427        index = (int(is_unidirectional) << 1) | int(not is_client)428        stream_id = self.next_stream_id[index]429        self.next_stream_id[index] = stream_id + 4430        return stream_id431 432    def done(self, _) -> layer.CommandGenerator[None]:  # pragma: no cover433        yield from ()434 
codekingpro/portable-devtools · Team Ai