Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Cloud:OpenStack:Newton
python-eventlet
0001-Fix-SSL-hanging-recv_into.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0001-Fix-SSL-hanging-recv_into.patch of Package python-eventlet
diff --git a/eventlet/green/ssl.py b/eventlet/green/ssl.py index 828f9bf..f6da878 100644 --- a/eventlet/green/ssl.py +++ b/eventlet/green/ssl.py @@ -191,18 +191,48 @@ class GreenSSLSocket(_original_sslsocket): raise def recv(self, buflen=1024, flags=0): + return self._base_recv(buflen, flags, into=False) + + def recv_into(self, buffer, nbytes=None, flags=0): + # Copied verbatim from CPython + if buffer and nbytes is None: + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + # end of CPython code + + return self._base_recv(nbytes, flags, into=True, buffer_=buffer) + + def _base_recv(self, nbytes, flags, into, buffer_=None): + if into: + plain_socket_function = socket.recv_into + else: + plain_socket_function = socket.recv + # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is if self._sslobj: if flags != 0: raise ValueError( - "non-zero flags not allowed in calls to recv() on %s" % - self.__class__) - read = self.read(buflen) + "non-zero flags not allowed in calls to %s() on %s" % + plain_socket_function.__name__, self.__class__) + if sys.version_info < (2, 7) and into: + # Python 2.6 SSLSocket.read() doesn't support reading into + # a given buffer so we need to emulate + data = self.read(nbytes) + buffer_[:len(data)] = data + read = len(data) + elif into: + read = self.read(nbytes, buffer_) + else: + read = self.read(nbytes) return read else: while True: try: - return socket.recv(self, buflen, flags) + args = [self, nbytes, flags] + if into: + args.insert(1, buffer_) + return plain_socket_function(*args) except orig_socket.error as e: if self.act_non_blocking: raise @@ -218,12 +248,6 @@ class GreenSSLSocket(_original_sslsocket): return b'' raise - def recv_into(self, buffer, nbytes=None, flags=0): - if not self.act_non_blocking: - trampoline(self, read=True, timeout=self.gettimeout(), - timeout_exc=timeout_exc('timed out')) - return super(GreenSSLSocket, self).recv_into(buffer, nbytes, flags) - def recvfrom(self, addr, buflen=1024, flags=0): if not self.act_non_blocking: trampoline(self, read=True, timeout=self.gettimeout(), diff --git a/tests/ssl_test.py b/tests/ssl_test.py index 13e090c..901e758 100644 --- a/tests/ssl_test.py +++ b/tests/ssl_test.py @@ -1,12 +1,15 @@ +import contextlib import socket import warnings import eventlet from eventlet import greenio +from eventlet.green import socket try: from eventlet.green import ssl except ImportError: pass +from eventlet.support import six import tests @@ -213,3 +216,63 @@ class SSLTest(tests.LimitedTestCase): listener.close() loopt.wait() eventlet.sleep(0) + + def test_receiving_doesnt_block_if_there_is_already_decrypted_buffered_data(self): + # Here's what could (and would) happen before the relevant bug was fixed (assuming method + # M was trampolining unconditionally before actually reading): + # 1. One side sends n bytes, leaves connection open (important) + # 2. The other side uses method M to read m (where m < n) bytes, the underlying SSL + # implementation reads everything from the underlying socket, decrypts all n bytes, + # returns m of them and buffers n-m to be read later. + # 3. The other side tries to read the remainder of the data (n-m bytes), this blocks + # because M trampolines uncoditionally and trampoline will hang because reading from + # the underlying socket would block. It would block because there's no data to be read + # and the connection is still open; leaving the connection open /mentioned in 1./ is + # important because otherwise trampoline would return immediately and the test would pass + # even with the bug still present in the code). + # + # The solution is to first request data from the underlying SSL implementation and only + # trampoline if we actually need to read some data from the underlying socket. + # + # GreenSSLSocket.recv() wasn't broken but I've added code to test it as well for + # completeness. + content = b'xy' + + def recv(sock, expected): + assert sock.recv(len(expected)) == expected + + def recv_into(sock, expected): + buf = bytearray(len(expected)) + assert sock.recv_into(buf, len(expected)) == len(expected) + assert buf == expected + + for read_function in [recv, recv_into]: + print('Trying %s...' % (read_function,)) + listener = listen_ssl_socket() + + def accept(listener): + sock, addr = listener.accept() + sock.sendall(content) + return sock + + accepter = eventlet.spawn(accept, listener) + + client_to_server = None + try: + client_to_server = ssl.wrap_socket(eventlet.connect(listener.getsockname())) + for character in six.iterbytes(content): + character = six.int2byte(character) + print('We have %d already decrypted bytes pending, expecting: %s' % ( + client_to_server.pending(), character)) + read_function(client_to_server, character) + finally: + if client_to_server is not None: + client_to_server.close() + server_to_client = accepter.wait() + + # Very important: we only want to close the socket *after* the other side has + # read the data it wanted already, otherwise this would defeat the purpose of the + # test (see the comment at the top of this test). + server_to_client.close() + + listener.close()
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor