various fixes

This commit is contained in:
Maximilian Hils 2015-08-18 14:15:08 +02:00
parent 99129ab5a1
commit 96de7ad562
11 changed files with 77 additions and 25 deletions

View File

@ -246,14 +246,14 @@ class FSrc(_Rex):
help = "Match source address" help = "Match source address"
def __call__(self, f): def __call__(self, f):
return f.client_conn and re.search(self.expr, repr(f.client_conn.address)) return f.client_conn.address and re.search(self.expr, repr(f.client_conn.address))
class FDst(_Rex): class FDst(_Rex):
code = "dst" code = "dst"
help = "Match destination address" help = "Match destination address"
def __call__(self, f): def __call__(self, f):
return f.server_conn and re.search(self.expr, repr(f.server_conn.address)) return f.server_conn.address and re.search(self.expr, repr(f.server_conn.address))
class _Int(_Action): class _Int(_Action):
def __init__(self, num): def __init__(self, num):

View File

@ -729,6 +729,7 @@ class RequestReplayThread(threading.Thread):
if not self.flow.response: if not self.flow.response:
# In all modes, we directly connect to the server displayed # In all modes, we directly connect to the server displayed
if self.config.mode == "upstream": if self.config.mode == "upstream":
# FIXME
server_address = self.config.mode.get_upstream_server( server_address = self.config.mode.get_upstream_server(
self.flow.client_conn self.flow.client_conn
)[2:] )[2:]

View File

