Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

select and ssl in python

Tags:

python

select

ssl

I've got a server application using select.select(), and now I'm trying to add SSL to it, however I get the following error when listening to the "raw" sockets:

ValueError: file descriptor cannot be a negative integer (-1)

so I figured I'd use the ssl streams returned by ssl.wrap_socket in select instead. Doing so, it doesn't return any errors but it doesn't work either - I'm not really sure what the problem is, I've done a lot of research and encountered posts with similar problems, but I've found no solution to this yet.

Really appreciate any help.

like image 915
Andreas Avatar asked Dec 16 '22 23:12

Andreas


1 Answers

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)
like image 51
Aleksi Torhamo Avatar answered Jan 03 '23 10:01

Aleksi Torhamo