I needed to be able to remote debug a process that was hidden behind a middle server so I made a Python script that can create a ‘middle man’ port to allow for this.
Basically if you have server-B which is only accessible through server-A then the script creates a port on server-A that links to the desired remote debug port on server-B and provides a new port to forward all traffic through.
It’s probably possible to do this all with some cryptic SSH command but this works better for me, plus I got to do some more Python. It uses the gevent library, which is an extremely fast and efficient socket library and can be downloaded from any good repository.
The usage is quite simple –
1 2 |
python Tunnel.py --remoteHost hiddenMachine --remotePort 8888 --localPort 9999 --localHost visibleServer |
The code –
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 97 98 99 100 101 102 103 104 105 |
#!/usr/bin/python import optparse import signal import sys import logging import gevent from gevent.server import StreamServer from gevent.socket import create_connection, gethostbyname DEBUG = False class Logger: def __init__(self, name, stdout): self.logger = logging.getLogger('') self.logger.setLevel(logging.DEBUG) if stdout: the_format = "%(asctime)s\t%(levelname)s\t%(message)s" logging.basicConfig(format=the_format) def get_logger(self): return self.logger class PortForwarder(StreamServer): def __init__(self, source, dest, logger, **kwargs): StreamServer.__init__(self, source, **kwargs) self.source = source self.dest = dest self.logger = logger self.closed = False self.output_socket = None def handle(self, source, address): self.logger.info('{0}:{1} accepted'.format(address[0], address[1])) try: if not self.output_socket: self.output_socket = create_connection(self.dest) gevent.spawn(join, source, self.output_socket, self.logger) gevent.spawn(join, self.output_socket, source, self.logger) except IOError as ex: self.logger.info('{0}:{1} failed to connect to {2}:{3} - {4}'.format(address[0], address[1], self.dest[0], self.dest[1], ex)) return def close(self): if self.closed: self.logger.info('Multiple exit signals received - aborting.') sys.exit(0) else: self.logger.info('Closing listener socket') #StreamServer.close(self) def join(input_socket, output_socket, logger): try: if DEBUG: input_destination = "{0}:{1}".format(input_socket.getsockname()[0], input_socket.getsockname()[1]) output_destination = "{0}:{1}".format(output_socket.getsockname()[0], output_socket.getsockname()[1]) logger.info("{0} -> {1}".format(input_destination, output_destination)) while True: data = input_socket.recv(1024) if not data: break output_socket.sendall(data) except Exception as e: logger.info("Exception encounted: {0}".format(e)) finally: input_socket.close() output_socket.close() def get_opts(): p = optparse.OptionParser() p.add_option('--remoteHost', default=None, dest='remote_host', help='Remote host name') p.add_option('--remotePort', default=None, dest='remote_port', help='Remote port') p.add_option('--localHost', default=None, dest='local_host', help='Local host name') p.add_option('--localPort', default=None, dest='local_port', help='Local port') (opts, args) = p.parse_args() if opts.remote_port is None or opts.local_port is None: p.print_help() sys.exit(1) return opts if __name__ == "__main__": opts = get_opts() logger = Logger('Tunnel', True) source = (opts.local_host, int(opts.local_port)) dest = (opts.remote_host, int(opts.remote_port)) server = PortForwarder(source, dest, logger.get_logger()) logger.get_logger().info("Starting port forwarder {0}:{1} -> {2}:{3}".format(source[0], source[1], dest[0], dest[1])) gevent.signal(signal.SIGTERM, server.close) gevent.signal(signal.SIGINT, server.close) try: gevent.joinall([ server.serve_forever() ]) except KeyboardInterrupt as e: logger.get_logger().info("Shutting down.") except Exception as e: logger.get_logger().info("Unknown exception: '{0}'".format(e)) finally: server.stop() |
Questions? Comments?