implement passthrough mode, fixes #175

This commit is contained in:
Maximilian Hils 2014-08-09 03:03:21 +02:00
parent f4a1459ebe
commit ecf8081ba6
4 changed files with 42 additions and 23 deletions

View File

@ -262,9 +262,14 @@ def common_options(parser):
help="Address to bind proxy to (defaults to all interfaces)"
)
group.add_argument(
"-U",
action="store", type=parse_server_spec, dest="upstream_proxy", default=None,
help="Forward all requests to upstream proxy server: http[s]://host[:port]"
"-I", "--ignore",
action="append", type=str, dest="ignore", default=[],
metavar="HOST",
help="Ignore host and forward all traffic without processing it. "
"In transparent mode, it is recommended to use an IP address (range), not the hostname. "
"In regular mode, only SSL traffic is ignored and the hostname should be used. "
"The supplied value is interpreted as a regular expression and matched on the ip or the hostname. "
"Can be passed multiple times. "
)
group.add_argument(
"-n",
@ -286,6 +291,11 @@ def common_options(parser):
action="store_true", dest="transparent_proxy", default=False,
help="Set transparent proxy mode."
)
group.add_argument(
"-U",
action="store", type=parse_server_spec, dest="upstream_proxy", default=None,
help="Forward all requests to upstream proxy server: http://host[:port]"
)
group = parser.add_argument_group(
"Advanced Proxy Options",

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import
import select, socket
from .primitives import ProtocolHandler
from netlib.utils import cleanBin
class TCPHandler(ProtocolHandler):
"""
@ -57,9 +58,9 @@ class TCPHandler(ProtocolHandler):
# 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, contents[:100]), "debug")
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, buf[:100]), "debug")
self.c.log("%s %s\r\n%s" % (direction, dst_str, cleanBin(buf.tobytes())), "debug")
dst.connection.send(buf[:size])

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import
import os
from .. import utils, platform
import re
from netlib import http_auth, certutils
from .primitives import ConstUpstreamServerResolver, TransparentUpstreamServerResolver
@ -13,7 +14,7 @@ class ProxyConfig:
def __init__(self, confdir=CONF_DIR, clientcerts=None,
no_upstream_cert=False, body_size_limit=None,
mode=None, upstream_server=None, http_form_in=None, http_form_out=None,
authenticator=None,
authenticator=None, ignore=[],
ciphers=None, certs=[], certforward=False):
self.ciphers = ciphers
self.clientcerts = clientcerts
@ -40,6 +41,7 @@ class ProxyConfig:
self.get_upstream_server = get_upstream_server
self.http_form_in = http_form_in
self.http_form_out = http_form_out
self.ignore = [re.compile(i, re.IGNORECASE) for i in ignore]
self.authenticator = authenticator
self.confdir = os.path.expanduser(confdir)
self.ca_file = os.path.join(self.confdir, CONF_BASENAME + "-ca.pem")
@ -118,6 +120,7 @@ def process_proxy_options(parser, options):
upstream_server=upstream_server,
http_form_in=options.http_form_in,
http_form_out=options.http_form_out,
ignore=options.ignore,
authenticator=authenticator,
ciphers=options.ciphers,
certs=certs,

View File

@ -1,4 +1,5 @@
from __future__ import absolute_import
import re
import socket
from OpenSSL import SSL
@ -66,31 +67,34 @@ class ConnectionHandler:
self.channel, self.server_version = channel, server_version
self.close = False
self.conntype = None
self.conntype = "http"
self.sni = None
def handle(self):
self.log("clientconnect", "info")
self.channel.ask("clientconnect", self)
self.determine_conntype()
try:
# Can we already identify the target server and connect to it?
client_ssl, server_ssl = False, False
if self.config.get_upstream_server:
upstream_info = self.config.get_upstream_server(
self.client_conn.connection)
self.set_server_address(upstream_info[2:], AddressPriority.FROM_SETTINGS)
client_ssl, server_ssl = upstream_info[:2]
self.determine_conntype()
self.channel.ask("clientconnect", self)
if self.server_conn:
self.establish_server_connection()
if client_ssl or server_ssl:
self.establish_server_connection()
self.establish_ssl(client=client_ssl, server=server_ssl)
while not self.close:
try:
handle_messages(self.conntype, self)
except ConnectionTypeChange:
self.log("Connection Type Changed: %s" % self.conntype, "info")
self.log("Connection type changed: %s" % self.conntype, "info")
continue
except (ProxyError, tcp.NetLibError), e:
@ -121,8 +125,11 @@ class ConnectionHandler:
self.sni = None
def determine_conntype(self):
#TODO: Add ruleset to select correct protocol depending on mode/target port etc.
self.conntype = "http"
if self.server_conn and any(rex.search(self.server_conn.address.host) for rex in self.config.ignore):
self.log("Ignore host: %s" % self.server_conn.address.host, "info")
self.conntype = "tcp"
else:
self.conntype = "http"
def set_server_address(self, address, priority):
"""
@ -135,7 +142,7 @@ class ConnectionHandler:
if self.server_conn.priority > priority:
self.log("Attempt to change server address, "
"but priority is too low (is: %s, got: %s)" % (
self.server_conn.priority, priority), "info")
self.server_conn.priority, priority), "debug")
return
if self.server_conn.address == address:
self.server_conn.priority = priority # Possibly increase priority
@ -171,14 +178,12 @@ class ConnectionHandler:
as specified by the parameters. If the target server is on the pass-through list,
the conntype attribute will be changed and a ConnTypeChanged exception will be raised.
"""
# TODO: Implement SSL pass-through handling and change conntype
passthrough = [
# "echo.websocket.org",
# "174.129.224.73" # echo.websocket.org, transparent mode
]
if self.server_conn.address.host in passthrough or self.sni in passthrough:
self.conntype = "tcp"
raise ConnectionTypeChange
# If the host is on our ignore list, change to passthrough/ignore mode.
for host in (self.server_conn.address.host, self.sni):
if host and any(rex.search(host) for rex in self.config.ignore):
self.log("Ignore host: %s" % host, "info")
self.conntype = "tcp"
raise ConnectionTypeChange()
# Logging
if client or server: