improve error handling

This commit is contained in:
Maximilian Hils 2014-09-02 18:13:18 +02:00
parent 1a41c15c03
commit 1e4e332ef9
5 changed files with 77 additions and 71 deletions

View File

@ -22,7 +22,7 @@ def get_line(fp):
if line == "\r\n" or line == "\n": # Possible leftover from previous message if line == "\r\n" or line == "\n": # Possible leftover from previous message
line = fp.readline() line = fp.readline()
if line == "": if line == "":
raise tcp.NetLibDisconnect raise tcp.NetLibDisconnect()
return line return line
@ -927,7 +927,7 @@ class HTTPHandler(ProtocolHandler):
body_size_limit=self.c.config.body_size_limit, include_body=include_body) body_size_limit=self.c.config.body_size_limit, include_body=include_body)
return res return res
except (tcp.NetLibDisconnect, http.HttpErrorConnClosed), v: except (tcp.NetLibDisconnect, http.HttpErrorConnClosed), v:
self.c.log("error in server communication: %s" % str(v), level="debug") self.c.log("error in server communication: %s" % repr(v), level="debug")
if i < 1: if i < 1:
# In any case, we try to reconnect at least once. # 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 # This is necessary because it might be possible that we already initiated an upstream connection
@ -1064,23 +1064,23 @@ class HTTPHandler(ProtocolHandler):
def handle_error(self, error, flow=None): def handle_error(self, error, flow=None):
message = repr(error) message = repr(error)
code = getattr(error, "code", 502)
headers = getattr(error, "headers", None)
if "tlsv1 alert unknown ca" in message: if "tlsv1 alert unknown ca" in message:
message = message + " \nThe client does not trust the proxy's certificate." message += " The client does not trust the proxy's certificate."
self.c.log("error: %s" % message, level="info") if isinstance(error, tcp.NetLibDisconnect):
self.c.log("TCP connection closed unexpectedly.", "debug", [repr(error)])
else:
self.c.log("error: %s" % message, level="info")
if flow: if flow:
flow.error = Error(message) # TODO: no flows without request or with both request and response at the moment.
# FIXME: no flows without request or with both request and response at the moment.
if flow.request and not flow.response: if flow.request and not flow.response:
flow.error = Error(message)
self.c.channel.ask("error", flow.error) self.c.channel.ask("error", flow.error)
else:
pass
try: try:
code = getattr(error, "code", 502)
headers = getattr(error, "headers", None)
self.send_error(code, message, headers) self.send_error(code, message, headers)
except: except:
pass pass

View File

@ -16,51 +16,55 @@ class TCPHandler(ProtocolHandler):
server = "%s:%s" % self.c.server_conn.address()[:2] server = "%s:%s" % self.c.server_conn.address()[:2]
buf = memoryview(bytearray(self.chunk_size)) buf = memoryview(bytearray(self.chunk_size))
conns = [self.c.client_conn.rfile, self.c.server_conn.rfile] conns = [self.c.client_conn.rfile, self.c.server_conn.rfile]
while True:
r, _, _ = select.select(conns, [], [], 10)
for rfile in r:
if self.c.client_conn.rfile == rfile:
src, dst = self.c.client_conn, self.c.server_conn
direction = "-> tcp ->"
src_str, dst_str = "client", server
else:
dst, src = self.c.client_conn, self.c.server_conn
direction = "<- tcp <-"
dst_str, src_str = "client", server
closed = False try:
if src.ssl_established: while True:
# Unfortunately, pyOpenSSL lacks a recv_into function. r, _, _ = select.select(conns, [], [], 10)
contents = src.rfile.read(1) # We need to read a single byte before .pending() becomes usable for rfile in r:
contents += src.rfile.read(src.connection.pending()) if self.c.client_conn.rfile == rfile:
if not contents: src, dst = self.c.client_conn, self.c.server_conn
closed = True direction = "-> tcp ->"
else: src_str, dst_str = "client", server
size = src.connection.recv_into(buf)
if not size:
closed = True
if closed:
conns.remove(src.rfile)
# Shutdown connection to the other peer
if dst.ssl_established:
dst.connection.shutdown()
else: else:
dst.connection.shutdown(socket.SHUT_WR) dst, src = self.c.client_conn, self.c.server_conn
direction = "<- tcp <-"
dst_str, src_str = "client", server
if len(conns) == 0: closed = False
return if src.ssl_established:
continue # Unfortunately, pyOpenSSL lacks a recv_into function.
contents = src.rfile.read(1) # We need to read a single byte before .pending() becomes usable
contents += src.rfile.read(src.connection.pending())
if not contents:
closed = True
else:
size = src.connection.recv_into(buf)
if not size:
closed = True
if src.ssl_established or dst.ssl_established: if closed:
# if one of the peers is over SSL, we need to send bytes/strings conns.remove(src.rfile)
if not src.ssl_established: # only ssl to dst, i.e. we revc'd into buf but need bytes/string now. # Shutdown connection to the other peer
contents = buf[:size].tobytes() if dst.ssl_established:
self.c.log("%s %s\r\n%s" % (direction, dst_str, cleanBin(contents)), "debug") dst.connection.shutdown()
dst.connection.send(contents) else:
else: dst.connection.shutdown(socket.SHUT_WR)
# socket.socket.send supports raw bytearrays/memoryviews
self.c.log("%s %s\r\n%s" % (direction, dst_str, cleanBin(buf.tobytes())), "debug") if len(conns) == 0:
dst.connection.send(buf[:size]) return
continue
if src.ssl_established or dst.ssl_established:
# if one of the peers is over SSL, we need to send bytes/strings
if not src.ssl_established: # only ssl to dst, i.e. we revc'd into buf but need bytes/string now.
contents = buf[:size].tobytes()
# self.c.log("%s %s\r\n%s" % (direction, dst_str, cleanBin(contents)), "debug")
dst.connection.send(contents)
else:
# socket.socket.send supports raw bytearrays/memoryviews
# self.c.log("%s %s\r\n%s" % (direction, dst_str, cleanBin(buf.tobytes())), "debug")
dst.connection.send(buf[:size])
except socket.error as e:
self.c.log("TCP connection closed unexpectedly.", "debug", [repr(e)])
return

