aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/proxy
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2015-07-24 13:31:55 +0200
committerMaximilian Hils <git@maximilianhils.com>2015-08-11 20:32:08 +0200
commit863113f989ee2a089c86b06a88a22e92d840348b (patch)
tree2d8e05cccb38a95881e2cf74b24c00f134be85b2 /libmproxy/proxy
parenta9fcef868b369568163e19c73651c55ccea4604d (diff)
downloadmitmproxy-863113f989ee2a089c86b06a88a22e92d840348b.tar.gz
mitmproxy-863113f989ee2a089c86b06a88a22e92d840348b.tar.bz2
mitmproxy-863113f989ee2a089c86b06a88a22e92d840348b.zip
first initial proof-of-concept
Diffstat (limited to 'libmproxy/proxy')
-rw-r--r--libmproxy/proxy/layer.py181
-rw-r--r--libmproxy/proxy/message.py40
-rw-r--r--libmproxy/proxy/server.py69
3 files changed, 282 insertions, 8 deletions
diff --git a/libmproxy/proxy/layer.py b/libmproxy/proxy/layer.py
new file mode 100644
index 00000000..500fb6ba
--- /dev/null
+++ b/libmproxy/proxy/layer.py
@@ -0,0 +1,181 @@
+from __future__ import (absolute_import, print_function, division, unicode_literals)
+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):
+ def __init__(self):
+ self._server_address = None
+ self.server_conn = None
+
+ def _handle_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_conn.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_conn.address)])
+ try:
+ self.server_conn.connect()
+ except tcp.NetLibError as e:
+ raise ProxyError2("Server connection to '%s' failed: %s" % (self._server_address, repr(e)), e)
+
+ def _set_address(self, address):
+ a = tcp.Address.wrap(address)
+ self.log("Set new server address: " + repr(a), "debug")
+ self.server_conn = ServerConnection(a)
+
+
+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:
+ raise ProxyError2(str(e), e)
+
+ self._set_address(address)
+
+ layer = TcpLayer(self)
+ for message in layer():
+ if not self._handle_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):
+ print("establish server conn called") \ No newline at end of file
diff --git a/libmproxy/proxy/message.py b/libmproxy/proxy/message.py
new file mode 100644
index 00000000..a667123c
--- /dev/null
+++ b/libmproxy/proxy/message.py
@@ -0,0 +1,40 @@
+"""
+This module contains all valid messages layers can send to the underlying layers.
+"""
+
+
+class _Message(object):
+ def __eq__(self, other):
+ # Allow message == Connect checks.
+ # FIXME: make Connect == message work.
+ if isinstance(self, other):
+ return True
+ return self is other
+
+
+class Connect(_Message):
+ """
+ Connect to the server
+ """
+
+
+class Reconnect(_Message):
+ """
+ Re-establish the server connection
+ """
+
+
+class ChangeServer(_Message):
+ """
+ Change the upstream server.
+ """
+
+ def __init__(self, address, server_ssl, sni, depth=1):
+ self.address = address
+ self.server_ssl = server_ssl
+ self.sni = sni
+
+ # upstream proxy scenario: you may want to change either the final target or the upstream proxy.
+ # We can express this neatly as the "nth-server-providing-layer"
+ # ServerConnection could get a `via` attribute.
+ self.depth = depth
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index 2f6ee061..a45276d4 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -1,13 +1,14 @@
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
+import traceback
+import sys
import socket
-from OpenSSL import SSL
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 version
class DummyServer:
@@ -46,7 +47,7 @@ class ProxyServer(tcp.TCPServer):
self.channel = channel
def handle_client_connection(self, conn, client_address):
- h = ConnectionHandler(
+ h = ConnectionHandler2(
self.config,
conn,
client_address,
@@ -56,6 +57,57 @@ class ProxyServer(tcp.TCPServer):
h.finish()
+class ConnectionHandler2:
+ # FIXME: parameter ordering
+ # FIXME: remove server attribute
+ def __init__(self, config, client_conn, client_address, server, channel):
+ self.config = config
+ """@type: libmproxy.proxy.config.ProxyConfig"""
+ self.client_conn = ClientConnection(
+ client_conn,
+ client_address,
+ server)
+ """@type: libmproxy.proxy.connection.ClientConnection"""
+ self.channel = channel
+ """@type: libmproxy.controller.Channel"""
+
+ def handle(self):
+ self.log("clientconnect", "info")
+
+ root_context = layer.RootContext(
+ self.client_conn,
+ self.config,
+ self.channel
+ )
+ root_layer = layer.Socks5IncomingLayer(root_context)
+
+ try:
+ for message in root_layer():
+ print("Root layer receveived: %s" % message)
+ except layer.ProxyError2 as e:
+ self.log(e, "info")
+ except Exception:
+ self.log(traceback.format_exc(), "error")
+ print(traceback.format_exc(), file=sys.stderr)
+ print("mitmproxy has crashed!", file=sys.stderr)
+ print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr)
+
+ def finish(self):
+ self.client_conn.finish()
+
+ def log(self, msg, level, subs=()):
+ # FIXME: Duplicate code
+ 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 ConnectionHandler:
def __init__(
self,
@@ -74,6 +126,7 @@ class ConnectionHandler:
self.server_conn = None
"""@type: libmproxy.proxy.connection.ServerConnection"""
self.channel = channel
+ """@type: libmproxy.controller.Channel"""
self.conntype = "http"
@@ -144,9 +197,9 @@ class ConnectionHandler:
import sys
self.log(traceback.format_exc(), "error")
- print >> sys.stderr, traceback.format_exc()
- print >> sys.stderr, "mitmproxy has crashed!"
- print >> sys.stderr, "Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy"
+ print(traceback.format_exc(), file=sys.stderr)
+ print("mitmproxy has crashed!", file=sys.stderr)
+ print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr)
finally:
# Make sure that we close the server connection in any case.
# The client connection is closed by the ProxyServer and does not
@@ -201,7 +254,7 @@ class ConnectionHandler:
"serverconnect", "debug", [
"%s:%s" %
self.server_conn.address()[
- :2]])
+ :2]])
if ask:
self.channel.ask("serverconnect", self)
try: