class Bunny::Transport
@private
Constants
- DEFAULT_CONNECTION_TIMEOUT
Default TCP connection timeout
- DEFAULT_READ_TIMEOUT
- DEFAULT_WRITE_TIMEOUT
Attributes
connect_timeout[R]
disconnect_timeout[R]
host[R]
port[R]
read_timeout[RW]
session[R]
socket[R]
tls_context[R]
write_timeout[R]
Public Class Methods
new(session, host, port, opts)
click to toggle source
# File lib/bunny/transport.rb, line 33 def initialize(session, host, port, opts) @session = session @session_thread = opts[:session_thread] @host = host @port = port @opts = opts @logger = session.logger @tls_enabled = tls_enabled?(opts) @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT @read_timeout = nil if @read_timeout == 0 @write_timeout = opts[:socket_timeout] # Backwards compatability @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT @write_timeout = nil if @write_timeout == 0 @connect_timeout = self.timeout_from(opts) @connect_timeout = nil if @connect_timeout == 0 @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout @writes_mutex = @session.mutex_impl.new prepare_tls_context(opts) if @tls_enabled end
ping!(host, port, timeout)
click to toggle source
# File lib/bunny/transport.rb, line 273 def self.ping!(host, port, timeout) raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout) end
reacheable?(host, port, timeout)
click to toggle source
# File lib/bunny/transport.rb, line 260 def self.reacheable?(host, port, timeout) begin s = Bunny::SocketImpl.open(host, port, :connect_timeout => timeout) true rescue SocketError, Timeout::Error => e false ensure s.close if s end end
Public Instance Methods
close(reason = nil)
click to toggle source
# File lib/bunny/transport.rb, line 196 def close(reason = nil) @socket.close if open? end
closed?()
click to toggle source
# File lib/bunny/transport.rb, line 204 def closed? !open? end
configure_socket(&block)
click to toggle source
# File lib/bunny/transport.rb, line 98 def configure_socket(&block) block.call(@socket) if @socket end
configure_tls_context(&block)
click to toggle source
# File lib/bunny/transport.rb, line 102 def configure_tls_context(&block) block.call(@tls_context) if @tls_context end
connect()
click to toggle source
# File lib/bunny/transport.rb, line 79 def connect if uses_ssl? @socket.connect if uses_tls? && @verify_peer @socket.post_connection_check(host) end @status = :connected @socket else # no-op end end
connected?()
click to toggle source
# File lib/bunny/transport.rb, line 94 def connected? :not_connected == @status && open? end
flush()
click to toggle source
# File lib/bunny/transport.rb, line 208 def flush @socket.flush if @socket end
hostname()
click to toggle source
# File lib/bunny/transport.rb, line 60 def hostname @host end
initialize_socket()
click to toggle source
# File lib/bunny/transport.rb, line 277 def initialize_socket begin @socket = Bunny::SocketImpl.open(@host, @port, :keepalive => @opts[:keepalive], :connect_timeout => @connect_timeout) rescue StandardError, ClientTimeout => e @status = :not_connected raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port) end @socket end
local_address()
click to toggle source
# File lib/bunny/transport.rb, line 64 def local_address @socket.local_address end
maybe_initialize_socket()
click to toggle source
# File lib/bunny/transport.rb, line 290 def maybe_initialize_socket initialize_socket if !@socket || closed? end
open?()
click to toggle source
# File lib/bunny/transport.rb, line 200 def open? @socket && !@socket.closed? end
post_initialize_socket()
click to toggle source
# File lib/bunny/transport.rb, line 294 def post_initialize_socket @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl) wrap_in_tls_socket(@socket) else @socket end end
read_fully(count)
click to toggle source
# File lib/bunny/transport.rb, line 212 def read_fully(count) begin @socket.read_fully(count, @read_timeout) rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})" close @status = :not_connected if @session.automatically_recover? @session.handle_network_failure(e) else @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end
read_next_frame(opts = {})
click to toggle source
Exposed primarily for Bunny::Channel @private
# File lib/bunny/transport.rb, line 235 def read_next_frame(opts = {}) header = read_fully(7) # TODO: network issues here will sometimes cause # the socket method return an empty string. We need to log # and handle this better. # type, channel, size = begin # AMQ::Protocol::Frame.decode_header(header) # rescue AMQ::Protocol::EmptyResponseError => e # puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}" # end type, channel, size = AMQ::Protocol::Frame.decode_header(header) payload = read_fully(size) frame_end = read_fully(1) # 1) the size is miscalculated if payload.bytesize != size raise BadLengthError.new(size, payload.bytesize) end # 2) the size is OK, but the string doesn't end with FINAL_OCTET raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET AMQ::Protocol::Frame.new(type, payload, channel) end
read_ready?(timeout = nil)
click to toggle source
# File lib/bunny/transport.rb, line 228 def read_ready?(timeout = nil) io = IO.select([@socket].compact, nil, nil, timeout) io && io[0].include?(@socket) end
send_frame(frame)
click to toggle source
Sends frame to the peer.
@raise [ConnectionClosedError] @private
# File lib/bunny/transport.rb, line 175 def send_frame(frame) if closed? @session.handle_network_failure(ConnectionClosedError.new(frame)) else write(frame.encode) end end
send_frame_without_timeout(frame)
click to toggle source
Sends frame to the peer without timeout control.
@raise [ConnectionClosedError] @private
# File lib/bunny/transport.rb, line 187 def send_frame_without_timeout(frame) if closed? @session.handle_network_failure(ConnectionClosedError.new(frame)) else write_without_timeout(frame.encode) end end
uses_ssl?()
click to toggle source
# File lib/bunny/transport.rb, line 73 def uses_ssl? @tls_enabled end
Also aliased as: ssl?
uses_tls?()
click to toggle source
# File lib/bunny/transport.rb, line 68 def uses_tls? @tls_enabled end
Also aliased as: tls?
write(data)
click to toggle source
Writes data to the socket.
# File lib/bunny/transport.rb, line 108 def write(data) return write_without_timeout(data) unless @write_timeout begin if open? @writes_mutex.synchronize do @socket.write(data) end end rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})" close @status = :not_connected if @session.automatically_recover? @session.handle_network_failure(e) else @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end
write_without_timeout(data)
click to toggle source
Writes data to the socket without timeout checks
# File lib/bunny/transport.rb, line 156 def write_without_timeout(data) begin @writes_mutex.synchronize { @socket.write(data) } @socket.flush rescue SystemCallError, Bunny::ConnectionError, IOError => e close if @session.automatically_recover? @session.handle_network_failure(e) else @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end
Protected Instance Methods
check_local_certificate_path!(s)
click to toggle source
# File lib/bunny/transport.rb, line 371 def check_local_certificate_path!(s) raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s) end
check_local_key_path!(s)
click to toggle source
# File lib/bunny/transport.rb, line 375 def check_local_key_path!(s) raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s) end
default_tls_certificates()
click to toggle source
# File lib/bunny/transport.rb, line 432 def default_tls_certificates if defined?(JRUBY_VERSION) # see https://github.com/jruby/jruby/issues/1055. MK. [] else default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR [ default_ca_file, File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE ].uniq end end
initialize_tls_certificate_store(certs)
click to toggle source
# File lib/bunny/transport.rb, line 449 def initialize_tls_certificate_store(certs) cert_files = [] cert_inlines = [] certs.each do |cert| # if it starts with / then it's a file path that may or may not # exists (e.g. a default OpenSSL path). MK. if File.readable?(cert) || cert =~ /^\// cert_files.push(cert) else cert_inlines.push(cert) end end @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" } @logger.debug { "Using #{cert_inlines.count} inline CA certificates" } if certs.empty? @logger.error "No CA certificates found, add one with :tls_ca_certificates" end OpenSSL::X509::Store.new.tap do |store| cert_files.select { |path| File.readable?(path) }. each { |path| store.add_file(path) } cert_inlines. each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) } end end
initialize_tls_context(ctx, opts={})
click to toggle source
# File lib/bunny/transport.rb, line 393 def initialize_tls_context(ctx, opts={}) ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate ctx.key = OpenSSL::PKey::RSA.new(@tls_key) if @tls_key ctx.cert_store = if @tls_certificate_store @tls_certificate_store else initialize_tls_certificate_store(@tls_ca_certificates) end if !@tls_certificate @logger.warn <<-MSG Using TLS but no client certificate is provided! If RabbitMQ is configured to verify peer certificate, connection upgrade will fail! MSG end if @tls_certificate && !@tls_key @logger.warn "Using TLS but no client private key is provided!" end verify_mode = if @verify_peer OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT else OpenSSL::SSL::VERIFY_NONE end ctx.verify_mode = verify_mode if !@verify_peer @logger.warn <<-MSG Using TLS but peer hostname verification is disabled. This is convenient for local development but prone man-in-the-middle attacks. Please set :verify_peer => true in production! MSG end ssl_version = opts[:tls_protocol] || opts[:ssl_version] ctx.ssl_version = ssl_version if ssl_version ctx end
inline_client_certificate_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 335 def inline_client_certificate_from(opts) opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert] end
inline_client_key_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 339 def inline_client_key_from(opts) opts[:tls_key] || opts[:ssl_key_string] end
prepare_tls_context(opts)
click to toggle source
# File lib/bunny/transport.rb, line 343 def prepare_tls_context(opts) if (opts[:verify_ssl] || opts[:verify_peer]).nil? opts[:verify_peer] = true end # client cert/key paths @tls_certificate_path = tls_certificate_path_from(opts) @tls_key_path = tls_key_path_from(opts) # client cert/key @tls_certificate = tls_certificate_from(opts) @tls_key = tls_key_from(opts) @tls_certificate_store = opts[:tls_certificate_store] @tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates) @verify_peer = (opts[:verify_ssl] || opts[:verify_peer]) @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts) end
read_client_certificate!()
click to toggle source
# File lib/bunny/transport.rb, line 379 def read_client_certificate! if @tls_certificate_path check_local_certificate_path!(@tls_certificate_path) @tls_certificate = File.read(@tls_certificate_path) end end
read_client_key!()
click to toggle source
# File lib/bunny/transport.rb, line 386 def read_client_key! if @tls_key_path check_local_key_path!(@tls_key_path) @tls_key = File.read(@tls_key_path) end end
timeout_from(options)
click to toggle source
# File lib/bunny/transport.rb, line 474 def timeout_from(options) options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT end
tls_certificate_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 318 def tls_certificate_from(opts) begin read_client_certificate! rescue MissingTLSCertificateFile => e inline_client_certificate_from(opts) end end
tls_certificate_path_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 310 def tls_certificate_path_from(opts) opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path] end
tls_enabled?(opts)
click to toggle source
# File lib/bunny/transport.rb, line 304 def tls_enabled?(opts) return opts[:tls] unless opts[:tls].nil? return opts[:ssl] unless opts[:ssl].nil? (opts[:port] == AMQ::Protocol::TLS_PORT) || false end
tls_key_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 326 def tls_key_from(opts) begin read_client_key! rescue MissingTLSKeyFile => e inline_client_key_from(opts) end end
tls_key_path_from(opts)
click to toggle source
# File lib/bunny/transport.rb, line 314 def tls_key_path_from(opts) opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path] end
wrap_in_tls_socket(socket)
click to toggle source
# File lib/bunny/transport.rb, line 362 def wrap_in_tls_socket(socket) raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context s = Bunny::SSLSocketImpl.new(socket, @tls_context) s.sync_close = true s end