Fix transparent mode initialisation, refactor mitmproxy.platform (#1787)

This commit is contained in:
Maximilian Hils 2016-11-25 17:32:23 +01:00 committed by Thomas Kriechbaumer
parent 0386740404
commit 568f40c810
10 changed files with 74 additions and 68 deletions

View File

@ -1,17 +1,35 @@
import sys
import re import re
import socket
import sys
from typing import Tuple
def init_transparent_mode() -> None:
"""
Initialize transparent mode.
"""
def original_addr(csock: socket.socket) -> Tuple[str, int]:
"""
Get the original destination for the given socket.
This function will be None if transparent mode is not supported.
"""
resolver = None
if re.match(r"linux(?:2)?", sys.platform): if re.match(r"linux(?:2)?", sys.platform):
from . import linux from . import linux
resolver = linux.Resolver
elif sys.platform == "darwin": original_addr = linux.original_addr # noqa
elif sys.platform == "darwin" or sys.platform.startswith("freebsd"):
from . import osx from . import osx
resolver = osx.Resolver
elif sys.platform.startswith("freebsd"): original_addr = osx.original_addr # noqa
from . import osx
resolver = osx.Resolver
elif sys.platform == "win32": elif sys.platform == "win32":
from . import windows from . import windows
resolver = windows.Resolver
resolver = windows.Resolver()
init_transparent_mode = resolver.setup # noqa
original_addr = resolver.original_addr # noqa
else:
original_addr = None # noqa

View File

@ -5,10 +5,8 @@ import struct
SO_ORIGINAL_DST = 80 SO_ORIGINAL_DST = 80
class Resolver: def original_addr(csock: socket.socket):
odestdata = csock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16)
def original_addr(self, csock): _, port, a1, a2, a3, a4 = struct.unpack("!HHBBBBxxxxxxxx", odestdata)
odestdata = csock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 16) address = "%d.%d.%d.%d" % (a1, a2, a3, a4)
_, port, a1, a2, a3, a4 = struct.unpack("!HHBBBBxxxxxxxx", odestdata) return address, port
address = "%d.%d.%d.%d" % (a1, a2, a3, a4)
return address, port

View File

