Recipe 13.13. Forwarding and Redirecting Network Ports
Credit: Simon Foster
Problem
You need to forward a network port to another host
(forwarding), possibly to a different port
number (redirecting).
Solution
Classes using the tHReading and
socket modules can provide port forwarding and
redirecting:
import sys, socket, time, threadingA short ending to this pinhole.py module, with
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( )
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( )
Discussion
Port 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 webserverforwards all incoming HTTP sessions on standard port 80 to host
webserver;
# python pinhole.py 23 localhost 2323redirects 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 Also
Documentation for the standard library modules
socket and threading in the
Library Reference and Python in a
Nutshell.