aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/protocol/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/protocol/base.py')
-rw-r--r--libmproxy/protocol/base.py184
1 files changed, 99 insertions, 85 deletions
diff --git a/libmproxy/protocol/base.py b/libmproxy/protocol/base.py
index 40ec0536..b92aeea1 100644
--- a/libmproxy/protocol/base.py
+++ b/libmproxy/protocol/base.py
@@ -1,38 +1,8 @@
-"""
-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 settings (lowest layer first):
-
-Transparent HTTP proxy, no SSL:
- TransparentProxy
- Http1Layer
- HttpLayer
-
-Regular proxy, CONNECT request with WebSockets over SSL:
- HttpProxy
- Http1Layer
- HttpLayer
- SslLayer
- WebsocketLayer (or TcpLayer)
-
-Automated protocol detection by peeking into the buffer:
- TransparentProxy
- TLSLayer
- Http2Layer
- HttpLayer
-
-Communication between layers is done as follows:
- - lower layers provide context information to higher layers
- - higher layers can call functions provided by 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)
+import sys
+
+import six
+
from netlib import tcp
from ..models import ServerConnection
from ..exceptions import ProtocolException
@@ -43,8 +13,8 @@ class _LayerCodeCompletion(object):
Dummy class that provides type hinting in PyCharm, which simplifies development a lot.
"""
- def __init__(self, *args, **kwargs): # pragma: nocover
- super(_LayerCodeCompletion, self).__init__(*args, **kwargs)
+ def __init__(self, **mixin_args): # pragma: nocover
+ super(_LayerCodeCompletion, self).__init__(**mixin_args)
if True:
return
self.config = None
@@ -55,43 +25,64 @@ class _LayerCodeCompletion(object):
"""@type: libmproxy.models.ServerConnection"""
self.channel = None
"""@type: libmproxy.controller.Channel"""
+ self.ctx = None
+ """@type: libmproxy.protocol.Layer"""
class Layer(_LayerCodeCompletion):
- def __init__(self, ctx, *args, **kwargs):
+ """
+ Base class for all layers. All other protocol layers should inherit from this class.
+ """
+
+ def __init__(self, ctx, **mixin_args):
"""
+ Each layer usually passes itself to its child layers as a context. Properties of the
+ context are transparently mapped to the layer, so that the following works:
+
+ .. code-block:: python
+
+ root_layer = Layer(None)
+ root_layer.client_conn = 42
+ sub_layer = Layer(root_layer)
+ print(sub_layer.client_conn) # 42
+
+ The root layer is passed a :py:class:`libmproxy.proxy.RootContext` object,
+ which provides access to :py:attr:`.client_conn <libmproxy.proxy.RootContext.client_conn>`,
+ :py:attr:`.next_layer <libmproxy.proxy.RootContext.next_layer>` and other basic attributes.
+
Args:
- ctx: The (read-only) higher layer.
+ ctx: The (read-only) parent layer / context.
"""
self.ctx = ctx
- """@type: libmproxy.protocol.Layer"""
- super(Layer, self).__init__(*args, **kwargs)
+ """
+ The parent layer.
- def __call__(self):
+ :type: :py:class:`Layer`
"""
- Logic of the layer.
+ super(Layer, self).__init__(**mixin_args)
+
+ def __call__(self):
+ """Logic of the layer.
+
+ Returns:
+ Once the protocol has finished without exceptions.
+
Raises:
- ProtocolException in case of protocol exceptions.
+ ~libmproxy.exceptions.ProtocolException: if an exception occurs. No other exceptions must be raised.
"""
raise NotImplementedError()
def __getattr__(self, name):
"""
- Attributes not present on the current layer may exist on a higher layer.
+ Attributes not present on the current layer are looked up on the context.
"""
return getattr(self.ctx, name)
- def log(self, msg, level, subs=()):
- full_msg = [
- "{}: {}".format(repr(self.client_conn.address), msg)
- ]
- for i in subs:
- full_msg.append(" -> " + i)
- full_msg = "\n".join(full_msg)
- self.channel.tell("log", Log(full_msg, level))
-
@property
def layers(self):
+ """
+ List of all layers, including the current layer (``[self, self.ctx, self.ctx.ctx, ...]``)
+ """
return [self] + self.ctx.layers
def __repr__(self):
@@ -101,20 +92,28 @@ class Layer(_LayerCodeCompletion):
class ServerConnectionMixin(object):
"""
Mixin that provides a layer with the capabilities to manage a server connection.
+ The server address can be passed in the constructor or set by calling :py:meth:`set_server`.
+ Subclasses are responsible for calling :py:meth:`disconnect` before returning.
+
+ Recommended Usage:
+
+ .. code-block:: python
+
+ class MyLayer(Layer, ServerConnectionMixin):
+ def __call__(self):
+ try:
+ # Do something.
+ finally:
+ if self.server_conn:
+ self.disconnect()
"""
def __init__(self, server_address=None):
super(ServerConnectionMixin, self).__init__()
self.server_conn = ServerConnection(server_address)
- self._check_self_connect()
-
- def reconnect(self):
- address = self.server_conn.address
- self._disconnect()
- self.server_conn.address = address
- self.connect()
+ self.__check_self_connect()
- def _check_self_connect(self):
+ def __check_self_connect(self):
"""
We try to protect the proxy from _accidentally_ connecting to itself,
e.g. because of a failed transparent lookup or an invalid configuration.
@@ -131,31 +130,45 @@ class ServerConnectionMixin(object):
"The proxy shall not connect to itself.".format(repr(address))
)
- def set_server(self, address, server_tls=None, sni=None, depth=1):
- if depth == 1:
- if self.server_conn:
- self._disconnect()
- self.log("Set new server address: " + repr(address), "debug")
- self.server_conn.address = address
- self._check_self_connect()
- if server_tls:
- raise ProtocolException(
- "Cannot upgrade to TLS, no TLS layer on the protocol stack."
- )
- else:
- self.ctx.set_server(address, server_tls, sni, depth - 1)
+ def set_server(self, address, server_tls=None, sni=None):
+ """
+ Sets a new server address. If there is an existing connection, it will be closed.
+
+ Raises:
+ ~libmproxy.exceptions.ProtocolException:
+ if ``server_tls`` is ``True``, but there was no TLS layer on the
+ protocol stack which could have processed this.
+ """
+ if self.server_conn:
+ self.disconnect()
+ self.log("Set new server address: " + repr(address), "debug")
+ self.server_conn.address = address
+ self.__check_self_connect()
+ if server_tls:
+ raise ProtocolException(
+ "Cannot upgrade to TLS, no TLS layer on the protocol stack."
+ )
- def _disconnect(self):
+ def disconnect(self):
"""
Deletes (and closes) an existing server connection.
+ Must not be called if there is no existing connection.
"""
self.log("serverdisconnect", "debug", [repr(self.server_conn.address)])
+ address = self.server_conn.address
self.server_conn.finish()
self.server_conn.close()
self.channel.tell("serverdisconnect", self.server_conn)
- self.server_conn = ServerConnection(None)
+ self.server_conn = ServerConnection(address)
def connect(self):
+ """
+ Establishes a server connection.
+ Must not be called if there is an existing connection.
+
+ Raises:
+ ~libmproxy.exceptions.ProtocolException: if the connection could not be established.
+ """
if not self.server_conn.address:
raise ProtocolException("Cannot connect to server, no server address given.")
self.log("serverconnect", "debug", [repr(self.server_conn.address)])
@@ -163,17 +176,18 @@ class ServerConnectionMixin(object):
try:
self.server_conn.connect()
except tcp.NetLibError as e:
- raise ProtocolException(
- "Server connection to %s failed: %s" % (repr(self.server_conn.address), e), e)
-
-
-class Log(object):
- def __init__(self, msg, level="info"):
- self.msg = msg
- self.level = level
+ six.reraise(
+ ProtocolException,
+ ProtocolException(
+ "Server connection to {} failed: {}".format(
+ repr(self.server_conn.address), str(e)
+ )
+ ),
+ sys.exc_info()[2]
+ )
class Kill(Exception):
"""
- Kill a connection.
+ Signal that both client and server connection(s) should be killed immediately.
"""