From 31ea4f0734a086f2345aaea57fd6fc1c3ea4a87e Mon Sep 17 00:00:00 2001 From: Steven Noonan Date: Mon, 15 Oct 2012 04:09:33 -0700 Subject: [PATCH] SSHClient: add 'sock' parameter to connect() for tunneling Re #77 This parameter, if set, can be used to make Paramiko wrap an existing socket connected to a remote SSH server. For instance, you could set up another SSHClient directly connected to a "gateway" host, and then create a direct-tcpip tunnel to a "target" host directly accessible from the gateway's perspective (e.g. think of trying to establish an SSH connection to hosts behind a NAT). The gateway host would then establish a TCP connection to the target host directly, and a channel is exposed on the client side. This channel could be wrapped by an SSHClient class using the connect() function, avoiding the need to establish a new TCP connnection. This effectively allows you to create tunneled SSH connections. Based on work by Oskari Saarenmaa , in Paramiko pull request #39. Signed-off-by: Steven Noonan --- paramiko/client.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 0f40897..0a9ced7 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -229,7 +229,7 @@ class SSHClient (object): def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, - compress=False): + compress=False, sock=None): """ Connect to an SSH server and authenticate to it. The server's host key is checked against the system host keys (see L{load_system_host_keys}) @@ -272,6 +272,9 @@ class SSHClient (object): @type look_for_keys: bool @param compress: set to True to turn on compression @type compress: bool + @param sock: an open socket or direct-tcpip channel from another + SSHClient class to use for communication with the target host. + @type channel: socket @raise BadHostKeyException: if the server's host key could not be verified @@ -280,21 +283,23 @@ class SSHClient (object): establishing an SSH session @raise socket.error: if a socket error occurred while connecting """ - for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): - if socktype == socket.SOCK_STREAM: - af = family - addr = sockaddr - break - else: - # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( - af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) - sock = socket.socket(af, socket.SOCK_STREAM) - if timeout is not None: - try: - sock.settimeout(timeout) - except: - pass - retry_on_signal(lambda: sock.connect(addr)) + if not sock: + for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): + if socktype == socket.SOCK_STREAM: + af = family + addr = sockaddr + break + else: + # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( + af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) + sock = socket.socket(af, socket.SOCK_STREAM) + if timeout is not None: + try: + sock.settimeout(timeout) + except: + pass + retry_on_signal(lambda: sock.connect(addr)) + t = self._transport = Transport(sock) t.use_compression(compress=compress) if self._log_channel is not None: