30
30
FILE_FLAGS = ssh2_sftp .LIBSSH2_FXF_CREAT | ssh2_sftp .LIBSSH2_FXF_WRITE
31
31
32
32
33
+ def _create_connect_socket (host , port , timeout , ipv6 = False , ipv4_fallback = True , sock = None ):
34
+ """Create a socket and establish a connection to the specified host and port.
35
+
36
+ Args:
37
+ host (str): The hostname or IP address of the remote server.
38
+ port (int): The port number to connect to.
39
+ timeout (float): The timeout value in seconds for the socket connection.
40
+ ipv6 (bool, optional): Whether to use IPv6. Defaults to False.
41
+ ipv4_fallback (bool, optional): Whether to fallback to IPv4 if IPv6 fails. Defaults to True.
42
+ sock (socket.socket, optional): An existing socket object to use. Defaults to None.
43
+
44
+ Returns:
45
+ socket.socket: The connected socket object.
46
+ bool: True if IPv6 was used, False otherwise.
47
+
48
+ Raises:
49
+ exceptions.ConnectionError: If unable to establish a connection to the host.
50
+ """
51
+ if ipv6 and not sock :
52
+ try :
53
+ sock = socket .socket (socket .AF_INET6 , socket .SOCK_STREAM )
54
+ except OSError as err :
55
+ if ipv4_fallback :
56
+ logger .warning (f"IPv6 failed with { err } . Falling back to IPv4." )
57
+ return _create_connect_socket (host , port , timeout , ipv6 = False )
58
+ else :
59
+ raise exceptions .ConnectionError (
60
+ f"Unable to establish IPv6 connection to { host } ."
61
+ ) from err
62
+ elif not sock :
63
+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
64
+ sock .settimeout (timeout )
65
+ if ipv6 :
66
+ try :
67
+ sock .connect ((host , port ))
68
+ except socket .gaierror as err :
69
+ if ipv4_fallback :
70
+ logger .warning (f"IPv6 connection failed to { host } . Falling back to IPv4." )
71
+ return _create_connect_socket (host , port , timeout , ipv6 = False , sock = sock )
72
+ else :
73
+ raise exceptions .ConnectionError (
74
+ f"Unable to establish IPv6 connection to { host } ."
75
+ ) from err
76
+ else :
77
+ sock .connect ((host , port ))
78
+ return sock , ipv6
79
+
80
+
33
81
class Session :
34
82
"""Wrapper around ssh2-python's auth/connection system."""
35
83
@@ -43,22 +91,30 @@ def __init__(self, **kwargs):
43
91
port (int): The port number to connect to. Defaults to 22.
44
92
key_filename (str): The path to the private key file to use for authentication.
45
93
password (str): The password to use for authentication.
94
+ ipv6 (bool): Whether or not to use IPv6. Defaults to False.
95
+ ipv4_fallback (bool): Whether or not to fallback to IPv4 if IPv6 fails. Defaults to True.
46
96
47
97
Raises:
48
98
AuthException: If no password or key file is provided.
99
+ ConnectionError: If the connection fails.
49
100
FileNotFoundError: If the key file is not found.
50
101
"""
51
102
host = kwargs .get ("hostname" , "localhost" )
52
103
user = kwargs .get ("username" , "root" )
53
- sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
54
- sock .settimeout (kwargs .get ("timeout" ))
55
104
port = kwargs .get ("port" , 22 )
56
105
key_filename = kwargs .get ("key_filename" )
57
106
password = kwargs .get ("password" )
58
107
timeout = kwargs .get ("timeout" , 60 )
59
- helpers .simple_retry (sock .connect , [(host , port )], max_timeout = timeout )
108
+ # create the socket
109
+ self .sock , self .is_ipv6 = _create_connect_socket (
110
+ host ,
111
+ port ,
112
+ timeout ,
113
+ ipv6 = kwargs .get ("ipv6" , False ),
114
+ ipv4_fallback = kwargs .get ("ipv4_fallback" , True ),
115
+ )
60
116
self .session = ssh2_Session ()
61
- self .session .handshake (sock )
117
+ self .session .handshake (self . sock )
62
118
try :
63
119
if key_filename :
64
120
auth_type = "Key"
0 commit comments