garaio_bunny 2.19.1
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 +7 -0
- data/README.md +231 -0
- data/lib/amq/protocol/extensions.rb +16 -0
- data/lib/bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/bunny/channel.rb +2055 -0
- data/lib/bunny/channel_id_allocator.rb +82 -0
- data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
- data/lib/bunny/concurrent/condition.rb +66 -0
- data/lib/bunny/concurrent/continuation_queue.rb +62 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/bunny/consumer.rb +128 -0
- data/lib/bunny/consumer_tag_generator.rb +23 -0
- data/lib/bunny/consumer_work_pool.rb +122 -0
- data/lib/bunny/cruby/socket.rb +110 -0
- data/lib/bunny/cruby/ssl_socket.rb +118 -0
- data/lib/bunny/delivery_info.rb +93 -0
- data/lib/bunny/exceptions.rb +269 -0
- data/lib/bunny/exchange.rb +275 -0
- data/lib/bunny/framing.rb +56 -0
- data/lib/bunny/get_response.rb +83 -0
- data/lib/bunny/heartbeat_sender.rb +71 -0
- data/lib/bunny/jruby/socket.rb +57 -0
- data/lib/bunny/jruby/ssl_socket.rb +58 -0
- data/lib/bunny/message_properties.rb +119 -0
- data/lib/bunny/queue.rb +393 -0
- data/lib/bunny/reader_loop.rb +158 -0
- data/lib/bunny/return_info.rb +74 -0
- data/lib/bunny/session.rb +1483 -0
- data/lib/bunny/socket.rb +14 -0
- data/lib/bunny/ssl_socket.rb +14 -0
- data/lib/bunny/test_kit.rb +41 -0
- data/lib/bunny/timeout.rb +7 -0
- data/lib/bunny/transport.rb +526 -0
- data/lib/bunny/version.rb +6 -0
- data/lib/bunny/versioned_delivery_tag.rb +28 -0
- data/lib/bunny.rb +92 -0
- metadata +127 -0
data/lib/bunny/socket.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
module Bunny
|
|
6
|
+
# Unit, integration and stress testing toolkit
|
|
7
|
+
class TestKit
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
def poll_while(timeout = 60, &probe)
|
|
11
|
+
Timeout.timeout(timeout) {
|
|
12
|
+
sleep 0.1 while probe.call
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
def poll_until(timeout = 60, &probe)
|
|
16
|
+
Timeout.timeout(timeout) {
|
|
17
|
+
sleep 0.1 until probe.call
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Integer] Random integer in the range of [a, b]
|
|
22
|
+
# @api private
|
|
23
|
+
def random_in_range(a, b)
|
|
24
|
+
Range.new(a, b).to_a.sample
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param [Integer] a Lower bound of message size, in KB
|
|
28
|
+
# @param [Integer] b Upper bound of message size, in KB
|
|
29
|
+
# @param [Integer] i Random number to use in message generation
|
|
30
|
+
# @return [String] Message payload of length in the given range, with non-ASCII characters
|
|
31
|
+
# @api public
|
|
32
|
+
def message_in_kb(a, b, i)
|
|
33
|
+
s = "Ю#{i}"
|
|
34
|
+
n = random_in_range(a, b) / s.bytesize
|
|
35
|
+
|
|
36
|
+
s * n * 1024
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
require "thread"
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require "openssl"
|
|
7
|
+
rescue LoadError => _le
|
|
8
|
+
$stderr.puts "Could not load OpenSSL"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require "bunny/exceptions"
|
|
12
|
+
require "bunny/socket"
|
|
13
|
+
|
|
14
|
+
module Bunny
|
|
15
|
+
# @private
|
|
16
|
+
class Transport
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# API
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
# Default TCP connection timeout
|
|
23
|
+
DEFAULT_CONNECTION_TIMEOUT = 30.0
|
|
24
|
+
|
|
25
|
+
DEFAULT_READ_TIMEOUT = 30.0
|
|
26
|
+
DEFAULT_WRITE_TIMEOUT = 30.0
|
|
27
|
+
|
|
28
|
+
attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
|
|
29
|
+
attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
|
|
30
|
+
|
|
31
|
+
def read_timeout=(v)
|
|
32
|
+
@read_timeout = v
|
|
33
|
+
@read_timeout = nil if @read_timeout == 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(session, host, port, opts)
|
|
37
|
+
@session = session
|
|
38
|
+
@session_error_handler = opts[:session_error_handler]
|
|
39
|
+
@host = host
|
|
40
|
+
@port = port
|
|
41
|
+
@opts = opts
|
|
42
|
+
|
|
43
|
+
@logger = session.logger
|
|
44
|
+
@tls_enabled = tls_enabled?(opts)
|
|
45
|
+
|
|
46
|
+
@read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
|
|
47
|
+
@read_timeout = nil if @read_timeout == 0
|
|
48
|
+
|
|
49
|
+
@write_timeout = opts[:socket_timeout] # Backwards compatability
|
|
50
|
+
|
|
51
|
+
@write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
|
|
52
|
+
@write_timeout = nil if @write_timeout == 0
|
|
53
|
+
|
|
54
|
+
@connect_timeout = self.timeout_from(opts)
|
|
55
|
+
@connect_timeout = nil if @connect_timeout == 0
|
|
56
|
+
@disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
|
|
57
|
+
|
|
58
|
+
@writes_mutex = @session.mutex_impl.new
|
|
59
|
+
|
|
60
|
+
@socket = nil
|
|
61
|
+
|
|
62
|
+
prepare_tls_context(opts) if @tls_enabled
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def hostname
|
|
66
|
+
@host
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def local_address
|
|
70
|
+
@socket.local_address
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def uses_tls?
|
|
74
|
+
@tls_enabled
|
|
75
|
+
end
|
|
76
|
+
alias tls? uses_tls?
|
|
77
|
+
|
|
78
|
+
def uses_ssl?
|
|
79
|
+
@tls_enabled
|
|
80
|
+
end
|
|
81
|
+
alias ssl? uses_ssl?
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def connect
|
|
85
|
+
if uses_tls?
|
|
86
|
+
begin
|
|
87
|
+
@socket.connect
|
|
88
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
89
|
+
@logger.error { "TLS connection failed: #{e.message}" }
|
|
90
|
+
raise e
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
|
|
94
|
+
log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
@socket.post_connection_check(host) if @verify_peer
|
|
98
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
99
|
+
@logger.error do
|
|
100
|
+
msg = "Peer verification of target server failed: #{e.message}. "
|
|
101
|
+
msg += "Target hostname: #{hostname}, see peer certificate chain details below."
|
|
102
|
+
msg
|
|
103
|
+
end
|
|
104
|
+
log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
|
|
105
|
+
log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
|
|
106
|
+
|
|
107
|
+
raise e
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@status = :connected
|
|
111
|
+
|
|
112
|
+
@socket
|
|
113
|
+
else
|
|
114
|
+
# no-op
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def connected?
|
|
119
|
+
:connected == @status && open?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def configure_socket(&block)
|
|
123
|
+
block.call(@socket) if @socket
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def configure_tls_context(&block)
|
|
127
|
+
block.call(@tls_context) if @tls_context
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if defined?(JRUBY_VERSION)
|
|
131
|
+
# Writes data to the socket.
|
|
132
|
+
def write(data)
|
|
133
|
+
return write_without_timeout(data) unless @write_timeout
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
if open?
|
|
137
|
+
@writes_mutex.synchronize do
|
|
138
|
+
@socket.write(data)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
|
142
|
+
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
|
143
|
+
close
|
|
144
|
+
@status = :not_connected
|
|
145
|
+
|
|
146
|
+
if @session.automatically_recover?
|
|
147
|
+
@session.handle_network_failure(e)
|
|
148
|
+
else
|
|
149
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
# Writes data to the socket. If read/write timeout was specified the operation will return after that
|
|
155
|
+
# amount of time has elapsed waiting for the socket.
|
|
156
|
+
def write(data)
|
|
157
|
+
return write_without_timeout(data) unless @write_timeout
|
|
158
|
+
|
|
159
|
+
begin
|
|
160
|
+
if open?
|
|
161
|
+
@writes_mutex.synchronize do
|
|
162
|
+
@socket.write_nonblock_fully(data, @write_timeout)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
|
166
|
+
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
|
167
|
+
close
|
|
168
|
+
@status = :not_connected
|
|
169
|
+
|
|
170
|
+
if @session.automatically_recover?
|
|
171
|
+
@session.handle_network_failure(e)
|
|
172
|
+
else
|
|
173
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Writes data to the socket without timeout checks
|
|
180
|
+
def write_without_timeout(data, raise_exceptions = false)
|
|
181
|
+
begin
|
|
182
|
+
@writes_mutex.synchronize { @socket.write(data) }
|
|
183
|
+
@socket.flush
|
|
184
|
+
rescue SystemCallError, Bunny::ConnectionError, IOError => e
|
|
185
|
+
close
|
|
186
|
+
raise e if raise_exceptions
|
|
187
|
+
|
|
188
|
+
if @session.automatically_recover?
|
|
189
|
+
@session.handle_network_failure(e)
|
|
190
|
+
else
|
|
191
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Sends frame to the peer.
|
|
197
|
+
#
|
|
198
|
+
# @raise [ConnectionClosedError]
|
|
199
|
+
# @private
|
|
200
|
+
def send_frame(frame)
|
|
201
|
+
if closed?
|
|
202
|
+
@session.handle_network_failure(ConnectionClosedError.new(frame))
|
|
203
|
+
else
|
|
204
|
+
write(frame.encode)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Sends frame to the peer without timeout control.
|
|
209
|
+
#
|
|
210
|
+
# @raise [ConnectionClosedError]
|
|
211
|
+
# @private
|
|
212
|
+
def send_frame_without_timeout(frame)
|
|
213
|
+
if closed?
|
|
214
|
+
@session.handle_network_failure(ConnectionClosedError.new(frame))
|
|
215
|
+
else
|
|
216
|
+
write_without_timeout(frame.encode)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def close(reason = nil)
|
|
222
|
+
@socket.close if open?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def open?
|
|
226
|
+
@socket && !@socket.closed?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def closed?
|
|
230
|
+
!open?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def flush
|
|
234
|
+
@socket.flush if @socket
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def read_fully(count)
|
|
238
|
+
begin
|
|
239
|
+
@socket.read_fully(count, @read_timeout)
|
|
240
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
|
241
|
+
@logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
|
|
242
|
+
close
|
|
243
|
+
@status = :not_connected
|
|
244
|
+
|
|
245
|
+
if @session.automatically_recover?
|
|
246
|
+
raise
|
|
247
|
+
else
|
|
248
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def read_ready?(timeout = nil)
|
|
254
|
+
io = IO.select([@socket].compact, nil, nil, timeout)
|
|
255
|
+
io && io[0].include?(@socket)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Exposed primarily for Bunny::Channel
|
|
259
|
+
# @private
|
|
260
|
+
def read_next_frame(opts = {})
|
|
261
|
+
header = read_fully(7)
|
|
262
|
+
type, channel, size = AMQ::Protocol::Frame.decode_header(header)
|
|
263
|
+
payload = if size > 0
|
|
264
|
+
read_fully(size)
|
|
265
|
+
else
|
|
266
|
+
''
|
|
267
|
+
end
|
|
268
|
+
frame_end = read_fully(1)
|
|
269
|
+
|
|
270
|
+
# 1) the size is miscalculated
|
|
271
|
+
if payload.bytesize != size
|
|
272
|
+
raise BadLengthError.new(size, payload.bytesize)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
|
276
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
|
277
|
+
AMQ::Protocol::Frame.new(type, payload, channel)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def self.reacheable?(host, port, timeout)
|
|
282
|
+
begin
|
|
283
|
+
s = Bunny::SocketImpl.open(host, port,
|
|
284
|
+
:connect_timeout => timeout)
|
|
285
|
+
|
|
286
|
+
true
|
|
287
|
+
rescue SocketError, Timeout::Error => _e
|
|
288
|
+
false
|
|
289
|
+
ensure
|
|
290
|
+
s.close if s
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def self.ping!(host, port, timeout)
|
|
295
|
+
raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def initialize_socket
|
|
299
|
+
begin
|
|
300
|
+
@socket = Bunny::SocketImpl.open(@host, @port,
|
|
301
|
+
:keepalive => @opts[:keepalive],
|
|
302
|
+
:connect_timeout => @connect_timeout)
|
|
303
|
+
rescue StandardError, ClientTimeout => e
|
|
304
|
+
@status = :not_connected
|
|
305
|
+
raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
@socket
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def maybe_initialize_socket
|
|
312
|
+
initialize_socket if !@socket || closed?
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def post_initialize_socket
|
|
316
|
+
@socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
|
|
317
|
+
wrap_in_tls_socket(@socket)
|
|
318
|
+
else
|
|
319
|
+
@socket
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
protected
|
|
324
|
+
|
|
325
|
+
def tls_enabled?(opts)
|
|
326
|
+
return !!opts[:tls] unless opts[:tls].nil?
|
|
327
|
+
return !!opts[:ssl] unless opts[:ssl].nil?
|
|
328
|
+
(opts[:port] == AMQ::Protocol::TLS_PORT) || false
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def tls_ca_certificates_paths_from(opts)
|
|
332
|
+
Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def tls_certificate_path_from(opts)
|
|
336
|
+
opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def tls_key_path_from(opts)
|
|
340
|
+
opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def tls_certificate_from(opts)
|
|
344
|
+
begin
|
|
345
|
+
read_client_certificate!
|
|
346
|
+
rescue MissingTLSCertificateFile => _e
|
|
347
|
+
inline_client_certificate_from(opts)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def tls_key_from(opts)
|
|
352
|
+
begin
|
|
353
|
+
read_client_key!
|
|
354
|
+
rescue MissingTLSKeyFile => _e
|
|
355
|
+
inline_client_key_from(opts)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
|
|
360
|
+
exts = peer_cert.extensions.map { |x| x.value }
|
|
361
|
+
# Subject Alternative Names
|
|
362
|
+
sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
|
|
363
|
+
|
|
364
|
+
msg = "#{prefix} subject: #{peer_cert.subject}, "
|
|
365
|
+
msg += "subject alternative names: #{sans.join(', ')}, "
|
|
366
|
+
msg += "issuer: #{peer_cert.issuer}, "
|
|
367
|
+
msg += "not valid after: #{peer_cert.not_after}, "
|
|
368
|
+
msg += "X.509 usage extensions: #{exts.join(', ')}"
|
|
369
|
+
|
|
370
|
+
msg
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
|
|
374
|
+
@logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def log_peer_certificate_chain_info(severity, chain)
|
|
378
|
+
chain.each do |cert|
|
|
379
|
+
self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def inline_client_certificate_from(opts)
|
|
384
|
+
opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def inline_client_key_from(opts)
|
|
388
|
+
opts[:tls_key] || opts[:ssl_key_string]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def prepare_tls_context(opts)
|
|
392
|
+
if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
|
|
393
|
+
opts[:verify_peer] = true
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# client cert/key paths
|
|
397
|
+
@tls_certificate_path = tls_certificate_path_from(opts)
|
|
398
|
+
@tls_key_path = tls_key_path_from(opts)
|
|
399
|
+
# client cert/key
|
|
400
|
+
@tls_certificate = tls_certificate_from(opts)
|
|
401
|
+
@tls_key = tls_key_from(opts)
|
|
402
|
+
@tls_certificate_store = opts[:tls_certificate_store]
|
|
403
|
+
|
|
404
|
+
@verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
|
|
405
|
+
|
|
406
|
+
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def as_boolean(val)
|
|
410
|
+
case val
|
|
411
|
+
when true then true
|
|
412
|
+
when false then false
|
|
413
|
+
when "true" then true
|
|
414
|
+
when "false" then false
|
|
415
|
+
else
|
|
416
|
+
!!val
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def wrap_in_tls_socket(socket)
|
|
421
|
+
raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket
|
|
422
|
+
raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
|
|
423
|
+
|
|
424
|
+
s = Bunny::SSLSocketImpl.new(socket, @tls_context)
|
|
425
|
+
|
|
426
|
+
# always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
|
|
427
|
+
# that TLS clients supporting the extensions can talk to TLS servers that do not
|
|
428
|
+
s.hostname = @host if s.respond_to?(:hostname)
|
|
429
|
+
|
|
430
|
+
s.sync_close = true
|
|
431
|
+
s
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def check_local_certificate_path!(s)
|
|
435
|
+
raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def check_local_key_path!(s)
|
|
439
|
+
raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def read_client_certificate!
|
|
443
|
+
if @tls_certificate_path
|
|
444
|
+
check_local_certificate_path!(@tls_certificate_path)
|
|
445
|
+
@tls_certificate = File.read(@tls_certificate_path)
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def read_client_key!
|
|
450
|
+
if @tls_key_path
|
|
451
|
+
check_local_key_path!(@tls_key_path)
|
|
452
|
+
@tls_key = File.read(@tls_key_path)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def initialize_tls_context(ctx, opts = {})
|
|
457
|
+
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
|
|
458
|
+
ctx.key = OpenSSL::PKey.read(@tls_key) if @tls_key
|
|
459
|
+
ctx.cert_store = if @tls_certificate_store
|
|
460
|
+
@tls_certificate_store
|
|
461
|
+
else
|
|
462
|
+
# this ivar exists so that this value can be exposed in the API
|
|
463
|
+
@tls_ca_certificates = tls_ca_certificates_paths_from(opts)
|
|
464
|
+
initialize_tls_certificate_store(@tls_ca_certificates)
|
|
465
|
+
end
|
|
466
|
+
should_silence_warnings = opts.fetch(:tls_silence_warnings, false)
|
|
467
|
+
|
|
468
|
+
if !@tls_certificate && !should_silence_warnings
|
|
469
|
+
@logger.warn <<-MSG
|
|
470
|
+
Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
|
|
471
|
+
certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
|
|
472
|
+
MSG
|
|
473
|
+
end
|
|
474
|
+
if @tls_certificate && !@tls_key
|
|
475
|
+
@logger.warn "Using TLS but no client private key is provided!"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
verify_mode = if @verify_peer
|
|
479
|
+
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
480
|
+
else
|
|
481
|
+
OpenSSL::SSL::VERIFY_NONE
|
|
482
|
+
end
|
|
483
|
+
@logger.debug { "Will use peer verification mode #{verify_mode}" }
|
|
484
|
+
ctx.verify_mode = verify_mode
|
|
485
|
+
|
|
486
|
+
if !@verify_peer && !should_silence_warnings
|
|
487
|
+
@logger.warn <<-MSG
|
|
488
|
+
Using TLS but peer hostname verification is disabled. This is convenient for local development
|
|
489
|
+
but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
|
|
490
|
+
MSG
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2
|
|
494
|
+
ctx.ssl_version = ssl_version if ssl_version
|
|
495
|
+
|
|
496
|
+
ctx
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def initialize_tls_certificate_store(certs)
|
|
500
|
+
cert_files = []
|
|
501
|
+
cert_inlines = []
|
|
502
|
+
certs.each do |cert|
|
|
503
|
+
# if it starts with / or C:/ then it's a file path that may or may not
|
|
504
|
+
# exist (e.g. a default OpenSSL path). MK.
|
|
505
|
+
if File.readable?(cert) || cert =~ /\A([a-z]:?)?\//i
|
|
506
|
+
cert_files.push(cert)
|
|
507
|
+
else
|
|
508
|
+
cert_inlines.push(cert)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
@logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
|
|
512
|
+
@logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
|
|
513
|
+
OpenSSL::X509::Store.new.tap do |store|
|
|
514
|
+
store.set_default_paths
|
|
515
|
+
cert_files.select { |path| File.readable?(path) }.
|
|
516
|
+
each { |path| store.add_file(path) }
|
|
517
|
+
cert_inlines.
|
|
518
|
+
each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def timeout_from(options)
|
|
523
|
+
options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Bunny
|
|
2
|
+
# Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could
|
|
3
|
+
# detect stale tags after connection recovery.
|
|
4
|
+
#
|
|
5
|
+
# @private
|
|
6
|
+
class VersionedDeliveryTag
|
|
7
|
+
attr_reader :tag
|
|
8
|
+
attr_reader :version
|
|
9
|
+
|
|
10
|
+
def initialize(tag, version)
|
|
11
|
+
raise ArgumentError.new("tag cannot be nil") unless tag
|
|
12
|
+
raise ArgumentError.new("version cannot be nil") unless version
|
|
13
|
+
|
|
14
|
+
@tag = tag.to_i
|
|
15
|
+
@version = version.to_i
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_i
|
|
19
|
+
@tag
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stale?(version)
|
|
23
|
+
raise ArgumentError.new("version cannot be nil") unless version
|
|
24
|
+
|
|
25
|
+
@version < version.to_i
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|