diff options
author | Olle Lundberg <geek@nerd.sh> | 2014-08-15 12:23:02 +0200 |
---|---|---|
committer | Olle Lundberg <geek@nerd.sh> | 2014-08-15 21:14:33 +0200 |
commit | 74bd98fcf5cdd1fa63e5caf085e5b8fdafb0cb4e (patch) | |
tree | 7ca447638095d661cacbd88580ea2861ea1f77c8 | |
parent | 6fe52ccac45f527ced2d4dc3f2d4911f9db7a396 (diff) |
Let packetizer handle 0-length sends from channel.
If the channel is closed the send method returs a response length
of 0. This is not handled correctly by the packetizer and puts it
in an infinite loop. (Fixes #156 for real :-)
We make sure we don't do more than 10 iteration on a 0 length respose,
but raise an EOFError.
-rw-r--r-- | paramiko/packet.py | 10 | ||||
-rw-r--r-- | tests/test_packetizer.py | 46 |
2 files changed, 56 insertions, 0 deletions
diff --git a/paramiko/packet.py b/paramiko/packet.py index e97d92f0..f516ff9b 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -231,6 +231,7 @@ class Packetizer (object): def write_all(self, out): self.__keepalive_last = time.time() + iteration_with_zero_as_return_value = 0 while len(out) > 0: retry_write = False try: @@ -254,6 +255,15 @@ class Packetizer (object): n = 0 if self.__closed: n = -1 + else: + if n == 0 and iteration_with_zero_as_return_value > 10: + # We shouldn't retry the write, but we didn't + # manage to send anything over the socket. This might be an + # indication that we have lost contact with the remote side, + # but are yet to receive an EOFError or other socket errors. + # Let's give it some iteration to try and catch up. + n = -1 + iteration_with_zero_as_return_value += 1 if n < 0: raise EOFError() if n == len(out): diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py index a8c0f973..8faec03c 100644 --- a/tests/test_packetizer.py +++ b/tests/test_packetizer.py @@ -74,3 +74,49 @@ class PacketizerTest (unittest.TestCase): self.assertEqual(100, m.get_int()) self.assertEqual(1, m.get_int()) self.assertEqual(900, m.get_int()) + + def test_3_closed(self): + rsock = LoopSocket() + wsock = LoopSocket() + rsock.link(wsock) + p = Packetizer(wsock) + p.set_log(util.get_logger('paramiko.transport')) + p.set_hexdump(True) + cipher = AES.new(zero_byte * 16, AES.MODE_CBC, x55 * 16) + p.set_outbound_cipher(cipher, 16, sha1, 12, x1f * 20) + + # message has to be at least 16 bytes long, so we'll have at least one + # block of data encrypted that contains zero random padding bytes + m = Message() + m.add_byte(byte_chr(100)) + m.add_int(100) + m.add_int(1) + m.add_int(900) + wsock.send = lambda x: 0 + from functools import wraps + import errno + import os + import signal + + class TimeoutError(Exception): + pass + + def timeout(seconds=1, error_message=os.strerror(errno.ETIME)): + def decorator(func): + def _handle_timeout(signum, frame): + raise TimeoutError(error_message) + + def wrapper(*args, **kwargs): + signal.signal(signal.SIGALRM, _handle_timeout) + signal.alarm(seconds) + try: + result = func(*args, **kwargs) + finally: + signal.alarm(0) + return result + + return wraps(func)(wrapper) + + return decorator + send = timeout()(p.send_message) + self.assertRaises(EOFError, send, m) |