Skip to content

Commit

Permalink
Let packetizer handle 0-length sends from channel.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lndbrg committed Aug 15, 2014
1 parent 6fe52cc commit 74bd98f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 0 deletions.
10 changes: 10 additions & 0 deletions paramiko/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ def read_all(self, n, check_rekey=False):

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:
Expand All @@ -254,6 +255,15 @@ def write_all(self, out):
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):
Expand Down
46 changes: 46 additions & 0 deletions tests/test_packetizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,49 @@ def test_2_read(self):
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)

0 comments on commit 74bd98f

Please sign in to comment.