mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-02-01 15:55:28 +00:00
get server reconnect right, fix timestamps
This commit is contained in:
parent
862b532fff
commit
6c24b1d0d2
@ -3,12 +3,10 @@ from netlib import http, http_status, tcp
|
|||||||
import netlib.utils
|
import netlib.utils
|
||||||
from netlib.odict import ODictCaseless
|
from netlib.odict import ODictCaseless
|
||||||
import select
|
import select
|
||||||
from proxy import ProxyError
|
from proxy import ProxyError, KILL
|
||||||
|
|
||||||
KILL = 0 # FIXME: Remove duplication with proxy module
|
|
||||||
LEGACY = True
|
LEGACY = True
|
||||||
|
|
||||||
|
|
||||||
def _handle(msg, conntype, connection_handler, *args, **kwargs):
|
def _handle(msg, conntype, connection_handler, *args, **kwargs):
|
||||||
handler = None
|
handler = None
|
||||||
if conntype == "http":
|
if conntype == "http":
|
||||||
@ -106,11 +104,11 @@ class HTTPResponse(HTTPMessage):
|
|||||||
if not include_content:
|
if not include_content:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
timestamp_start = libmproxy.utils.timestamp()
|
|
||||||
httpversion, code, msg, headers, content = http.read_response(
|
httpversion, code, msg, headers, content = http.read_response(
|
||||||
rfile,
|
rfile,
|
||||||
request_method,
|
request_method,
|
||||||
body_size_limit)
|
body_size_limit)
|
||||||
|
timestamp_start = rfile.first_byte_timestamp
|
||||||
timestamp_end = libmproxy.utils.timestamp()
|
timestamp_end = libmproxy.utils.timestamp()
|
||||||
return HTTPResponse(httpversion, code, msg, headers, content, timestamp_start, timestamp_end)
|
return HTTPResponse(httpversion, code, msg, headers, content, timestamp_start, timestamp_end)
|
||||||
|
|
||||||
@ -165,8 +163,8 @@ class HTTPRequest(HTTPMessage):
|
|||||||
httpversion, host, port, scheme, method, path, headers, content, timestamp_start, timestamp_end \
|
httpversion, host, port, scheme, method, path, headers, content, timestamp_start, timestamp_end \
|
||||||
= None, None, None, None, None, None, None, None, None, None
|
= None, None, None, None, None, None, None, None, None, None
|
||||||
|
|
||||||
timestamp_start = libmproxy.utils.timestamp()
|
|
||||||
request_line = HTTPHandler.get_line(rfile)
|
request_line = HTTPHandler.get_line(rfile)
|
||||||
|
timestamp_start = rfile.first_byte_timestamp
|
||||||
|
|
||||||
request_line_parts = http.parse_init(request_line)
|
request_line_parts = http.parse_init(request_line)
|
||||||
if not request_line_parts:
|
if not request_line_parts:
|
||||||
@ -210,33 +208,34 @@ class HTTPHandler(ProtocolHandler):
|
|||||||
pass
|
pass
|
||||||
self.c.close = True
|
self.c.close = True
|
||||||
|
|
||||||
"""
|
def get_response_from_server(self, request):
|
||||||
def wait_for_message(self):
|
request_raw = request._assemble()
|
||||||
"""
|
|
||||||
Check both the client connection and the server connection (if present) for readable data.
|
for i in range(2):
|
||||||
"""
|
try:
|
||||||
conns = [self.c.client_conn.rfile]
|
self.c.server_conn.wfile.write(request_raw)
|
||||||
if self.c.server_conn:
|
self.c.server_conn.wfile.flush()
|
||||||
conns.append(self.c.server_conn.rfile)
|
return HTTPResponse.from_stream(self.c.server_conn.rfile, request.method,
|
||||||
while True:
|
body_size_limit=self.c.config.body_size_limit)
|
||||||
readable, _, _ = select.select(conns, [], [], 10)
|
except (tcp.NetLibDisconnect, http.HttpErrorConnClosed), v:
|
||||||
if self.c.client_conn.rfile in readable:
|
self.c.log("error in server communication: %s" % str(v))
|
||||||
return
|
if i < 1:
|
||||||
if self.c.server_conn.rfile in readable:
|
# In any case, we try to reconnect at least once.
|
||||||
data = self.c.server_conn.rfile.read(1)
|
# This is necessary because it might be possible that we already initiated an upstream connection
|
||||||
if data == "":
|
# after clientconnect that has already been expired, e.g consider the following event log:
|
||||||
raise tcp.NetLibDisconnect
|
# > clientconnect (transparent mode destination known)
|
||||||
elif data == "\r" or data == "\n":
|
# > serverconnect
|
||||||
self.c.log("Received an empty line from server")
|
# > read n% of large request
|
||||||
pass # Possible leftover from previous message
|
# > server detects timeout, disconnects
|
||||||
|
# > read (100-n)% of large request
|
||||||
|
# > send large request upstream
|
||||||
|
self.c.server_reconnect()
|
||||||
else:
|
else:
|
||||||
raise ProxyError(502, "Unexpected message from server")
|
raise v
|
||||||
"""
|
|
||||||
|
|
||||||
def handle_flow(self):
|
def handle_flow(self):
|
||||||
flow = HTTPFlow(self.c.client_conn, self.c.server_conn, None, None, None)
|
flow = HTTPFlow(self.c.client_conn, self.c.server_conn, None, None, None)
|
||||||
try:
|
try:
|
||||||
# self.wait_for_message()
|
|
||||||
flow.request = HTTPRequest.from_stream(self.c.client_conn.rfile,
|
flow.request = HTTPRequest.from_stream(self.c.client_conn.rfile,
|
||||||
body_size_limit=self.c.config.body_size_limit)
|
body_size_limit=self.c.config.body_size_limit)
|
||||||
self.c.log("request", [flow.request._assemble_request_line(flow.request.form_in)])
|
self.c.log("request", [flow.request._assemble_request_line(flow.request.form_in)])
|
||||||
@ -250,11 +249,7 @@ class HTTPHandler(ProtocolHandler):
|
|||||||
if isinstance(request_reply, HTTPResponse):
|
if isinstance(request_reply, HTTPResponse):
|
||||||
flow.response = request_reply
|
flow.response = request_reply
|
||||||
else:
|
else:
|
||||||
raw = flow.request._assemble()
|
flow.response = self.get_response_from_server(flow.request)
|
||||||
self.c.server_conn.wfile.write(raw)
|
|
||||||
self.c.server_conn.wfile.flush()
|
|
||||||
flow.response = HTTPResponse.from_stream(self.c.server_conn.rfile, flow.request.method,
|
|
||||||
body_size_limit=self.c.config.body_size_limit)
|
|
||||||
|
|
||||||
self.c.log("response", [flow.response._assemble_response_line()])
|
self.c.log("response", [flow.response._assemble_response_line()])
|
||||||
response_reply = self.c.channel.ask("response" if LEGACY else "httpresponse",
|
response_reply = self.c.channel.ask("response" if LEGACY else "httpresponse",
|
||||||
|
@ -13,7 +13,7 @@ class ProxyError(Exception):
|
|||||||
self.code, self.msg, self.headers = code, msg, headers
|
self.code, self.msg, self.headers = code, msg, headers
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "ProxyError(%s, %s)"%(self.code, self.msg)
|
return "ProxyError(%s, %s)" % (self.code, self.msg)
|
||||||
|
|
||||||
|
|
||||||
import protocol
|
import protocol
|
||||||
@ -25,7 +25,8 @@ class Log:
|
|||||||
|
|
||||||
|
|
||||||
class ProxyConfig:
|
class ProxyConfig:
|
||||||
def __init__(self, certfile = None, cacert = None, clientcerts = None, no_upstream_cert=False, body_size_limit = None, reverse_proxy=None, forward_proxy=None, transparent_proxy=None, authenticator=None):
|
def __init__(self, certfile=None, cacert=None, clientcerts=None, no_upstream_cert=False, body_size_limit=None,
|
||||||
|
reverse_proxy=None, forward_proxy=None, transparent_proxy=None, authenticator=None):
|
||||||
self.certfile = certfile
|
self.certfile = certfile
|
||||||
self.cacert = cacert
|
self.cacert = cacert
|
||||||
self.clientcerts = clientcerts
|
self.clientcerts = clientcerts
|
||||||
@ -91,7 +92,6 @@ class ServerConnection(tcp.TCPClient):
|
|||||||
raise ProxyError(400, str(v))
|
raise ProxyError(400, str(v))
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
if self.connection: # Eventually, we had an error during .connect() and aren't even connected.
|
|
||||||
tcp.TCPClient.finish(self)
|
tcp.TCPClient.finish(self)
|
||||||
self.timestamp_end = utils.timestamp()
|
self.timestamp_end = utils.timestamp()
|
||||||
|
|
||||||
@ -141,9 +141,9 @@ class ConnectionHandler:
|
|||||||
self.mode = "transparent"
|
self.mode = "transparent"
|
||||||
|
|
||||||
def del_server_connection(self):
|
def del_server_connection(self):
|
||||||
if self.server_conn:
|
if self.server_conn and self.server_conn.connection:
|
||||||
self.server_conn.finish()
|
self.server_conn.finish()
|
||||||
self.log("serverdisconnect", ["%s:%s"%(self.server_conn.host, self.server_conn.port)])
|
self.log("serverdisconnect", ["%s:%s" % (self.server_conn.host, self.server_conn.port)])
|
||||||
self.channel.tell("serverdisconnect", self)
|
self.channel.tell("serverdisconnect", self)
|
||||||
self.server_conn = None
|
self.server_conn = None
|
||||||
self.sni = None
|
self.sni = None
|
||||||
@ -161,10 +161,11 @@ class ConnectionHandler:
|
|||||||
if self.config.reverse_proxy:
|
if self.config.reverse_proxy:
|
||||||
server_address = self.config.reverse_proxy[1:]
|
server_address = self.config.reverse_proxy[1:]
|
||||||
elif self.config.transparent_proxy:
|
elif self.config.transparent_proxy:
|
||||||
server_address = self.config.transparent_proxy["resolver"].original_addr(self.client_conn.connection)
|
server_address = self.config.transparent_proxy["resolver"].original_addr(
|
||||||
|
self.client_conn.connection)
|
||||||
if not server_address:
|
if not server_address:
|
||||||
raise ProxyError(502, "Transparent mode failure: could not resolve original destination.")
|
raise ProxyError(502, "Transparent mode failure: could not resolve original destination.")
|
||||||
self.log("transparent to %s:%s"%server_address)
|
self.log("transparent to %s:%s" % server_address)
|
||||||
|
|
||||||
self.determine_conntype()
|
self.determine_conntype()
|
||||||
|
|
||||||
@ -216,10 +217,10 @@ class ConnectionHandler:
|
|||||||
self.server_conn.connect()
|
self.server_conn.connect()
|
||||||
except tcp.NetLibError, v:
|
except tcp.NetLibError, v:
|
||||||
raise ProxyError(502, v)
|
raise ProxyError(502, v)
|
||||||
self.log("serverconnect", ["%s:%s"%(host, port)])
|
self.log("serverconnect", ["%s:%s" % (host, port)])
|
||||||
self.channel.tell("serverconnect", self)
|
self.channel.tell("serverconnect", self)
|
||||||
|
|
||||||
def establish_ssl(self, client, server):
|
def establish_ssl(self, client=False, server=False):
|
||||||
"""
|
"""
|
||||||
Establishes SSL on the existing connection(s) to the server or the client,
|
Establishes SSL on the existing connection(s) to the server or the client,
|
||||||
as specified by the parameters. If the target server is on the pass-through list,
|
as specified by the parameters. If the target server is on the pass-through list,
|
||||||
@ -241,12 +242,20 @@ class ConnectionHandler:
|
|||||||
self.client_conn.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert,
|
self.client_conn.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert,
|
||||||
handle_sni=self.handle_sni)
|
handle_sni=self.handle_sni)
|
||||||
|
|
||||||
|
def server_reconnect(self):
|
||||||
|
self.log("server reconnect")
|
||||||
|
had_ssl, sni = self.server_conn.ssl_established, self.sni
|
||||||
|
self.establish_server_connection(*self.server_conn.address)
|
||||||
|
if had_ssl:
|
||||||
|
self.sni = sni
|
||||||
|
self.establish_ssl(server=True)
|
||||||
|
|
||||||
def log(self, msg, subs=()):
|
def log(self, msg, subs=()):
|
||||||
msg = [
|
msg = [
|
||||||
"%s:%s: %s" % (self.client_conn.host, self.client_conn.port, msg)
|
"%s:%s: %s" % (self.client_conn.host, self.client_conn.port, msg)
|
||||||
]
|
]
|
||||||
for i in subs:
|
for i in subs:
|
||||||
msg.append(" -> "+i)
|
msg.append(" -> " + i)
|
||||||
msg = "\n".join(msg)
|
msg = "\n".join(msg)
|
||||||
self.channel.tell("log", Log(msg))
|
self.channel.tell("log", Log(msg))
|
||||||
|
|
||||||
@ -291,12 +300,14 @@ class ConnectionHandler:
|
|||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProxyServerError(Exception): pass
|
class ProxyServerError(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
class ProxyServer(tcp.TCPServer):
|
class ProxyServer(tcp.TCPServer):
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
bound = True
|
bound = True
|
||||||
|
|
||||||
def __init__(self, config, port, address='', server_version=version.NAMEVERSION):
|
def __init__(self, config, port, address='', server_version=version.NAMEVERSION):
|
||||||
"""
|
"""
|
||||||
Raises ProxyServerError if there's a startup problem.
|
Raises ProxyServerError if there's a startup problem.
|
||||||
@ -324,6 +335,7 @@ class ProxyServer(tcp.TCPServer):
|
|||||||
|
|
||||||
class DummyServer:
|
class DummyServer:
|
||||||
bound = False
|
bound = False
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
@ -339,22 +351,21 @@ def certificate_option_group(parser):
|
|||||||
group = parser.add_argument_group("SSL")
|
group = parser.add_argument_group("SSL")
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--cert", action="store",
|
"--cert", action="store",
|
||||||
type = str, dest="cert", default=None,
|
type=str, dest="cert", default=None,
|
||||||
help = "User-created SSL certificate file."
|
help="User-created SSL certificate file."
|
||||||
)
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--client-certs", action="store",
|
"--client-certs", action="store",
|
||||||
type = str, dest = "clientcerts", default=None,
|
type=str, dest="clientcerts", default=None,
|
||||||
help = "Client certificate directory."
|
help="Client certificate directory."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def process_proxy_options(parser, options):
|
def process_proxy_options(parser, options):
|
||||||
if options.cert:
|
if options.cert:
|
||||||
options.cert = os.path.expanduser(options.cert)
|
options.cert = os.path.expanduser(options.cert)
|
||||||
if not os.path.exists(options.cert):
|
if not os.path.exists(options.cert):
|
||||||
return parser.error("Manually created certificate does not exist: %s"%options.cert)
|
return parser.error("Manually created certificate does not exist: %s" % options.cert)
|
||||||
|
|
||||||
cacert = os.path.join(options.confdir, "mitmproxy-ca.pem")
|
cacert = os.path.join(options.confdir, "mitmproxy-ca.pem")
|
||||||
cacert = os.path.expanduser(cacert)
|
cacert = os.path.expanduser(cacert)
|
||||||
@ -368,8 +379,8 @@ def process_proxy_options(parser, options):
|
|||||||
if not platform.resolver:
|
if not platform.resolver:
|
||||||
return parser.error("Transparent mode not supported on this platform.")
|
return parser.error("Transparent mode not supported on this platform.")
|
||||||
trans = dict(
|
trans = dict(
|
||||||
resolver = platform.resolver(),
|
resolver=platform.resolver(),
|
||||||
sslports = TRANSPARENT_SSL_PORTS
|
sslports=TRANSPARENT_SSL_PORTS
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
trans = None
|
trans = None
|
||||||
@ -377,14 +388,14 @@ def process_proxy_options(parser, options):
|
|||||||
if options.reverse_proxy:
|
if options.reverse_proxy:
|
||||||
rp = utils.parse_proxy_spec(options.reverse_proxy)
|
rp = utils.parse_proxy_spec(options.reverse_proxy)
|
||||||
if not rp:
|
if not rp:
|
||||||
return parser.error("Invalid reverse proxy specification: %s"%options.reverse_proxy)
|
return parser.error("Invalid reverse proxy specification: %s" % options.reverse_proxy)
|
||||||
else:
|
else:
|
||||||
rp = None
|
rp = None
|
||||||
|
|
||||||
if options.forward_proxy:
|
if options.forward_proxy:
|
||||||
fp = utils.parse_proxy_spec(options.forward_proxy)
|
fp = utils.parse_proxy_spec(options.forward_proxy)
|
||||||
if not fp:
|
if not fp:
|
||||||
return parser.error("Invalid forward proxy specification: %s"%options.forward_proxy)
|
return parser.error("Invalid forward proxy specification: %s" % options.forward_proxy)
|
||||||
else:
|
else:
|
||||||
fp = None
|
fp = None
|
||||||
|
|
||||||
@ -392,7 +403,7 @@ def process_proxy_options(parser, options):
|
|||||||
options.clientcerts = os.path.expanduser(options.clientcerts)
|
options.clientcerts = os.path.expanduser(options.clientcerts)
|
||||||
if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts):
|
if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts):
|
||||||
return parser.error(
|
return parser.error(
|
||||||
"Client certificate directory does not exist or is not a directory: %s"%options.clientcerts
|
"Client certificate directory does not exist or is not a directory: %s" % options.clientcerts
|
||||||
)
|
)
|
||||||
|
|
||||||
if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd):
|
if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd):
|
||||||
@ -413,13 +424,13 @@ def process_proxy_options(parser, options):
|
|||||||
authenticator = http_auth.NullProxyAuth(None)
|
authenticator = http_auth.NullProxyAuth(None)
|
||||||
|
|
||||||
return ProxyConfig(
|
return ProxyConfig(
|
||||||
certfile = options.cert,
|
certfile=options.cert,
|
||||||
cacert = cacert,
|
cacert=cacert,
|
||||||
clientcerts = options.clientcerts,
|
clientcerts=options.clientcerts,
|
||||||
body_size_limit = body_size_limit,
|
body_size_limit=body_size_limit,
|
||||||
no_upstream_cert = options.no_upstream_cert,
|
no_upstream_cert=options.no_upstream_cert,
|
||||||
reverse_proxy = rp,
|
reverse_proxy=rp,
|
||||||
forward_proxy = fp,
|
forward_proxy=fp,
|
||||||
transparent_proxy = trans,
|
transparent_proxy=trans,
|
||||||
authenticator = authenticator
|
authenticator=authenticator
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user