Non-blocking SSL socket negotiation in Ruby. Possible?

943 views Asked by At

Intro

I have a client that makes numerous SSL connections to a 3rd party service. In certain cases, the 3rd party stops responding during the socket and ssl negotiation process. When this occurs, my current implementation "sits" for hours on end before timing out.

To combat this, I'm trying to implement the following process:

require 'socket'
require 'openssl'

# variables

host = '....'
port = ...
p12  = #OpenSSL::PKCS12 object 

# set up socket

addr = Socket.getaddrinfo(host, nil)
sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])

socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

begin
  socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
  if IO.select(nil, [socket], nil, timeout)
    begin
      socket.connect_nonblock(sockaddr)
    rescue Errno::EISCONN
      puts "socket connected"
    rescue
      puts "socket error"
      socket.close
      raise
    end
  else
    socket.close
    raise "Connection timeout"
  end
end

# negotiate ssl

context      = OpenSSL::SSL::SSLContext.new
context.cert = p12.certificate
context.key  = p12.key

ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
ssl_socket.sync_close = true

puts "ssl connecting"
ssl_socket.connect_nonblock
puts "ssl connected"

# cleanup

ssl_socket.close
puts "socket closed"

ssl_socket.connect_nonblock will eventually be wrapped in a similar structure as socket.connect_nonblock is.

The Problem

The issue I'm running into is that ssl_socket.connect_nonblock raises the following when run:

`connect_nonblock': read would block (OpenSSL::SSL::SSLError)

Instead, I'd expect it to raise an IO::WaitWritable as socket.connect_nonblock does.

I've scoured the internet for information on this particular error but can't find anything of particular use. From what I gather, others have had success using this method, so I'm not sure what I'm missing. For the sake of completeness, I've found the same results with both ruby 2.2.0 and 1.9.3.

Any suggestions are greatly appreciated!

1

There are 1 answers

0
aligo On

Have same problem, I tried below, it seems works right for my situation.

ssl_socket = OpenSSL::SSL::SSLSocket.new socket, context
ssl_socket.sync = true

begin
  ssl_socket.connect_nonblock
rescue IO::WaitReadable
  if IO.select([ssl_socket], nil, nil, timeout)
    retry
  else
    # timeout
  end
rescue IO::WaitWritable
  if IO.select(nil, [ssl_socket], nil, timeout)
    retry
  else
    # timeout
  end
end