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.
Files changed (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +92 -87
  3. data/lib/amq/protocol/extensions.rb +2 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +2 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  7. data/lib/bunny/channel.rb +485 -186
  8. data/lib/bunny/channel_id_allocator.rb +8 -4
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +37 -13
  12. data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
  13. data/lib/bunny/consumer.rb +20 -13
  14. data/lib/bunny/consumer_tag_generator.rb +6 -2
  15. data/lib/bunny/consumer_work_pool.rb +37 -7
  16. data/lib/bunny/cruby/socket.rb +51 -22
  17. data/lib/bunny/cruby/ssl_socket.rb +68 -5
  18. data/lib/bunny/delivery_info.rb +3 -1
  19. data/lib/bunny/exceptions.rb +27 -4
  20. data/lib/bunny/exchange.rb +35 -29
  21. data/lib/bunny/framing.rb +2 -0
  22. data/lib/bunny/get_response.rb +85 -0
  23. data/lib/bunny/heartbeat_sender.rb +9 -6
  24. data/lib/bunny/message_properties.rb +2 -0
  25. data/lib/bunny/queue.rb +89 -41
  26. data/lib/bunny/reader_loop.rb +72 -28
  27. data/lib/bunny/return_info.rb +2 -0
  28. data/lib/bunny/session.rb +621 -225
  29. data/lib/bunny/socket.rb +7 -12
  30. data/lib/bunny/ssl_socket.rb +7 -12
  31. data/lib/bunny/test_kit.rb +15 -0
  32. data/lib/bunny/timeout.rb +3 -12
  33. data/lib/bunny/timestamp.rb +24 -0
  34. data/lib/bunny/transport.rb +223 -98
  35. data/lib/bunny/version.rb +2 -1
  36. data/lib/bunny/versioned_delivery_tag.rb +2 -0
  37. data/lib/bunny.rb +54 -8
  38. metadata +38 -224
  39. data/.gitignore +0 -22
  40. data/.rspec +0 -3
  41. data/.ruby-version +0 -1
  42. data/.travis.yml +0 -23
  43. data/.yardopts +0 -8
  44. data/ChangeLog.md +0 -1092
  45. data/Gemfile +0 -54
  46. data/LICENSE +0 -21
  47. data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
  48. data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
  49. data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
  50. data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
  51. data/benchmarks/channel_open.rb +0 -28
  52. data/benchmarks/mutex_and_monitor.rb +0 -42
  53. data/benchmarks/queue_declare.rb +0 -29
  54. data/benchmarks/queue_declare_and_bind.rb +0 -29
  55. data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
  56. data/benchmarks/synchronized_sorted_set.rb +0 -53
  57. data/benchmarks/write_vs_write_nonblock.rb +0 -49
  58. data/bin/ci/before_build.sh +0 -31
  59. data/bunny.gemspec +0 -40
  60. data/examples/connection/authentication_failure.rb +0 -16
  61. data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
  62. data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
  63. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
  64. data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
  65. data/examples/connection/channel_level_exception.rb +0 -35
  66. data/examples/connection/disabled_automatic_recovery.rb +0 -34
  67. data/examples/connection/heartbeat.rb +0 -17
  68. data/examples/connection/manually_reconnecting_consumer.rb +0 -23
  69. data/examples/connection/manually_reconnecting_publisher.rb +0 -28
  70. data/examples/connection/unknown_host.rb +0 -16
  71. data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
  72. data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
  73. data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
  74. data/examples/guides/exchanges/mandatory_messages.rb +0 -30
  75. data/examples/guides/extensions/alternate_exchange.rb +0 -28
  76. data/examples/guides/extensions/basic_nack.rb +0 -33
  77. data/examples/guides/extensions/connection_blocked.rb +0 -35
  78. data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
  79. data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
  80. data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
  81. data/examples/guides/extensions/per_message_ttl.rb +0 -36
  82. data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
  83. data/examples/guides/extensions/publisher_confirms.rb +0 -28
  84. data/examples/guides/extensions/queue_lease.rb +0 -26
  85. data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
  86. data/examples/guides/getting_started/blabbr.rb +0 -27
  87. data/examples/guides/getting_started/hello_world.rb +0 -20
  88. data/examples/guides/getting_started/weathr.rb +0 -47
  89. data/examples/guides/queues/one_off_consumer.rb +0 -23
  90. data/examples/guides/queues/redeliveries.rb +0 -79
  91. data/lib/bunny/compatibility.rb +0 -24
  92. data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
  93. data/lib/bunny/jruby/socket.rb +0 -40
  94. data/lib/bunny/jruby/ssl_socket.rb +0 -53
  95. data/lib/bunny/system_timer.rb +0 -20
  96. data/profiling/basic_publish/with_4K_messages.rb +0 -33
  97. data/repl +0 -3
  98. data/spec/compatibility/queue_declare_spec.rb +0 -44
  99. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  100. data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -71
  101. data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -76
  102. data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -225
  103. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
  104. data/spec/higher_level_api/integration/basic_get_spec.rb +0 -48
  105. data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -79
  106. data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -89
  107. data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -29
  108. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  109. data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -74
  110. data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
  111. data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
  112. data/spec/higher_level_api/integration/channel_flow_spec.rb +0 -21
  113. data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
  114. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  115. data/spec/higher_level_api/integration/connection_spec.rb +0 -400
  116. data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -26
  117. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  118. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
  119. data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -52
  120. data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
  121. data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -204
  122. data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
  123. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
  124. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
  125. data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -31
  126. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  127. data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
  128. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
  129. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -77
  130. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -65
  131. data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
  132. data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -190
  133. data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
  134. data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
  135. data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
  136. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
  137. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
  138. data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -127
  139. data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
  140. data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
  141. data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
  142. data/spec/issues/issue100_spec.rb +0 -42
  143. data/spec/issues/issue141_spec.rb +0 -44
  144. data/spec/issues/issue78_spec.rb +0 -75
  145. data/spec/issues/issue83_spec.rb +0 -31
  146. data/spec/issues/issue97_attachment.json +0 -1
  147. data/spec/issues/issue97_spec.rb +0 -176
  148. data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -69
  149. data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -100
  150. data/spec/spec_helper.rb +0 -64
  151. data/spec/stress/channel_open_stress_spec.rb +0 -51
  152. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
  153. data/spec/stress/concurrent_consumers_stress_spec.rb +0 -69
  154. data/spec/stress/concurrent_publishers_stress_spec.rb +0 -57
  155. data/spec/stress/connection_open_close_spec.rb +0 -40
  156. data/spec/stress/long_running_consumer_spec.rb +0 -83
  157. data/spec/tls/cacert.pem +0 -18
  158. data/spec/tls/client_cert.pem +0 -18
  159. data/spec/tls/client_key.pem +0 -27
  160. data/spec/tls/server_cert.pem +0 -18
  161. data/spec/tls/server_key.pem +0 -27
  162. data/spec/unit/bunny_spec.rb +0 -15
  163. data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
  164. data/spec/unit/concurrent/condition_spec.rb +0 -82
  165. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
  166. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
  167. data/spec/unit/system_timer_spec.rb +0 -10
  168. data/spec/unit/version_delivery_tag_spec.rb +0 -28
data/lib/bunny/socket.rb CHANGED
@@ -1,14 +1,9 @@
1
- # See #165. MK.
2
- if defined?(JRUBY_VERSION)
3
- require "bunny/jruby/socket"
1
+ # frozen_string_literal: true
4
2
 
5
- module Bunny
6
- SocketImpl = JRuby::Socket
7
- end
8
- else
9
- require "bunny/cruby/socket"
3
+ require "bunny/cruby/socket"
10
4
 
11
- module Bunny
12
- SocketImpl = Socket
13
- end
14
- end
5
+ module Bunny
6
+ # An alias for the standard MRI Socket,
7
+ # exists from the days of JRuby support.
8
+ SocketImpl = Socket
9
+ end
@@ -1,14 +1,9 @@
1
- # See #165. MK.
2
- if defined?(JRUBY_VERSION)
3
- require "bunny/jruby/ssl_socket"
1
+ # frozen_string_literal: true
4
2
 
5
- module Bunny
6
- SSLSocketImpl = JRuby::SSLSocket
7
- end
8
- else
9
- require "bunny/cruby/ssl_socket"
3
+ require "bunny/cruby/ssl_socket"
10
4
 
11
- module Bunny
12
- SSLSocketImpl = SSLSocket
13
- end
14
- end
5
+ module Bunny
6
+ # An alias for the standard SSLSocket,
7
+ # exists from the days of JRuby support.
8
+ SSLSocketImpl = SSLSocket
9
+ end
@@ -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
- # Unifies Ruby standard library's Timeout (which is not accurate on
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
@@ -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 => le
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 = 5.0
24
- # Default TLS protocol version to use.
25
- # Currently SSLv3, same as in RabbitMQ Java client
26
- DEFAULT_TLS_PROTOCOL = "SSLv3"
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, :read_write_timeout, :disconnect_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
- @session_thread = opts[:session_thread]
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
- @read_write_timeout = opts[:socket_timeout] || 3
43
- @read_write_timeout = nil if @read_write_timeout == 0
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 = @read_write_timeout || @connect_timeout
82
+ @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
47
83
 
48
84
  @writes_mutex = @session.mutex_impl.new
49
85
 
50
- maybe_initialize_socket
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 uses_ssl?
71
- @socket.connect
72
- @socket.post_connection_check(host) if uses_tls? && @verify_peer
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
- :not_connected == @status && open?
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, Bunny::ClientTimeout will be raised
95
- # if the operation times out.
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 @read_write_timeout
101
- Bunny::Timeout.timeout(@read_write_timeout, Bunny::ClientTimeout) do
102
- if open?
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, Bunny::ClientTimeout, Bunny::ConnectionError, IOError => e
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
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
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
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
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(*args)
184
- @socket.read_fully(*args)
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 = @socket.read_fully(7)
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 = @socket.read_fully(size)
207
- frame_end = @socket.read_fully(1)
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
- :socket_timeout => timeout)
285
+ :connect_timeout => timeout)
224
286
 
