bunny 1.0.7 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +92 -87
- data/lib/amq/protocol/extensions.rb +2 -0
- data/lib/bunny/authentication/credentials_encoder.rb +2 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
- data/lib/bunny/channel.rb +485 -186
- data/lib/bunny/channel_id_allocator.rb +8 -4
- data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
- data/lib/bunny/concurrent/condition.rb +2 -0
- data/lib/bunny/concurrent/continuation_queue.rb +37 -13
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
- data/lib/bunny/consumer.rb +20 -13
- data/lib/bunny/consumer_tag_generator.rb +6 -2
- data/lib/bunny/consumer_work_pool.rb +37 -7
- data/lib/bunny/cruby/socket.rb +51 -22
- data/lib/bunny/cruby/ssl_socket.rb +68 -5
- data/lib/bunny/delivery_info.rb +3 -1
- data/lib/bunny/exceptions.rb +27 -4
- data/lib/bunny/exchange.rb +35 -29
- data/lib/bunny/framing.rb +2 -0
- data/lib/bunny/get_response.rb +85 -0
- data/lib/bunny/heartbeat_sender.rb +9 -6
- data/lib/bunny/message_properties.rb +2 -0
- data/lib/bunny/queue.rb +89 -41
- data/lib/bunny/reader_loop.rb +72 -28
- data/lib/bunny/return_info.rb +2 -0
- data/lib/bunny/session.rb +621 -225
- data/lib/bunny/socket.rb +7 -12
- data/lib/bunny/ssl_socket.rb +7 -12
- data/lib/bunny/test_kit.rb +15 -0
- data/lib/bunny/timeout.rb +3 -12
- data/lib/bunny/timestamp.rb +24 -0
- data/lib/bunny/transport.rb +223 -98
- data/lib/bunny/version.rb +2 -1
- data/lib/bunny/versioned_delivery_tag.rb +2 -0
- data/lib/bunny.rb +54 -8
- metadata +38 -224
- data/.gitignore +0 -22
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -23
- data/.yardopts +0 -8
- data/ChangeLog.md +0 -1092
- data/Gemfile +0 -54
- data/LICENSE +0 -21
- data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
- data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
- data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
- data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
- data/benchmarks/channel_open.rb +0 -28
- data/benchmarks/mutex_and_monitor.rb +0 -42
- data/benchmarks/queue_declare.rb +0 -29
- data/benchmarks/queue_declare_and_bind.rb +0 -29
- data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
- data/benchmarks/synchronized_sorted_set.rb +0 -53
- data/benchmarks/write_vs_write_nonblock.rb +0 -49
- data/bin/ci/before_build.sh +0 -31
- data/bunny.gemspec +0 -40
- data/examples/connection/authentication_failure.rb +0 -16
- data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
- data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
- data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
- data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
- data/examples/connection/channel_level_exception.rb +0 -35
- data/examples/connection/disabled_automatic_recovery.rb +0 -34
- data/examples/connection/heartbeat.rb +0 -17
- data/examples/connection/manually_reconnecting_consumer.rb +0 -23
- data/examples/connection/manually_reconnecting_publisher.rb +0 -28
- data/examples/connection/unknown_host.rb +0 -16
- data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
- data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
- data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
- data/examples/guides/exchanges/mandatory_messages.rb +0 -30
- data/examples/guides/extensions/alternate_exchange.rb +0 -28
- data/examples/guides/extensions/basic_nack.rb +0 -33
- data/examples/guides/extensions/connection_blocked.rb +0 -35
- data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
- data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
- data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
- data/examples/guides/extensions/per_message_ttl.rb +0 -36
- data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
- data/examples/guides/extensions/publisher_confirms.rb +0 -28
- data/examples/guides/extensions/queue_lease.rb +0 -26
- data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
- data/examples/guides/getting_started/blabbr.rb +0 -27
- data/examples/guides/getting_started/hello_world.rb +0 -20
- data/examples/guides/getting_started/weathr.rb +0 -47
- data/examples/guides/queues/one_off_consumer.rb +0 -23
- data/examples/guides/queues/redeliveries.rb +0 -79
- data/lib/bunny/compatibility.rb +0 -24
- data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
- data/lib/bunny/jruby/socket.rb +0 -40
- data/lib/bunny/jruby/ssl_socket.rb +0 -53
- data/lib/bunny/system_timer.rb +0 -20
- data/profiling/basic_publish/with_4K_messages.rb +0 -33
- data/repl +0 -3
- data/spec/compatibility/queue_declare_spec.rb +0 -44
- data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
- data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -71
- data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -76
- data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -225
- data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
- data/spec/higher_level_api/integration/basic_get_spec.rb +0 -48
- data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -79
- data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -89
- data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -29
- data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
- data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -74
- data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
- data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
- data/spec/higher_level_api/integration/channel_flow_spec.rb +0 -21
- data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
- data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
- data/spec/higher_level_api/integration/connection_spec.rb +0 -400
- data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -26
- data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
- data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -52
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -204
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
- data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
- data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -31
- data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
- data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
- data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -77
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -65
- data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
- data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -190
- data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
- data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
- data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
- data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
- data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -127
- data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
- data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
- data/spec/issues/issue100_spec.rb +0 -42
- data/spec/issues/issue141_spec.rb +0 -44
- data/spec/issues/issue78_spec.rb +0 -75
- data/spec/issues/issue83_spec.rb +0 -31
- data/spec/issues/issue97_attachment.json +0 -1
- data/spec/issues/issue97_spec.rb +0 -176
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -69
- data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -100
- data/spec/spec_helper.rb +0 -64
- data/spec/stress/channel_open_stress_spec.rb +0 -51
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
- data/spec/stress/concurrent_consumers_stress_spec.rb +0 -69
- data/spec/stress/concurrent_publishers_stress_spec.rb +0 -57
- data/spec/stress/connection_open_close_spec.rb +0 -40
- data/spec/stress/long_running_consumer_spec.rb +0 -83
- data/spec/tls/cacert.pem +0 -18
- data/spec/tls/client_cert.pem +0 -18
- data/spec/tls/client_key.pem +0 -27
- data/spec/tls/server_cert.pem +0 -18
- data/spec/tls/server_key.pem +0 -27
- data/spec/unit/bunny_spec.rb +0 -15
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
- data/spec/unit/concurrent/condition_spec.rb +0 -82
- data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
- data/spec/unit/system_timer_spec.rb +0 -10
- data/spec/unit/version_delivery_tag_spec.rb +0 -28
data/lib/bunny/socket.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
|
-
#
|
2
|
-
if defined?(JRUBY_VERSION)
|
3
|
-
require "bunny/jruby/socket"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
SocketImpl = JRuby::Socket
|
7
|
-
end
|
8
|
-
else
|
9
|
-
require "bunny/cruby/socket"
|
3
|
+
require "bunny/cruby/socket"
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
module Bunny
|
6
|
+
# An alias for the standard MRI Socket,
|
7
|
+
# exists from the days of JRuby support.
|
8
|
+
SocketImpl = Socket
|
9
|
+
end
|
data/lib/bunny/ssl_socket.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
|
-
#
|
2
|
-
if defined?(JRUBY_VERSION)
|
3
|
-
require "bunny/jruby/ssl_socket"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
SSLSocketImpl = JRuby::SSLSocket
|
7
|
-
end
|
8
|
-
else
|
9
|
-
require "bunny/cruby/ssl_socket"
|
3
|
+
require "bunny/cruby/ssl_socket"
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
module Bunny
|
6
|
+
# An alias for the standard SSLSocket,
|
7
|
+
# exists from the days of JRuby support.
|
8
|
+
SSLSocketImpl = SSLSocket
|
9
|
+
end
|
data/lib/bunny/test_kit.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "timeout"
|
5
|
+
|
2
6
|
module Bunny
|
3
7
|
# Unit, integration and stress testing toolkit
|
4
8
|
class TestKit
|
5
9
|
class << self
|
6
10
|
|
11
|
+
def poll_while(timeout = 60, &probe)
|
12
|
+
Timeout.timeout(timeout) {
|
13
|
+
sleep 0.1 while probe.call
|
14
|
+
}
|
15
|
+
end
|
16
|
+
def poll_until(timeout = 60, &probe)
|
17
|
+
Timeout.timeout(timeout) {
|
18
|
+
sleep 0.1 until probe.call
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
7
22
|
# @return [Integer] Random integer in the range of [a, b]
|
8
23
|
# @api private
|
9
24
|
def random_in_range(a, b)
|
data/lib/bunny/timeout.rb
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunny
|
2
|
-
|
3
|
-
# Ruby 1.8) and SystemTimer (the gem)
|
4
|
-
Timeout = if RUBY_VERSION < "1.9"
|
5
|
-
begin
|
6
|
-
require "bunny/system_timer"
|
7
|
-
Bunny::SystemTimer
|
8
|
-
rescue LoadError
|
9
|
-
Timeout
|
10
|
-
end
|
11
|
-
else
|
12
|
-
Timeout
|
13
|
-
end
|
4
|
+
Timeout = ::Timeout
|
14
5
|
|
15
6
|
# Backwards compatibility
|
16
7
|
# @private
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bunny
|
4
|
+
# Abstracts away the Ruby (OS) method of retriving timestamps.
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
class Timestamp
|
8
|
+
def self.now
|
9
|
+
::Time.now
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.monotonic
|
13
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.non_monotonic
|
17
|
+
::Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.non_monotonic_utc
|
21
|
+
self.non_monotonic.utc
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/bunny/transport.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "socket"
|
2
4
|
require "thread"
|
3
5
|
require "monitor"
|
4
6
|
|
5
7
|
begin
|
6
8
|
require "openssl"
|
7
|
-
rescue LoadError =>
|
9
|
+
rescue LoadError => _le
|
8
10
|
$stderr.puts "Could not load OpenSSL"
|
9
11
|
end
|
10
12
|
|
@@ -20,18 +22,46 @@ module Bunny
|
|
20
22
|
#
|
21
23
|
|
22
24
|
# Default TCP connection timeout
|
23
|
-
DEFAULT_CONNECTION_TIMEOUT =
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
DEFAULT_CONNECTION_TIMEOUT = 30.0
|
26
|
+
|
27
|
+
DEFAULT_READ_TIMEOUT = 30.0
|
28
|
+
DEFAULT_WRITE_TIMEOUT = 30.0
|
29
|
+
|
30
|
+
# mimics METHODS_MAP in ssl.rb but also lists TLS 1.3
|
31
|
+
# and string constants
|
32
|
+
TLS_VERSION_ALIASES = {
|
33
|
+
TLSv1: OpenSSL::SSL::TLS1_VERSION,
|
34
|
+
TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
|
35
|
+
TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION,
|
36
|
+
"1.0": OpenSSL::SSL::TLS1_VERSION,
|
37
|
+
"1.1": OpenSSL::SSL::TLS1_1_VERSION,
|
38
|
+
"1.2": OpenSSL::SSL::TLS1_2_VERSION,
|
39
|
+
OpenSSL::SSL::TLS1_VERSION => OpenSSL::SSL::TLS1_VERSION,
|
40
|
+
OpenSSL::SSL::TLS1_1_VERSION => OpenSSL::SSL::TLS1_1_VERSION,
|
41
|
+
OpenSSL::SSL::TLS1_2_VERSION => OpenSSL::SSL::TLS1_2_VERSION
|
42
|
+
}
|
43
|
+
|
44
|
+
# older OpenSSL versions won't support for TLS 1.3 and won't
|
45
|
+
# have this constant defined.
|
46
|
+
if defined?(OpenSSL::SSL::TLS1_3_VERSION)
|
47
|
+
TLS_VERSION_ALIASES["1.3"] = OpenSSL::SSL::TLS1_3_VERSION
|
48
|
+
TLS_VERSION_ALIASES[:TLSv1_3] = OpenSSL::SSL::TLS1_3_VERSION
|
49
|
+
TLS_VERSION_ALIASES[OpenSSL::SSL::TLS1_3_VERSION] = OpenSSL::SSL::TLS1_3_VERSION
|
50
|
+
end
|
27
51
|
|
52
|
+
TLS_VERSION_ALIASES.freeze
|
28
53
|
|
29
|
-
attr_reader :session, :host, :port, :socket, :connect_timeout, :
|
30
|
-
attr_reader :tls_context
|
54
|
+
attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
|
55
|
+
attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
|
56
|
+
|
57
|
+
def read_timeout=(v)
|
58
|
+
@read_timeout = v
|
59
|
+
@read_timeout = nil if @read_timeout == 0
|
60
|
+
end
|
31
61
|
|
32
62
|
def initialize(session, host, port, opts)
|
33
63
|
@session = session
|
34
|
-
@
|
64
|
+
@session_error_handler = opts[:session_error_handler]
|
35
65
|
@host = host
|
36
66
|
@port = port
|
37
67
|
@opts = opts
|
@@ -39,15 +69,22 @@ module Bunny
|
|
39
69
|
@logger = session.logger
|
40
70
|
@tls_enabled = tls_enabled?(opts)
|
41
71
|
|
42
|
-
@
|
43
|
-
@
|
72
|
+
@read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
|
73
|
+
@read_timeout = nil if @read_timeout == 0
|
74
|
+
|
75
|
+
@write_timeout = opts[:socket_timeout] # Backwards compatability
|
76
|
+
|
77
|
+
@write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
|
78
|
+
@write_timeout = nil if @write_timeout == 0
|
79
|
+
|
44
80
|
@connect_timeout = self.timeout_from(opts)
|
45
81
|
@connect_timeout = nil if @connect_timeout == 0
|
46
|
-
@disconnect_timeout = @
|
82
|
+
@disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
|
47
83
|
|
48
84
|
@writes_mutex = @session.mutex_impl.new
|
49
85
|
|
50
|
-
|
86
|
+
@socket = nil
|
87
|
+
|
51
88
|
prepare_tls_context(opts) if @tls_enabled
|
52
89
|
end
|
53
90
|
|
@@ -55,6 +92,10 @@ module Bunny
|
|
55
92
|
@host
|
56
93
|
end
|
57
94
|
|
95
|
+
def local_address
|
96
|
+
@socket.local_address
|
97
|
+
end
|
98
|
+
|
58
99
|
def uses_tls?
|
59
100
|
@tls_enabled
|
60
101
|
end
|
@@ -67,9 +108,30 @@ module Bunny
|
|
67
108
|
|
68
109
|
|
69
110
|
def connect
|
70
|
-
if
|
71
|
-
|
72
|
-
|
111
|
+
if uses_tls?
|
112
|
+
begin
|
113
|
+
@socket.connect
|
114
|
+
rescue OpenSSL::SSL::SSLError => e
|
115
|
+
@logger.error { "TLS connection failed: #{e.message}" }
|
116
|
+
raise e
|
117
|
+
end
|
118
|
+
|
119
|
+
log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
|
120
|
+
log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
|
121
|
+
|
122
|
+
begin
|
123
|
+
@socket.post_connection_check(host) if @verify_peer
|
124
|
+
rescue OpenSSL::SSL::SSLError => e
|
125
|
+
@logger.error do
|
126
|
+
msg = "Peer verification of target server failed: #{e.message}. "
|
127
|
+
msg += "Target hostname: #{hostname}, see peer certificate chain details below."
|
128
|
+
msg
|
129
|
+
end
|
130
|
+
log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
|
131
|
+
log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
|
132
|
+
|
133
|
+
raise e
|
134
|
+
end
|
73
135
|
|
74
136
|
@status = :connected
|
75
137
|
|
@@ -80,7 +142,7 @@ module Bunny
|
|
80
142
|
end
|
81
143
|
|
82
144
|
def connected?
|
83
|
-
:
|
145
|
+
:connected == @status && open?
|
84
146
|
end
|
85
147
|
|
86
148
|
def configure_socket(&block)
|
@@ -91,26 +153,18 @@ module Bunny
|
|
91
153
|
block.call(@tls_context) if @tls_context
|
92
154
|
end
|
93
155
|
|
94
|
-
# Writes data to the socket. If read/write timeout was specified
|
95
|
-
#
|
96
|
-
#
|
97
|
-
# @raise [ClientTimeout]
|
156
|
+
# Writes data to the socket. If read/write timeout was specified the operation will return after that
|
157
|
+
# amount of time has elapsed waiting for the socket.
|
98
158
|
def write(data)
|
159
|
+
return write_without_timeout(data) unless @write_timeout
|
160
|
+
|
99
161
|
begin
|
100
|
-
if
|
101
|
-
|
102
|
-
|
103
|
-
@writes_mutex.synchronize { @socket.write(data) }
|
104
|
-
@socket.flush
|
105
|
-
end
|
106
|
-
end
|
107
|
-
else
|
108
|
-
if open?
|
109
|
-
@writes_mutex.synchronize { @socket.write(data) }
|
110
|
-
@socket.flush
|
162
|
+
if open?
|
163
|
+
@writes_mutex.synchronize do
|
164
|
+
@socket.write_nonblock_fully(data, @write_timeout)
|
111
165
|
end
|
112
166
|
end
|
113
|
-
rescue SystemCallError,
|
167
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
114
168
|
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
115
169
|
close
|
116
170
|
@status = :not_connected
|
@@ -118,23 +172,24 @@ module Bunny
|
|
118
172
|
if @session.automatically_recover?
|
119
173
|
@session.handle_network_failure(e)
|
120
174
|
else
|
121
|
-
@
|
175
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
122
176
|
end
|
123
177
|
end
|
124
178
|
end
|
125
179
|
|
126
180
|
# Writes data to the socket without timeout checks
|
127
|
-
def write_without_timeout(data)
|
181
|
+
def write_without_timeout(data, raise_exceptions = false)
|
128
182
|
begin
|
129
183
|
@writes_mutex.synchronize { @socket.write(data) }
|
130
184
|
@socket.flush
|
131
185
|
rescue SystemCallError, Bunny::ConnectionError, IOError => e
|
132
186
|
close
|
187
|
+
raise e if raise_exceptions
|
133
188
|
|
134
189
|
if @session.automatically_recover?
|
135
190
|
@session.handle_network_failure(e)
|
136
191
|
else
|
137
|
-
@
|
192
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
138
193
|
end
|
139
194
|
end
|
140
195
|
end
|
@@ -180,8 +235,20 @@ module Bunny
|
|
180
235
|
@socket.flush if @socket
|
181
236
|
end
|
182
237
|
|
183
|
-
def read_fully(
|
184
|
-
|
238
|
+
def read_fully(count)
|
239
|
+
begin
|
240
|
+
@socket.read_fully(count, @read_timeout)
|
241
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
242
|
+
@logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
|
243
|
+
close
|
244
|
+
@status = :not_connected
|
245
|
+
|
246
|
+
if @session.automatically_recover?
|
247
|
+
raise
|
248
|
+
else
|
249
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
250
|
+
end
|
251
|
+
end
|
185
252
|
end
|
186
253
|
|
187
254
|
def read_ready?(timeout = nil)
|
@@ -189,22 +256,17 @@ module Bunny
|
|
189
256
|
io && io[0].include?(@socket)
|
190
257
|
end
|
191
258
|
|
192
|
-
|
193
259
|
# Exposed primarily for Bunny::Channel
|
194
260
|
# @private
|
195
261
|
def read_next_frame(opts = {})
|
196
|
-
header
|
197
|
-
# TODO: network issues here will sometimes cause
|
198
|
-
# the socket method return an empty string. We need to log
|
199
|
-
# and handle this better.
|
200
|
-
# type, channel, size = begin
|
201
|
-
# AMQ::Protocol::Frame.decode_header(header)
|
202
|
-
# rescue AMQ::Protocol::EmptyResponseError => e
|
203
|
-
# puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
|
204
|
-
# end
|
262
|
+
header = read_fully(7)
|
205
263
|
type, channel, size = AMQ::Protocol::Frame.decode_header(header)
|
206
|
-
payload
|
207
|
-
|
264
|
+
payload = if size > 0
|
265
|
+
read_fully(size)
|
266
|
+
else
|
267
|
+
''
|
268
|
+
end
|
269
|
+
frame_end = read_fully(1)
|
208
270
|
|
209
271
|
# 1) the size is miscalculated
|
210
272
|
if payload.bytesize != size
|
@@ -220,10 +282,10 @@ module Bunny
|
|
220
282
|
def self.reacheable?(host, port, timeout)
|
221
283
|
begin
|
222
284
|
s = Bunny::SocketImpl.open(host, port,
|
223
|
-
:
|
285
|
+
:connect_timeout => timeout)
|
224
286
|
|
225
287
|
true
|
226
|
-
rescue SocketError, Timeout::Error =>
|
288
|
+
rescue SocketError, Timeout::Error => _e
|
227
289
|
false
|
228
290
|
ensure
|
229
291
|
s.close if s
|
@@ -235,12 +297,11 @@ module Bunny
|
|
235
297
|
end
|
236
298
|
|
237
299
|
def initialize_socket
|
300
|
+
@logger.debug("Usong connection timeout of #{@connect_timeout} when connecting to #{@host}:#{@port}")
|
238
301
|
begin
|
239
|
-
@socket = Bunny::
|
240
|
-
|
241
|
-
|
242
|
-
:socket_timeout => @connect_timeout)
|
243
|
-
end
|
302
|
+
@socket = Bunny::SocketImpl.open(@host, @port,
|
303
|
+
:keepalive => @opts[:keepalive],
|
304
|
+
:connect_timeout => @connect_timeout)
|
244
305
|
rescue StandardError, ClientTimeout => e
|
245
306
|
@status = :not_connected
|
246
307
|
raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
|
@@ -254,7 +315,7 @@ module Bunny
|
|
254
315
|
end
|
255
316
|
|
256
317
|
def post_initialize_socket
|
257
|
-
@socket = if uses_tls?
|
318
|
+
@socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
|
258
319
|
wrap_in_tls_socket(@socket)
|
259
320
|
else
|
260
321
|
@socket
|
@@ -264,21 +325,27 @@ module Bunny
|
|
264
325
|
protected
|
265
326
|
|
266
327
|
def tls_enabled?(opts)
|
267
|
-
opts[:tls]
|
328
|
+
return !!opts[:tls] unless opts[:tls].nil?
|
329
|
+
return !!opts[:ssl] unless opts[:ssl].nil?
|
330
|
+
(opts[:port] == AMQ::Protocol::TLS_PORT) || false
|
331
|
+
end
|
332
|
+
|
333
|
+
def tls_ca_certificates_paths_from(opts)
|
334
|
+
Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
|
268
335
|
end
|
269
336
|
|
270
337
|
def tls_certificate_path_from(opts)
|
271
|
-
opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
338
|
+
opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
272
339
|
end
|
273
340
|
|
274
341
|
def tls_key_path_from(opts)
|
275
|
-
opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
342
|
+
opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
276
343
|
end
|
277
344
|
|
278
345
|
def tls_certificate_from(opts)
|
279
346
|
begin
|
280
347
|
read_client_certificate!
|
281
|
-
rescue MissingTLSCertificateFile =>
|
348
|
+
rescue MissingTLSCertificateFile => _e
|
282
349
|
inline_client_certificate_from(opts)
|
283
350
|
end
|
284
351
|
end
|
@@ -286,14 +353,37 @@ module Bunny
|
|
286
353
|
def tls_key_from(opts)
|
287
354
|
begin
|
288
355
|
read_client_key!
|
289
|
-
rescue MissingTLSKeyFile =>
|
356
|
+
rescue MissingTLSKeyFile => _e
|
290
357
|
inline_client_key_from(opts)
|
291
358
|
end
|
292
359
|
end
|
293
360
|
|
361
|
+
def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
|
362
|
+
exts = peer_cert.extensions.map { |x| x.value }
|
363
|
+
# Subject Alternative Names
|
364
|
+
sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
|
365
|
+
|
366
|
+
msg = "#{prefix} subject: #{peer_cert.subject}, "
|
367
|
+
msg += "subject alternative names: #{sans.join(', ')}, "
|
368
|
+
msg += "issuer: #{peer_cert.issuer}, "
|
369
|
+
msg += "not valid after: #{peer_cert.not_after}, "
|
370
|
+
msg += "X.509 usage extensions: #{exts.join(', ')}"
|
371
|
+
|
372
|
+
msg
|
373
|
+
end
|
374
|
+
|
375
|
+
def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
|
376
|
+
@logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
|
377
|
+
end
|
378
|
+
|
379
|
+
def log_peer_certificate_chain_info(severity, chain)
|
380
|
+
chain.each do |cert|
|
381
|
+
self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
|
382
|
+
end
|
383
|
+
end
|
294
384
|
|
295
385
|
def inline_client_certificate_from(opts)
|
296
|
-
opts[:tls_certificate] || opts[:ssl_cert_string]
|
386
|
+
opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
|
297
387
|
end
|
298
388
|
|
299
389
|
def inline_client_key_from(opts)
|
@@ -301,6 +391,10 @@ module Bunny
|
|
301
391
|
end
|
302
392
|
|
303
393
|
def prepare_tls_context(opts)
|
394
|
+
if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
|
395
|
+
opts[:verify_peer] = true
|
396
|
+
end
|
397
|
+
|
304
398
|
# client cert/key paths
|
305
399
|
@tls_certificate_path = tls_certificate_path_from(opts)
|
306
400
|
@tls_key_path = tls_key_path_from(opts)
|
@@ -309,10 +403,20 @@ module Bunny
|
|
309
403
|
@tls_key = tls_key_from(opts)
|
310
404
|
@tls_certificate_store = opts[:tls_certificate_store]
|
311
405
|
|
312
|
-
@
|
313
|
-
@verify_peer = opts[:verify_ssl] || opts[:verify_peer]
|
406
|
+
@verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
|
314
407
|
|
315
|
-
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
|
408
|
+
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
|
409
|
+
end
|
410
|
+
|
411
|
+
def as_boolean(val)
|
412
|
+
case val
|
413
|
+
when true then true
|
414
|
+
when false then false
|
415
|
+
when "true" then true
|
416
|
+
when "false" then false
|
417
|
+
else
|
418
|
+
!!val
|
419
|
+
end
|
316
420
|
end
|
317
421
|
|
318
422
|
def wrap_in_tls_socket(socket)
|
@@ -320,6 +424,11 @@ module Bunny
|
|
320
424
|
raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
|
321
425
|
|
322
426
|
s = Bunny::SSLSocketImpl.new(socket, @tls_context)
|
427
|
+
|
428
|
+
# always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
|
429
|
+
# that TLS clients supporting the extensions can talk to TLS servers that do not
|
430
|
+
s.hostname = @host if s.respond_to?(:hostname)
|
431
|
+
|
323
432
|
s.sync_close = true
|
324
433
|
s
|
325
434
|
end
|
@@ -346,68 +455,84 @@ module Bunny
|
|
346
455
|
end
|
347
456
|
end
|
348
457
|
|
349
|
-
def initialize_tls_context(ctx)
|
458
|
+
def initialize_tls_context(ctx, opts = {})
|
350
459
|
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
|
351
|
-
ctx.key = OpenSSL::PKey
|
460
|
+
ctx.key = OpenSSL::PKey.read(@tls_key) if @tls_key
|
352
461
|
ctx.cert_store = if @tls_certificate_store
|
353
462
|
@tls_certificate_store
|
354
463
|
else
|
464
|
+
# this ivar exists so that this value can be exposed in the API
|
465
|
+
@tls_ca_certificates = tls_ca_certificates_paths_from(opts)
|
355
466
|
initialize_tls_certificate_store(@tls_ca_certificates)
|
356
467
|
end
|
468
|
+
should_silence_warnings = opts.fetch(:tls_silence_warnings, false)
|
357
469
|
|
358
|
-
if !@tls_certificate
|
470
|
+
if !@tls_certificate && !should_silence_warnings
|
359
471
|
@logger.warn <<-MSG
|
360
|
-
|
361
|
-
|
472
|
+
Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
|
473
|
+
certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
|
362
474
|
MSG
|
363
475
|
end
|
364
476
|
if @tls_certificate && !@tls_key
|
365
477
|
@logger.warn "Using TLS but no client private key is provided!"
|
366
478
|
end
|
367
479
|
|
368
|
-
# setting TLS/SSL version only works correctly when done
|
369
|
-
# vis set_params. MK.
|
370
|
-
ctx.set_params(:ssl_version => @opts.fetch(:tls_protocol, DEFAULT_TLS_PROTOCOL))
|
371
|
-
|
372
480
|
verify_mode = if @verify_peer
|
373
481
|
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
374
482
|
else
|
375
483
|
OpenSSL::SSL::VERIFY_NONE
|
376
484
|
end
|
485
|
+
@logger.debug { "Will use peer verification mode #{verify_mode}" }
|
486
|
+
ctx.verify_mode = verify_mode
|
377
487
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
488
|
+
if !@verify_peer && !should_silence_warnings
|
489
|
+
@logger.warn <<-MSG
|
490
|
+
Using TLS but peer hostname verification is disabled. This is convenient for local development
|
491
|
+
but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
|
492
|
+
MSG
|
493
|
+
end
|
382
494
|
|
383
|
-
|
384
|
-
if
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
|
389
|
-
default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
|
390
|
-
|
391
|
-
[
|
392
|
-
default_ca_file,
|
393
|
-
File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
|
394
|
-
File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
|
395
|
-
File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
|
396
|
-
].uniq
|
495
|
+
ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2
|
496
|
+
if ssl_version
|
497
|
+
v = tls_version_constant(ssl_version)
|
498
|
+
ctx.min_version = v
|
499
|
+
ctx.max_version = v
|
397
500
|
end
|
501
|
+
|
502
|
+
ctx
|
398
503
|
end
|
399
504
|
|
400
505
|
def initialize_tls_certificate_store(certs)
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
506
|
+
cert_files = []
|
507
|
+
cert_inlines = []
|
508
|
+
certs.each do |cert|
|
509
|
+
# if it starts with / or C:/ then it's a file path that may or may not
|
510
|
+
# exist (e.g. a default OpenSSL path). MK.
|
511
|
+
if File.readable?(cert) || cert =~ /\A([a-z]:?)?\//i
|
512
|
+
cert_files.push(cert)
|
513
|
+
else
|
514
|
+
cert_inlines.push(cert)
|
515
|
+
end
|
405
516
|
end
|
517
|
+
@logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
|
518
|
+
@logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
|
406
519
|
OpenSSL::X509::Store.new.tap do |store|
|
407
|
-
|
520
|
+
store.set_default_paths
|
521
|
+
cert_files.select { |path| File.readable?(path) }.
|
522
|
+
each { |path| store.add_file(path) }
|
523
|
+
cert_inlines.
|
524
|
+
each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
|
408
525
|
end
|
409
526
|
end
|
410
527
|
|
528
|
+
|
529
|
+
def tls_version_constant(value)
|
530
|
+
# OpenSSL::SSL::TLS1_3_VERSION and similar constants
|
531
|
+
# are just integers, so use the value itself as fallback since
|
532
|
+
# there is no class to case switch on
|
533
|
+
TLS_VERSION_ALIASES[value] || value
|
534
|
+
end
|
535
|
+
|
411
536
|
def timeout_from(options)
|
412
537
|
options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
|
413
538
|
end
|
data/lib/bunny/version.rb
CHANGED