@ -10,13 +10,14 @@ from libmproxy.protocol import KILL
from libmproxy.protocol.http import HTTPFlow from libmproxy.protocol.http import HTTPFlow
from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest
from netlib import tcp from netlib import tcp
from netlib.http import status_codes, http1, HttpErrorConnClosed from netlib.http import status_codes, http1, HttpErrorConnClosed, HttpError
from netlib.http.semantics import CONTENT_MISSING from netlib.http.semantics import CONTENT_MISSING
from netlib import odict from netlib import odict
from netlib.tcp import NetLibError, Address from netlib.tcp import NetLibError, Address
from netlib.http.http1 import HTTP1Protocol from netlib.http.http1 import HTTP1Protocol
from netlib.http.http2 import HTTP2Protocol from netlib.http.http2 import HTTP2Protocol
# TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite. # TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite.
@ -31,6 +32,7 @@ class Http1Layer(Layer):
layer = HttpLayer(self, self.mode) layer = HttpLayer(self, self.mode)
for message in layer(): for message in layer():
yield message yield message
self.server_protocol = HTTP1Protocol(self.server_conn)
class Http2Layer(Layer): class Http2Layer(Layer):
@ -41,10 +43,10 @@ class Http2Layer(Layer):
self.server_protocol = HTTP2Protocol(self.server_conn, is_server=False) self.server_protocol = HTTP2Protocol(self.server_conn, is_server=False)
def __call__(self): def __call__(self):
# FIXME: Handle Reconnect etc.
layer = HttpLayer(self, self.mode) layer = HttpLayer(self, self.mode)
for message in layer(): for message in layer():
yield message yield message
self.server_protocol = HTTP1Protocol(self.server_conn)
def make_error_response(status_code, message, headers=None): def make_error_response(status_code, message, headers=None):
@ -100,6 +102,7 @@ class ConnectServerConnection(object):
""" """
"Fake" ServerConnection to represent state after a CONNECT request to an upstream proxy. "Fake" ServerConnection to represent state after a CONNECT request to an upstream proxy.
""" """
def __init__(self, address, ctx): def __init__(self, address, ctx):
self.address = tcp.Address.wrap(address) self.address = tcp.Address.wrap(address)
self._ctx = ctx self._ctx = ctx
@ -124,6 +127,8 @@ class HttpLayer(Layer):
def __call__(self): def __call__(self):
while True: while True:
try: try:
flow = HTTPFlow(self.client_conn, self.server_conn, live=True)
try: try:
request = HTTPRequest.from_protocol( request = HTTPRequest.from_protocol(
self.client_protocol, self.client_protocol,
@ -148,7 +153,6 @@ class HttpLayer(Layer):
# Make sure that the incoming request matches our expectations # Make sure that the incoming request matches our expectations
self.validate_request(request) self.validate_request(request)
flow = HTTPFlow(self.client_conn, self.server_conn)
flow.request = request flow.request = request
for message in self.process_request_hook(flow): for message in self.process_request_hook(flow):
yield message yield message
@ -164,15 +168,22 @@ class HttpLayer(Layer):
if self.check_close_connection(flow): if self.check_close_connection(flow):
return return
# TODO: Implement HTTP Upgrade
# Upstream Proxy Mode: Handle CONNECT # Upstream Proxy Mode: Handle CONNECT
if flow.request.form_in == "authority" and flow.response.code == 200: if flow.request.form_in == "authority" and flow.response.code == 200:
for message in self.handle_upstream_mode_connect(flow.request.copy()): for message in self.handle_upstream_mode_connect(flow.request.copy()):
yield message yield message
return return
except (HttpErrorConnClosed, NetLibError) as e: except (HttpErrorConnClosed, NetLibError, HttpError) as e:
make_error_response(502, repr(e)) self.send_to_client(make_error_response(
getattr(e, "code", 502),
repr(e)
))
raise ProtocolException(repr(e), e) raise ProtocolException(repr(e), e)
finally:
flow.live = False
def handle_regular_mode_connect(self, request): def handle_regular_mode_connect(self, request):
yield SetServer((request.host, request.port), False, None) yield SetServer((request.host, request.port), False, None)
@ -267,15 +278,16 @@ class HttpLayer(Layer):
for chunk in chunks: for chunk in chunks:
for part in chunk: for part in chunk:
# TODO: That's going to fail.
self.send_to_client(part) self.send_to_client(part)
self.client_conn.wfile.flush() self.client_conn.wfile.flush()
flow.response.timestamp_end = utils.timestamp() flow.response.timestamp_end = utils.timestamp()
def get_response_from_server(self, flow): def get_response_from_server(self, flow):
# TODO: Add second attempt. def get_response():
self.send_to_server(flow.request) self.send_to_server(flow.request)
# Only get the headers at first...
flow.response = HTTPResponse.from_protocol( flow.response = HTTPResponse.from_protocol(
self.server_protocol, self.server_protocol,
flow.request.method, flow.request.method,
@ -283,6 +295,27 @@ class HttpLayer(Layer):
include_body=False, include_body=False,
) )
try:
get_response()
except (tcp.NetLibError, HttpErrorConnClosed) as v:
self.log(
"server communication error: %s" % repr(v),
level="debug"
)
# In any case, we try to reconnect at least once. This is
# necessary because it might be possible that we already
# initiated an upstream connection after clientconnect that
# has already been expired, e.g consider the following event
# log:
# > clientconnect (transparent mode destination known)
# > serverconnect (required for client tls handshake)
# > read n% of large request
# > server detects timeout, disconnects
# > read (100-n)% of large request
# > send large request upstream
yield Reconnect()
get_response()
# call the appropriate script hook - this is an opportunity for an # call the appropriate script hook - this is an opportunity for an
# inline script to set flow.stream = True # inline script to set flow.stream = True
flow = self.channel.ask("responseheaders", flow) flow = self.channel.ask("responseheaders", flow)
@ -293,7 +326,6 @@ class HttpLayer(Layer):
flow.response.content = CONTENT_MISSING flow.response.content = CONTENT_MISSING
else: else:
flow.response.content = self.server_protocol.read_http_body( flow.response.content = self.server_protocol.read_http_body(
self.server_conn,
flow.response.headers, flow.response.headers,
self.config.body_size_limit, self.config.body_size_limit,
flow.request.method, flow.request.method,
@ -405,7 +437,7 @@ class HttpLayer(Layer):
self.send_to_client(make_error_response( self.send_to_client(make_error_response(
407, 407,
"Proxy Authentication Required", "Proxy Authentication Required",
self.config.authenticator.auth_challenge_headers() odict.ODictCaseless([[k,v] for k, v in self.config.authenticator.auth_challenge_headers().items()])
)) ))
raise InvalidCredentials("Proxy Authentication Required") raise InvalidCredentials("Proxy Authentication Required")

View File

@ -10,7 +10,8 @@ class HttpProxy(Layer, ServerConnectionMixin):
for message in layer(): for message in layer():
if not self._handle_server_message(message): if not self._handle_server_message(message):
yield message yield message
if self.server_conn:
self._disconnect()
class HttpUpstreamProxy(Layer, ServerConnectionMixin): class HttpUpstreamProxy(Layer, ServerConnectionMixin):
def __init__(self, ctx, server_address): def __init__(self, ctx, server_address):
@ -21,3 +22,5 @@ class HttpUpstreamProxy(Layer, ServerConnectionMixin):
for message in layer(): for message in layer():
if not self._handle_server_message(message): if not self._handle_server_message(message):
yield message yield message
if self.server_conn:
self._disconnect()

View File

@ -109,7 +109,9 @@ class ServerConnectionMixin(object):
def _handle_server_message(self, message): def _handle_server_message(self, message):
if message == Reconnect: if message == Reconnect:
address = self.server_conn.address
self._disconnect() self._disconnect()
self.server_conn.address = address
self._connect() self._connect()
return True return True
elif message == Connect: elif message == Connect:

View File

@ -19,3 +19,5 @@ class ReverseProxy(Layer, ServerConnectionMixin):
for message in layer(): for message in layer():
if not self._handle_server_message(message): if not self._handle_server_message(message):
yield message yield message
if self.server_conn:
self._disconnect()

View File

@ -1,4 +1,5 @@
from __future__ import (absolute_import, print_function, division) from __future__ import (absolute_import, print_function, division)
import string
from .messages import Kill from .messages import Kill
from .rawtcp import RawTcpLayer from .rawtcp import RawTcpLayer
@ -36,6 +37,8 @@ class RootContext(object):
d[2] in ('\x00', '\x01', '\x02', '\x03') d[2] in ('\x00', '\x01', '\x02', '\x03')
) )
is_ascii = all(x in string.ascii_uppercase for x in d)
# TODO: build is_http2_magic check here, maybe this is an easy way to detect h2c # TODO: build is_http2_magic check here, maybe this is an easy way to detect h2c
if not d: if not d:
@ -43,9 +46,10 @@ class RootContext(object):
if is_tls_client_hello: if is_tls_client_hello:
return TlsLayer(top_layer, True, True) return TlsLayer(top_layer, True, True)
elif isinstance(top_layer, TlsLayer) and top_layer.client_conn.get_alpn_proto_negotiated() == 'h2': elif isinstance(top_layer, TlsLayer) and is_ascii:
if top_layer.client_conn.get_alpn_proto_negotiated() == 'h2':
return Http2Layer(top_layer, 'transparent') return Http2Layer(top_layer, 'transparent')
elif isinstance(top_layer, TlsLayer) and isinstance(top_layer.ctx, Http1Layer): else:
return Http1Layer(top_layer, "transparent") return Http1Layer(top_layer, "transparent")
else: else:
return RawTcpLayer(top_layer) return RawTcpLayer(top_layer)

View File

@ -20,3 +20,5 @@ class Socks5Proxy(ServerConnectionMixin, Layer):
for message in layer(): for message in layer():
if not self._handle_server_message(message): if not self._handle_server_message(message):
yield message yield message
if self.server_conn:
self._disconnect()

View File

@ -21,3 +21,5 @@ class TransparentProxy(Layer, ServerConnectionMixin):
for message in layer(): for message in layer():
if not self._handle_server_message(message): if not self._handle_server_message(message):
yield message yield message
if self.server_conn:
self._disconnect()

View File

@ -27,6 +27,9 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
self.timestamp_ssl_setup = None self.timestamp_ssl_setup = None
self.protocol = None self.protocol = None
def __nonzero__(self):
return bool(self.connection) and not self.finished
def __repr__(self): def __repr__(self):
return "<ClientConnection: {ssl}{host}:{port}>".format( return "<ClientConnection: {ssl}{host}:{port}>".format(
ssl="[ssl] " if self.ssl_established else "", ssl="[ssl] " if self.ssl_established else "",
@ -89,7 +92,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def __init__(self, address): def __init__(self, address):
tcp.TCPClient.__init__(self, address) tcp.TCPClient.__init__(self, address)
self.state = [] # a list containing (conntype, state) tuples self.via = None
self.timestamp_start = None self.timestamp_start = None
self.timestamp_end = None self.timestamp_end = None
self.timestamp_tcp_setup = None self.timestamp_tcp_setup = None
@ -97,7 +100,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
self.protocol = None self.protocol = None
def __nonzero__(self): def __nonzero__(self):
return bool(self.connection) return bool(self.connection) and not self.finished
def __repr__(self): def __repr__(self):
if self.ssl_established and self.sni: if self.ssl_established and self.sni:
@ -117,7 +120,6 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
return self.ssl_established return self.ssl_established
_stateobject_attributes = dict( _stateobject_attributes = dict(
state=list,
timestamp_start=float, timestamp_start=float,
timestamp_end=float, timestamp_end=float,
timestamp_tcp_setup=float, timestamp_tcp_setup=float,
@ -187,3 +189,5 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def finish(self): def finish(self):
tcp.TCPClient.finish(self) tcp.TCPClient.finish(self)
self.timestamp_end = utils.timestamp() self.timestamp_end = utils.timestamp()
ServerConnection._stateobject_attributes["via"] = ServerConnection

View File

@ -56,7 +56,7 @@ class TestInvalidRequests(tservers.HTTPProxTest):
p = self.pathoc() p = self.pathoc()
r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port))
assert r.status_code == 400 assert r.status_code == 400
assert "Must not CONNECT on already encrypted connection" in r.body assert "Invalid HTTP request form" in r.body
def test_relative_request(self): def test_relative_request(self):
p = self.pathoc_raw() p = self.pathoc_raw()