From: James Bunton Date: Mon, 23 Mar 2009 05:29:21 +0000 (+1100) Subject: Epic proxy update - added extra awesome X-Git-Url: https://code.delx.au/monosys/commitdiff_plain/596c804348b39d9280117bbc8b073447739abbf0 Epic proxy update - added extra awesome --- diff --git a/scripts/proxy.py b/scripts/proxy.py index ad49bc2..5864591 100755 --- a/scripts/proxy.py +++ b/scripts/proxy.py @@ -1,53 +1,208 @@ #!/usr/bin/env python -# Sample config file: +""" +Proxy Utility +------------- -# [proxy] -# listen_port = 8000 -# host = google.com -# port = 80 -# -# [allowed] -# host1 = delx.cjb.net -# host2 = 333.333.333.333 +With mode=basic any incoming connections on listen_port will be proxied +to host:port. The proxy will only accept connections from hosts in the +[allowed] section. + +With mode=proxy the first two lines of incoming connections must be +'host\nport\n'. Once again only connections from hosts in the [allowed] +section will be accepted. The proxy will connect to host:port and pass +bytes in both directions. + +The final mode, mode=interceptor is designed to be combined with a firewall +rule and another instance running mode=proxy on another computer. +Proxies running in interceptor mode listen on all interfaces. They make +connections to the host:port specified in the config file, passing the +'capturedhost\ncapturedport\n' onto the destination. They then pass bytes +in both directions. + + +Example - Basic Forwarder +------------------------- + +Config to forward all packets from port 8000 on localhost to google.com:80 +Connections will be accepted from whatever IP address alpha.example.com +and beta.example.com point to. + +[proxy] +mode = basic +listen_port = 8000 +host = google.com +port = 80 + +[allowed] +host1 = alpha.example.com +host2 = beta.example.com + + + +Example - Interceptor Proxy Combo +--------------------------------- + +Capture all packets destined for port 1935 and send them to an interceptor +configured to listen on example.com:9997. +On Linux: + # iptables -t nat -A PREROUTING -p tcp --dport 1935 + -j REDIRECT --to-ports 9997 + # iptables -t nat -A OUTPUT -p tcp --dport 1935 + -j REDIRECT --to-ports 9997 + +On Mac OS X: + # ipfw add 50000 fwd 127.0.0.1,9997 tcp from any to any dst-port 1935 + +Config to forward these connections to proxy.example.com + +[proxy] +mode = interceptor +listen_port = 9997 +host = proxy.example.com +port = 9997 + + + +Config file for proxy.example.com + +[proxy] +mode = proxy +listen_port = 9997 + +[allowed] +host1 = alpha.example.com +host2 = beta.example.com + + +""" import asyncore import ConfigParser import os import socket +import struct import sys + +if sys.platform == "linux2": + try: + socket.SO_ORIGINAL_DST + except AttributeError: + # There is a missing const in the socket module... So we will add it now + socket.SO_ORIGINAL_DST = 80 + + def get_original_dest(sock): + '''Gets the original destination address for connection that has been + redirected by netfilter.''' + # struct sockaddr_in { + # short sin_family; // e.g. AF_INET + # unsigned short sin_port; // e.g. htons(3490) + # struct in_addr sin_addr; // see struct in_addr, below + # char sin_zero[8]; // zero this if you want to + # }; + # struct in_addr { + # unsigned long s_addr; // load with inet_aton() + # }; + # getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr_in *)&dstaddr, &dstlen); + + data = sock.getsockopt(socket.SOL_IP, socket.SO_ORIGINAL_DST, 16) + _, port, a1, a2, a3, a4 = struct.unpack("!HHBBBBxxxxxxxx", data) + address = "%d.%d.%d.%d" % (a1, a2, a3, a4) + return address, port + + +elif sys.platform == "darwin": + def get_original_dest(sock): + '''Gets the original destination address for connection that has been + redirected by ipfw.''' + return sock.getsockname() + + + class Proxy(asyncore.dispatcher): def __init__(self, sock): asyncore.dispatcher.__init__(self, sock) self.other = None self.buffer = "" - + def meet(self, other): self.other = other other.other = self - + def handle_error(self): - print >>sys.stderr, "Connection closed..." + print >>sys.stderr, "Proxy error:", sys.exc_info() self.handle_close() def handle_read(self): data = self.recv(8192) - self.other.buffer += data - + if len(data) > 0: + self.other.buffer += data + def handle_write(self): sent = self.send(self.buffer) self.buffer = self.buffer[sent:] - + + def writable(self): + return len(self.buffer) > 0 + def handle_close(self): if not self.other: return + print >>sys.stderr, "Proxy closed" self.other.close() self.close() self.other = None -class Forwarder(asyncore.dispatcher): +class ConnectProxy(asyncore.dispatcher): + def __init__(self, sock): + asyncore.dispatcher.__init__(self, sock) + self.buffer = "" + + def handle_error(self): + print >>sys.stderr, "ConnectProxy error:", sys.exc_info() + self.handle_close() + + def handle_read(self): + self.buffer += self.recv(8192) + pos1 = self.buffer.find("\n") + if pos1 < 0: + return + host = self.buffer[:pos1].strip() + pos1 += 1 + pos2 = self.buffer[pos1:].find("\n") + if pos2 < 0: + return + pos2 += pos1 + port = int(self.buffer[pos1:pos2].strip()) + + self.buffer = self.buffer[pos2+1:] + self.done(host, port) + + def handle_write(self): + pass + + def handle_close(self): + print >>sys.stderr, "Proxy closed" + self.close() + + def done(self, host, port): + print >>sys.stderr, "Forwarding connection", host, port + + # Create server proxy + server_connection = socket.socket() + server_connection.connect((host, port)) + server = Proxy(server_connection) + + # Morph and connect + self.__class__ = Proxy + server.meet(self) + server.buffer = self.buffer + self.buffer = "" + + +class BasicForwarder(asyncore.dispatcher): def __init__(self, listen_port, host, port, allowed): asyncore.dispatcher.__init__(self) self.host = host @@ -56,10 +211,10 @@ class Forwarder(asyncore.dispatcher): self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", listen_port)) self.listen(5) - + def handle_error(self): - print >>sys.stderr, "Connection error!", sys.exc_info() - + print >>sys.stderr, "BasicForwarder error:", sys.exc_info() + def handle_accept(self): client_connection, (addr, port) = self.accept() if addr not in map(socket.gethostbyname, self.allowed): @@ -76,10 +231,67 @@ class Forwarder(asyncore.dispatcher): server = Proxy(server_connection) server.meet(client) -def main(listen_port, host, port, allowed): - proxy = Forwarder(listen_port, host, port, allowed) +class Forwarder(asyncore.dispatcher): + def __init__(self, listen_port, allowed): + asyncore.dispatcher.__init__(self) + self.allowed = allowed + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.bind(("", listen_port)) + self.listen(5) + + def handle_error(self): + print >>sys.stderr, "Forwarder error:", sys.exc_info() + + def handle_accept(self): + client_connection, (addr, port) = self.accept() + if addr not in map(socket.gethostbyname, self.allowed): + print >>sys.stderr, "Rejected connection from", addr + client_connection.close() + return + + print >>sys.stderr, "Accepted connection from", addr, port + ConnectProxy(client_connection) + +class Interceptor(asyncore.dispatcher): + def __init__(self, listen_port, host, port): + asyncore.dispatcher.__init__(self) + self.host = host + self.port = port + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.bind(("0.0.0.0", listen_port)) + self.listen(5) + + def handle_error(self): + print >>sys.stderr, "Interceptor error!", sys.exc_info() + + def handle_accept(self): + # Get sockets + client_connection, addr = self.accept() + dest = get_original_dest(client_connection) + print >>sys.stderr, "Accepted connection from", addr, port + server_connection = socket.socket() + server_connection.connect((self.host, self.port)) + + # Hook them up to the event loop + client = Proxy(client_connection) + server = Proxy(server_connection) + server.buffer += "%s\n%d\n" % dest + server.meet(client) + + +def main(listen_port, host, port, mode, allowed): + if mode == "basic": + proxy = BasicForwarder(listen_port, host, port, allowed) + elif mode == "proxy": + proxy = Forwarder(listen_port, allowed) + elif mode == "interceptor": + proxy = Interceptor(listen_port, host, port) + else: + print >>sys.stderr, "Unknown mode:", mode + return 1 asyncore.loop() + if __name__ == "__main__": try: if sys.argv[1] == "-d": @@ -95,18 +307,43 @@ if __name__ == "__main__": try: c = ConfigParser.RawConfigParser() c.read(config) - listen_port = c.getint("proxy", "listen_port") - host = c.get("proxy", "host") - port = c.getint("proxy", "port") - allowed = c.items("allowed") - allowed = [h for _,h in c.items("allowed")] except: print >>sys.stderr, "Error parsing config!" sys.exit(1) + def guard(func, message): + try: + return func() + except: + print >>sys.stderr, "Error:", message + raise + sys.exit(1) + + mode = guard(lambda:c.get("proxy", "mode").lower(), + "mode is a required field") + + listen_port = guard(lambda:c.getint("proxy", "listen_port"), + "listen_port is a required field") + + if mode in ["basic", "interceptor"]: + text = "%%s is a required field for mode=%s" % mode + host = guard(lambda:c.get("proxy", "host"), text % "host") + port = guard(lambda:c.getint("proxy", "port"), text % "port") + else: + host = None + port = None + + if mode in ["basic", "proxy"]: + allowed = guard(lambda:c.items("allowed"), + "[allowed] section is required for mode=%s" % mode) + allowed = [h for _,h in c.items("allowed")] + else: + allowed = None + + if not daemon: try: - main(listen_port, host, port, allowed) + main(listen_port, host, port, mode, allowed) except KeyboardInterrupt: print else: @@ -120,7 +357,7 @@ if __name__ == "__main__": if os.fork() == 0: # We are the child try: - main(listen_port, host, port, allowed) + sys.exit(main(listen_port, host, port, allowed)) except KeyboardInterrupt: print sys.exit(0)