Python - Implementing SSH Tunneling Function

Posted by k994519 on Fri, 01 May 2020 11:11:52 +0200

Please indicate the source of the reprint: http://blog.csdn.net/l1028386804/article/details/78845722

Imagine an environment where you can access an SSH server on your intranet and you also want to access a Web server on the same segment.You cannot access the Web server directly, but SSH servers can access the Web server and there are no tools installed on this SSH server that you want to use.
We can use Python to create a forwarded SH tunnel to accomplish these functions, as detailed in the following code:

# -*- coding:UTF-8 -*-
'''
//Files direct data traffic from one port opened by the SSH server to the port of another server specified
//For example, open the command line and enter the following code:
rforward.py 192.168.209.121 -p 8080 -r 192.168.209.122:80 --user root --password
//After entering the ssh password, the console prints as follows:

----------------------------Console Content Start----------------------------------------
D:\Workspaces\python27\py_hacker\com\lyz\chapter2>rforward.py 192.168.209.121 -p 8080 -r 192.168.209.122:80 --user root --password
Enter SSH password:
Connecting to ssh host 192.168.209.121 ...
D:\Program Files\Python27\lib\site-packages\paramiko\client.py:779: UserWarning: Unknown ssh-rsa host key for 192.168.209.121: 3bc514e5b8ad5377141030149ea79649
  key.get_name(), hostname, hexlify(key.get_fingerprint()),
Now forwarding remote port 8080 to 192.168.209.122:80 ...

----------------------------Console Content End----------------------------------------

//Indicates that the program has been started successfully.
rforward.py 192.168.209.121 -p 8080 -r 192.168.209.122:80 --user root --password The purpose is to:
//Data traffic accessing 192.168.209.121:8080 is directed to 192.168.209.122:80 through the SSH tunnel, that is, opening a browser to access http://192.168.209.121:8080 will be directed to http://192.168.209.122:80 through the SSH tunnel.
//In this way, as long as we can access http://192.168.209.121:8080, we cannot directly access http://192.168.209.122:80. In this way, we can also access http://192.168.209.122:80.


Created on 2017 December 19

@author: liuyazhuang
'''

import getpass
import os
import socket
import select
import sys
import threading
from optparse import OptionParser

import paramiko

SSH_PORT = 22
DEFAULT_PORT = 4000

g_verbose = True


def handler(chan, host, port):
    sock = socket.socket()
    try:
        sock.connect((host, port))
    except Exception as e:
        verbose('Forwarding request to %s:%d failed: %r' % (host, port, e))
        return
    
    verbose('Connected!  Tunnel open %r -> %r -> %r' % (chan.origin_addr,
                                                        chan.getpeername(), (host, port)))
    while True:
        r, w, x = select.select([sock, chan], [], [])
        if sock in r:
            data = sock.recv(1024)
            if len(data) == 0:
                break
            chan.send(data)
        if chan in r:
            data = chan.recv(1024)
            if len(data) == 0:
                break
            sock.send(data)
    chan.close()
    sock.close()
    verbose('Tunnel closed from %r' % (chan.origin_addr,))


def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
    transport.request_port_forward('', server_port)
    while True:
        chan = transport.accept(1000)
        if chan is None:
            continue
        thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
        thr.setDaemon(True)
        thr.start()


def verbose(s):
    if g_verbose:
        print(s)


HELP = """\
Set up a reverse forwarding tunnel across an SSH server, using paramiko. A
port on the SSH server (given with -p) is forwarded across an SSH session
back to the local machine, and out to a remote site reachable from this
network. This is similar to the openssh -R option.
"""


def get_host_port(spec, default_port):
    "parse 'hostname:22' into a host and port, with the port optional"
    args = (spec.split(':', 1) + [default_port])[:2]
    args[1] = int(args[1])
    return args[0], args[1]


def parse_options():
    global g_verbose
    
    parser = OptionParser(usage='usage: %prog [options] <ssh-server>[:<server-port>]',
                          version='%prog 1.0', description=HELP)
    parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
                      help='squelch all informational output')
    parser.add_option('-p', '--remote-port', action='store', type='int', dest='port',
                      default=DEFAULT_PORT,
                      help='port on server to forward (default: %d)' % DEFAULT_PORT)
    parser.add_option('-u', '--user', action='store', type='string', dest='user',
                      default=getpass.getuser(),
                      help='username for SSH authentication (default: %s)' % getpass.getuser())
    parser.add_option('-K', '--key', action='store', type='string', dest='keyfile',
                      default=None,
                      help='private key file to use for SSH authentication')
    parser.add_option('', '--no-key', action='store_false', dest='look_for_keys', default=True,
                      help='don\'t look for or use a private key file')
    parser.add_option('-P', '--password', action='store_true', dest='readpass', default=False,
                      help='read password (for key or password auth) from stdin')
    parser.add_option('-r', '--remote', action='store', type='string', dest='remote', default=None, metavar='host:port',
                      help='remote host and port to forward to')
    options, args = parser.parse_args()

    if len(args) != 1:
        parser.error('Incorrect number of arguments.')
    if options.remote is None:
        parser.error('Remote address required (-r).')
    
    g_verbose = options.verbose
    server_host, server_port = get_host_port(args[0], SSH_PORT)
    remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
    return options, (server_host, server_port), (remote_host, remote_port)


def main():
    options, server, remote = parse_options()
    
    password = None
    if options.readpass:
        password = getpass.getpass('Enter SSH password: ')
    
    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())

    verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1]))
    try:
        client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile,
                       look_for_keys=options.look_for_keys, password=password)
    except Exception as e:
        print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
        sys.exit(1)

    verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1]))

    try:
        reverse_forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
    except KeyboardInterrupt:
        print('C-c: Port forwarding stopped.')
        sys.exit(0)


if __name__ == '__main__':
    main()

Topics: ssh Web Server socket Python