Using SSL sockets with select()
isn't as straightforward as it may seem at first. While they work okay with it in the sense that it doesn't throw an error when you give it one, if you just use them like normal sockets, you're bound to bump into some weirdness sooner or later.
Since select()
needs a file descriptor, it's going to get the raw socket. But even if the raw socket becomes readable, that doesn't mean you will get data out of the SSL socket. You'll need to use non-blocking sockets (which is a good idea anyway when using select()
) and just ignore it if it throws SSL_ERROR_WANT_READ
(the SSL equivalent of EWOULDBLOCK
).
Another problem is, if you write 2048 bytes to the connection at the other end, the select()
on your end returns. But if you then only read 1024 bytes from the SSL socket, it is possible that the SSL socket internally reads more data, and the next select()
won't return even though there would be more data to read, possibly deadlocking the connection. This is because the raw socket, which is what select()
is using, doesn't have any data since it's already in the SSL socket's buffers.
The first solution that comes to mind would be to read more data until reading throws SSL_ERROR_WANT_READ
, thus emptying the buffer. However, if the other end generates data faster than you can process it, this would end up starving all your other connections until this one finishes generating data.
You can see how much buffered data the SSL socket is holding by calling sslsock.pending()
. A better approach, then, would be to first do one read for some amount of data, check the amount of pending data and issue a second read for exactly that amount of data, thus emptying the buffer without causing any more reads.
The man-page for SSL_pending()
(the C function behind the scenes) also says this:
SSL_pending() takes into account only bytes from the TLS/SSL record that is currently being processed (if any). If the SSL object's read_ahead flag is set, additional protocol bytes may have been read containing more TLS/SSL records; these are ignored by SSL_pending()
From what I understand, this means that if read_ahead
is set, you'd need to repeat the second step until SSL_pending()
returns 0. I'm pretty sure python doesn't set read_ahead
, but it's better be safe than sorry, so I've included the loop in the example code.
I'm not that familiar with this, but something like this should work:
# Put the SSL socket to non-blocking mode
sslsock.setblocking(0)
while True:
r, w, e = select.select([sslsock], [], [])
if sslsock in r:
try:
data = sslsock.recv(1024)
except ssl.SSLError as e:
# Ignore the SSL equivalent of EWOULDBLOCK, but re-raise other errors
if e.errno != ssl.SSL_ERROR_WANT_READ:
raise
continue
# No data means end of file
if not data:
break
# Drain the SSL socket's internal buffer.
# If you want to remove the loop, make sure you don't call recv()
# with a 0 length, since that could cause a read to the raw socket.
data_left = sslsock.pending()
while data_left:
data += sslsock.recv(data_left)
data_left = sslsock.pending()
# Process the data
process(data)