aboutsummaryrefslogtreecommitdiff
path: root/paramiko/transport.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/transport.py')
-rw-r--r--paramiko/transport.py27
1 files changed, 22 insertions, 5 deletions
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 36da304..31c27a2 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -295,6 +295,8 @@ class Transport (threading.Thread, ClosingContextManager):
self.global_response = None # response Message from an arbitrary global request
self.completion_event = None # user-defined event callbacks
self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner
+ self.handshake_timeout = 15 # how long (seconds) to wait for the handshake to finish after SSH banner sent.
+
# server mode:
self.server_mode = False
@@ -587,7 +589,7 @@ class Transport (threading.Thread, ClosingContextManager):
"""
return self.active
- def open_session(self, window_size=None, max_packet_size=None):
+ def open_session(self, window_size=None, max_packet_size=None, timeout=None):
"""
Request a new channel to the server, of type ``"session"``. This is
just an alias for calling `open_channel` with an argument of
@@ -612,7 +614,8 @@ class Transport (threading.Thread, ClosingContextManager):
"""
return self.open_channel('session',
window_size=window_size,
- max_packet_size=max_packet_size)
+ max_packet_size=max_packet_size,
+ timeout=timeout)
def open_x11_channel(self, src_addr=None):
"""
@@ -659,7 +662,8 @@ class Transport (threading.Thread, ClosingContextManager):
dest_addr=None,
src_addr=None,
window_size=None,
- max_packet_size=None):
+ max_packet_size=None,
+ timeout=None):
"""
Request a new channel to the server. `Channels <.Channel>` are
socket-like objects used for the actual transfer of data across the
@@ -683,17 +687,20 @@ class Transport (threading.Thread, ClosingContextManager):
optional window size for this session.
:param int max_packet_size:
optional max packet size for this session.
+ :param float timeout:
+ optional timeout opening a channel, default 3600s (1h)
:return: a new `.Channel` on success
- :raises SSHException: if the request is rejected or the session ends
- prematurely
+ :raises SSHException: if the request is rejected, the session ends
+ prematurely or there is a timeout openning a channel
.. versionchanged:: 1.15
Added the ``window_size`` and ``max_packet_size`` arguments.
"""
if not self.active:
raise SSHException('SSH session not active')
+ timeout = 3600 if timeout is None else timeout
self.lock.acquire()
try:
window_size = self._sanitize_window_size(window_size)
@@ -722,6 +729,7 @@ class Transport (threading.Thread, ClosingContextManager):
finally:
self.lock.release()
self._send_user_message(m)
+ start_ts = time.time()
while True:
event.wait(0.1)
if not self.active:
@@ -731,6 +739,8 @@ class Transport (threading.Thread, ClosingContextManager):
raise e
if event.is_set():
break
+ elif start_ts + timeout < time.time():
+ raise SSHException('Timeout openning channel.')
chan = self._channels.get(chanid)
if chan is not None:
return chan
@@ -1582,6 +1592,12 @@ class Transport (threading.Thread, ClosingContextManager):
try:
self.packetizer.write_all(b(self.local_version + '\r\n'))
self._check_banner()
+ # The above is actually very much part of the handshake, but sometimes the banner can be read
+ # but the machine is not responding, for example when the remote ssh daemon is loaded in to memory
+ # but we can not read from the disk/spawn a new shell.
+ # Make sure we can specify a timeout for the initial handshake.
+ # Re-use the banner timeout for now.
+ self.packetizer.start_handshake(self.handshake_timeout)
self._send_kex_init()
self._expect_packet(MSG_KEXINIT)
@@ -1631,6 +1647,7 @@ class Transport (threading.Thread, ClosingContextManager):
msg.add_byte(cMSG_UNIMPLEMENTED)
msg.add_int(m.seqno)
self._send_message(msg)
+ self.packetizer.complete_handshake()
except SSHException as e:
self._log(ERROR, 'Exception: ' + str(e))
self._log(ERROR, util.tb_strings())