@ -14,24 +14,23 @@ from . import pf
the output processing of pfctl (see pf.py). the output processing of pfctl (see pf.py).
""" """
STATECMD = ("sudo", "-n", "/sbin/pfctl", "-s", "state")
class Resolver:
STATECMD = ("sudo", "-n", "/sbin/pfctl", "-s", "state")
def original_addr(self, csock): def original_addr(csock):
peer = csock.getpeername() peer = csock.getpeername()
try: try:
stxt = subprocess.check_output(self.STATECMD, stderr=subprocess.STDOUT) stxt = subprocess.check_output(STATECMD, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if "sudo: a password is required" in e.output.decode(errors="replace"): if "sudo: a password is required" in e.output.decode(errors="replace"):
insufficient_priv = True insufficient_priv = True
else:
raise RuntimeError("Error getting pfctl state: " + repr(e))
else: else:
insufficient_priv = "sudo: a password is required" in stxt.decode(errors="replace") raise RuntimeError("Error getting pfctl state: " + repr(e))
else:
insufficient_priv = "sudo: a password is required" in stxt.decode(errors="replace")
if insufficient_priv: if insufficient_priv:
raise RuntimeError( raise RuntimeError(
"Insufficient privileges to access pfctl. " "Insufficient privileges to access pfctl. "
"See http://docs.mitmproxy.org/en/latest/transparent/osx.html for details.") "See http://docs.mitmproxy.org/en/latest/transparent/osx.html for details.")
return pf.lookup(peer[0], peer[1], stxt) return pf.lookup(peer[0], peer[1], stxt)

View File

@ -19,10 +19,13 @@ PROXY_API_PORT = 8085
class Resolver: class Resolver:
def __init__(self): def __init__(self):
TransparentProxy.setup()
self.socket = None self.socket = None
self.lock = threading.RLock() self.lock = threading.RLock()
self._connect()
def setup(self):
with self.lock:
TransparentProxy.setup()
self._connect()
def _connect(self): def _connect(self):
if self.socket: if self.socket:

View File

@ -7,11 +7,10 @@ class TransparentProxy(protocol.Layer, protocol.ServerConnectionMixin):
def __init__(self, ctx): def __init__(self, ctx):
super().__init__(ctx) super().__init__(ctx)
self.resolver = platform.resolver()
def __call__(self): def __call__(self):
try: try:
self.server_conn.address = self.resolver.original_addr(self.client_conn.connection) self.server_conn.address = platform.original_addr(self.client_conn.connection)
except Exception as e: except Exception as e:
raise exceptions.ProtocolException("Transparent mode failure: %s" % repr(e)) raise exceptions.ProtocolException("Transparent mode failure: %s" % repr(e))

View File

@ -6,6 +6,8 @@ from mitmproxy import exceptions
from mitmproxy import connections from mitmproxy import connections
from mitmproxy import http from mitmproxy import http
from mitmproxy import log from mitmproxy import log
from mitmproxy import platform
from mitmproxy.proxy import ProxyConfig
from mitmproxy.proxy import modes from mitmproxy.proxy import modes
from mitmproxy.proxy import root_context from mitmproxy.proxy import root_context
from mitmproxy.net import tcp from mitmproxy.net import tcp
@ -33,7 +35,7 @@ class ProxyServer(tcp.TCPServer):
allow_reuse_address = True allow_reuse_address = True
bound = True bound = True
def __init__(self, config): def __init__(self, config: ProxyConfig):
""" """
Raises ServerException if there's a startup problem. Raises ServerException if there's a startup problem.
""" """
@ -42,6 +44,8 @@ class ProxyServer(tcp.TCPServer):
super().__init__( super().__init__(
(config.options.listen_host, config.options.listen_port) (config.options.listen_host, config.options.listen_port)
) )
if config.options.mode == "transparent":
platform.init_transparent_mode()
except socket.error as e: except socket.error as e:
raise exceptions.ServerException( raise exceptions.ServerException(
'Error starting proxy server: ' + repr(e) 'Error starting proxy server: ' + repr(e)

View File

@ -174,7 +174,7 @@ def get_common_options(args):
mode, upstream_server = "regular", None mode, upstream_server = "regular", None
if args.transparent_proxy: if args.transparent_proxy:
c += 1 c += 1
if not platform.resolver: if not platform.original_addr:
raise exceptions.OptionsError( raise exceptions.OptionsError(
"Transparent mode not supported on this platform." "Transparent mode not supported on this platform."
) )

View File

@ -94,11 +94,11 @@ class TestProcessProxyOptions:
with tutils.tmpdir() as cadir: with tutils.tmpdir() as cadir:
self.assert_noerr("--cadir", cadir) self.assert_noerr("--cadir", cadir)
@mock.patch("mitmproxy.platform.resolver", None) @mock.patch("mitmproxy.platform.original_addr", None)
def test_no_transparent(self): def test_no_transparent(self):
self.assert_err("transparent mode not supported", "-T") self.assert_err("transparent mode not supported", "-T")
@mock.patch("mitmproxy.platform.resolver") @mock.patch("mitmproxy.platform.original_addr")
def test_modes(self, _): def test_modes(self, _):
self.assert_noerr("-R", "http://localhost") self.assert_noerr("-R", "http://localhost")
self.assert_err("expected one argument", "-R") self.assert_err("expected one argument", "-R")

View File

@ -2,6 +2,8 @@ import os
import socket import socket
import time import time
import mock
from mitmproxy.test import tutils from mitmproxy.test import tutils
from mitmproxy import controller from mitmproxy import controller
from mitmproxy import options from mitmproxy import options
@ -878,16 +880,10 @@ class TestKillResponse(tservers.HTTPProxyTest):
assert self.server.last_log() assert self.server.last_log()
class EResolver(tservers.TResolver):
def original_addr(self, sock):
raise RuntimeError("Could not resolve original destination.")
class TestTransparentResolveError(tservers.TransparentProxyTest): class TestTransparentResolveError(tservers.TransparentProxyTest):
resolver = EResolver @mock.patch("mitmproxy.platform.original_addr")
def test_resolve_error(self, original_addr):
def test_resolve_error(self): original_addr.side_effect = RuntimeError
assert self.pathod("304").status_code == 502 assert self.pathod("304").status_code == 502

View File

@ -1,9 +1,9 @@
import os.path import os.path
import threading import threading
import tempfile import tempfile
import mock
import sys import sys
import mitmproxy.platform
from mitmproxy.proxy.config import ProxyConfig from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer from mitmproxy.proxy.server import ProxyServer
from mitmproxy import master from mitmproxy import master
@ -207,33 +207,22 @@ class HTTPProxyTest(ProxyTestBase):
return p.request("get:'http://%s%s'" % (options.APP_HOST, page)) return p.request("get:'http://%s%s'" % (options.APP_HOST, page))
class TResolver:
def __init__(self, port):
self.port = port
def original_addr(self, sock):
return ("127.0.0.1", self.port)
class TransparentProxyTest(ProxyTestBase): class TransparentProxyTest(ProxyTestBase):
ssl = None ssl = None
resolver = TResolver
@classmethod @classmethod
def setup_class(cls): def setup_class(cls):
cls._init_transparent_mode = mitmproxy.platform.init_transparent_mode
cls._original_addr = mitmproxy.platform.original_addr
mitmproxy.platform.init_transparent_mode = lambda: True
mitmproxy.platform.original_addr = lambda sock: ("127.0.0.1", cls.server.port)
super().setup_class() super().setup_class()
cls._resolver = mock.patch(
"mitmproxy.platform.resolver",
new=lambda: cls.resolver(cls.server.port)
)
cls._resolver.start()
@classmethod @classmethod
def teardown_class(cls): def teardown_class(cls):
cls._resolver.stop()
super().teardown_class() super().teardown_class()
mitmproxy.platform.init_transparent_mode = cls._init_transparent_mode
mitmproxy.platform.original_addr = cls._original_addr
@classmethod @classmethod
def get_options(cls): def get_options(cls):