snmp4em 0.2.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.
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
+