This forward the TCP traffic from your machine to another host, and back in the the other way. It uses asynchronous socket thanks to ye olde asyncore module, which was used by Zope up until recently (they integrated the Twisted reactor). As a consequence, it should be able to handle a great number of connections without crumbling under the weight of many threads.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
import socket,asyncore class forwarder(asyncore.dispatcher): def __init__(self, ip, port, remoteip,remoteport,backlog=5): asyncore.dispatcher.__init__(self) self.remoteip=remoteip self.remoteport=remoteport self.create_socket(socket.AF_INET,socket.SOCK_STREAM) self.set_reuse_addr() self.bind((ip,port)) self.listen(backlog) def handle_accept(self): conn, addr = self.accept() # print '--- Connect --- ' sender(receiver(conn),self.remoteip,self.remoteport) class receiver(asyncore.dispatcher): def __init__(self,conn): asyncore.dispatcher.__init__(self,conn) self.from_remote_buffer='' self.to_remote_buffer='' self.sender=None def handle_connect(self): pass def handle_read(self): read = self.recv(4096) # print '%04i -->'%len(read) self.from_remote_buffer += read def writable(self): return (len(self.to_remote_buffer) > 0) def handle_write(self): sent = self.send(self.to_remote_buffer) # print '%04i <--'%sent self.to_remote_buffer = self.to_remote_buffer[sent:] def handle_close(self): self.close() if self.sender: self.sender.close() class sender(asyncore.dispatcher): def __init__(self, receiver, remoteaddr,remoteport): asyncore.dispatcher.__init__(self) self.receiver=receiver receiver.sender=self self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((remoteaddr, remoteport)) def handle_connect(self): pass def handle_read(self): read = self.recv(4096) # print '<-- %04i'%len(read) self.receiver.to_remote_buffer += read def writable(self): return (len(self.receiver.from_remote_buffer) > 0) def handle_write(self): sent = self.send(self.receiver.from_remote_buffer) # print '--> %04i'%sent self.receiver.from_remote_buffer = self.receiver.from_remote_buffer[sent:] def handle_close(self): self.close() self.receiver.close() if __name__=='__main__': import optparse parser = optparse.OptionParser() parser.add_option( '-l','--local-ip', dest='local_ip',default='127.0.0.1', help='Local IP address to bind to') parser.add_option( '-p','--local-port', type='int',dest='local_port',default=80, help='Local port to bind to') parser.add_option( '-r','--remote-ip',dest='remote_ip', help='Local IP address to bind to') parser.add_option( '-P','--remote-port', type='int',dest='remote_port',default=80, help='Remote port to bind to') options, args = parser.parse_args() forwarder(options.local_ip,options.local_port,options.remote_ip,options.remote_port) asyncore.loop()
See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/483730 for a threaded version. Their UI is better, with a configuration file parser, but with one thread per server socket and two threads per connection, the Python runtime could be under heavy stress as soon as you have more than a few hundred simultaneous connections.
I've tested my version with 252 simultaneous connections (using three Apache Benchmark processes each with 64 concurrent connections) and it handles the load like a charm. asyncore is really an impressive module...
Note that if you need to run more than one forwarder at a time, you just have to build as many forwarder instance as you want before calling asyncore.loop(). In fact, instead of a configuration file, you could just replace the __main__ part with something like :
if __name__ == '__main__': forwarder('127.0.0.1',80,'www.foobar.com',80) forwarder('127.0.0.1',443,'www.foobar.com',443) forwarder('127.0.0.1',81,'www.anotherone.com',80) forwarder('127.0.0.1',444,'www.anotherone.com',443) asyncore.loop()
Nice! We create solutions for which we hope there is a problem. :)
Indeed :). I guess 99.99% of the time a port forwarder is used with very few connections, so the threaded version does not have any problems. I've put this asynchronous version as a tribute to the asyncore module, since I've wrote this little port forwarder as I needed one and thought "what the heck, let's do it asynchronously" :)
closed connection for receiver. There is a bug. The receiver connection is closed by the sender.handle_close() so if the receiver is slow (asynchronous connection) the receiver connection is closed before it has a chance to send the data. So check in sender.handle_close() if to_remote_buffer is empty first then close otherwise let receiver close it using its own receiver.handle_close(). This was after much checking of buffers and realising the ovezealous sender had closed the receiver connection in haste!!
change: class sender(asyncore.dispatcher): def handle_close(self): self.close() if len(self.receiver.to_remote_buffer) == 0: self.receiver.close()
Can I change remoteip, remoteport on the fly? If the fly remoteip, remoteport not possible to change how koektno stop?