net-snmp2 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -1
- data/examples/agent.rb +1 -0
- data/examples/inform.rb +23 -0
- data/examples/trap_handler.rb +15 -0
- data/examples/{v1_trap_session.rb → v1_trap.rb} +2 -7
- data/examples/{v2_trap_session.rb → v2_trap.rb} +2 -7
- data/history.md +17 -0
- data/lib/net-snmp2.rb +1 -1
- data/lib/net/snmp/agent/agent.rb +44 -13
- data/lib/net/snmp/agent/provider.rb +56 -1
- data/lib/net/snmp/agent/provider_dsl.rb +11 -0
- data/lib/net/snmp/debug.rb +10 -19
- data/lib/net/snmp/error.rb +13 -6
- data/lib/net/snmp/listener.rb +2 -4
- data/lib/net/snmp/message.rb +52 -25
- data/lib/net/snmp/mib/mib.rb +2 -1
- data/lib/net/snmp/mib/node.rb +1 -1
- data/lib/net/snmp/oid.rb +5 -2
- data/lib/net/snmp/pdu.rb +15 -10
- data/lib/net/snmp/repl/manager_repl.rb +20 -7
- data/lib/net/snmp/session.rb +78 -45
- data/lib/net/snmp/trap_handler/trap_handler.rb +13 -9
- data/lib/net/snmp/trap_handler/v2_inform_dsl.rb +16 -0
- data/lib/net/snmp/trap_session.rb +64 -42
- data/lib/net/snmp/version.rb +1 -1
- data/lib/net/snmp/wrapper.rb +2 -8
- data/net-snmp2.gemspec +1 -1
- data/spec/README.md +36 -40
- data/spec/async_spec.rb +92 -87
- data/spec/em_spec.rb +3 -3
- data/spec/error_spec.rb +2 -2
- data/spec/fiber_spec.rb +8 -7
- data/spec/mib_spec.rb +8 -8
- data/spec/net-snmp_spec.rb +3 -3
- data/spec/oid_spec.rb +5 -5
- data/spec/spec_helper.rb +5 -0
- data/spec/sync_spec.rb +50 -56
- data/spec/test_agent.rb +150 -0
- data/spec/test_mib.rb +6 -0
- data/spec/thread_spec.rb +4 -4
- data/spec/trap_spec.rb +3 -12
- data/spec/utility_spec.rb +4 -4
- data/spec/wrapper_spec.rb +6 -6
- metadata +11 -6
- data/lib/net/snmp/agent/request_dispatcher.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 764e833cea5160e1dcd90749dc1e34497bc19cd7
|
4
|
+
data.tar.gz: dab11ed0e33675f7d37848b71a9eea33f94584e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9af6ba3d026772a98ee3d08d988859324b53f7e552e938ec400509f59a8fd0598c2716290af3a21b1c1374d8ff81c5b64f3905d21540b17ca5b46edf79b3d67d
|
7
|
+
data.tar.gz: 232ffa5c7ffdbe94a06cae89a545bc8795dfaf9f9ba0011c81edd6c85ebdefaa6ca4ab84bbcf20236aa18c5f2375efe76f48ff3ee93ee63e0b1ecce5c3d6404e
|
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
net-snmp2
|
2
2
|
=========
|
3
3
|
|
4
|
-
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/net-snmp2.svg)](http://badge.fury.io/rb/net-snmp2)
|
5
|
+
|
6
|
+
Just getting started? Checkout the [wiki](https://github.com/jbreeden/net-snmp2/wiki)
|
5
7
|
|
6
8
|
An object oriented Ruby wrapper around the C [netsnmp](http://www.net-snmp.org) libraries.
|
7
9
|
It provides classes for manager sessions, agents, pdus, varbinds, MIB inspection, and more.
|
8
10
|
|
9
11
|
The gem also includes some useful executables including:
|
12
|
+
|
10
13
|
- **net-snmp2**
|
11
14
|
+ An interactive REPL for inspecting the MIB, and acting as an SNMP manager.
|
12
15
|
+ Supports managing multiple agents at once.
|
@@ -18,6 +21,7 @@ The gem also includes some useful executables including:
|
|
18
21
|
Features
|
19
22
|
--------
|
20
23
|
|
24
|
+
* Improved Windows support over original net-snmp gem
|
21
25
|
* Supports SNMP versions 1, 2c, and 3
|
22
26
|
* Supports both synchronous and asynchronous calls
|
23
27
|
* Supports sending of snmpv1 traps and snmpv2 traps/informs using TrapSession
|
@@ -26,3 +30,16 @@ Features
|
|
26
30
|
* MIB support
|
27
31
|
* Convenience methods such as session.walk, session.get_columns, and session.table
|
28
32
|
* SNMP Agent support
|
33
|
+
* Trap handler support
|
34
|
+
|
35
|
+
Notes
|
36
|
+
-----
|
37
|
+
|
38
|
+
- TrapHandler & Agent support doesn't work on default Windows build of net-snmp libraries, due to certain functions not being exported in the dll.
|
39
|
+
+ There is a [net-snmp-clone](https://github.com/jbreeden/net-snmp-clone) repo with the fixes made if you'd like to build this from source.
|
40
|
+
+ Or, you can try my [pre-built DLL](https://github.com/jbreeden/net-snmp-clone/raw/master/win32/bin/release/netsnmp.dll) from the same repo.
|
41
|
+
+ I've submitted this as a [feature request](https://sourceforge.net/p/net-snmp/feature-requests/181/) & patch to the net-snmp team. If it works for you, please go make a comment indicating that on the feature request to help it get pulled into the core library.
|
42
|
+
- Original gem was using a different comment format... I've opted for markdown. The generated rdocs are probably garbage.
|
43
|
+
+ The [wiki](https://github.com/jbreeden/net-snmp2/wiki) has the best documentation for now.
|
44
|
+
+ All the specs are passing, so that's another good place to go for information.
|
45
|
+
+ There is also the built-in net-snmp2 program you can run. This is an interactive Pry shell that is very useful for digging around the code and getting a feel for how to do things.
|
data/examples/agent.rb
CHANGED
data/examples/inform.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << '../lib'
|
2
|
+
require 'net-snmp2'
|
3
|
+
|
4
|
+
# Initialize SNMP and give it a logger
|
5
|
+
Net::SNMP.init
|
6
|
+
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
|
+
Net::SNMP::Debug.logger.level = Logger::INFO
|
8
|
+
|
9
|
+
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '2c', :community => 'public')
|
10
|
+
|
11
|
+
extend Net::SNMP::Debug
|
12
|
+
|
13
|
+
100000.times do |i|
|
14
|
+
time "Inform ##{i}" do
|
15
|
+
puts "Inform #{i}: " + session.inform(
|
16
|
+
oid: '1.3.1.1',
|
17
|
+
uptime: 1000,
|
18
|
+
varbinds: [
|
19
|
+
{oid: '1.3.2.2', value: 'test'}
|
20
|
+
]
|
21
|
+
).to_s
|
22
|
+
end
|
23
|
+
end
|
data/examples/trap_handler.rb
CHANGED
@@ -35,6 +35,21 @@ handler = Net::SNMP::TrapHandler.new do
|
|
35
35
|
Varbinds: #{varbinds.map {|vb| "#{vb.oid.label}(#{vb.oid}) = #{vb.value}"}.join(', ')}
|
36
36
|
EOF
|
37
37
|
end
|
38
|
+
|
39
|
+
inform do
|
40
|
+
info <<-EOF
|
41
|
+
|
42
|
+
|
43
|
+
Got Inform
|
44
|
+
----------
|
45
|
+
|
46
|
+
Trap OID: #{trap_oid}
|
47
|
+
Uptime: #{uptime}
|
48
|
+
Varbinds: #{varbinds.map {|vb| "#{vb.oid.label}(#{vb.oid}) = #{vb.value}"}.join(', ')}
|
49
|
+
EOF
|
50
|
+
|
51
|
+
ok
|
52
|
+
end
|
38
53
|
end
|
39
54
|
|
40
55
|
# If the program gets the interrupt signal, tell the trap handler
|
@@ -6,19 +6,14 @@ Net::SNMP.init
|
|
6
6
|
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
7
|
Net::SNMP::Debug.logger.level = Logger::DEBUG
|
8
8
|
|
9
|
-
puts "Opening session"
|
10
9
|
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '1', :community => 'public')
|
11
10
|
|
12
|
-
puts "Sending trap"
|
13
|
-
|
14
11
|
100000.times do |i|
|
15
|
-
session.trap(
|
12
|
+
puts "#{i + 1}: " + session.trap(
|
16
13
|
enterprise: '1.3.1',
|
17
14
|
trap_type: 6,
|
18
15
|
specific_type: 1,
|
19
16
|
uptime: 1000,
|
20
17
|
agent_addr: '127.0.0.1'
|
21
|
-
)
|
18
|
+
).to_s
|
22
19
|
end
|
23
|
-
|
24
|
-
sleep 20
|
@@ -6,16 +6,11 @@ Net::SNMP.init
|
|
6
6
|
Net::SNMP::Debug.logger = Logger.new(STDOUT)
|
7
7
|
Net::SNMP::Debug.logger.level = Logger::DEBUG
|
8
8
|
|
9
|
-
puts "Opening session"
|
10
9
|
session = Net::SNMP::TrapSession.open(:peername => 'localhost', :version => '2c', :community => 'public')
|
11
10
|
|
12
|
-
puts "Sending trap"
|
13
|
-
|
14
11
|
100000.times do |i|
|
15
|
-
puts session.trap_v2(
|
12
|
+
puts "#{i + 1}: " + session.trap_v2(
|
16
13
|
oid: '1.3.1.1',
|
17
14
|
uptime: 1000
|
18
|
-
)
|
15
|
+
).to_s
|
19
16
|
end
|
20
|
-
|
21
|
-
sleep 20
|
data/history.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History
|
2
|
+
=======
|
3
|
+
|
4
|
+
Version 0.3.1
|
5
|
+
-------------
|
6
|
+
|
7
|
+
- Internal changes
|
8
|
+
+ Adding source_address & source_port to message object
|
9
|
+
* Changes the callback signature for Listener#on_message
|
10
|
+
+ Adding Message#respond method
|
11
|
+
* Used for creating a one-off session for responding to a message
|
12
|
+
and sending a single pdu over it. (Used in TrapHandler & Agent)
|
13
|
+
|
14
|
+
Version 0.3.0
|
15
|
+
-------------
|
16
|
+
|
17
|
+
- First gem release as net-snmp2
|
data/lib/net-snmp2.rb
CHANGED
@@ -21,6 +21,7 @@ require 'logger'
|
|
21
21
|
snmp/trap_handler/trap_handler
|
22
22
|
snmp/trap_handler/v1_trap_dsl
|
23
23
|
snmp/trap_handler/v2_trap_dsl
|
24
|
+
snmp/trap_handler/v2_inform_dsl
|
24
25
|
snmp/mib/mib
|
25
26
|
snmp/mib/node
|
26
27
|
snmp/mib/module
|
@@ -29,7 +30,6 @@ require 'logger'
|
|
29
30
|
snmp/agent/agent
|
30
31
|
snmp/agent/provider
|
31
32
|
snmp/agent/provider_dsl
|
32
|
-
snmp/agent/request_dispatcher
|
33
33
|
snmp/message
|
34
34
|
).each do |f|
|
35
35
|
require "#{File.dirname(__FILE__)}/net/#{f}"
|
data/lib/net/snmp/agent/agent.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module Net::SNMP
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# + Dispatches the messages to (sub) Agents
|
7
|
-
# + Serializes the response from the subagents and sends it to the caller
|
3
|
+
# Agents delegate messages from the Net::SNMP::Listener to
|
4
|
+
# providers, which supply the actual responses to the varbinds
|
5
|
+
# in the request. See Net::SNMP::ProviderDsl
|
8
6
|
|
9
7
|
class Agent
|
10
8
|
include Debug
|
@@ -19,10 +17,16 @@ module Net::SNMP
|
|
19
17
|
@listener.on_message(&method(:process_message))
|
20
18
|
end
|
21
19
|
|
20
|
+
# This method is called with a block to define a provider
|
21
|
+
# for some subtree of the MIB. When a request comes in with varbinds
|
22
|
+
# in that providers subtree, the provider's handlers will be called
|
23
|
+
# to generate the varbind to send back in the response PDU.
|
24
|
+
#
|
25
|
+
# Arguments
|
26
|
+
#
|
27
|
+
# - oid: The root OID of the MIB subtree this provider is responsible for.
|
28
|
+
# - &block: A block, to be instance_evaled on the new Provider.
|
22
29
|
def provide(oid = :all, &block)
|
23
|
-
# Need a trailing dot on the oid so we can avoid
|
24
|
-
# considering 1.3.22 a child of 1.3.2
|
25
|
-
oid = (oid.to_sym == :all || oid.end_with?('.')) ? oid : "#{oid}."
|
26
30
|
provider = Provider.new(oid)
|
27
31
|
provider.instance_eval(&block)
|
28
32
|
|
@@ -35,13 +39,40 @@ module Net::SNMP
|
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
# The callback given to the Listener object for handling an SNMP message.
|
43
|
+
# Calls `dispatch`, then sends the response PDU, if one is returned.
|
44
|
+
def process_message(message)
|
45
|
+
# TODO: May want to ignore some messages (say, if the community string is wrong)
|
46
|
+
message.respond(dispatch(message))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Collects responses for the given message from the available providers
|
50
|
+
def dispatch(message)
|
51
|
+
response_pdu = message.make_response_pdu
|
52
|
+
context = ProviderDsl.new
|
53
|
+
context.message = message
|
54
|
+
context.response_pdu = response_pdu
|
55
|
+
message.pdu.varbinds.each_with_index do |vb, index|
|
56
|
+
context.varbind = vb
|
57
|
+
provider = providers.find { |p| p.provides?(vb.oid) }
|
58
|
+
if provider
|
59
|
+
if message.pdu.command == Constants::SNMP_MSG_GETBULK && index < message.pdu.non_repeaters
|
60
|
+
handler = provider.handler_for(Constants::SNMP_MSG_GETNEXT)
|
61
|
+
else
|
62
|
+
handler = provider.handler_for(message)
|
63
|
+
end
|
64
|
+
if handler
|
65
|
+
context.instance_exec(&handler)
|
66
|
+
else
|
67
|
+
warn "No handler for command: #{message.pdu.command} @ #{vb.oid}"
|
68
|
+
context.no_such_object
|
69
|
+
end
|
70
|
+
else
|
71
|
+
warn "No provider for oid: #{vb.oid}"
|
72
|
+
context.no_such_object
|
43
73
|
end
|
44
74
|
end
|
75
|
+
response_pdu
|
45
76
|
end
|
46
77
|
|
47
78
|
end
|
@@ -5,6 +5,39 @@ module Net::SNMP
|
|
5
5
|
# of the subtree provided, and handlers for the various request types.
|
6
6
|
# The handlers are executed for each varbind of the incoming message
|
7
7
|
# individually in the context of a ProviderDsl object.
|
8
|
+
#
|
9
|
+
# Clients do not create Providers directly. Instead, they call `provide` on
|
10
|
+
# an Agent, passing in a block. Within the block they use the `get`, `get_next`,
|
11
|
+
# `get_bulk`, and `set` methods to configure handlers for these request types.
|
12
|
+
# Within these handlers, the DSL methods from the ProviderDsl class can be
|
13
|
+
# used to inspect the current request.
|
14
|
+
#
|
15
|
+
# Example
|
16
|
+
#
|
17
|
+
# require 'net-snmp2'
|
18
|
+
# agent = Net::SNMP::Agent.new
|
19
|
+
# agent.provide :all do
|
20
|
+
#
|
21
|
+
# get do
|
22
|
+
# reply get_value_somehow(oid)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# set do
|
26
|
+
# reply set_value_somehow(oid)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# get_next do
|
30
|
+
# reply get_next_value_somehow(oid)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# get_bulk do
|
34
|
+
# (0..max_repetitions).each do |i|
|
35
|
+
# add get_bulk_vlue_somehow(oid, i)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# end
|
40
|
+
# agent.listen(161)
|
8
41
|
|
9
42
|
class Provider
|
10
43
|
attr_accessor :oid,
|
@@ -13,10 +46,26 @@ module Net::SNMP
|
|
13
46
|
:get_next_handler,
|
14
47
|
:get_bulk_handler
|
15
48
|
|
49
|
+
# Creates a new Provider with `oid` as the root of its subtree
|
16
50
|
def initialize(oid)
|
17
|
-
|
51
|
+
if oid.kind_of?(Symbol)
|
52
|
+
unless oid == :all
|
53
|
+
raise "Cannot provide symbol '#{oid}'. (Did you mean to use :all?)"
|
54
|
+
end
|
55
|
+
@oid = oid
|
56
|
+
else
|
57
|
+
# Guarantee OID is in numeric form
|
58
|
+
@oid = OID.new(oid).to_s
|
59
|
+
end
|
18
60
|
end
|
19
61
|
|
62
|
+
# Gets the handler for the given command type from this provider.
|
63
|
+
#
|
64
|
+
# Arguments
|
65
|
+
#
|
66
|
+
# - command
|
67
|
+
# + As an integer, specifies the command type a handler is needed for.
|
68
|
+
# + May also be a Message or PDU object, from which the command type is read.
|
20
69
|
def handler_for(command)
|
21
70
|
# User might be tempted to just pass in the message, or pdu,
|
22
71
|
# if so, just pluck the command off of it.
|
@@ -40,6 +89,12 @@ module Net::SNMP
|
|
40
89
|
end
|
41
90
|
end
|
42
91
|
|
92
|
+
# Returns a boolean indicating whether this provider provides
|
93
|
+
# the given `oid`
|
94
|
+
def provides?(oid)
|
95
|
+
self.oid == :all || oid.to_s =~ %r[#{self.oid.to_s}(\.|$)]
|
96
|
+
end
|
97
|
+
|
43
98
|
[:get, :set, :get_next, :get_bulk].each do |request_type|
|
44
99
|
self.class_eval %Q[
|
45
100
|
def #{request_type}(&proc)
|
@@ -38,6 +38,11 @@ module Net::SNMP
|
|
38
38
|
varbind.oid
|
39
39
|
end
|
40
40
|
|
41
|
+
# The MIB variable name of the current varbind
|
42
|
+
def variable
|
43
|
+
oid.label
|
44
|
+
end
|
45
|
+
|
41
46
|
# The OID of the current varbind being processed as a string.
|
42
47
|
def oid_str
|
43
48
|
varbind.oid.to_s
|
@@ -101,6 +106,12 @@ module Net::SNMP
|
|
101
106
|
add_varbind(oid: oid, type: Constants::SNMP_NOSUCHINSTANCE)
|
102
107
|
end
|
103
108
|
|
109
|
+
# Adds a varbind to the response indicating that the END OF MIB has been reached
|
110
|
+
def end_of_mib
|
111
|
+
oid ||= varbind.oid
|
112
|
+
add_varbind(oid: oid, type: Constants::SNMP_ENDOFMIBVIEW)
|
113
|
+
end
|
114
|
+
|
104
115
|
# Adds a varbind to the response PDU.
|
105
116
|
# MUST use this method (or one that delegates to it)
|
106
117
|
# to set response varbinds on the response_pdu to make sure
|
data/lib/net/snmp/debug.rb
CHANGED
@@ -5,31 +5,22 @@ module Debug
|
|
5
5
|
attr_accessor :logger
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Debug.logger.warn msg if Debug.logger
|
18
|
-
end
|
19
|
-
|
20
|
-
def error(msg)
|
21
|
-
Debug.logger.error msg if Debug.logger
|
22
|
-
end
|
23
|
-
|
24
|
-
def fatal(msg)
|
25
|
-
Debug.logger.fatal msg if Debug.logger
|
8
|
+
[:debug, :info, :warn, :error, :fatal].each do |log_level|
|
9
|
+
self.module_eval %Q{
|
10
|
+
def #{log_level}(msg = nil, &block)
|
11
|
+
if Debug.logger && (Debug.logger.level <= Logger::#{log_level.upcase})
|
12
|
+
Debug.logger.send(:#{log_level}, msg)
|
13
|
+
block[] if block_given?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
}
|
26
17
|
end
|
27
18
|
|
28
19
|
def time(label, &block)
|
29
20
|
t_start = Time.now
|
30
21
|
block[]
|
31
22
|
t_end = Time.now
|
32
|
-
|
23
|
+
debug "#{label}: #{(t_end - t_start)*1000}ms"
|
33
24
|
end
|
34
25
|
|
35
26
|
def print_packet(packet)
|
data/lib/net/snmp/error.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Net
|
2
2
|
module SNMP
|
3
3
|
class Error < RuntimeError
|
4
|
+
include Debug
|
5
|
+
|
4
6
|
attr_accessor :status, :errno, :snmp_err, :snmp_msg
|
5
7
|
def initialize(opts = {})
|
6
8
|
@status = opts[:status]
|
@@ -14,12 +16,17 @@ module Net
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def print
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
message = <<-EOF
|
20
|
+
|
21
|
+
SNMP Error: #{self.class.to_s}
|
22
|
+
message = #{message}
|
23
|
+
status = #{@status}
|
24
|
+
errno = #{@errno}
|
25
|
+
snmp_err = #{@snmp_err}
|
26
|
+
snmp_msg = #{@snmp_msg}
|
27
|
+
EOF
|
28
|
+
|
29
|
+
error(message.gsub /^\s*/, '')
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
data/lib/net/snmp/listener.rb
CHANGED
@@ -37,13 +37,11 @@ module Net::SNMP
|
|
37
37
|
#
|
38
38
|
# The block provided will be called back for each message as follows:
|
39
39
|
#
|
40
|
-
# block[message
|
40
|
+
# block[message]
|
41
41
|
#
|
42
42
|
# Where
|
43
43
|
#
|
44
44
|
# - `message` is the parsed Net::SNMP::Message object
|
45
|
-
# - `from_address` is a string representing the address of the host sending the request
|
46
|
-
# - `from_port` is the port the host sent the request from
|
47
45
|
def on_message(&block)
|
48
46
|
@callback = block
|
49
47
|
end
|
@@ -62,7 +60,7 @@ module Net::SNMP
|
|
62
60
|
return if @killed
|
63
61
|
time "Message Processing" do
|
64
62
|
message = Message.parse(@packet)
|
65
|
-
@callback[message
|
63
|
+
@callback[message] if @callback
|
66
64
|
end
|
67
65
|
rescue Timeout::Error => timeout
|
68
66
|
next
|