diff options
author | Torsten Landschoff <torsten.landschoff@dynamore.de> | 2011-08-12 11:25:36 +0200 |
---|---|---|
committer | Torsten Landschoff <torsten@landschoff.net> | 2013-10-24 01:47:24 +0200 |
commit | 27b800bf3f1c0ee7063221538d2913cec7c048c1 (patch) | |
tree | 8ad47e324f30278394a412464f963200e77a881b | |
parent | a9a5f69c1affae60fd3c390927e0389325f719a1 (diff) |
Issue #22: Try IPv4 as well as IPv6 when port is not open on IPv6.
With this change, paramiko tries the next address family when the connection
gets refused or the target port is unreachable.
-rw-r--r-- | paramiko/client.py | 53 |
1 files changed, 39 insertions, 14 deletions
diff --git a/paramiko/client.py b/paramiko/client.py index c5a2d1ac..da08ad0a 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -25,6 +25,7 @@ import getpass import os import socket import warnings +from errno import ECONNREFUSED, EHOSTUNREACH from paramiko.agent import Agent from paramiko.common import * @@ -231,6 +232,29 @@ class SSHClient (object): """ self._policy = policy + def _families_and_addresses(self, hostname, port): + """ + Yield pairs of address families and addresses to try for connecting. + + @param hostname: the server to connect to + @type hostname: str + @param port: the server port to connect to + @type port: int + @rtype: generator + """ + guess = True + addrinfos = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) + for (family, socktype, proto, canonname, sockaddr) in addrinfos: + if socktype == socket.SOCK_STREAM: + yield family, sockaddr + guess = False + + # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( + # We only do this if we did not get a single result marked as socktype == SOCK_STREAM. + if guess: + for family, _, _, _, sockaddr in addrinfos: + yield family, sockaddr + 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, sock=None): @@ -288,21 +312,22 @@ class SSHClient (object): @raise socket.error: if a socket error occurred while connecting """ 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: + for af, addr in self._families_and_addresses(hostname, port): try: - sock.settimeout(timeout) - except: - pass - retry_on_signal(lambda: sock.connect(addr)) + 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)) + # Break out of the loop on success + break + except socket.error, e: + # If the port is not open on IPv6 for example, we may still try IPv4. + # Likewise if the host is not reachable using that address family. + if e.errno not in (ECONNREFUSED, EHOSTUNREACH): + raise t = self._transport = Transport(sock) t.use_compression(compress=compress) |