225
287
  true
226
- rescue SocketError, Timeout::Error => e
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::Timeout.timeout(@connect_timeout, ClientTimeout) do
240
- Bunny::SocketImpl.open(@host, @port,
241
- :keepalive => @opts[:keepalive],
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] || opts[:ssl] || (opts[:port] == AMQ::Protocol::TLS_PORT) || false
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 => e
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 => e
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
- @tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates)
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::RSA.new(@tls_key) if @tls_key
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
- Using TLS but no client certificate is provided! If RabbitMQ is configured to verify peer
361
- certificate, connection upgrade will fail!
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
- ctx.set_params(:verify_mode => verify_mode)
379
-
380
- ctx
381
- end
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
- def default_tls_certificates
384
- if defined?(JRUBY_VERSION)
385
- # see https://github.com/jruby/jruby/issues/1055. MK.
386
- []
387
- else
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
- certs = certs.select { |path| File.readable? path }
402
- @logger.debug "Using CA certificates at #{certs.join(', ')}"
403
- if certs.empty?
404
- @logger.error "No CA certificates found, add one with :tls_ca_certificates"
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
- certs.each { |path| store.add_file(path) }
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
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Bunny
4
5
  # @return [String] Version of the library
5
- VERSION = "1.0.7"
6
+ VERSION = "2.24.0"
6
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunny
2
4
  # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could
3
5
  # detect stale tags after connection recovery.