SSLSocket in non-blocking mode

Hi,
I've had a little trouble getting SSLSocket to behave as I want it to.
It's #pending method returns 0 until I call #sysread(1)... which throws
EOFError if there is nothing to read, or 1 byte after some bytes have
become available.

I suspect that the implementation does not fill its internal buffer with
raw bytes to decrypt to its output buffer until #sysread is called on an
empty output buffer. Given that the SSLSocket class almost certainly
would not be implementing its own thread to do this automatically, that
seems reasonable.

But my solution seems a bit "hackish", and I would appreciate if someone
knows a better way to do this?

(Maybe IO:select on the tcp socket to see if it has bytes ready, then
call #sysread)

Following is a simplified code snippet that shows essentially what I'm
doing:

class SslSocketServer
  def initialize(port, hostname, cert_fname, key_fname)
    @socket_server = Socket.new(AF_INET, SOCK_STREAM, 0)
    sockaddr = Socket.sockaddr_in(port, hostname)
    @socket_server.bind(sockaddr)
    @socket_server.listen(5)
    @ssl_context = OpenSSL::SSL::SSLContext.new
    cert_file = File.open(cert_fname)
    key_file = File.open(key_fname)
    @ssl_context.cert = OpenSSL::X509::Certificate.new(cert_file)
    @ssl_context.key = OpenSSL::PKey::RSA.new(key_file)
  end

  def accept
    ssl = nil
    begin
      sock, addr = @socket_server.accept
      begin
        sslsock = OpenSSL::SSL::SSLSocket.new(sock, @ssl_context)
        sslsock.sync_close = true
        sslsock.accept
        ssl = SslConnection.new(sock, sslsock, addr)
      rescue SSLError => ex
        sock.close
        raise ex
      end
    rescue IO::WaitReadable
    rescue Errno::EINTR => e
      STDERR.puts("#{e.class}: #{e.to_s}")
      STDERR.puts(e.backtrace.join("\n"))
    end
    ssl
  end
end

class SslConnection
  def initialize(sock, sslsock, addr)
    @sock = sock
    @sslsock = sslsock
    @addr = addr
  end

  def recvfrom_nonblock(size)
    available = @sslsock.pending
    if available == 0
      begin
        byte = @sslsock.sysread(1)
        [byte, @addr]
      rescue EOFError
        raise SslNotEnoughPending, "EOFError"
      end
    else
      size = available if available < size
      [@sslsock.sysread(size), @addr]
    end
  end

  def close
    @sslsock.close
    @sock.close
  end

  def write(bytes)
    @sslsock.syswrite(bytes)
  end
end

Then I read from the SslConnection as if it were a socket returned from
Socket#accept, using the rcvfrom_nonblock and write methods only.

···

--
Posted via http://www.ruby-forum.com/.

I've done some more work on this since posting, and tested out an idea I
hinted at in my previous post:
I check the underlying tcpsocket (@sock in my code) using IO.select.
(Actually I call select passing it an array of the currently connected
tcp sockets, and if there are any bytes waiting, then if I call sysread
on the SSLSocket, it will fill its input buffer from the tcpsocket,
decrypt them, and return them.
So I guess I answered my own question.

Also, it seems logical to me now that #pending will actually only tell
me how many bytes are remaining in the output buffer of the SLSocket,
because the SSLSocket won't read from the tcpsocket unless its buffer is
-empty-. I.e. when #pending is returning zero :slight_smile:
So I have to be optimistic, read one byte (only when I know, from the
call to IO.select, that there are bytes ready for it to process)...
which will trigger it to read from the TCPSocket, and decrypt it, and
fill its output buffer, and then #pending will be non zero.

I'm making assumptions here, and would be grateful if someone can
provide an authoritative answer.

···

--
Posted via http://www.ruby-forum.com/.

Not according to this documentation:
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/SSL/SSLSocket.html

... but according to irb, yes! Thanks

irb(main):015:0> sslsock.methods
=> [:io, :context, :hostname, :hostname=, :sync_close, :sync_close=,
:to_io, :connect, :connect_nonblock, :accept, :accept_nonblock,
:sysread, :syswrite, :sysclose, :cert, :peer_cert, :peer_cert_chain,
:cipher, :state, :pending, :session_reused?, :session=, :verify_result,
:client_ca, :post_connection_check, :session, :addr, :peeraddr,
:setsockopt, :getsockopt, :fcntl, :closed?, :do_not_reverse_lookup=,
:sync, :sync=, :read, :readpartial, :read_nonblock, :gets, :each,
:each_line, :readlines, :readline, :getc, :each_byte, :readchar,
:ungetc, :eof?, :eof, :write, :write_nonblock, :<<, :puts, :print,
:printf, :flush, :close, :to_a, :entries, :sort, :sort_by, :grep,
:count, :find, :detect, :find_index, :find_all, :select, :reject,
:collect, :map, :flat_map, :collect_concat, :inject, :reduce,
:partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max,
:minmax, :min_by, :max_by, :minmax_by, :member?, :include?,
:each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons,
:each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle,
:chunk, :slice_before, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>,
:class, :singleton_class, :clone, :dup, :initialize_dup,
:initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?,
:trust, :freeze, :frozen?, :to_s, :inspect, :methods,
:singleton_methods, :protected_methods, :private_methods,
:public_methods, :instance_variables, :instance_variable_get,
:instance_variable_set, :instance_variable_defined?, :instance_of?,
:kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?,
:respond_to_missing?, :extend, :display, :method, :public_method,
:define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?,
:!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

···

--
Posted via http://www.ruby-forum.com/.

Are you not aware OpenSSL::SSL::SSLSocket has a #read_nonblock method?

···

On Wed, Jan 30, 2013 at 11:52 AM, Brian Modra <lists@ruby-forum.com> wrote:

I've done some more work on this since posting, and tested out an idea I
hinted at in my previous post:
I check the underlying tcpsocket (@sock in my code) using IO.select.
(Actually I call select passing it an array of the currently connected
tcp sockets, and if there are any bytes waiting, then if I call sysread
on the SSLSocket, it will fill its input buffer from the tcpsocket,
decrypt them, and return them.
So I guess I answered my own question.

Also, it seems logical to me now that #pending will actually only tell
me how many bytes are remaining in the output buffer of the SLSocket,
because the SSLSocket won't read from the tcpsocket unless its buffer is
-empty-. I.e. when #pending is returning zero :slight_smile:
So I have to be optimistic, read one byte (only when I know, from the
call to IO.select, that there are bytes ready for it to process)...
which will trigger it to read from the TCPSocket, and decrypt it, and
fill its output buffer, and then #pending will be non zero.

I'm making assumptions here, and would be grateful if someone can
provide an authoritative answer.

--
Posted via http://www.ruby-forum.com/\.

--
Tony Arcieri

Weird, they seem to be "documented" here instead. Seems bad:

http://www.ruby-doc.org/gems/docs/o/openssl-nonblock-0.2.0/OpenSSL/SSL/SSLSocket.html#method-i-read_nonblock

···

On Wed, Jan 30, 2013 at 9:05 PM, Brian Modra <lists@ruby-forum.com> wrote:

Not according to this documentation:

Class: OpenSSL::SSL::SSLSocket (Ruby 1.9.3)

... but according to irb, yes! Thanks

irb(main):015:0> sslsock.methods
=> [:io, :context, :hostname, :hostname=, :sync_close, :sync_close=,
:to_io, :connect, :connect_nonblock, :accept, :accept_nonblock,
:sysread, :syswrite, :sysclose, :cert, :peer_cert, :peer_cert_chain,
:cipher, :state, :pending, :session_reused?, :session=, :verify_result,
:client_ca, :post_connection_check, :session, :addr, :peeraddr,
:setsockopt, :getsockopt, :fcntl, :closed?, :do_not_reverse_lookup=,
:sync, :sync=, :read, :readpartial, :read_nonblock, :gets, :each,
:each_line, :readlines, :readline, :getc, :each_byte, :readchar,
:ungetc, :eof?, :eof, :write, :write_nonblock, :<<, :puts, :print,
:printf, :flush, :close, :to_a, :entries, :sort, :sort_by, :grep,
:count, :find, :detect, :find_index, :find_all, :select, :reject,
:collect, :map, :flat_map, :collect_concat, :inject, :reduce,
:partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max,
:minmax, :min_by, :max_by, :minmax_by, :member?, :include?,
:each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons,
:each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle,
:chunk, :slice_before, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>,
:class, :singleton_class, :clone, :dup, :initialize_dup,
:initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?,
:trust, :freeze, :frozen?, :to_s, :inspect, :methods,
:singleton_methods, :protected_methods, :private_methods,
:public_methods, :instance_variables, :instance_variable_get,
:instance_variable_set, :instance_variable_defined?, :instance_of?,
:kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?,
:respond_to_missing?, :extend, :display, :method, :public_method,
:define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?,
:!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

--
Posted via http://www.ruby-forum.com/\.

--
Tony Arcieri