mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
Unify mode specification
We now have: --mode regular (the default) --mode transparent --mode socks5 --mode reverse:SPEC --mode upstream:SPEC Where SPEC is a host specification.
This commit is contained in:
parent
f5fb6972aa
commit
82163a1e68
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
|
@ -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:")
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user