Recipe 13.13. Forwarding and Redirecting Network PortsCredit: Simon Foster ProblemYou need to forward a network port to another host (forwarding), possibly to a different port number (redirecting). SolutionClasses using the tHReading and socket modules can provide port forwarding and redirecting: import sys, socket, time, threading LOGGING = True loglock = threading.Lock( ) def log(s, *a): if LOGGING: loglock.acquire( ) try: print '%s:%s' % (time.ctime( ), (s % a)) sys.stdout.flush( ) finally: loglock.release( ) class PipeThread(threading.Thread): pipes = [ ] pipeslock = threading.Lock( ) def _ _init_ _(self, source, sink): Thread._ _init_ _(self) self.source = source self.sink = sink log('Creating new pipe thread %s ( %s -> %s )', self, source.getpeername( ), sink.getpeername( )) self.pipeslock.acquire( ) try: self.pipes.append(self) finally: self.pipeslock.release( ) self.pipeslock.acquire( ) try: pipes_now = len(self.pipes) finally: self.pipeslock.release( ) log('%s pipes now active', pipes_now) def run(self): while True: try: data = self.source.recv(1024) if not data: break self.sink.send(data) except: break log('%s terminating', self) self.pipeslock.acquire( ) try: self.pipes.remove(self) finally: self.pipeslock.release( ) self.pipeslock.acquire( ) try: pipes_left = len(self.pipes) finally: self.pipeslock.release( ) log('%s pipes still active', pipes_left) class Pinhole(threading.Thread): def _ _init_ _(self, port, newhost, newport): Thread._ _init_ _(self) log('Redirecting: localhost:%s -> %s:%s', port, newhost, newport) self.newhost = newhost self.newport = newport self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.bind(('', port)) self.sock.listen(5) def run(self): while True: newsock, address = self.sock.accept( ) log('Creating new session for %s:%s', *address) fwd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) fwd.connect((self.newhost, self.newport)) PipeThread(newsock, fwd).start( ) PipeThread(fwd, newsock).start( ) A short ending to this pinhole.py module, with the usual guard to run this part only when pinhole is run as a main script rather than imported, lets us offer this recipe's functionality as a command-line script: if _ _name_ _ == '_ _main_ _': print 'Starting Pinhole port forwarder/redirector' import sys # get the arguments, give help in case of errors try: port = int(sys.argv[1]) newhost = sys.argv[2] try: newport = int(sys.argv[3]) except IndexError: newport = port except (ValueError, IndexError): print 'Usage: %s port newhost [newport]' % sys.argv[0] sys.exit(1) # start operations sys.stdout = open('pinhole.log', 'w') Pinhole(port, newhost, newport).start( ) DiscussionPort forwarding and redirecting can often come in handy when you're operating a network, even a small one. Applications or other services, possibly not under your control, may be hardwired to connect to servers on certain addresses or ports; by interposing a forwarder and redirector, you can send such applications' connection requests onto any other host and/or port that suits you better. The code in this recipe supplies two classes that liberally use threading to provide this functionality and a small "main script" at the end, with the usual if _ _name_ _ = = '_ _main_ _' guard, to deliver this functionality as a command-line script. For once, the small "main script" is not just for demonstration and testing purposes but is actually quite useful on its own. For example: # python pinhole.py 80 webserver forwards all incoming HTTP sessions on standard port 80 to host webserver; # python pinhole.py 23 localhost 2323 redirects all incoming telnet sessions on standard port 23 to port 2323 on this same host (since localhost is the conventional hostname for "this host" in all TCP/IP implementations). See AlsoDocumentation for the standard library modules socket and threading in the Library Reference and Python in a Nutshell. |