class SyslogTls::SSLTransport

Supports SSL connection to remote host

Constants

CONNECT_TIMEOUT
WRITE_TIMEOUT

READ_TIMEOUT = 5

Attributes

ca_cert[R]
client_cert[R]
client_key[R]
host[R]
idle_timeout[R]
port[R]
retries[W]
socket[RW]
ssl_version[R]
verify_cert_name[R]

Public Class Methods

new(host, port, idle_timeout: nil, ca_cert: 'system', client_cert: nil, client_key: nil, verify_cert_name: true, ssl_version: :TLSv1_2, max_retries: 1) click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 32
def initialize(host, port, idle_timeout: nil, ca_cert: 'system', client_cert: nil, client_key: nil, verify_cert_name: true, ssl_version: :TLSv1_2, max_retries: 1)
  @host = host
  @port = port
  @idle_timeout = idle_timeout
  @ca_cert = ca_cert
  @client_cert = client_cert
  @client_key = client_key
  @verify_cert_name = verify_cert_name
  @ssl_version = ssl_version
  @retries = max_retries
  connect
end

Public Instance Methods

connect() click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 45
def connect
  @socket = get_ssl_connection
  begin
    begin
      @socket.connect_nonblock
    rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
      select_with_timeout(@socket, :connect_read) && retry
    rescue IO::WaitWritable
      select_with_timeout(@socket, :connect_write) && retry
    end
  rescue Errno::ETIMEDOUT
    raise 'Socket timeout during connect'
  end
  @last_write = Time.now if idle_timeout
end
do_write(data) click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 149
def do_write(data)
  data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
  loop do
    sent = 0
    begin
      sent = @socket.write_nonblock(data)
    rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
      if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
        raise e
      else
        select_with_timeout(@socket, :write) && retry
      end
    end

    break if sent >= data.size
    data = data[sent, data.size]
  end
end
get_ssl_connection() click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 94
def get_ssl_connection
  tcp = get_tcp_connection

  ctx = OpenSSL::SSL::SSLContext.new
  ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
  ctx.ssl_version = ssl_version

  ctx.verify_hostname = verify_cert_name != false

  case ca_cert
  when true, 'true', 'system'
    # use system certs, same as openssl cli
    ctx.cert_store = OpenSSL::X509::Store.new
    ctx.cert_store.set_default_paths
  when false, 'false'
    ctx.verify_hostname = false
    ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
  when %r{/$} # ends in /
    ctx.ca_path = ca_cert
  when String
    ctx.ca_file = ca_cert
  end

  ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
  ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
  socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
  socket.hostname = host
  socket.sync_close = true
  socket
end
get_tcp_connection() click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 61
def get_tcp_connection
  tcp = nil

  family = Socket::Constants::AF_UNSPEC
  sock_type = Socket::Constants::SOCK_STREAM
  addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
  _, port, _, address, family, sock_type = addr_info

  begin
    sock_addr = Socket.sockaddr_in(port, address)
    tcp = Socket.new(family, sock_type, 0)
    tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
    tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
    tcp.connect_nonblock(sock_addr)
  rescue Errno::EINPROGRESS
    select_with_timeout(tcp, :connect_write)
    begin
      tcp.connect_nonblock(sock_addr)
    rescue Errno::EISCONN
      # all good
    rescue SystemCallError
      tcp.close rescue nil
      raise
    end
  rescue SystemCallError
    tcp.close rescue nil
    raise
  end

  tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
  tcp
end
method_missing(method_sym, *arguments, &block) click to toggle source

Forward any methods directly to SSLSocket

# File lib/syslog_tls/ssl_transport.rb, line 185
def method_missing(method_sym, *arguments, &block)
  @socket.send(method_sym, *arguments, &block)
end
select_with_timeout(tcp, type) click to toggle source
# File lib/syslog_tls/ssl_transport.rb, line 168
def select_with_timeout(tcp, type)
  case type
  when :connect_read
    args = [[tcp], nil, nil, CONNECT_TIMEOUT]
  when :connect_write
    args = [nil, [tcp], nil, CONNECT_TIMEOUT]
  # when :read
  #   args = [[tcp], nil, nil, READ_TIMEOUT]
  when :write
    args = [nil, [tcp], nil, WRITE_TIMEOUT]
  else
    raise "Unknown select type #{type}"
  end
  IO.select(*args) || raise("Socket timeout during #{type}")
end
write(s) click to toggle source

Allow to retry on failed writes

# File lib/syslog_tls/ssl_transport.rb, line 126
def write(s)
  if idle_timeout
    if (t=Time.now) > @last_write + idle_timeout
      @socket.close rescue nil
      connect
    else
      @last_write = t
    end
  end
  begin
    retry_id ||= 0
    do_write(s)
  rescue => e
    if (retry_id += 1) < @retries
      @socket.close rescue nil
      connect
      retry
    else
      raise e
    end
  end
end