aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/protocol2/root_context.py
blob: 4d69204f6aed901eb193417790e22a0738194e34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from __future__ import (absolute_import, print_function, division)

from netlib.http.http1 import HTTP1Protocol
from netlib.http.http2 import HTTP2Protocol

from .rawtcp import RawTcpLayer
from .tls import TlsLayer, is_tls_record_magic
from .http import Http1Layer, Http2Layer


class RootContext(object):
    """
    The outmost context provided to the root layer.
    As a consequence, every layer has .client_conn, .channel, .next_layer() and .config.
    """

    def __init__(self, client_conn, config, channel):
        self.client_conn = client_conn  # Client Connection
        self.channel = channel  # provides .ask() method to communicate with FlowMaster
        self.config = config  # Proxy Configuration

    def next_layer(self, top_layer):
        """
        This function determines the next layer in the protocol stack.

        Arguments:
            top_layer: the current top layer.

        Returns:
            The next layer
        """

        # 1. Check for --ignore.
        if self.config.check_ignore(top_layer.server_conn.address):
            return RawTcpLayer(top_layer, logging=False)

        # 2. Check for TLS
        # TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2
        # http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello
        d = top_layer.client_conn.rfile.peek(3)
        if is_tls_record_magic(d):
            return TlsLayer(top_layer, True, True)

        # 3. Check for --tcp
        if self.config.check_tcp(top_layer.server_conn.address):
            return RawTcpLayer(top_layer)

        # 4. Check for TLS ALPN (HTTP1/HTTP2)
        if isinstance(top_layer, TlsLayer):
            alpn = top_layer.client_conn.get_alpn_proto_negotiated()
            if alpn == HTTP2Protocol.ALPN_PROTO_H2:
                return Http2Layer(top_layer, 'transparent')
            if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1:
                return Http1Layer(top_layer, 'transparent')

        # 5. Assume HTTP1 by default
        return Http1Layer(top_layer, 'transparent')

        # In a future version, we want to implement TCP passthrough as the last fallback,
        # but we don't have the UI part ready for that.
        #
        # d = top_layer.client_conn.rfile.peek(3)
        # is_ascii = (
        #     len(d) == 3 and
        #     # better be safe here and don't expect uppercase...
        #     all(x in string.ascii_letters for x in d)
        # )
        # # TODO: This could block if there are not enough bytes available?
        # d = top_layer.client_conn.rfile.peek(len(HTTP2Protocol.CLIENT_CONNECTION_PREFACE))
        # is_http2_magic = (d == HTTP2Protocol.CLIENT_CONNECTION_PREFACE)

    @property
    def layers(self):
        return []

    def __repr__(self):
        return "RootContext"