snmp4em 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
@@ -0,0 +1,16 @@
1
+ README
2
+ Rakefile
3
+ lib/snmp4em.rb
4
+ lib/snmp4em/common.rb
5
+ lib/snmp4em/handler.rb
6
+ lib/snmp4em/requests/snmp_get_request.rb
7
+ lib/snmp4em/requests/snmp_getbulk_request.rb
8
+ lib/snmp4em/requests/snmp_getnext_request.rb
9
+ lib/snmp4em/requests/snmp_set_request.rb
10
+ lib/snmp4em/requests/snmp_walk_request.rb
11
+ lib/snmp4em/snmp_connection.rb
12
+ lib/snmp4em/snmp_request.rb
13
+ lib/snmp4em/snmp_v1.rb
14
+ lib/snmp4em/snmp_v2.rb
15
+ snmp4em.gemspec
16
+ Manifest
data/README ADDED
@@ -0,0 +1,156 @@
1
+ = SNMP Library for EventMachine
2
+
3
+ == Summary
4
+
5
+ This library extends the Ruby-SNMP[http://snmplib.rubyforge.org] to use the asynchronous EventMachine[http://rubyeventmachine.com] library for added performance and scalability. This allows code to scale monitoring applications to access a very high number of devices without the need for complex asynchronous I/O handling.
6
+
7
+
8
+ == Features
9
+
10
+ The initial version 0.1.0 of this software supports:
11
+
12
+ * SNMP v1 and v2 only
13
+ * SNMP Get, GetNext, GetBulk (v2 only), Set, and Walk requests.
14
+ * Ability to query/set/walk multiple OIDs in parallel.
15
+
16
+ Future revisions of this library may support:
17
+
18
+ * Ability to act as an SNMP agent, responding to external queries.
19
+ * Ability to send/receive SNMP traps
20
+
21
+ There are no plans to support SNMP v3.
22
+
23
+
24
+ == Acknowledgements
25
+
26
+ * The SNMP packet processing is handled by the Ruby-SNMP[http://snmplib.rubyforge.org] library, by David Halliday
27
+ * EventMachine[http://rubyeventmachine.com], by Francis Cianfrocca and Aman Gupta
28
+ * All the helpful folks on the Freenode #eventmachine channel
29
+
30
+
31
+ == Examples
32
+
33
+ A few definitions:
34
+
35
+ OID_SYSTEM = "1.3.6.1.2.1.1"
36
+ OID_SYSNAME = "1.3.6.1.2.1.1.5.0"
37
+ OID_SYSLOCATION = "1.3.6.1.2.1.1.6.0"
38
+
39
+ A simple SNMP-GET:
40
+
41
+ EM::run do
42
+ snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
43
+
44
+ request = snmp.get([OID_SYSNAME, OID_SYSLOCATION])
45
+
46
+ request.callback do |response|
47
+ puts "System name = #{response[OID_SYSNAME]}"
48
+ puts "System location = #{response[OID_SYSLOCATION]}"
49
+ end
50
+
51
+ request.errback do |error|
52
+ puts "GET got error #{error}"
53
+ end
54
+ end
55
+
56
+ A simple SNMP-GETNEXT:
57
+
58
+ EM::run do
59
+ snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
60
+
61
+ request = snmp.getnext([OID_SYSNAME])
62
+
63
+ request.callback do |response|
64
+ r = response[OID_SYSNAME]
65
+ puts "The next OID is #{r[0]}, the next value is #{r[1]}"
66
+ end
67
+
68
+ request.errback do |error|
69
+ puts "GET got error #{error}"
70
+ end
71
+ end
72
+
73
+ A simple SNMP-SET:
74
+
75
+ EM::run do
76
+ snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
77
+
78
+ request = snmp.set({OID_SYSNAME => "My System Name", OID_SYSLOCATION => "My System Location"})
79
+
80
+ request.callback do |response|
81
+ if (response[OID_SYSNAME] == true)
82
+ puts "System name set successful"
83
+ else
84
+ puts "System name set unsuccessful: #{response[OID_SYSNAME]}"
85
+ end
86
+
87
+ if (response[OID_SYSLOCATION] == true)
88
+ puts "System location set successful"
89
+ else
90
+ puts "System location set unsuccessful: #{response[OID_SYSLOCATION]}"
91
+ end
92
+ end
93
+
94
+ request.errback do |error|
95
+ puts "GET got error #{error}"
96
+ end
97
+ end
98
+
99
+ A simple SNMP-WALK:
100
+
101
+ EM::run do
102
+ snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
103
+
104
+ request = snmp.walk([OID_SYSTEM])
105
+
106
+ request.callback do |response|
107
+ if (response[OID_SYSTEM].is_a? Array)
108
+ response[OID_SYSTEM].each do |vb|
109
+ puts "#{vb[0]} = #{vb[1]}"
110
+ end
111
+ else
112
+ puts "Got error: #{response[OID_SYSTEM]}"
113
+ end
114
+ end
115
+
116
+ request.errback do |error|
117
+ puts "GET got error #{error}"
118
+ end
119
+ end
120
+
121
+ A simple SNMP-GET-BULK:
122
+
123
+ EM::run do
124
+ snmp = SNMP4EM::SNMPv2.new(:host => "192.168.1.1")
125
+
126
+ request = snmp.getbulk([OID_SYSTEM])
127
+
128
+ request.callback do |response|
129
+ if (response[OID_SYSTEM].is_a? Array)
130
+ response[OID_SYSTEM].each do |vb|
131
+ puts "#{vb[0]} = #{vb[1]}"
132
+ end
133
+ else
134
+ puts "Got error: #{response[OID_SYSTEM]}"
135
+ end
136
+ end
137
+
138
+ request.errback do |error|
139
+ puts "GET got error #{error}"
140
+ end
141
+ end
142
+
143
+ == Change Log
144
+
145
+ Version 0.1.0:
146
+
147
+ * Initial deployment, ability to run get/getnext/set/walk requests in parallel
148
+
149
+ Version 0.2.0:
150
+
151
+ * Added support for SNMPv2, including GET-BULK operations
152
+
153
+ == Developer Contact Information
154
+
155
+ Norman Elton
156
+ normelton@gmail.com
@@ -0,0 +1,39 @@
1
+ module SNMP #:nodoc:
2
+ class Null #:nodoc:
3
+ class << self
4
+ def rubify
5
+ nil
6
+ end
7
+ end
8
+ end
9
+
10
+ class OctetString #:nodoc:
11
+ alias :rubify :to_s
12
+ end
13
+
14
+ class Integer #:nodoc:
15
+ alias :rubify :to_i
16
+ end
17
+
18
+ class ObjectId #:nodoc:
19
+ alias :rubify :to_s
20
+ end
21
+
22
+ class IpAddress #:nodoc:
23
+ alias :rubify :to_s
24
+ end
25
+
26
+ class ResponseError
27
+ attr_reader :error_status
28
+ alias :rubify :error_status #:nodoc:
29
+
30
+ def initialize(error_status) #:nodoc:
31
+ @error_status = error_status
32
+ end
33
+
34
+ # String representation of this error
35
+ def to_s
36
+ @error_status.to_s
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module SNMP4EM
2
+ module Handler #:nodoc:
3
+ def receive_data(data)
4
+ begin
5
+ message = SNMP::Message.decode(data)
6
+ rescue Exception => err
7
+ return
8
+ end
9
+
10
+ response = message.pdu
11
+ request_id = response.request_id
12
+
13
+ if (request = SnmpConnection.pending_requests.select{|r| r.snmp_id == request_id}.first)
14
+ request.handle_response(response)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,76 @@
1
+ module SNMP4EM
2
+
3
+ # Returned from SNMP4EM::SNMPv1.get(). This implements EM::Deferrable, so you can hang a callback()
4
+ # or errback() to retrieve the results.
5
+
6
+ class SnmpGetRequest < SnmpRequest
7
+ attr_reader :snmp_id
8
+
9
+ # For an SNMP-GET request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be fetched. As
10
+ # responses come back from the agent, this array will be pruned of any error-producing OIDs. Once no errors
11
+ # are returned, the @responses hash will be populated and returned.
12
+
13
+ def initialize(sender, oids, args = {}) #:nodoc:
14
+ @sender = sender
15
+
16
+ @timeout_timer = nil
17
+ @timeout_retries = @sender.retries
18
+ @error_retries = oids.size
19
+
20
+ @version = args[:version] || :SNMPv1
21
+ @return_raw = args[:return_raw] || false
22
+
23
+ @responses = Hash.new
24
+ @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
25
+
26
+ init_callbacks
27
+ send
28
+ end
29
+
30
+ def handle_response(response) #:nodoc:
31
+ if (response.error_status == :noError)
32
+ # No errors, populate the @responses object so it can be returned
33
+ response.each_varbind do |vb|
34
+ request_oid = @pending_oids.shift
35
+ @responses[request_oid.to_s] = vb.value
36
+ end
37
+
38
+ else
39
+ # Got an error, remove that oid from @pending_oids so we can try again
40
+ error_oid = @pending_oids.delete_at(response.error_index - 1)
41
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(response.error_status)
42
+ end
43
+
44
+ if (@pending_oids.empty? || @error_retries.zero?)
45
+ until @pending_oids.empty?
46
+ error_oid = @pending_oids.shift
47
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(:genErr)
48
+ end
49
+
50
+ @responses.each_pair do |oid, value|
51
+ @responses[oid] = value.rubify if (!@return_raw && value.respond_to?(:rubify))
52
+ end
53
+
54
+ # Send the @responses back to the requester, we're done!
55
+ succeed @responses
56
+ else
57
+ @error_retries -= 1
58
+ send
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def send
65
+ # Send the contents of @pending_oids
66
+
67
+ @snmp_id = generate_snmp_id
68
+
69
+ vb_list = SNMP::VarBindList.new(@pending_oids)
70
+ request = SNMP::GetRequest.new(@snmp_id, vb_list)
71
+ message = SNMP::Message.new(@version, @sender.community_ro, request)
72
+
73
+ super(message)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,103 @@
1
+ module SNMP4EM
2
+
3
+ # Returned from SNMP4EM::SNMPv1.get(). This implements EM::Deferrable, so you can hang a callback()
4
+ # or errback() to retrieve the results.
5
+
6
+ class SnmpGetBulkRequest < SnmpRequest
7
+ attr_reader :snmp_id
8
+
9
+ # For an SNMP-GETBULK request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be fetched. As
10
+ # responses come back from the agent, this array will be pruned of any error-producing OIDs. Once no errors
11
+ # are returned, the @responses hash will be populated and returned.
12
+
13
+ def initialize(sender, oids, args = {}) #:nodoc:
14
+ @sender = sender
15
+
16
+ @timeout_timer = nil
17
+ @timeout_retries = @sender.retries
18
+ @error_retries = oids.size
19
+
20
+ @version = args[:version] || :SNMPv2c
21
+ @return_raw = args[:return_raw] || false
22
+
23
+ @nonrepeaters = args[:nonrepeaters] || 0
24
+ @maxrepetitions = args[:maxrepetitions] || 10
25
+
26
+ @responses = Hash.new
27
+ @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
28
+
29
+ init_callbacks
30
+ send
31
+ end
32
+
33
+ def handle_response(response) #:nodoc:
34
+ if (response.error_status == :noError)
35
+ # No errors, populate the @responses object so it can be returned
36
+
37
+ @nonrepeaters.times do |i|
38
+ request_oid = @pending_oids.shift
39
+ response_vb = response.vb_list[i]
40
+
41
+ @responses[request_oid.to_s] = [[response_vb.name, response_vb.value]]
42
+ end
43
+
44
+ (@nonrepeaters ... response.vb_list.size).each do |i|
45
+ request_oid = @pending_oids[(i - @nonrepeaters) % @pending_oids.size]
46
+ response_vb = response.vb_list[i]
47
+
48
+ @responses[request_oid.to_s] ||= Array.new
49
+ @responses[request_oid.to_s] << [response_vb.name, response_vb.value]
50
+ end
51
+
52
+ @pending_oids.clear
53
+
54
+ else
55
+ # Got an error, remove that oid from @pending_oids so we can try again
56
+ error_oid = @pending_oids.delete_at(response.error_index - 1)
57
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(response.error_status)
58
+ end
59
+
60
+ if (@pending_oids.empty? || @error_retries.zero?)
61
+ until @pending_oids.empty?
62
+ error_oid = @pending_oids.shift
63
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(:genErr)
64
+ end
65
+
66
+ if (!@return_raw)
67
+ @responses.each_pair do |search_oid, values|
68
+ values.collect! do |oid_value|
69
+ oid_value[1] = oid_value[1].rubify if oid_value[1].respond_to?(:rubify)
70
+ oid_value
71
+ end
72
+
73
+ @responses[search_oid] = values
74
+ end
75
+ end
76
+
77
+ # Send the @responses back to the requester, we're done!
78
+ succeed @responses
79
+ else
80
+ @error_retries -= 1
81
+ send
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def send
88
+ # Send the contents of @pending_oids
89
+
90
+ @snmp_id = generate_snmp_id
91
+
92
+ vb_list = SNMP::VarBindList.new(@pending_oids)
93
+ request = SNMP::GetBulkRequest.new(@snmp_id, vb_list)
94
+
95
+ request.max_repetitions = @maxrepetitions
96
+ request.non_repeaters = @nonrepeaters
97
+
98
+ message = SNMP::Message.new(@version, @sender.community_ro, request)
99
+
100
+ super(message)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,80 @@
1
+ module SNMP4EM
2
+
3
+ # Returned from SNMP4EM::SNMPv1.getnext(). This implements EM::Deferrable, so you can hang a callback()
4
+ # or errback() to retrieve the results.
5
+
6
+ class SnmpGetNextRequest < SnmpRequest
7
+ attr_reader :snmp_id
8
+
9
+ # For an SNMP-GETNEXT request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be fetched. As
10
+ # responses come back from the agent, this array will be pruned of any error-producing OIDs. Once no errors
11
+ # are returned, the @responses hash will be populated and returned. The values of the hash will consist of a
12
+ # two-element array, in the form of [OID, VALUE], showing the next oid & value.
13
+
14
+ def initialize(sender, oids, args = {}) #:nodoc:
15
+ @sender = sender
16
+
17
+ @timeout_timer = nil
18
+ @timeout_retries = @sender.retries
19
+ @error_retries = oids.size
20
+
21
+ @return_raw = args[:return_raw] || false
22
+
23
+ @responses = Hash.new
24
+ @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
25
+
26
+ init_callbacks
27
+ send
28
+ end
29
+
30
+ def handle_response(response) #:nodoc:
31
+ if (response.error_status == :noError)
32
+ # No errors, populate the @responses object so it can be returned
33
+ response.each_varbind do |vb|
34
+ request_oid = @pending_oids.shift
35
+ @responses[request_oid.to_s] = [vb.name, vb.value]
36
+ end
37
+
38
+ else
39
+ # Got an error, remove that oid from @pending_oids so we can try again
40
+ error_oid = @pending_oids.delete_at(response.error_index - 1)
41
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(response.error_status)
42
+ end
43
+
44
+ if (@pending_oids.empty? || @error_retries.zero?)
45
+ until @pending_oids.empty?
46
+ error_oid = @pending_oids.shift
47
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(:genErr)
48
+ end
49
+
50
+ @responses.each_pair do |oid, value|
51
+ if value.is_a? Array
52
+ @responses[oid][1] = value[1].rubify if (!@return_raw && value[1].respond_to?(:rubify))
53
+ else
54
+ @responses[oid] = value.rubify if (!@return_raw && value.respond_to?(:rubify))
55
+ end
56
+ end
57
+
58
+ # Send the @responses back to the requester, we're done!
59
+ succeed @responses
60
+ else
61
+ @error_retries -= 1
62
+ send
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def send #:nodoc:
69
+ # Send the contents of @pending_oids
70
+
71
+ @snmp_id = generate_snmp_id
72
+
73
+ vb_list = SNMP::VarBindList.new(@pending_oids)
74
+ request = SNMP::GetNextRequest.new(@snmp_id, vb_list)
75
+ message = SNMP::Message.new(:SNMPv1, @sender.community_ro, request)
76
+
77
+ super(message)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,90 @@
1
+ module SNMP4EM
2
+
3
+ # Returned from SNMP4EM::SNMPv1.set(). This implements EM::Deferrable, so you can hang a callback()
4
+ # or errback() to retrieve the results.
5
+
6
+ class SnmpSetRequest < SnmpRequest
7
+ attr_reader :snmp_id
8
+
9
+ # For an SNMP-SET request, @pending_varbinds will by an SNMP::VarBindList, initially populated from the
10
+ # provided oids hash. Variables can be passed as specific types from the SNMP library (i.e. SNMP::IpAddress)
11
+ # or as ruby native objects, in which case they will be cast into the appropriate SNMP object. As responses
12
+ # are returned, the @pending_varbinds object will be pruned of any error-producing varbinds. Once no errors
13
+ # are produced, the @responses object is populated and returned.
14
+
15
+ def initialize(sender, oids, args = {}) #:nodoc:
16
+ @sender = sender
17
+
18
+ @timeout_timer = nil
19
+ @timeout_retries = @sender.retries
20
+ @error_retries = oids.size
21
+
22
+ @return_raw = args[:return_raw] || false
23
+
24
+ @responses = Hash.new
25
+ @pending_varbinds = SNMP::VarBindList.new()
26
+
27
+ oids.each_pair do |oid,value|
28
+ if value.is_a? Integer
29
+ snmp_value = SNMP::Integer.new(value)
30
+ elsif value.is_a? String
31
+ snmp_value = SNMP::OctetString.new(value)
32
+ end
33
+
34
+ @pending_varbinds << SNMP::VarBind.new(oid,snmp_value)
35
+ end
36
+
37
+ init_callbacks
38
+ send
39
+ end
40
+
41
+ def handle_response(response) #:nodoc:
42
+ if (response.error_status == :noError)
43
+ # No errors, set any remaining varbinds to true
44
+ response.each_varbind do |vb|
45
+ response_vb = @pending_varbinds.shift
46
+ @responses[response_vb.name.to_s] = true
47
+ end
48
+
49
+ else
50
+ # Got an error, remove that varbind from @pending_varbinds so we can try again
51
+ error_vb = @pending_varbinds.delete_at(response.error_index - 1)
52
+ @responses[error_vb.name.to_s] = SNMP::ResponseError.new(response.error_status)
53
+ end
54
+
55
+ if (@pending_varbinds.empty? || @error_retries.zero?)
56
+ until @pending_varbinds.empty?
57
+ error_vb = @pending_varbinds.shift
58
+ @responses[error_vb.name.to_s] = SNMP::ResponseError.new(:genErr)
59
+ end
60
+
61
+ unless @return_raw
62
+ @responses.each_pair do |oid, value|
63
+ @responses[oid] = value.rubify if value.respond_to?(:rubify)
64
+ end
65
+ end
66
+
67
+ # Send the @responses back to the requester, we're done!
68
+ succeed @responses
69
+ else
70
+ @error_retries -= 1
71
+
72
+ send
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def send
79
+ # Send the contents of @pending_varbinds
80
+
81
+ @snmp_id = generate_snmp_id
82
+
83
+ vb_list = SNMP::VarBindList.new(@pending_varbinds)
84
+ request = SNMP::SetRequest.new(@snmp_id, vb_list)
85
+ message = SNMP::Message.new(:SNMPv1, @sender.community_rw, request)
86
+
87
+ super(message)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,104 @@
1
+ module SNMP4EM
2
+
3
+ # Returned from SNMP4EM::SNMPv1.walk(). This implements EM::Deferrable, so you can hang a callback()
4
+ # or errback() to retrieve the results.
5
+
6
+ class SnmpWalkRequest < SnmpRequest
7
+ attr_reader :snmp_id
8
+
9
+ # For an SNMP-WALK request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be walked.
10
+ # Note that this library supports walking multiple OIDs in parallel. Once any error-producing OIDs are removed,
11
+ # a series of SNMP-GETNEXT requests are sent. Each response OID is checked to see if it begins with the walk OID.
12
+ # If so, the incoming OID/value pair is appended to the @response hash, and will be used in subsequent GETNEXT
13
+ # requests. Once an OID is returned that does not begin with the walk OID, that walk OID is removed from the
14
+ # @pending_oids array.
15
+
16
+ def initialize(sender, oids, args = {}) #:nodoc:
17
+ @sender = sender
18
+
19
+ @timeout_timer = nil
20
+ @timeout_retries = @sender.retries
21
+ @error_retries = oids.size
22
+
23
+ @return_raw = args[:return_raw] || false
24
+ @max_results = args[:max_results] || nil
25
+
26
+ @responses = Hash.new
27
+ @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
28
+
29
+ init_callbacks
30
+ send
31
+ end
32
+
33
+ def handle_response(response) #:nodoc:
34
+ oids_to_delete = []
35
+
36
+ if (response.error_status == :noError)
37
+ response.varbind_list.each_index do |i|
38
+ walk_oid = @pending_oids[i]
39
+ response_vb = response.varbind_list[i]
40
+
41
+ # Initialize the responses array if necessary
42
+ @responses[walk_oid.to_s] ||= Array.new
43
+
44
+ # If the incoming response-oid begins with the walk-oid, then append the pairing
45
+ # to the @response array. Otherwise, add it to the list of oids ready to delete
46
+ if (response_vb.name[0,walk_oid.length] == walk_oid)
47
+ @responses[walk_oid.to_s] << [response_vb.name, response_vb.value]
48
+ else
49
+ # If we were to delete thid oid from @pending_oids now, it would mess up the
50
+ # @pending_oids[i] call above.
51
+ oids_to_delete << walk_oid
52
+ end
53
+ end
54
+
55
+ @max_results -= 1 unless @max_results.nil?
56
+
57
+ else
58
+ error_oid = @pending_oids[response.error_index - 1]
59
+ oids_to_delete << error_oid
60
+
61
+ @responses[error_oid.to_s] = SNMP::ResponseError.new(response.error_status)
62
+ @error_retries -= 1
63
+ end
64
+
65
+ oids_to_delete.each{|oid| @pending_oids.delete oid}
66
+
67
+ if (@pending_oids.empty? || (@error_retries < 0) || (@max_results.to_i < 0))
68
+ @responses.each_pair do |oid, value|
69
+ @responses[oid] = value.rubify if (!@return_raw && value.respond_to?(:rubify))
70
+ end
71
+
72
+ # Send the @responses back to the requester, we're done!
73
+ succeed @responses
74
+ else
75
+ send
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def send
82
+ @snmp_id = generate_snmp_id
83
+
84
+ # This oids array will consist of all the oids that need to be getnext'd
85
+ oids = Array.new
86
+
87
+ @pending_oids.each do |oid|
88
+ # If there's already a response for this walk-oid, then use the last returned oid, otherwise
89
+ # start with the walk-oid.
90
+ if @responses.has_key?(oid.to_s)
91
+ oids << @responses[oid.to_s].last.first
92
+ else
93
+ oids << oid
94
+ end
95
+ end
96
+
97
+ vb_list = SNMP::VarBindList.new(oids)
98
+ request = SNMP::GetNextRequest.new(@snmp_id, vb_list)
99
+ message = SNMP::Message.new(:SNMPv1, @sender.community_ro, request)
100
+
101
+ super(message)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,43 @@
1
+ # The SNMP4EM library
2
+
3
+ module SNMP4EM
4
+ class SnmpConnection
5
+ @pending_requests = []
6
+ @socket = nil
7
+
8
+ class << self
9
+ attr_reader :pending_requests
10
+ attr_reader :socket
11
+
12
+ def init_socket #:nodoc:
13
+ if @socket.nil?
14
+ @socket = EM::open_datagram_socket("0.0.0.0", 0, Handler)
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_reader :host, :port, :timeout, :retries
20
+
21
+ # Creates a new object to communicate with SNMPv1 agents. Optionally pass in the following parameters:
22
+ # * _host_ - IP/hostname of remote agent (default: 127.0.0.1)
23
+ # * _port_ - UDP port on remote agent (default: 161)
24
+ # * _community_ - Community string to use (default: public)
25
+ # * _community_ro_ - Read-only community string to use for get/getnext/walk operations (default: public)
26
+ # * _community_rw_ - Read-write community string to use for set operations (default: public)
27
+ # * _timeout_ - Number of seconds to wait before a request times out (default: 1)
28
+ # * _retries_ - Number of retries before failing (default: 3)
29
+
30
+ def initialize(args = {})
31
+ @host = args[:host] || "127.0.0.1"
32
+ @port = args[:port] || 161
33
+ @timeout = args[:timeout] || 1
34
+ @retries = args[:retries] || 3
35
+
36
+ self.class.init_socket
37
+ end
38
+
39
+ def send(message) #:nodoc:
40
+ self.class.socket.send_datagram message.encode, @host, @port
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ module SNMP4EM
2
+ class SnmpRequest #:nodoc:
3
+ include EM::Deferrable
4
+
5
+ def generate_snmp_id
6
+ begin
7
+ snmp_id = rand(1073741823) # Largest Fixnum
8
+ end until (SnmpConnection.pending_requests.select{|r| r.snmp_id == snmp_id}.empty?)
9
+
10
+ return snmp_id
11
+ end
12
+
13
+ def init_callbacks
14
+ self.callback do
15
+ SnmpConnection.pending_requests.delete_if {|r| r.snmp_id == @snmp_id}
16
+ @timeout_timer.cancel
17
+ end
18
+
19
+ self.errback do
20
+ SnmpConnection.pending_requests.delete_if {|r| r.snmp_id == @snmp_id}
21
+ @timeout_timer.cancel
22
+ end
23
+ end
24
+
25
+ def send(msg)
26
+ @sender.send msg
27
+
28
+ @timeout_timer.cancel if @timeout_timer.is_a?(EM::Timer)
29
+
30
+ @timeout_timer = EM::Timer.new(@sender.timeout) do
31
+ if (@timeout_retries > 0)
32
+ send
33
+ @timeout_retries -= 1
34
+ else
35
+ fail("timeout")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ # The SNMP4EM library
2
+
3
+ module SNMP4EM
4
+ class SNMPv1 < SnmpConnection
5
+ attr_reader :community_ro, :community_rw
6
+
7
+ # Creates a new object to communicate with SNMPv1 agents. Optionally pass in the following parameters:
8
+ # * _host_ - IP/hostname of remote agent (default: 127.0.0.1)
9
+ # * _port_ - UDP port on remote agent (default: 161)
10
+ # * _community_ - Community string to use (default: public)
11
+ # * _community_ro_ - Read-only community string to use for get/getnext/walk operations (default: public)
12
+ # * _community_rw_ - Read-write community string to use for set operations (default: public)
13
+ # * _timeout_ - Number of seconds to wait before a request times out (default: 1)
14
+ # * _retries_ - Number of retries before failing (default: 3)
15
+
16
+ def initialize(args = {})
17
+ super(args)
18
+
19
+ @community_ro = args[:community_ro] || args[:community] || "public"
20
+ @community_rw = args[:community_rw] || args[:community] || "public"
21
+ end
22
+
23
+ # Sends an SNMP-GET request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
24
+ # which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the result will be
25
+ # a hash, mapping requested OID values to results.
26
+ #
27
+ # Optional arguments can be passed into _args_, including:
28
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
29
+
30
+ def get(oids, args = {})
31
+ request = SnmpGetRequest.new(self, oids, args.merge(:version => :SNMPv1))
32
+ SnmpConnection.pending_requests << request
33
+ return request
34
+ end
35
+
36
+ # Sends an SNMP-GETNEXT request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
37
+ # which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the result will be
38
+ # a hash, mapping requested OID values to two-element arrays consisting of [_next_oid_ , _next_value_]. Any values that produced an
39
+ # error will map to a symbol representing the error.
40
+ #
41
+ # Optional arguments can be passed into _args_, including:
42
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
43
+
44
+ def getnext(oids, args = {})
45
+ request = SnmpGetNextRequest.new(self, oids, args.merge(:version => :SNMPv1))
46
+ SnmpConnection.pending_requests << request
47
+ return request
48
+ end
49
+
50
+ # Sends an SNMP-SET request to the remote agent for all OIDs specified in the _oids_ hash. The hash must map OID values to requested
51
+ # values. Values can either be specified as Ruby native strings/integers, or as SNMP-specific classes (SNMP::IpAddress, etc).
52
+ # Returns a SnmpSetRequest object, which implements EM::Deferrable. From there, implement a callback/errback to fetch the result.
53
+ # On success, the result will be a hash, mapping requested OID values to the returned value from the agent. Any values that were stored
54
+ # successfully will map to _true_, otherwise, the value will map to a symbol representing the error.
55
+ #
56
+ # Optional arguments can be passed into _args_, including:
57
+ # * _return_raw_ - Return error objects as SNMP::ResponseError instead of a symbol
58
+
59
+ def set(oids, args = {})
60
+ request = SnmpSetRequest.new(self, oids, args.merge(:version => :SNMPv1))
61
+ SnmpConnection.pending_requests << request
62
+ return request
63
+ end
64
+
65
+ # Sends a series of SNMP-GETNEXT requests to simulate an SNMP "walk" operation. Given an OID prefix, the library will keep requesting the
66
+ # next OID until that returned OID does not begin with the requested prefix. This gives the ability to retrieve entire portions of the
67
+ # SNMP tree in one "operation". Multiple OID prefixes can be passed into the _oids_ array, and will be fetched in parallel. The function returns
68
+ # a SnmpWalkRequest object, which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the
69
+ # result will be a hash, mapping requested OID prefixes to the returned value. Successful walks will be mapped to an array of two-element arrays,
70
+ # each of which consists of [_oid_ , _value_]. Unsuccessful walks will be mapped to a symbol representing the error.
71
+
72
+ # Optional arguments can be passed into _args_, including:
73
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
74
+ # * _max_results_ - Maximum number of results to be returned for any single OID prefix (default: nil = unlimited)
75
+
76
+ def walk(oids, args = {})
77
+ request = SnmpWalkRequest.new(self, oids, args.merge(:version => :SNMPv1))
78
+ SnmpConnection.pending_requests << request
79
+ return request
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,104 @@
1
+ # The SNMP4EM library
2
+
3
+ module SNMP4EM
4
+ class SNMPv2 < SnmpConnection
5
+ attr_reader :community_ro, :community_rw
6
+
7
+ # Creates a new object to communicate with SNMPv1 agents. Optionally pass in the following parameters:
8
+ # * _host_ - IP/hostname of remote agent (default: 127.0.0.1)
9
+ # * _port_ - UDP port on remote agent (default: 161)
10
+ # * _community_ - Community string to use (default: public)
11
+ # * _community_ro_ - Read-only community string to use for get/getnext/walk operations (default: public)
12
+ # * _community_rw_ - Read-write community string to use for set operations (default: public)
13
+ # * _timeout_ - Number of seconds to wait before a request times out (default: 1)
14
+ # * _retries_ - Number of retries before failing (default: 3)
15
+
16
+ def initialize(args = {})
17
+ super(args)
18
+
19
+ @community_ro = args[:community_ro] || args[:community] || "public"
20
+ @community_rw = args[:community_rw] || args[:community] || "public"
21
+ end
22
+
23
+ # Sends an SNMP-GET request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
24
+ # which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the result will be
25
+ # a hash, mapping requested OID values to results.
26
+ #
27
+ # Optional arguments can be passed into _args_, including:
28
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
29
+
30
+ def get(oids, args = {})
31
+ request = SnmpGetRequest.new(self, oids, args.merge(:version => :SNMPv2c))
32
+ SnmpConnection.pending_requests << request
33
+ return request
34
+ end
35
+
36
+ # Sends an SNMP-GETNEXT request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
37
+ # which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the result will be
38
+ # a hash, mapping requested OID values to two-element arrays consisting of [_next_oid_ , _next_value_]. Any values that produced an
39
+ # error will map to a symbol representing the error.
40
+ #
41
+ # Optional arguments can be passed into _args_, including:
42
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
43
+
44
+ def getnext(oids, args = {})
45
+ request = SnmpGetNextRequest.new(self, oids, args.merge(:version => :SNMPv2c))
46
+ SnmpConnection.pending_requests << request
47
+ return request
48
+ end
49
+
50
+ # Sends an SNMP-SET request to the remote agent for all OIDs specified in the _oids_ hash. The hash must map OID values to requested
51
+ # values. Values can either be specified as Ruby native strings/integers, or as SNMP-specific classes (SNMP::IpAddress, etc).
52
+ # Returns a SnmpSetRequest object, which implements EM::Deferrable. From there, implement a callback/errback to fetch the result.
53
+ # On success, the result will be a hash, mapping requested OID values to the returned value from the agent. Any values that were stored
54
+ # successfully will map to _true_, otherwise, the value will map to a symbol representing the error.
55
+ #
56
+ # Optional arguments can be passed into _args_, including:
57
+ # * _return_raw_ - Return error objects as SNMP::ResponseError instead of a symbol
58
+
59
+ def set(oids, args = {})
60
+ request = SnmpSetRequest.new(self, oids, args.merge(:version => :SNMPv2c))
61
+ SnmpConnection.pending_requests << request
62
+ return request
63
+ end
64
+
65
+ # Sends a series of SNMP-GETNEXT requests to simulate an SNMP "walk" operation. Given an OID prefix, the library will keep requesting the
66
+ # next OID until that returned OID does not begin with the requested prefix. This gives the ability to retrieve entire portions of the
67
+ # SNMP tree in one "operation". Multiple OID prefixes can be passed into the _oids_ array, and will be fetched in parallel. The function returns
68
+ # a SnmpWalkRequest object, which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the
69
+ # result will be a hash, mapping requested OID prefixes to the returned value. Successful walks will be mapped to an array of two-element arrays,
70
+ # each of which consists of [_oid_ , _value_]. Unsuccessful walks will be mapped to a symbol representing the error.
71
+
72
+ # Optional arguments can be passed into _args_, including:
73
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
74
+ # * _max_results_ - Maximum number of results to be returned for any single OID prefix (default: nil = unlimited)
75
+
76
+ def walk(oids, args = {})
77
+ request = SnmpWalkRequest.new(self, oids, args.merge(:version => :SNMPv2c))
78
+ SnmpConnection.pending_requests << request
79
+ return request
80
+ end
81
+
82
+ # Sends an SNMPv2 GET-BULK request to fetch multiple OID-value pairings simultaneously. This produces similar results to an SNMP-WALK using a single
83
+ # request/response transaction (SNMP-WALK is actually an inefficient series of GET-NEXTs). Multiple OIDs can be passed into the _oids_ array. Two
84
+ # additional parameters control how this list is processed. Setting the parameter _nonrepeaters_ to value _N_ indicates that the first _N_ OIDs will
85
+ # fetch a single value. This is identical to running a single GET-NEXT for the OID. Any remaining OIDs will fetch multiple values. The number of values
86
+ # fetched is controlled by the parameter _maxrepetitions_. The function returns a SnmpGetBulkRequest object, which implements EM::Deferrable. From there,
87
+ # implement a callback/errback to fetch the result. On success, the result will be a hash, mapping requested OID prefixes to the returned value.
88
+ # Successful fetches will be mapped to an array of two-element arrays, each of which consists of [_oid_ , _value_]. Unsuccessful fetches will be mapped to a
89
+ # symbol representing the error.
90
+
91
+ # For more information, see http://tools.ietf.org/html/rfc1905#section-4.2.3
92
+
93
+ # Optional arguments can be passed into _args_, including:
94
+ # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
95
+ # * _nonrepeaters_ - Number of OIDs passed to which exactly one result will be returned
96
+ # * _maxrepetitions_ - Number of OID-value pairs to be returned for each OID
97
+
98
+ def getbulk(oids, args = {})
99
+ request = SnmpGetBulkRequest.new(self, oids, args.merge(:version => :SNMPv2c))
100
+ SnmpConnection.pending_requests << request
101
+ return request
102
+ end
103
+ end
104
+ end
data/lib/snmp4em.rb ADDED
@@ -0,0 +1,15 @@
1
+ $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
2
+
3
+ require 'eventmachine'
4
+ require 'snmp'
5
+ require 'snmp4em/common'
6
+ require 'snmp4em/handler'
7
+ require 'snmp4em/snmp_connection'
8
+ require 'snmp4em/snmp_v1'
9
+ require 'snmp4em/snmp_v2'
10
+ require 'snmp4em/snmp_request'
11
+ require 'snmp4em/requests/snmp_get_request'
12
+ require 'snmp4em/requests/snmp_getbulk_request'
13
+ require 'snmp4em/requests/snmp_getnext_request'
14
+ require 'snmp4em/requests/snmp_set_request'
15
+ require 'snmp4em/requests/snmp_walk_request'
data/snmp4em.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{snmp4em}
5
+ s.version = "0.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Norman Elton"]
9
+ s.date = %q{2010-05-16}
10
+ s.description = %q{A high-performance SNMP engine built on EventMachine and Ruby-SNMP, supporting SNMPv1 and SNMPv2 operations}
11
+ s.email = %q{normelton@gmail.com}
12
+ s.extra_rdoc_files = ["README"]
13
+ s.files = ["lib/snmp4em.rb", "lib/snmp4em/common.rb", "lib/snmp4em/handler.rb", "lib/snmp4em/requests/snmp_get_request.rb", "lib/snmp4em/requests/snmp_getbulk_request.rb", "lib/snmp4em/requests/snmp_getnext_request.rb", "lib/snmp4em/requests/snmp_set_request.rb", "lib/snmp4em/requests/snmp_walk_request.rb", "lib/snmp4em/snmp_request.rb", "lib/snmp4em/snmp_connection.rb", "lib/snmp4em/snmp_v1.rb", "lib/snmp4em/snmp_v2.rb", "snmp4em.gemspec", "Manifest"]
14
+ s.homepage = %q{http://github.com/normelton/snmp4em}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Snmp4em", "--main", "README"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{snmp4em}
18
+ s.rubygems_version = %q{1.3.6}
19
+ s.summary = %q{A high-performance SNMP engine built on EventMachine and Ruby-SNMP}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snmp4em
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Norman Elton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-05-16 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A high-performance SNMP engine built on EventMachine and Ruby-SNMP, supporting SNMPv1 and SNMPv2 operations
17
+ email: normelton@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - lib/snmp4em.rb
26
+ - lib/snmp4em/common.rb
27
+ - lib/snmp4em/handler.rb
28
+ - lib/snmp4em/requests/snmp_get_request.rb
29
+ - lib/snmp4em/requests/snmp_getbulk_request.rb
30
+ - lib/snmp4em/requests/snmp_getnext_request.rb
31
+ - lib/snmp4em/requests/snmp_set_request.rb
32
+ - lib/snmp4em/requests/snmp_walk_request.rb
33
+ - lib/snmp4em/snmp_request.rb
34
+ - lib/snmp4em/snmp_connection.rb
35
+ - lib/snmp4em/snmp_v1.rb
36
+ - lib/snmp4em/snmp_v2.rb
37
+ - snmp4em.gemspec
38
+ - Manifest
39
+ - README
40
+ has_rdoc: true
41
+ homepage: http://github.com/normelton/snmp4em
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --line-numbers
47
+ - --inline-source
48
+ - --title
49
+ - Snmp4em
50
+ - --main
51
+ - README
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "1.2"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project: snmp4em
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A high-performance SNMP engine built on EventMachine and Ruby-SNMP
73
+ test_files: []
74
+