diff options
-rw-r--r-- | mitmproxy/addons/core.py | 29 | ||||
-rw-r--r-- | mitmproxy/master.py | 2 | ||||
-rw-r--r-- | mitmproxy/options.py | 15 | ||||
-rw-r--r-- | mitmproxy/proxy/config.py | 8 | ||||
-rw-r--r-- | mitmproxy/proxy/protocol/http.py | 2 | ||||
-rw-r--r-- | mitmproxy/proxy/protocol/http_replay.py | 2 | ||||
-rw-r--r-- | mitmproxy/proxy/server.py | 4 | ||||
-rw-r--r-- | mitmproxy/tools/cmdline.py | 67 | ||||
-rw-r--r-- | mitmproxy/tools/console/statusbar.py | 10 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_core.py | 18 | ||||
-rw-r--r-- | test/mitmproxy/proxy/test_server.py | 4 | ||||
-rw-r--r-- | test/mitmproxy/test_flow.py | 3 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy.py | 37 | ||||
-rw-r--r-- | test/mitmproxy/tservers.py | 8 |
14 files changed, 80 insertions, 129 deletions
diff --git a/mitmproxy/addons/core.py b/mitmproxy/addons/core.py index 5d2cf57b..8a2ea525 100644 --- a/mitmproxy/addons/core.py +++ b/mitmproxy/addons/core.py @@ -3,18 +3,37 @@ checked by other addons. """ from mitmproxy import exceptions +from mitmproxy import options +from mitmproxy import platform from mitmproxy.utils import human class Core: - def configure(self, options, updated): - if "body_size_limit" in updated and options.body_size_limit: + def configure(self, opts, updated): + if "body_size_limit" in updated and opts.body_size_limit: try: - options._processed["body_size_limit"] = human.parse_size( - options.body_size_limit + opts._processed["body_size_limit"] = human.parse_size( + opts.body_size_limit ) except ValueError as e: raise exceptions.OptionsError( "Invalid body size limit specification: %s" % - options.body_size_limit + opts.body_size_limit + ) + if "mode" in updated: + mode = opts.mode + if mode.startswith("reverse:") or mode.startswith("upstream:"): + spec = options.get_mode_spec(mode) + if not spec: + raise exceptions.OptionsError( + "Invalid mode specification: %s" % mode + ) + elif mode == "transparent": + if not platform.original_addr: + raise exceptions.OptionsError( + "Transparent mode not supported on this platform." + ) + elif mode not in ["regular", "socks5"]: + raise exceptions.OptionsError( + "Invalid mode specification: %s" % mode ) diff --git a/mitmproxy/master.py b/mitmproxy/master.py index 633f32aa..8855452c 100644 --- a/mitmproxy/master.py +++ b/mitmproxy/master.py @@ -148,7 +148,7 @@ class Master: Loads a flow """ if isinstance(f, http.HTTPFlow): - if self.server and self.options.mode == "reverse": + if self.server and self.options.mode.startswith("reverse:"): f.request.host = self.server.config.upstream_server.address[0] f.request.port = self.server.config.upstream_server.address[1] f.request.scheme = self.server.config.upstream_server.scheme diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 84ab1ecf..41e02031 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -22,6 +22,11 @@ view_orders = [ "size", ] + +def get_mode_spec(m): + return m.split(":", maxsplit=1)[1] + + APP_HOST = "mitm.it" APP_PORT = 80 CA_DIR = "~/.mitmproxy" @@ -247,7 +252,14 @@ class Options(optmanager.OptManager): "upstream_bind_address", "", str, "Address to bind upstream requests to (defaults to none)" ) - self.add_option("mode", "regular", str) + self.add_option( + "mode", "regular", str, + """ + Mode can be "regular", "transparent", "socks5", "reverse:SPEC", + or "upstream:SPEC". For reverse and upstream proxy modes, SPEC + is proxy specification in the form of "http[s]://host[:port]". + """ + ) self.add_option( "upstream_cert", True, bool, "Connect to upstream server to look up certificate details." @@ -285,7 +297,6 @@ class Options(optmanager.OptManager): "Use the client's IP for server-side connections. " "Combine with --upstream-bind-address to spoof a fixed source address." ) - self.add_option("upstream_server", None, Optional[str]) self.add_option( "upstream_auth", None, Optional[str], """ diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index 61d8e1b7..9cf2b00f 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -121,7 +121,7 @@ class ProxyConfig: raise exceptions.OptionsError( "Invalid certificate format: %s" % cert ) - - self.upstream_server = None - if options.upstream_server: - self.upstream_server = parse_server_spec(options.upstream_server) + m = options.mode + if m.startswith("upstream:") or m.startswith("reverse:"): + spec = moptions.get_mode_spec(options.mode) + self.upstream_server = parse_server_spec(spec) diff --git a/mitmproxy/proxy/protocol/http.py b/mitmproxy/proxy/protocol/http.py index d2f6d374..d9e53fed 100644 --- a/mitmproxy/proxy/protocol/http.py +++ b/mitmproxy/proxy/protocol/http.py @@ -290,7 +290,7 @@ class HttpLayer(base.Layer): request.first_line_format = "relative" # update host header in reverse proxy mode - if self.config.options.mode == "reverse" and not self.config.options.keep_host_header: + if self.config.options.mode.startswith("reverse:") and not self.config.options.keep_host_header: f.request.host_header = self.config.upstream_server.address[0] # Determine .scheme, .host and .port attributes for inline scripts. For diff --git a/mitmproxy/proxy/protocol/http_replay.py b/mitmproxy/proxy/protocol/http_replay.py index 161816e7..25867871 100644 --- a/mitmproxy/proxy/protocol/http_replay.py +++ b/mitmproxy/proxy/protocol/http_replay.py @@ -45,7 +45,7 @@ class RequestReplayThread(basethread.BaseThread): if not self.f.response: # In all modes, we directly connect to the server displayed - if self.config.options.mode == "upstream": + if self.config.options.mode.startswith("upstream:"): server_address = self.config.upstream_server.address server = connections.ServerConnection(server_address, (self.config.options.listen_host, 0)) server.connect() diff --git a/mitmproxy/proxy/server.py b/mitmproxy/proxy/server.py index 8082cb64..16692234 100644 --- a/mitmproxy/proxy/server.py +++ b/mitmproxy/proxy/server.py @@ -85,14 +85,14 @@ class ConnectionHandler: ) mode = self.config.options.mode - if mode == "upstream": + if mode.startswith("upstream:"): return modes.HttpUpstreamProxy( root_ctx, self.config.upstream_server.address ) elif mode == "transparent": return modes.TransparentProxy(root_ctx) - elif mode == "reverse": + elif mode.startswith("reverse:"): server_tls = self.config.upstream_server.scheme == "https" return modes.ReverseProxy( root_ctx, diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py index 5e83e828..1160e1e4 100644 --- a/mitmproxy/tools/cmdline.py +++ b/mitmproxy/tools/cmdline.py @@ -3,7 +3,6 @@ import os from mitmproxy import exceptions from mitmproxy import options -from mitmproxy import platform from mitmproxy import version @@ -15,34 +14,6 @@ class ParseException(Exception): def get_common_options(args): - # Establish proxy mode - c = 0 - mode, upstream_server = "regular", None - if args.transparent_proxy: - c += 1 - if not platform.original_addr: - raise exceptions.OptionsError( - "Transparent mode not supported on this platform." - ) - mode = "transparent" - if args.socks_proxy: - c += 1 - mode = "socks5" - if args.reverse_proxy: - c += 1 - mode = "reverse" - upstream_server = args.reverse_proxy - if args.upstream_proxy: - c += 1 - mode = "upstream" - upstream_server = args.upstream_proxy - if c > 1: - raise exceptions.OptionsError( - "Transparent, SOCKS5, reverse and upstream proxy mode " - "are mutually exclusive. Read the docs on proxy modes " - "to understand why." - ) - if args.add_upstream_certs_to_client_chain and not args.upstream_cert: raise exceptions.OptionsError( "The no-upstream-cert and add-upstream-certs-to-client-chain " @@ -99,7 +70,7 @@ def get_common_options(args): listen_host = args.listen_host, listen_port = args.listen_port, upstream_bind_address = args.upstream_bind_address, - mode = mode, + mode = args.mode, upstream_cert = args.upstream_cert, spoof_source_address = args.spoof_source_address, @@ -108,7 +79,6 @@ def get_common_options(args): websocket = args.websocket, rawtcp = args.rawtcp, - upstream_server = upstream_server, upstream_auth = args.upstream_auth, ssl_version_client = args.ssl_version_client, ssl_version_server = args.ssl_version_server, @@ -154,39 +124,6 @@ def basic_options(parser, opts): opts.make_parser(parser, "stream_large_bodies") -def proxy_modes(parser, opts): - group = parser.add_argument_group("Proxy Modes") - group.add_argument( - "-R", "--reverse", - action="store", - type=str, - dest="reverse_proxy", - help=""" - Forward all requests to upstream HTTP server: - http[s]://host[:port]. Clients can always connect both - via HTTPS and HTTP, the connection to the server is - determined by the specified scheme. - """ - ) - group.add_argument( - "--socks", - action="store_true", dest="socks_proxy", - help="Set SOCKS5 proxy mode." - ) - group.add_argument( - "-T", "--transparent", - action="store_true", dest="transparent_proxy", - help="Set transparent proxy mode." - ) - group.add_argument( - "-U", "--upstream", - action="store", - type=str, - dest="upstream_proxy", - help="Forward all requests to upstream proxy server: http://host[:port]" - ) - - def proxy_options(parser, opts): group = parser.add_argument_group("Proxy Options") opts.make_parser(group, "listen_host") @@ -315,8 +252,8 @@ def common_options(parser, opts): metavar="PATH", help="Configuration file" ) + opts.make_parser(parser, "mode") basic_options(parser, opts) - proxy_modes(parser, opts) proxy_options(parser, opts) proxy_ssl_options(parser, opts) onboarding_app(parser, opts) diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index a5611b28..3e524972 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -2,7 +2,6 @@ import os.path import urwid -import mitmproxy.net.http.url from mitmproxy.tools.console import common from mitmproxy.tools.console import pathedit from mitmproxy.tools.console import signals @@ -234,13 +233,8 @@ class StatusBar(urwid.WidgetWrap): if opts: r.append("[%s]" % (":".join(opts))) - if self.master.options.mode in ["reverse", "upstream"]: - dst = self.master.server.config.upstream_server - r.append("[dest:%s]" % mitmproxy.net.http.url.unparse( - dst.scheme, - dst.address[0], - dst.address[1], - )) + if self.master.options.mode != "regular": + r.append("[%s]" % self.master.options.mode) if self.master.options.scripts: r.append("[") r.append(("heading_key", "s")) diff --git a/test/mitmproxy/addons/test_core.py b/test/mitmproxy/addons/test_core.py index 95272716..533eb58e 100644 --- a/test/mitmproxy/addons/test_core.py +++ b/test/mitmproxy/addons/test_core.py @@ -2,6 +2,7 @@ from mitmproxy import exceptions from mitmproxy.addons import core from mitmproxy.test import taddons import pytest +from unittest import mock def test_simple(): @@ -11,3 +12,20 @@ def test_simple(): tctx.configure(sa, body_size_limit = "invalid") tctx.configure(sa, body_size_limit = "1m") assert tctx.options._processed["body_size_limit"] + + +@mock.patch("mitmproxy.platform.original_addr", None) +def test_no_transparent(): + sa = core.Core() + with taddons.context() as tctx: + with pytest.raises(Exception, match="Transparent mode not supported"): + tctx.configure(sa, mode = "transparent") + + +@mock.patch("mitmproxy.platform.original_addr") +def test_modes(m): + sa = core.Core() + with taddons.context() as tctx: + tctx.configure(sa, mode = "reverse:http://localhost") + with pytest.raises(Exception, match="Invalid mode"): + tctx.configure(sa, mode = "reverse:") diff --git a/test/mitmproxy/proxy/test_server.py b/test/mitmproxy/proxy/test_server.py index bcfecf6f..b90840ab 100644 --- a/test/mitmproxy/proxy/test_server.py +++ b/test/mitmproxy/proxy/test_server.py @@ -10,7 +10,7 @@ from mitmproxy import options from mitmproxy.addons import script from mitmproxy.addons import proxyauth from mitmproxy import http -from mitmproxy.proxy.config import HostMatcher, parse_server_spec +from mitmproxy.proxy.config import HostMatcher import mitmproxy.net.http from mitmproxy.net import tcp from mitmproxy.net import socks @@ -579,8 +579,6 @@ class TestHttps2Http(tservers.ReverseProxyTest): @classmethod def get_options(cls): opts = super().get_options() - s = parse_server_spec(opts.upstream_server) - opts.upstream_server = "http://{}:{}".format(s.address[0], s.address[1]) return opts def pathoc(self, ssl, sni=None): diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 0ac3bfd6..f4d32cbb 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -63,8 +63,7 @@ class TestSerialize: r = self._treader() s = tservers.TestState() opts = options.Options( - mode="reverse", - upstream_server="https://use-this-domain" + mode="reverse:https://use-this-domain" ) conf = ProxyConfig(opts) fm = master.Master(opts, DummyServer(conf)) diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 5dd4a9e3..784a7d84 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -49,31 +49,6 @@ class TestProcessProxyOptions: with tutils.tmpdir() as cadir: self.assert_noerr("--cadir", cadir) - @mock.patch("mitmproxy.platform.original_addr", None) - def test_no_transparent(self): - with pytest.raises(Exception, match="Transparent mode not supported"): - self.p("-T") - - @mock.patch("mitmproxy.platform.original_addr") - def test_modes(self, _): - self.assert_noerr("-R", "http://localhost") - with pytest.raises(Exception, match="expected one argument"): - self.p("-R") - with pytest.raises(Exception, match="Invalid server specification"): - self.p("-R", "reverse") - - self.assert_noerr("-T") - - self.assert_noerr("-U", "http://localhost") - with pytest.raises(Exception, match="Invalid server specification"): - self.p("-U", "upstream") - - self.assert_noerr("--upstream-auth", "test:test") - with pytest.raises(Exception, match="expected one argument"): - self.p("--upstream-auth") - with pytest.raises(Exception, match="mutually exclusive"): - self.p("-R", "http://localhost", "-T") - def test_client_certs(self): with tutils.tmpdir() as cadir: self.assert_noerr("--client-certs", cadir) @@ -131,19 +106,19 @@ class TestDummyServer: class TestConnectionHandler: def test_fatal_error(self, capsys): - config = mock.Mock() - root_layer = mock.Mock() - root_layer.side_effect = RuntimeError - config.options.mode.return_value = root_layer + opts = options.Options() + pconf = config.ProxyConfig(opts) + channel = mock.Mock() def ask(_, x): - return x + raise RuntimeError + channel.ask = ask c = ConnectionHandler( mock.MagicMock(), ("127.0.0.1", 8080), - config, + pconf, channel ) c.handle() diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index 9a289ae5..a8aaa358 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -288,7 +288,7 @@ class ReverseProxyTest(ProxyTestBase): @classmethod def get_options(cls): opts = ProxyTestBase.get_options() - opts.upstream_server = "".join( + s = "".join( [ "https" if cls.ssl else "http", "://", @@ -296,7 +296,7 @@ class ReverseProxyTest(ProxyTestBase): str(cls.server.port) ] ) - opts.mode = "reverse" + opts.mode = "reverse:" + s return opts def pathoc(self, sni=None): @@ -373,9 +373,9 @@ class ChainProxyTest(ProxyTestBase): def get_options(cls): opts = super().get_options() if cls.chain: # First proxy is in normal mode. + s = "http://127.0.0.1:%s" % cls.chain[0].port opts.update( - mode="upstream", - upstream_server="http://127.0.0.1:%s" % cls.chain[0].port + mode="upstream:" + s, ) return opts |