View File

@ -3,7 +3,6 @@ import copy
import os import os
from netlib import tcp, certutils from netlib import tcp, certutils
from .. import stateobject, utils from .. import stateobject, utils
from .primitives import ProxyError
class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject): class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject):
@ -156,11 +155,8 @@ class ServerConnection(tcp.TCPClient, stateobject.SimpleStateObject):
path = os.path.join(clientcerts, self.address.host.encode("idna")) + ".pem" path = os.path.join(clientcerts, self.address.host.encode("idna")) + ".pem"
if os.path.exists(path): if os.path.exists(path):
clientcert = path clientcert = path
try: self.convert_to_ssl(cert=clientcert, sni=sni)
self.convert_to_ssl(cert=clientcert, sni=sni) self.timestamp_ssl_setup = utils.timestamp()
self.timestamp_ssl_setup = utils.timestamp()
except tcp.NetLibError, v:
raise ProxyError(400, repr(v))
def finish(self): def finish(self):
tcp.TCPClient.finish(self) tcp.TCPClient.finish(self)

View File

@ -95,7 +95,7 @@ class ConnectionHandler:
# Delegate handling to the protocol handler # Delegate handling to the protocol handler
protocol_handler(self.conntype)(self).handle_messages() protocol_handler(self.conntype)(self).handle_messages()
except (ProxyError, tcp.NetLibError), e: except ProxyError as e:
protocol_handler(self.conntype)(self).handle_error(e) protocol_handler(self.conntype)(self).handle_error(e)
except Exception: except Exception:
import traceback, sys import traceback, sys
@ -190,18 +190,24 @@ class ConnectionHandler:
raise ProxyError(502, "No server connection.") raise ProxyError(502, "No server connection.")
if self.server_conn.ssl_established: if self.server_conn.ssl_established:
raise ProxyError(502, "SSL to Server already established.") raise ProxyError(502, "SSL to Server already established.")
self.server_conn.establish_ssl(self.config.clientcerts, self.sni) try:
self.server_conn.establish_ssl(self.config.clientcerts, self.sni)
except tcp.NetLibError as v:
raise ProxyError(502, repr(v))
if client: if client:
if self.client_conn.ssl_established: if self.client_conn.ssl_established:
raise ProxyError(502, "SSL to Client already established.") raise ProxyError(502, "SSL to Client already established.")
cert, key = self.find_cert() cert, key = self.find_cert()
self.client_conn.convert_to_ssl( try:
cert, key, self.client_conn.convert_to_ssl(
handle_sni=self.handle_sni, cert, key,
cipher_list=self.config.ciphers, handle_sni=self.handle_sni,
dhparams=self.config.certstore.dhparams, cipher_list=self.config.ciphers,
ca_file=self.config.ca_file dhparams=self.config.certstore.dhparams,
) ca_file=self.config.ca_file
)
except tcp.NetLibError as v:
raise ProxyError(400, repr(v))
def server_reconnect(self): def server_reconnect(self):
address = self.server_conn.address address = self.server_conn.address

View File

@ -179,7 +179,7 @@ class TestHTTPConnectSSLError(tservers.HTTPProxTest):
p = self.pathoc_raw() p = self.pathoc_raw()
dst = ("localhost", self.proxy.port) dst = ("localhost", self.proxy.port)
p.connect(connect_to=dst) p.connect(connect_to=dst)
tutils.raises("400 - Bad Request", p.http_connect, dst) tutils.raises("502 - Bad Gateway", p.http_connect, dst)
class TestHTTPS(tservers.HTTPProxTest, CommonMixin): class TestHTTPS(tservers.HTTPProxTest, CommonMixin):
@ -244,7 +244,7 @@ class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin):
p = pathoc.Pathoc(("localhost", self.proxy.port)) p = pathoc.Pathoc(("localhost", self.proxy.port))
p.connect() p.connect()
r = p.request("get:/") r = p.request("get:/")
assert r.status_code == 502 assert r.status_code == 400
class TestProxy(tservers.HTTPProxTest): class TestProxy(tservers.HTTPProxTest):