diff options
-rw-r--r-- | libmproxy/protocol2/__init__.py | 157 | ||||
-rw-r--r-- | libmproxy/protocol2/messages.py (renamed from libmproxy/proxy/message.py) | 1 | ||||
-rw-r--r-- | libmproxy/protocol2/rawtcp.py | 13 | ||||
-rw-r--r-- | libmproxy/protocol2/socks.py | 26 | ||||
-rw-r--r-- | libmproxy/protocol2/ssl.py (renamed from libmproxy/proxy/layer.py) | 192 | ||||
-rw-r--r-- | libmproxy/proxy/__init__.py | 2 | ||||
-rw-r--r-- | libmproxy/proxy/primitives.py | 6 | ||||
-rw-r--r-- | libmproxy/proxy/server.py | 14 |
8 files changed, 215 insertions, 196 deletions
diff --git a/libmproxy/protocol2/__init__.py b/libmproxy/protocol2/__init__.py new file mode 100644 index 00000000..9374a5bf --- /dev/null +++ b/libmproxy/protocol2/__init__.py @@ -0,0 +1,157 @@ +""" +mitmproxy protocol architecture + +In mitmproxy, protocols are implemented as a set of layers, which are composed on top each other. +For example, the following scenarios depict possible scenarios (lowest layer first): + +Transparent HTTP proxy, no SSL: + TransparentModeLayer + HttpLayer + +Regular proxy, CONNECT request with WebSockets over SSL: + RegularModeLayer + HttpLayer + SslLayer + WebsocketLayer (or TcpLayer) + +Automated protocol detection by peeking into the buffer: + TransparentModeLayer + AutoLayer + SslLayer + AutoLayer + Http2Layer + +Communication between layers is done as follows: + - lower layers provide context information to higher layers + - higher layers can "yield" commands to lower layers, + which are propagated until they reach a suitable layer. + +Further goals: + - Connections should always be peekable to make automatic protocol detection work. + - Upstream connections should be established as late as possible; + inline scripts shall have a chance to handle everything locally. +""" + +from __future__ import (absolute_import, print_function, division, unicode_literals) +from netlib import tcp +from ..proxy import ProxyError2, Log +from ..proxy.connection import ServerConnection +from .messages import * + + +class RootContext(object): + """ + The outmost context provided to the root layer. + As a consequence, every layer has .client_conn, .channel 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 __getattr__(self, name): + """ + Accessing a nonexisting attribute does not throw an error but returns None instead. + """ + return None + + +class _LayerCodeCompletion(object): + """ + Dummy class that provides type hinting in PyCharm, which simplifies development a lot. + """ + + def __init__(self): + if True: + return + self.config = None + """@type: libmproxy.proxy.config.ProxyConfig""" + self.client_conn = None + """@type: libmproxy.proxy.connection.ClientConnection""" + self.channel = None + """@type: libmproxy.controller.Channel""" + + +class Layer(_LayerCodeCompletion): + def __init__(self, ctx): + """ + Args: + ctx: The (read-only) higher layer. + """ + super(Layer, self).__init__() + self.ctx = ctx + + def __call__(self): + """ + Logic of the layer. + Raises: + ProxyError2 in case of protocol exceptions. + """ + raise NotImplementedError + + def __getattr__(self, name): + """ + Attributes not present on the current layer may exist on a higher layer. + """ + return getattr(self.ctx, name) + + def log(self, msg, level, subs=()): + full_msg = [ + "%s:%s: %s" % + (self.client_conn.address.host, + self.client_conn.address.port, + msg)] + for i in subs: + full_msg.append(" -> " + i) + full_msg = "\n".join(full_msg) + self.channel.tell("log", Log(full_msg, level)) + + +class ServerConnectionMixin(object): + """ + Mixin that provides a layer with the capabilities to manage a server connection. + """ + + def __init__(self): + self.server_address = None + self.server_conn = None + + def _handle_server_message(self, message): + if message == Reconnect: + self._disconnect() + self._connect() + return True + elif message == Connect: + self._connect() + return True + elif message == ChangeServer: + raise NotImplementedError + return False + + def _disconnect(self): + """ + Deletes (and closes) an existing server connection. + """ + self.log("serverdisconnect", "debug", [repr(self.server_address)]) + self.server_conn.finish() + self.server_conn.close() + # self.channel.tell("serverdisconnect", self) + self.server_conn = None + + def _connect(self): + self.log("serverconnect", "debug", [repr(self.server_address)]) + self.server_conn = ServerConnection(self.server_address) + try: + self.server_conn.connect() + except tcp.NetLibError as e: + raise ProxyError2("Server connection to '%s' failed: %s" % (self.server_address, e), e) + + def _set_address(self, address): + a = tcp.Address.wrap(address) + self.log("Set new server address: " + repr(a), "debug") + self.server_address = address + + +from .socks import Socks5IncomingLayer +from .rawtcp import TcpLayer
\ No newline at end of file diff --git a/libmproxy/proxy/message.py b/libmproxy/protocol2/messages.py index 7eb59344..52bb5a44 100644 --- a/libmproxy/proxy/message.py +++ b/libmproxy/protocol2/messages.py @@ -1,6 +1,7 @@ """ This module contains all valid messages layers can send to the underlying layers. """ +from __future__ import (absolute_import, print_function, division, unicode_literals) class _Message(object): diff --git a/libmproxy/protocol2/rawtcp.py b/libmproxy/protocol2/rawtcp.py new file mode 100644 index 00000000..db9a48fa --- /dev/null +++ b/libmproxy/protocol2/rawtcp.py @@ -0,0 +1,13 @@ +from . import Layer, Connect +from ..protocol.tcp import TCPHandler + + +class TcpLayer(Layer): + def __call__(self): + yield Connect() + tcp_handler = TCPHandler(self) + tcp_handler.handle_messages() + + def establish_server_connection(self): + pass + # FIXME: Remove method, currently just here to mock TCPHandler's call to it. diff --git a/libmproxy/protocol2/socks.py b/libmproxy/protocol2/socks.py new file mode 100644 index 00000000..90771015 --- /dev/null +++ b/libmproxy/protocol2/socks.py @@ -0,0 +1,26 @@ +from __future__ import (absolute_import, print_function, division, unicode_literals) + +from ..proxy import ProxyError, Socks5ProxyMode, ProxyError2 +from . import Layer, ServerConnectionMixin +from .rawtcp import TcpLayer +from .ssl import SslLayer + + +class Socks5IncomingLayer(Layer, ServerConnectionMixin): + def __call__(self): + try: + s5mode = Socks5ProxyMode(self.config.ssl_ports) + address = s5mode.get_upstream_server(self.client_conn)[2:] + except ProxyError as e: + # TODO: Unmonkeypatch + raise ProxyError2(str(e), e) + + self._set_address(address) + + if address[1] == 443: + layer = SslLayer(self, True, True) + else: + layer = TcpLayer(self) + for message in layer(): + if not self._handle_server_message(message): + yield message diff --git a/libmproxy/proxy/layer.py b/libmproxy/protocol2/ssl.py index e4878bdf..6b44bf42 100644 --- a/libmproxy/proxy/layer.py +++ b/libmproxy/protocol2/ssl.py @@ -2,196 +2,12 @@ from __future__ import (absolute_import, print_function, division, unicode_liter import Queue import threading import traceback -from libmproxy.protocol.tcp import TCPHandler -from libmproxy.proxy.connection import ServerConnection from netlib import tcp -from .primitives import Socks5ProxyMode, ProxyError, Log -from .message import Connect, Reconnect, ChangeServer -""" -mitmproxy protocol architecture - -In mitmproxy, protocols are implemented as a set of layers, which are composed on top each other. -For example, the following scenarios depict possible scenarios (lowest layer first): - -Transparent HTTP proxy, no SSL: - TransparentModeLayer - HttpLayer - -Regular proxy, CONNECT request with WebSockets over SSL: - RegularModeLayer - HttpLayer - SslLayer - WebsocketLayer (or TcpLayer) - -Automated protocol detection by peeking into the buffer: - TransparentModeLayer - AutoLayer - SslLayer - AutoLayer - Http2Layer - -Communication between layers is done as follows: - - lower layers provide context information to higher layers - - higher layers can "yield" commands to lower layers, - which are propagated until they reach a suitable layer. - -Further goals: - - Connections should always be peekable to make automatic protocol detection work. - - Upstream connections should be established as late as possible; - inline scripts shall have a chance to handle everything locally. -""" - - -class ProxyError2(Exception): - def __init__(self, message, cause=None): - super(ProxyError2, self).__init__(message) - self.cause = cause - - -class RootContext(object): - """ - The outmost context provided to the root layer. - As a consequence, every layer has .client_conn, .channel 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 __getattr__(self, name): - """ - Accessing a nonexisting attribute does not throw an error but returns None instead. - """ - return None - - -class _LayerCodeCompletion(object): - """ - Dummy class that provides type hinting in PyCharm, which simplifies development a lot. - """ - - def __init__(self): - if True: - return - self.config = None - """@type: libmproxy.proxy.config.ProxyConfig""" - self.client_conn = None - """@type: libmproxy.proxy.connection.ClientConnection""" - self.channel = None - """@type: libmproxy.controller.Channel""" - - -class Layer(_LayerCodeCompletion): - def __init__(self, ctx): - """ - Args: - ctx: The (read-only) higher layer. - """ - super(Layer, self).__init__() - self.ctx = ctx - - def __call__(self): - """ - Logic of the layer. - Raises: - ProxyError2 in case of protocol exceptions. - """ - raise NotImplementedError - - def __getattr__(self, name): - """ - Attributes not present on the current layer may exist on a higher layer. - """ - return getattr(self.ctx, name) - - def log(self, msg, level, subs=()): - full_msg = [ - "%s:%s: %s" % - (self.client_conn.address.host, - self.client_conn.address.port, - msg)] - for i in subs: - full_msg.append(" -> " + i) - full_msg = "\n".join(full_msg) - self.channel.tell("log", Log(full_msg, level)) - - -class _ServerConnectionMixin(object): - """ - Mixin that provides a layer with the capabilities to manage a server connection. - """ - - def __init__(self): - self.server_address = None - self.server_conn = None - - def _handle_server_message(self, message): - if message == Reconnect: - self._disconnect() - self._connect() - return True - elif message == Connect: - self._connect() - return True - elif message == ChangeServer: - raise NotImplementedError - return False - - def _disconnect(self): - """ - Deletes (and closes) an existing server connection. - """ - self.log("serverdisconnect", "debug", [repr(self.server_address)]) - self.server_conn.finish() - self.server_conn.close() - # self.channel.tell("serverdisconnect", self) - self.server_conn = None - - def _connect(self): - self.log("serverconnect", "debug", [repr(self.server_address)]) - self.server_conn = ServerConnection(self.server_address) - try: - self.server_conn.connect() - except tcp.NetLibError as e: - raise ProxyError2("Server connection to '%s' failed: %s" % (self.server_address, e), e) - - def _set_address(self, address): - a = tcp.Address.wrap(address) - self.log("Set new server address: " + repr(a), "debug") - self.server_address = address - - -class Socks5IncomingLayer(Layer, _ServerConnectionMixin): - def __call__(self): - try: - s5mode = Socks5ProxyMode(self.config.ssl_ports) - address = s5mode.get_upstream_server(self.client_conn)[2:] - except ProxyError as e: - # TODO: Unmonkeypatch - raise ProxyError2(str(e), e) - - self._set_address(address) - - if address[1] == 443: - layer = SslLayer(self, True, True) - else: - layer = TcpLayer(self) - for message in layer(): - if not self._handle_server_message(message): - yield message - - -class TcpLayer(Layer): - def __call__(self): - yield Connect() - tcp_handler = TCPHandler(self) - tcp_handler.handle_messages() - - def establish_server_connection(self): - pass - # FIXME: Remove method, currently just here to mock TCPHandler's call to it. +from ..proxy import ProxyError2 +from . import Layer +from .messages import Connect, Reconnect, ChangeServer +from .rawtcp import TcpLayer class ReconnectRequest(object): diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py index f33d323b..7d664707 100644 --- a/libmproxy/proxy/__init__.py +++ b/libmproxy/proxy/__init__.py @@ -1,2 +1,2 @@ from .primitives import * -from .config import ProxyConfig, process_proxy_options +from .config import ProxyConfig, process_proxy_options
\ No newline at end of file diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py index a9f31181..fd4eb882 100644 --- a/libmproxy/proxy/primitives.py +++ b/libmproxy/proxy/primitives.py @@ -2,6 +2,12 @@ from __future__ import absolute_import from netlib import socks, tcp +class ProxyError2(Exception): + def __init__(self, message, cause=None): + super(ProxyError2, self).__init__(message) + self.cause = cause + + class ProxyError(Exception): def __init__(self, code, message, headers=None): super(ProxyError, self).__init__(message) diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index a45276d4..c8990a9a 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -3,12 +3,12 @@ from __future__ import absolute_import, print_function import traceback import sys import socket - from netlib import tcp -from . import layer -from .primitives import ProxyServerError, Log, ProxyError -from .connection import ClientConnection, ServerConnection + from ..protocol.handle import protocol_handler +from .. import protocol2 +from .primitives import ProxyServerError, Log, ProxyError, ProxyError2 +from .connection import ClientConnection, ServerConnection class DummyServer: @@ -74,17 +74,17 @@ class ConnectionHandler2: def handle(self): self.log("clientconnect", "info") - root_context = layer.RootContext( + root_context = protocol2.RootContext( self.client_conn, self.config, self.channel ) - root_layer = layer.Socks5IncomingLayer(root_context) + root_layer = protocol2.Socks5IncomingLayer(root_context) try: for message in root_layer(): print("Root layer receveived: %s" % message) - except layer.ProxyError2 as e: + except ProxyError2 as e: self.log(e, "info") except Exception: self.log(traceback.format_exc(), "error") |