Class: Socketry::SSL::Socket

Inherits:
TCP::Socket show all
Defined in:
lib/socketry/ssl/socket.rb

Overview

SSL Sockets

Constant Summary

Constants included from Timeout

Timeout::DEFAULT_TIMEOUTS, Timeout::DEFAULT_TIMER

Instance Attribute Summary

Attributes inherited from TCP::Socket

#local_addr, #local_port, #read_timeout, #remote_addr, #remote_port, #resolver, #socket_class, #write_timeout

Instance Method Summary collapse

Methods inherited from TCP::Socket

connect, #connected?, #nodelay, #nodelay=, #readpartial, #reconnect, #to_io, #writepartial

Methods included from Timeout

#clear_timeout, #lifetime, #set_timeout, #start_timer, #time_remaining

Constructor Details

#initialize(ssl_socket_class: OpenSSL::SSL::SSLSocket, ssl_params: nil, **args) ⇒ Socketry::SSL::Socket

Create an unconnected Socketry::SSL::Socket

Parameters:

  • read_timeout (Numeric)

    Seconds to wait before an uncompleted read errors

  • write_timeout (Numeric)

    Seconds to wait before an uncompleted write errors

  • timer (Object)

    A timekeeping object to use for measuring timeouts

  • resolver (Object)

    A resolver object to use for resolving DNS names

  • socket_class (Object)

    Underlying socket class which implements I/O ops

Raises:

  • (TypeError)


16
17
18
19
20
21
22
23
24
25
26
# File 'lib/socketry/ssl/socket.rb', line 16

def initialize(ssl_socket_class: OpenSSL::SSL::SSLSocket, ssl_params: nil, **args)
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)

  @ssl_socket_class = ssl_socket_class
  @ssl_context = OpenSSL::SSL::SSLContext.new
  @ssl_context.set_params(ssl_params) if ssl_params
  @ssl_context.freeze
  @ssl_socket = nil

  super(**args)
end

Instance Method Details

#closetrue, false

Close the socket

Returns:

  • (true, false)

    true if the socket was open, false if closed



139
140
141
142
# File 'lib/socketry/ssl/socket.rb', line 139

def close
  @ssl_socket.close
  super
end

#connect(remote_addr, remote_port, local_addr: nil, local_port: nil, timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect], verify_hostname: true) ⇒ self

Make an SSL connection to a remote host

Parameters:

  • remote_addr (String)

    DNS name or IP address of the host to connect to

  • remote_port (Fixnum)

    TCP port to connect to

  • local_addr (String) (defaults to: nil)

    DNS name or IP address to bind to locally

  • local_port (Fixnum) (defaults to: nil)

    Local TCP port to bind to

  • timeout (Numeric) (defaults to: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect])

    Number of seconds to wait before aborting connect

  • socket_class (Class)

    Custom low-level socket class

Returns:

  • (self)

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/socketry/ssl/socket.rb', line 40

def connect(
  remote_addr,
  remote_port,
  local_addr: nil,
  local_port: nil,
  timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect],
  verify_hostname: true
)
  super(remote_addr, remote_port, local_addr: local_addr, local_port: local_port, timeout: timeout)

  @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, @ssl_context)
  @ssl_socket.hostname = remote_addr

  begin
    @ssl_socket.connect_nonblock
  rescue IO::WaitReadable
    retry if @socket.wait_readable(timeout)
    raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
  rescue IO::WaitWritable
    retry if @socket.wait_writable(timeout)
    raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
  rescue OpenSSL::SSL::SSLError => ex
    raise Socketry::SSL::Error, ex.message, ex.backtrace
  end

  begin
    @ssl_socket.post_connection_check(remote_addr) if verify_hostname
  rescue OpenSSL::SSL::SSLError => ex
    raise Socketry::SSL::HostnameError, ex.message, ex.backtrace
  end

  self
rescue => ex
  @socket.close rescue nil
  @socket = nil
  @ssl_socket.close rescue nil
  @ssl_socket = nil
  raise ex
end

#from_socket(socket, ssl_socket) ⇒ self

Wrap a Ruby OpenSSL::SSL::SSLSocket (or other low-level SSL socket)

Parameters:

  • socket (::Socket)

    (or specified socket_class) low-level socket to wrap

  • ssl_socket (OpenSSL::SSL::SSLSocket)

    SSL socket class associated with this socket

Returns:

  • (self)

Raises:

  • (TypeError)


85
86
87
88
89
90
91
92
93
94
95
# File 'lib/socketry/ssl/socket.rb', line 85

def from_socket(socket, ssl_socket)
  raise TypeError, "expected #{@socket_class}, got #{socket.class}" unless socket.is_a?(@socket_class)
  raise TypeError, "expected #{@ssl_socket_class}, got #{ssl_socket.class}" unless ssl_socket.is_a?(@ssl_socket_class)
  raise StateError, "already connected" if @socket && @socket != socket

  @socket = socket
  @ssl_socket = ssl_socket
  @ssl_socket.sync_close = true

  self
end

#read_nonblock(size) ⇒ String, :wait_readable

Perform a non-blocking read operation

Parameters:

  • size (Fixnum)

    number of bytes to attempt to read

  • outbuf (String, NilClass)

    an optional buffer into which data should be read

Returns:

  • (String, :wait_readable)

    data read, or :wait_readable if operation would block

Raises:



103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/socketry/ssl/socket.rb', line 103

def read_nonblock(size)
  ensure_connected
  @ssl_socket.read_nonblock(size, exception: false)
# Some buggy Rubies continue to raise exceptions in these cases
rescue IO::WaitReadable
  :wait_readable
# Due to SSL, we may need to write to complete a read (e.g. renegotiation)
rescue IO::WaitWritable
  :wait_writable
rescue => ex
  # TODO: more specific exceptions
  raise Socketry::Error, ex.message, ex.backtrace
end

#write_nonblock(data) ⇒ Fixnum, :wait_writable

Perform a non-blocking write operation

Parameters:

  • data (String)

    number of bytes to attempt to read

Returns:

  • (Fixnum, :wait_writable)

    number of bytes written, or :wait_writable if op would block

Raises:



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/socketry/ssl/socket.rb', line 122

def write_nonblock(data)
  ensure_connected
  @ssl_socket.write_nonblock(data, exception: false)
# Some buggy Rubies continue to raise this exception
rescue IO::WaitWriteable
  :wait_writable
# Due to SSL, we may need to write to complete a read (e.g. renegotiation)
rescue IO::WaitReadable
  :wait_readable
rescue => ex
  # TODO: more specific exceptions
  raise Socketry::Error, ex.message, ex.backtrace
end