Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
connection.py360 linesDownload Raw Back to mitmproxy
1import dataclasses2import time3import uuid4import warnings5from abc import ABCMeta6from collections.abc import Sequence7from dataclasses import dataclass8from dataclasses import field9from enum import Flag10from typing import Literal11 12from mitmproxy import certs13from mitmproxy.coretypes import serializable14from mitmproxy.net import server_spec15from mitmproxy.proxy import mode_specs16from mitmproxy.utils import human17 18 19class ConnectionState(Flag):20    """The current state of the underlying socket."""21 22    CLOSED = 023    CAN_READ = 124    CAN_WRITE = 225    OPEN = CAN_READ | CAN_WRITE26 27 28TransportProtocol = Literal["tcp", "udp"]29 30# https://docs.openssl.org/master/man3/SSL_get_version/#return-values31TlsVersion = Literal[32    "SSLv3",33    "TLSv1",34    "TLSv1.1",35    "TLSv1.2",36    "TLSv1.3",37    "DTLSv0.9",38    "DTLSv1",39    "DTLSv1.2",40    "QUICv1",41]42 43# practically speaking we may have IPv6 addresses with flowinfo and scope_id,44# but type checking isn't good enough to properly handle tuple unions.45# this version at least provides useful type checking messages.46Address = tuple[str, int]47 48 49@dataclass(kw_only=True)50class Connection(serializable.SerializableDataclass, metaclass=ABCMeta):51    """52    Base class for client and server connections.53 54    The connection object only exposes metadata about the connection, but not the underlying socket object.55    This is intentional, all I/O should be handled by `mitmproxy.proxy.server` exclusively.56    """57 58    peername: Address | None59    """The remote's `(ip, port)` tuple for this connection."""60    sockname: Address | None61    """Our local `(ip, port)` tuple for this connection."""62 63    state: ConnectionState = field(64        default=ConnectionState.CLOSED, metadata={"serialize": False}65    )66    """The current connection state."""67 68    # all connections have a unique id. While69    # f.client_conn == f2.client_conn already holds true for live flows (where we have object identity),70    # we also want these semantics for recorded flows.71    id: str = field(default_factory=lambda: str(uuid.uuid4()))72    """A unique UUID to identify the connection."""73    transport_protocol: TransportProtocol = field(default="tcp")74    """The connection protocol in use."""75    error: str | None = None76    """77    A string describing a general error with connections to this address.78 79    The purpose of this property is to signal that new connections to the particular endpoint should not be attempted,80    for example because it uses an untrusted TLS certificate. Regular (unexpected) disconnects do not set the error81    property. This property is only reused per client connection.82    """83 84    tls: bool = False85    """86    `True` if TLS should be established, `False` otherwise.87    Note that this property only describes if a connection should eventually be protected using TLS.88    To check if TLS has already been established, use `Connection.tls_established`.89    """90    certificate_list: Sequence[certs.Cert] = ()91    """92    The TLS certificate list as sent by the peer.93    The first certificate is the end-entity certificate.94 95    > [RFC 8446] Prior to TLS 1.3, "certificate_list" ordering required each96    > certificate to certify the one immediately preceding it; however,97    > some implementations allowed some flexibility.  Servers sometimes98    > send both a current and deprecated intermediate for transitional99    > purposes, and others are simply configured incorrectly, but these100    > cases can nonetheless be validated properly.  For maximum101    > compatibility, all implementations SHOULD be prepared to handle102    > potentially extraneous certificates and arbitrary orderings from any103    > TLS version, with the exception of the end-entity certificate which104    > MUST be first.105    """106    alpn: bytes | None = None107    """The application-layer protocol as negotiated using108    [ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation)."""109    alpn_offers: Sequence[bytes] = ()110    """The ALPN offers as sent in the ClientHello."""111    # we may want to add SSL_CIPHER_description here, but that's currently not exposed by cryptography112    cipher: str | None = None113    """The active cipher name as returned by OpenSSL's `SSL_CIPHER_get_name`."""114    cipher_list: Sequence[str] = ()115    """Ciphers accepted by the proxy server on this connection."""116    tls_version: TlsVersion | None = None117    """The active TLS version."""118    sni: str | None = None119    """120    The [Server Name Indication (SNI)](https://en.wikipedia.org/wiki/Server_Name_Indication) sent in the ClientHello.121    """122 123    timestamp_start: float | None = None124    timestamp_end: float | None = None125    """*Timestamp:* Connection has been closed."""126    timestamp_tls_setup: float | None = None127    """*Timestamp:* TLS handshake has been completed successfully."""128 129    @property130    def connected(self) -> bool:131        """*Read-only:* `True` if Connection.state is ConnectionState.OPEN, `False` otherwise."""132        return self.state is ConnectionState.OPEN133 134    @property135    def tls_established(self) -> bool:136        """*Read-only:* `True` if TLS has been established, `False` otherwise."""137        return self.timestamp_tls_setup is not None138 139    def __eq__(self, other):140        if isinstance(other, Connection):141            return self.id == other.id142        return False143 144    def __hash__(self):145        return hash(self.id)146 147    def __repr__(self):148        attrs = {149            # ensure these come first.150            "id": None,151            "address": None,152        }153        for f in dataclasses.fields(self):154            val = getattr(self, f.name)155            if val != f.default:156                if f.name == "cipher_list":157                    val = f"<{len(val)} ciphers>"158                elif f.name == "id":159                    val = f"…{val[-6:]}"160                attrs[f.name] = val161        return f"{type(self).__name__}({attrs!r})"162 163    @property164    def alpn_proto_negotiated(self) -> bytes | None:  # pragma: no cover165        """*Deprecated:* An outdated alias for Connection.alpn."""166        warnings.warn(167            "Connection.alpn_proto_negotiated is deprecated, use Connection.alpn instead.",168            DeprecationWarning,169            stacklevel=2,170        )171        return self.alpn172 173 174@dataclass(eq=False, repr=False, kw_only=True)175class Client(Connection):  # type: ignore[override]176    """A connection between a client and mitmproxy."""177 178    peername: Address179    """The client's address."""180    sockname: Address181    """The local address we received this connection on."""182 183    mitmcert: certs.Cert | None = None184    """185    The certificate used by mitmproxy to establish TLS with the client.186    """187 188    proxy_mode: mode_specs.ProxyMode = field(189        default=mode_specs.ProxyMode.parse("regular")190    )191    """The proxy server type this client has been connecting to."""192 193    timestamp_start: float = field(default_factory=time.time)194    """*Timestamp:* TCP SYN received"""195 196    def __str__(self):197        if self.alpn:198            tls_state = f", alpn={self.alpn.decode(errors='replace')}"199        elif self.tls_established:200            tls_state = ", tls"201        else:202            tls_state = ""203        state = self.state.name204        assert state205        return f"Client({human.format_address(self.peername)}, state={state.lower()}{tls_state})"206 207    @property208    def address(self):  # pragma: no cover209        """*Deprecated:* An outdated alias for Client.peername."""210        warnings.warn(211            "Client.address is deprecated, use Client.peername instead.",212            DeprecationWarning,213            stacklevel=2,214        )215        return self.peername216 217    @address.setter218    def address(self, x):  # pragma: no cover219        warnings.warn(220            "Client.address is deprecated, use Client.peername instead.",221            DeprecationWarning,222            stacklevel=2,223        )224        self.peername = x225 226    @property227    def cipher_name(self) -> str | None:  # pragma: no cover228        """*Deprecated:* An outdated alias for Connection.cipher."""229        warnings.warn(230            "Client.cipher_name is deprecated, use Client.cipher instead.",231            DeprecationWarning,232            stacklevel=2,233        )234        return self.cipher235 236    @property237    def clientcert(self) -> certs.Cert | None:  # pragma: no cover238        """*Deprecated:* An outdated alias for Connection.certificate_list[0]."""239        warnings.warn(240            "Client.clientcert is deprecated, use Client.certificate_list instead.",241            DeprecationWarning,242            stacklevel=2,243        )244        if self.certificate_list:245            return self.certificate_list[0]246        else:247            return None248 249    @clientcert.setter250    def clientcert(self, val):  # pragma: no cover251        warnings.warn(252            "Client.clientcert is deprecated, use Client.certificate_list instead.",253            DeprecationWarning,254            stacklevel=2,255        )256        if val:257            self.certificate_list = [val]258        else:259            self.certificate_list = []260 261 262@dataclass(eq=False, repr=False, kw_only=True)263class Server(Connection):264    """A connection between mitmproxy and an upstream server."""265 266    address: Address | None  # type: ignore267    """268    The server's `(host, port)` address tuple.269 270    The host can either be a domain or a plain IP address.271    Which of those two will be present depends on the proxy mode and the client.272    For explicit proxies, this value will reflect what the client instructs mitmproxy to connect to.273    For example, if the client starts off a connection with `CONNECT example.com HTTP/1.1`, it will be `example.com`.274    For transparent proxies such as WireGuard mode, this value will be an IP address.275    """276 277    peername: Address | None = None278    """279    The server's resolved `(ip, port)` tuple. Will be set during connection establishment.280    May be `None` in upstream proxy mode when the address is resolved by the upstream proxy only.281    """282    sockname: Address | None = None283 284    timestamp_start: float | None = None285    """286    *Timestamp:* Connection establishment started.287 288    For IP addresses, this corresponds to sending a TCP SYN; for domains, this corresponds to starting a DNS lookup.289    """290    timestamp_tcp_setup: float | None = None291    """*Timestamp:* TCP ACK received."""292 293    via: server_spec.ServerSpec | None = None294    """An optional proxy server specification via which the connection should be established."""295 296    def __str__(self):297        if self.alpn:298            tls_state = f", alpn={self.alpn.decode(errors='replace')}"299        elif self.tls_established:300            tls_state = ", tls"301        else:302            tls_state = ""303        if self.sockname:304            local_port = f", src_port={self.sockname[1]}"305        else:306            local_port = ""307        state = self.state.name308        assert state309        return f"Server({human.format_address(self.address)}, state={state.lower()}{tls_state}{local_port})"310 311    def __setattr__(self, name, value):312        if name in ("address", "via"):313            connection_open = (314                self.__dict__.get("state", ConnectionState.CLOSED)315                is ConnectionState.OPEN316            )317            # assigning the current value is okay, that may be an artifact of calling .set_state().318            attr_changed = self.__dict__.get(name) != value319            if connection_open and attr_changed:320                raise RuntimeError(f"Cannot change server.{name} on open connection.")321        return super().__setattr__(name, value)322 323    @property324    def ip_address(self) -> Address | None:  # pragma: no cover325        """*Deprecated:* An outdated alias for `Server.peername`."""326        warnings.warn(327            "Server.ip_address is deprecated, use Server.peername instead.",328            DeprecationWarning,329            stacklevel=2,330        )331        return self.peername332 333    @property334    def cert(self) -> certs.Cert | None:  # pragma: no cover335        """*Deprecated:* An outdated alias for `Connection.certificate_list[0]`."""336        warnings.warn(337            "Server.cert is deprecated, use Server.certificate_list instead.",338            DeprecationWarning,339            stacklevel=2,340        )341        if self.certificate_list:342            return self.certificate_list[0]343        else:344            return None345 346    @cert.setter347    def cert(self, val):  # pragma: no cover348        warnings.warn(349            "Server.cert is deprecated, use Server.certificate_list instead.",350            DeprecationWarning,351            stacklevel=2,352        )353        if val:354            self.certificate_list = [val]355        else:356            self.certificate_list = []357 358 359__all__ = ["Connection", "Client", "Server", "ConnectionState"]360 
codekingpro/portable-devtools · Team Ai