Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a two-way SSL socket in Ruby

I am building a client Ruby library that connects to a server and waits for data, but also allows users to send data by calling a method.

The mechanism I use is to have a class that initializes a socket pair, like so:

def initialize
  @pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0)
end

The method that I allow developers to call to send data to the server looks like this:

def send(data)
  @pipe_w.write(data)
  @pipe_w.flush
end

Then I have a loop in a separate thread, where I select from a socket connected to the server and from the @pipe_r:

def socket_loop
  Thread.new do
    socket = TCPSocket.new(host, port)

    loop do
      ready = IO.select([socket, @pipe_r])

      if ready[0].include?(@pipe_r)
        data_to_send = @pipe_r.read_nonblock(1024)
        socket.write(data_to_send)
      end

      if ready[0].include?(socket)
        data_received = socket.read_nonblock(1024)
        h2 << data_received
        break if socket.nil? || socket.closed? || socket.eof?
      end
    end
  end
end

This works beautifully, but only with a normal TCPSocket as per the example. I need to use an OpenSSL::SSL::SSLSocket instead, however as per the IO.select docs:

The best way to use IO.select is invoking it after nonblocking methods such as read_nonblock, write_nonblock, etc.

[...]

Especially, the combination of nonblocking methods and IO.select is preferred for IO like objects such as OpenSSL::SSL::SSLSocket.

According to this, I need to call IO.select after nonblocking methods, while in my loop I use it before so I can select from 2 different IO objects.

The given example on how to use IO.select with an SSL socket is:

begin
  result = socket.read_nonblock(1024)
rescue IO::WaitReadable
  IO.select([socket])
  retry
rescue IO::WaitWritable
  IO.select(nil, [socket])
  retry
end

However this works only if IO.select is used with a single IO object.

My question is: how can I make my previous example work with an SSL socket, given that I need to select from both the @pipe_r and the socket objects?

EDIT: I've tried what @steffen-ullrich suggested, however to no avail. I was able to make my tests pass using the following:

loop do

  begin
    data_to_send = @pipe_r.read_nonblock(1024)
    socket.write(data_to_send)
  rescue IO::WaitReadable, IO::WaitWritable
  end

  begin
    data_received = socket.read_nonblock(1024)
    h2 << data_received
    break if socket.nil? || socket.closed? || socket.eof?

  rescue IO::WaitReadable
    IO.select([socket, @pipe_r])

  rescue IO::WaitWritable
    IO.select([@pipe_r], [socket])

  end
end

This doesn't look so bad, but any input is welcome.

like image 491
ostinelli Avatar asked Apr 22 '16 20:04

ostinelli


1 Answers

I'm not familiar with ruby itself, but with the problems of using select with SSL based sockets. SSL sockets behave differently to TCP sockets since SSL data are transferred in frames and not as a data stream, but nevertheless stream semantics are applied to the socket interface.

Let's explain this with an example, first using a simple TCP connection:

  • The server sends 1000 bytes in a single write.
  • The data will be delivered to the client and put into the in-kernel socket buffer. Thus select will return that data are available.
  • The client application reads only 100 bytes first.
  • The other 900 bytes will then be kept in the in-kernel socket buffer. The next call of select will again return that data are available, since select looks at the in-kernel socket buffer.

With SSL this is different:

  • The server sends 1000 bytes in a single write. The SSL stack will then encrypt these 1000 bytes into a single SSL frame and send this frame to the client.
  • This SSL frame will be delivered to the client and put into the in-kernel socket buffer. Thus select will return that data are available.
  • Now the client application reads only 100 bytes. Since the SSL frame contains 1000 bytes and since it needs the full frame to decrypt the data, the SSL stack will read the full frame from the socket, leaving nothing in the in-kernel socket buffer. It will then decrypt the frame and return the requested 100 bytes to the application. The rest of decrypted 900 bytes will be kept in the SSL stack in user space.
  • Since the in-kernel socket buffer is empty the next call of select will not return that data are available. Thus if the application only deals with select it will not now that there are still unread data, i.e. the 900 bytes kept in the SSL stack.

How to deal with this difference:

  • One way is to always try to read at least 32768 data, because this is the maximum size of an SSL frame. This way one can be sure that no data are still kept in the SSL stack (SSL read will not read over SSL frame boundaries).
  • Another way is to check the SSL stack for already decrypted data before calling select. Only if no data are kept in the SSL stack select should be called. To check for such "pending data" use the pending method.
  • Try to read more data from the non-blocking socket until no more data are available. This way you can be sure that no data are still pending in the SSL stack. But note that this will also do reads on the underlying TCP socket and thus might prefer data on the SSL socket compared to data on other sockets (which are only read after successful select). This is the recommendation you've cited, but I would prefer the others.
like image 155
Steffen Ullrich Avatar answered Sep 19 '22 03:09

Steffen Ullrich