net-snmp2 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +20 -0
  6. data/README.md +28 -0
  7. data/Rakefile +24 -0
  8. data/TODO.md +6 -0
  9. data/bin/mib2rb +129 -0
  10. data/bin/net-snmp2 +64 -0
  11. data/examples/agent.rb +104 -0
  12. data/examples/manager.rb +11 -0
  13. data/examples/trap_handler.rb +46 -0
  14. data/examples/v1_trap_session.rb +24 -0
  15. data/examples/v2_trap_session.rb +21 -0
  16. data/lib/net-snmp2.rb +85 -0
  17. data/lib/net/snmp.rb +27 -0
  18. data/lib/net/snmp/agent/agent.rb +48 -0
  19. data/lib/net/snmp/agent/provider.rb +51 -0
  20. data/lib/net/snmp/agent/provider_dsl.rb +124 -0
  21. data/lib/net/snmp/agent/request_dispatcher.rb +38 -0
  22. data/lib/net/snmp/constants.rb +287 -0
  23. data/lib/net/snmp/debug.rb +54 -0
  24. data/lib/net/snmp/dispatcher.rb +108 -0
  25. data/lib/net/snmp/error.rb +29 -0
  26. data/lib/net/snmp/listener.rb +76 -0
  27. data/lib/net/snmp/message.rb +142 -0
  28. data/lib/net/snmp/mib/mib.rb +67 -0
  29. data/lib/net/snmp/mib/module.rb +39 -0
  30. data/lib/net/snmp/mib/node.rb +122 -0
  31. data/lib/net/snmp/mib/templates.rb +48 -0
  32. data/lib/net/snmp/oid.rb +134 -0
  33. data/lib/net/snmp/pdu.rb +235 -0
  34. data/lib/net/snmp/repl/manager_repl.rb +243 -0
  35. data/lib/net/snmp/session.rb +560 -0
  36. data/lib/net/snmp/trap_handler/trap_handler.rb +42 -0
  37. data/lib/net/snmp/trap_handler/v1_trap_dsl.rb +44 -0
  38. data/lib/net/snmp/trap_handler/v2_trap_dsl.rb +38 -0
  39. data/lib/net/snmp/trap_session.rb +92 -0
  40. data/lib/net/snmp/utility.rb +10 -0
  41. data/lib/net/snmp/varbind.rb +57 -0
  42. data/lib/net/snmp/version.rb +5 -0
  43. data/lib/net/snmp/wrapper.rb +450 -0
  44. data/net-snmp2.gemspec +30 -0
  45. data/spec/README.md +105 -0
  46. data/spec/async_spec.rb +123 -0
  47. data/spec/em_spec.rb +23 -0
  48. data/spec/error_spec.rb +34 -0
  49. data/spec/fiber_spec.rb +41 -0
  50. data/spec/mib_spec.rb +68 -0
  51. data/spec/net-snmp_spec.rb +18 -0
  52. data/spec/oid_spec.rb +21 -0
  53. data/spec/spec_helper.rb +10 -0
  54. data/spec/sync_spec.rb +132 -0
  55. data/spec/thread_spec.rb +19 -0
  56. data/spec/trap_spec.rb +45 -0
  57. data/spec/utility_spec.rb +10 -0
  58. data/spec/wrapper_spec.rb +69 -0
  59. metadata +166 -0
@@ -0,0 +1,243 @@
1
+ module Net::SNMP
2
+ class ManagerRepl
3
+ include Net::SNMP
4
+ attr_accessor :sessions, :pdu
5
+ alias response pdu
6
+
7
+ NO_SESSION_PROMPT = <<EOF
8
+
9
+ No manager sessions active
10
+ --------------------------
11
+
12
+ - Use `manage(options)` to start a session.
13
+ - run `? manage` for more details
14
+
15
+ EOF
16
+
17
+ def self.start(session=nil)
18
+ pry_context = self.new(session)
19
+ Pry.config.prompt_name = "snmp"
20
+ pry_context.pry
21
+ end
22
+
23
+ def initialize(session=nil)
24
+ @sessions = []
25
+ if session
26
+ @sessions << session
27
+ end
28
+ end
29
+
30
+ # Adds a session for managing options[:peername] to the sessions list.
31
+ # - Note that all requests are sent to all active sessions, and their
32
+ # responses are all displayed under their peername.
33
+ # - You may close a session by calling `close(peername)`
34
+ #
35
+ # Arguments
36
+ # - options: A Hash or String object
37
+ # + As a Hash, accepts the following keys
38
+ # - peername: ADDRESS_OF_AGENT
39
+ # - port: PORT (defaults to 161)
40
+ # - version: '1'|'2c'|'3' (defaults to '2c')
41
+ # + As a string, implies the peername, with the version defaulting to '2c'
42
+ #
43
+ # Examples:
44
+ # manage('192.168.1.5')
45
+ # manage('192.168.1.5:162')
46
+ # manage(peername: '192.168.1.5', port: 161, version '2c')
47
+ # manage(peername: '192.168.1.5:161', version '2c')
48
+ def manage(options)
49
+ options = {:peername => options} if options.kind_of?(String)
50
+ sessions << Session.open(options)
51
+ "Opened session to manage peer: #{options[:peername]}"
52
+ end
53
+
54
+ # Close the session with the given `peername`
55
+ #
56
+ # Arguments
57
+ # - peername: May be a string, matching the peername, or an index.
58
+ # Run the `peer` command to see indexes & peernames for
59
+ # all active sessions
60
+ #
61
+ # Example:
62
+ # [7] net-snmp2> peers
63
+ #
64
+ # Currently Managing
65
+ # ------------------
66
+ #
67
+ # [0] localhost:161
68
+ # [1] localhost:162
69
+ # [2] localhost:163
70
+ #
71
+ # => nil
72
+ # [8] net-snmp2> close 'localhost:163'
73
+ # => "Closed session for peer: localhost:163"
74
+ # [9] net-snmp2> close 1
75
+ # => "Closed session for peer: localhost:162"
76
+ def close(peername)
77
+ case peername
78
+ when Numeric
79
+ index = peername
80
+ if index < (sessions.length)
81
+ session = sessions[index]
82
+ session.close
83
+ sessions.delete(session)
84
+ "Closed session for peer: #{session.peername}"
85
+ else
86
+ "Invalid session index #{index}. Use `peers` to list active sessions."
87
+ end
88
+ when String
89
+ session = sessions.find { |sess| sess.peername.to_s == peername.to_s }
90
+ if (session)
91
+ session.close
92
+ sessions.delete(session)
93
+ "Closed session for peer: #{session.peername}"
94
+ else
95
+ "No session active for '#{peername}'. Use `peers` to list active sessions."
96
+ end
97
+ end
98
+ end
99
+
100
+ # List the peers currently being managed
101
+ def peers
102
+ if sessions.count > 0
103
+ puts
104
+ puts "Currently Managing"
105
+ puts "------------------"
106
+ puts
107
+ sessions.each_with_index do |session, i|
108
+ puts "[#{i}] #{session.peername}"
109
+ end
110
+ puts
111
+ else
112
+ puts "No active sessions"
113
+ end
114
+ nil
115
+ end
116
+
117
+ # Translates a numerical oid to it's MIB name, or a name to numerical oid
118
+ #
119
+ # Arguments
120
+ # - oid: Either a string, as the numerical OID or MIB variable name,
121
+ # or an OID object
122
+ def translate(oid)
123
+ MIB.translate(oid)
124
+ end
125
+
126
+ # Prints a description of a MIB variable
127
+ #
128
+ # Arguments
129
+ # - oid: May be either a numeric OID, or MIB variable name
130
+ def describe(oid)
131
+ nodes = [MIB[oid]]
132
+ puts ERB.new(Net::SNMP::MIB::Templates::DESCRIBE, nil, "-").result(binding)
133
+ end
134
+
135
+ # Prints a description of a the MIB subtree starting at root `oid`
136
+ #
137
+ # Arguments
138
+ # - oid: May be either a numeric OID, or MIB variable name
139
+ def describe_tree(oid)
140
+ root = MIB[oid]
141
+ nodes = [root] + root.descendants.to_a
142
+ puts ERB.new(Net::SNMP::MIB::Templates::DESCRIBE, nil, "-").result(binding)
143
+ end
144
+
145
+ # Issue an SNMP GET Request to all active peers
146
+ #
147
+ # Arguments
148
+ # - oids: A single oid, or an array of oids
149
+ def get(oids, options = {})
150
+ each_session do |session|
151
+ @pdu = session.get(oids)
152
+ @pdu.print
153
+ puts "ERROR" if @pdu.error?
154
+ end
155
+ "GET"
156
+ end
157
+
158
+ # Issue an SNMP GETNEXT Request to all active peers
159
+ #
160
+ # Arguments
161
+ # - oids: A single oid, or an array of oids
162
+ def get_next(oids, options = {})
163
+ each_session do |session|
164
+ @pdu = session.get_next(oids)
165
+ @pdu.print
166
+ puts "ERROR" if @pdu.error?
167
+ end
168
+ "GETNEXT"
169
+ end
170
+
171
+ # Issue an SNMP GETBULK Request to all active peers
172
+ #
173
+ # Arguments
174
+ # - oids: A single oid, or an array of oids
175
+ # - options: A Hash accepting the typical options keys for a request, plus
176
+ # + non_repeaters: The number of non-repeated oids in the request
177
+ # + max_repititions: The maximum repititions to return for all repeaters
178
+ # Note that the non-repeating varbinds must be added first.
179
+ def get_bulk(oids, options = {})
180
+ each_session do |session|
181
+ @pdu = session.get_bulk(oids)
182
+ @pdu.print
183
+ puts "ERROR" if @pdu.error?
184
+ end
185
+ "GETBULK"
186
+ end
187
+
188
+ # Performs a walk on all active peers for each oid provided
189
+ #
190
+ # Arguments
191
+ # - oids: A single oid, or an array of oids
192
+ def walk(oids, options = {})
193
+ each_session do |session|
194
+ session.walk(oids).each { |oid, value|
195
+ puts "#{MIB.translate(oid)}(#{oid}) = #{value}"
196
+ }
197
+ end
198
+ "WALK"
199
+ end
200
+
201
+ # Issue an SNMP Set Request to all active peers
202
+ #
203
+ # Arguments
204
+ # - varbinds: An single varbind, or an array of varbinds, each of which may be
205
+ # + An Array of length 3 `[oid, type, value]`
206
+ # + An Array of length 2 `[oid, value]`
207
+ # + Or a Hash `{oid: oid, type: type, value: value}`
208
+ # * Hash syntax is the same as supported by PDU.add_varbind
209
+ # * If type is not supplied, it is infered by the value
210
+ def set(varbinds, options = {})
211
+ each_session do |session|
212
+ @pdu = session.set(varbinds)
213
+ @pdu.print
214
+ puts "ERROR" if @pdu.error?
215
+ end
216
+ "SET"
217
+ end
218
+
219
+ private
220
+
221
+ def each_session(&block)
222
+ unless sessions.count > 0
223
+ puts NO_SESSION_PROMPT
224
+ raise 'No active session'
225
+ end
226
+
227
+ sessions.each_with_index do |session, i|
228
+ name = "#{session.peername}"
229
+ hrule = (['-'] * name.length).join ''
230
+ # Add a blank line before the first result
231
+ puts if i == 0
232
+ puts hrule
233
+ puts name
234
+ puts hrule
235
+ block[session]
236
+ # Add a blank line after each result
237
+ puts
238
+ end
239
+
240
+ nil
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,560 @@
1
+ require 'thread'
2
+ require 'forwardable'
3
+ require 'pp'
4
+ module Net
5
+ module SNMP
6
+ # Provides an API for managing SNMP agents
7
+ class Session
8
+ extend Forwardable
9
+ include Net::SNMP::Debug
10
+ attr_accessor :struct, :callback, :requests, :peername, :port, :community
11
+ attr_reader :version
12
+ def_delegator :@struct, :pointer
13
+ @lock = Mutex.new
14
+ @sessions = {}
15
+
16
+ class << self
17
+ attr_accessor :sessions, :lock
18
+
19
+ # Open a new session. Accepts a block which yields the session.
20
+ #
21
+ # Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'public') do |sess|
22
+ # pdu = sess.get(["sysDescr.0"])
23
+ # pdu.print
24
+ # end
25
+ #
26
+ # Arguments
27
+ # - options: A Hash or String object
28
+ # + As a Hash, supports the following keys
29
+ # - peername: hostname
30
+ # - community: snmp community string. Default is public
31
+ # - version: snmp version. Possible values include 1, '2c', and 3. Default is 1.
32
+ # - timeout: snmp timeout in seconds
33
+ # - retries: snmp retries. default = 5
34
+ # - security_level: SNMPv3 only. default = Net::SNMP::Constants::SNMP_SEC_LEVEL_NOAUTH
35
+ # - auth_protocol: SNMPv3 only. default is nil (usmNoAuthProtocol). Possible values include :md5, :sha1, and nil
36
+ # - priv_protocol: SNMPv3 only. default is nil (usmNoPrivProtocol). Possible values include :des, :aes, and nil
37
+ # - context: SNMPv3 only.
38
+ # - username: SNMPv3 only.
39
+ # - auth_password: SNMPv3 only.
40
+ # - priv_password: SNMPv3 only.
41
+ #
42
+ # Returns a new Net::SNMP::Session
43
+ def open(options = {})
44
+ session = new(options)
45
+ if Net::SNMP::thread_safe
46
+ Net::SNMP::Session.lock.synchronize {
47
+ Net::SNMP::Session.sessions[session.sessid] = session
48
+ }
49
+ else
50
+ Net::SNMP::Session.sessions[session.sessid] = session
51
+ end
52
+ if block_given?
53
+ yield session
54
+ end
55
+ session
56
+ end
57
+ end
58
+
59
+ def initialize(options = {})
60
+ options = {:peername => options} if options.kind_of?(String)
61
+ @timeout = options[:timeout] || 1
62
+ @retries = options[:retries] || 5
63
+ @requests = {}
64
+ @peername = options[:peername] || 'localhost'
65
+ # If the port is supplied in the peername, don't
66
+ # worry about the port option (avoids appending two port numbers)
67
+ unless @peername[':']
68
+ options[:port] ||= 161
69
+ @port = options[:port]
70
+ @peername = "#{@peername}:#{options[:port]}"
71
+ end
72
+ @community = options[:community] || "public"
73
+ options[:community_len] = @community.length
74
+ options[:version] ||= Constants::SNMP_VERSION_2c
75
+ @version = options[:version]
76
+ @sess = Wrapper::SnmpSession.new(nil)
77
+ Wrapper.snmp_sess_init(@sess.pointer)
78
+ @sess.community = FFI::MemoryPointer.from_string(@community)
79
+ @sess.community_len = @community.length
80
+ @sess.peername = FFI::MemoryPointer.from_string(@peername)
81
+ @sess.version = case @version.to_s
82
+ when '1'
83
+ Constants::SNMP_VERSION_1
84
+ when '2', '2c'
85
+ Constants::SNMP_VERSION_2c
86
+ when '3'
87
+ Constants::SNMP_VERSION_3
88
+ else
89
+ Constants::SNMP_VERSION_1
90
+ end
91
+ debug "setting timeout = #{@timeout} retries = #{@retries}"
92
+ @sess.timeout = @timeout * 1000000
93
+ @sess.retries = @retries
94
+
95
+ if @sess.version == Constants::SNMP_VERSION_3
96
+ @sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
97
+ @sess.securityAuthProto = case options[:auth_protocol]
98
+ when :sha1
99
+ OID.new("1.3.6.1.6.3.10.1.1.3").pointer
100
+ when :md5
101
+ OID.new("1.3.6.1.6.3.10.1.1.2").pointer
102
+ when nil
103
+ OID.new("1.3.6.1.6.3.10.1.1.1").pointer
104
+ end
105
+ @sess.securityPrivProto = case options[:priv_protocol]
106
+ when :aes
107
+ OID.new("1.3.6.1.6.3.10.1.2.4").pointer
108
+ when :des
109
+ OID.new("1.3.6.1.6.3.10.1.2.2").pointer
110
+ when nil
111
+ OID.new("1.3.6.1.6.3.10.1.2.1").pointer
112
+ end
113
+
114
+ @sess.securityAuthProtoLen = 10
115
+ @sess.securityAuthKeyLen = Constants::USM_AUTH_KU_LEN
116
+
117
+ @sess.securityPrivProtoLen = 10
118
+ @sess.securityPrivKeyLen = Constants::USM_PRIV_KU_LEN
119
+
120
+
121
+ if options[:context]
122
+ @sess.contextName = FFI::MemoryPointer.from_string(options[:context])
123
+ @sess.contextNameLen = options[:context].length
124
+ end
125
+
126
+ # Do not generate_Ku, unless we're Auth or AuthPriv
127
+ unless @sess.securityLevel == Constants::SNMP_SEC_LEVEL_NOAUTH
128
+ options[:auth_password] ||= options[:password] # backward compatability
129
+ if options[:username].nil? or options[:auth_password].nil?
130
+ raise Net::SNMP::Error.new "SecurityLevel requires username and password"
131
+ end
132
+ if options[:username]
133
+ @sess.securityName = FFI::MemoryPointer.from_string(options[:username])
134
+ @sess.securityNameLen = options[:username].length
135
+ end
136
+
137
+ auth_len_ptr = FFI::MemoryPointer.new(:size_t)
138
+ auth_len_ptr.write_int(Constants::USM_AUTH_KU_LEN)
139
+ auth_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
140
+ @sess.securityAuthProtoLen,
141
+ options[:auth_password],
142
+ options[:auth_password].length,
143
+ @sess.securityAuthKey,
144
+ auth_len_ptr)
145
+ @sess.securityAuthKeyLen = auth_len_ptr.read_int
146
+
147
+ if @sess.securityLevel == Constants::SNMP_SEC_LEVEL_AUTHPRIV
148
+ priv_len_ptr = FFI::MemoryPointer.new(:size_t)
149
+ priv_len_ptr.write_int(Constants::USM_PRIV_KU_LEN)
150
+
151
+ # NOTE I know this is handing off the AuthProto, but generates a proper
152
+ # key for encryption, and using PrivProto does not.
153
+ priv_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
154
+ @sess.securityAuthProtoLen,
155
+ options[:priv_password],
156
+ options[:priv_password].length,
157
+ @sess.securityPrivKey,
158
+ priv_len_ptr)
159
+ @sess.securityPrivKeyLen = priv_len_ptr.read_int
160
+ end
161
+
162
+ unless auth_key_result == Constants::SNMPERR_SUCCESS and priv_key_result == Constants::SNMPERR_SUCCESS
163
+ Wrapper.snmp_perror("netsnmp")
164
+ end
165
+ end
166
+ end
167
+ # General callback just takes the pdu, calls the session callback if any, then the request specific callback.
168
+
169
+ @struct = Wrapper.snmp_sess_open(@sess.pointer)
170
+ end
171
+
172
+ # Close the snmp session and free associated resources.
173
+ def close
174
+ if Net::SNMP.thread_safe
175
+ self.class.lock.synchronize {
176
+ Wrapper.snmp_sess_close(@struct)
177
+ self.class.sessions.delete(self.sessid)
178
+ }
179
+ else
180
+ Wrapper.snmp_sess_close(@struct)
181
+ self.class.sessions.delete(self.sessid)
182
+ end
183
+ end
184
+
185
+ # Issue an SNMP GET Request.
186
+ # See #send_pdu
187
+ def get(oidlist, options = {}, &block)
188
+ pdu = PDU.new(Constants::SNMP_MSG_GET)
189
+ oidlist = [oidlist] unless oidlist.kind_of?(Array)
190
+ oidlist.each do |oid|
191
+ pdu.add_varbind(:oid => oid)
192
+ end
193
+ send_pdu(pdu, options, &block)
194
+ end
195
+
196
+ # Issue an SNMP GETNEXT Request
197
+ # See #send_pdu
198
+ def get_next(oidlist, options = {}, &block)
199
+ pdu = PDU.new(Constants::SNMP_MSG_GETNEXT)
200
+ oidlist = [oidlist] unless oidlist.kind_of?(Array)
201
+ oidlist.each do |oid|
202
+ pdu.add_varbind(:oid => oid)
203
+ end
204
+ send_pdu(pdu, options, &block)
205
+ end
206
+
207
+ # Issue an SNMP GETBULK Request
208
+ # Supports typical options, plus
209
+ # - `:non_repeaters` The number of non-repeated oids in the request
210
+ # - `:max_repititions` The maximum repititions to return for all repeaters
211
+ # Note that the non-repeating varbinds must be added first.
212
+ # See #send_pdu
213
+ def get_bulk(oidlist, options = {}, &block)
214
+ pdu = PDU.new(Constants::SNMP_MSG_GETBULK)
215
+ oidlist = [oidlist] unless oidlist.kind_of?(Array)
216
+ oidlist.each do |oid|
217
+ pdu.add_varbind(:oid => oid)
218
+ end
219
+ pdu.non_repeaters = options[:non_repeaters] || 0
220
+ pdu.max_repetitions = options[:max_repetitions] || 10
221
+ send_pdu(pdu, options, &block)
222
+ end
223
+
224
+ # Issue an SNMP Set Request.
225
+ # - vb_list: An single varbind, or an array of varbinds, each of which may be
226
+ # + An Array of length 3 `[oid, type, value]`
227
+ # + An Array of length 2 `[oid, value]`
228
+ # + Or a Hash `{oid: oid, type: type, value: value}`
229
+ # * Hash syntax is the same as supported by PDU.add_varbind
230
+ # * If type is not supplied, it is infered by the value
231
+ # See #send_pdu
232
+ def set(vb_list, options = {}, &block)
233
+ pdu = PDU.new(Constants::SNMP_MSG_SET)
234
+
235
+ # Normalize input to an array if a single varbind is supplied
236
+ if vb_list.kind_of?(Hash) || (vb_list.kind_of?(Enumerable) && !vb_list.first.kind_of?(Enumerable))
237
+ vb_list = [vb_list]
238
+ end
239
+
240
+ vb_list.each do |vb|
241
+ if vb.kind_of?(Hash)
242
+ pdu.add_varbind(vb)
243
+ elsif vb.kind_of?(Enumerable) && vb.length == 3
244
+ pdu.add_varbind(:oid => vb[0], :type => vb[1], :value => vb[2])
245
+ elsif vb.kind_of?(Enumerable) && vb.length == 2
246
+ pdu.add_varbind(:oid => vb[0], :value => vb[1])
247
+ else
248
+ raise "Invalid varbind: #{vb}"
249
+ end
250
+ end
251
+ send_pdu(pdu, options, &block)
252
+ end
253
+
254
+ # Proxy getters to the C struct representing the session
255
+ def method_missing(m, *args)
256
+ if @struct.respond_to?(m)
257
+ @struct.send(m, *args)
258
+ else
259
+ super
260
+ end
261
+ end
262
+
263
+ def default_max_repeaters
264
+ # We could do something based on transport here. 25 seems safe
265
+ 25
266
+ end
267
+
268
+ # Raise a NET::SNMP::Error with the session attached
269
+ def error(msg, options = {})
270
+ #Wrapper.snmp_sess_perror(msg, @sess.pointer)
271
+ err = Error.new({:session => self}.merge(options))
272
+ raise err, msg
273
+ end
274
+
275
+
276
+ # Check the session for SNMP responses from asynchronous SNMP requests
277
+ # This method will check for new responses and call the associated
278
+ # response callbacks.
279
+ # +timeout+ A timeout of nil indicates a poll and will return immediately.
280
+ # A value of false will block until data is available. Otherwise, pass
281
+ # the number of seconds to block.
282
+ # Returns the number of file descriptors handled.
283
+ def select(timeout = nil)
284
+ if @fdset
285
+ # Re-use the same fd set buffer to avoid
286
+ # multiple allocation overhead.
287
+ @fdset.clear
288
+ else
289
+ # 8K should be plenty of space
290
+ @fdset = FFI::MemoryPointer.new(1024 * 8)
291
+ end
292
+
293
+ num_fds = FFI::MemoryPointer.new(:int)
294
+ tv_sec = timeout ? timeout.round : 0
295
+ tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
296
+ tval = Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
297
+ block = FFI::MemoryPointer.new(:int)
298
+ if timeout.nil?
299
+ block.write_int(0)
300
+ else
301
+ block.write_int(1)
302
+ end
303
+
304
+ Wrapper.snmp_sess_select_info(@struct, num_fds, @fdset, tval.pointer, block )
305
+ tv = (timeout == false ? nil : tval)
306
+ #debug "Calling select #{Time.now}"
307
+ num_ready = FFI::LibC.select(num_fds.read_int, @fdset, nil, nil, tv)
308
+ #debug "Done select #{Time.now}"
309
+ if num_ready > 0
310
+ Wrapper.snmp_sess_read(@struct, @fdset)
311
+ elsif num_ready == 0
312
+ Wrapper.snmp_sess_timeout(@struct)
313
+ elsif num_ready == -1
314
+ # error. check snmp_error?
315
+ error("select")
316
+ else
317
+ error("wtf is wrong with select?")
318
+ end
319
+ num_ready
320
+ end
321
+
322
+ alias :poll :select
323
+
324
+ # Issue repeated getnext requests on each oid passed in until
325
+ # the result is no longer a child. Returns a hash with the numeric
326
+ # oid strings as keys.
327
+ # XXX work in progress. only works synchronously (except with EM + fibers).
328
+ # Need to do better error checking and use getbulk when avaiable.
329
+ def walk(oidlist, options = {})
330
+ oidlist = [oidlist] unless oidlist.kind_of?(Array)
331
+ oidlist = oidlist.map {|o| o.kind_of?(OID) ? o : OID.new(o)}
332
+ all_results = {}
333
+ base_list = oidlist
334
+ while(!oidlist.empty? && pdu = get_next(oidlist, options))
335
+ debug "================ Walk: Get Next ====================="
336
+ debug "base_list: \n#{base_list.map { |o| " - #{o.to_s}" }.join("\n")}"
337
+ prev_base = base_list.dup
338
+ oidlist = []
339
+ pdu.varbinds.each_with_index do |vb, i|
340
+ if prev_base[i].parent_of?(vb.oid) && vb.object_type != Constants::SNMP_ENDOFMIBVIEW
341
+ # Still in subtree. Store results and add next oid to list
342
+ debug "adding #{vb.oid} to oidlist"
343
+ all_results[vb.oid.to_s] = vb.value
344
+ oidlist << vb.oid
345
+ else
346
+ # End of subtree. Don't add to list or results
347
+ debug "End of subtree"
348
+ base_list.delete_at(i)
349
+ debug "not adding #{vb.oid}"
350
+ end
351
+ # If get a pdu error, we can only tell the first failing varbind,
352
+ # So we remove it and resend all the rest
353
+ if pdu.error? && pdu.errindex == i + 1
354
+ oidlist.pop # remove the bad oid
355
+ debug "caught error"
356
+ if pdu.varbinds.size > i+1
357
+ # recram rest of oids on list
358
+ ((i+1)..pdu.varbinds.size).each do |j|
359
+ debug "j = #{j}"
360
+ debug "adding #{j} = #{prev_list[j]}"
361
+ oidlist << prev_list[j]
362
+ end
363
+ # delete failing oid from base_list
364
+ base_list.delete_at(i)
365
+ end
366
+ break
367
+ end
368
+ end
369
+ end
370
+ if block_given?
371
+ yield all_results
372
+ end
373
+ all_results
374
+ end
375
+
376
+
377
+ # Given a list of columns (e.g ['ifIndex', 'ifDescr'], will return a hash with
378
+ # the indexes as keys and hashes as values.
379
+ # puts sess.get_columns(['ifIndex', 'ifDescr']).inspect
380
+ # {'1' => {'ifIndex' => '1', 'ifDescr' => 'lo0'}, '2' => {'ifIndex' => '2', 'ifDescr' => 'en0'}}
381
+ def columns(columns, options = {})
382
+ columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)}
383
+ walk_hash = walk(columns, options)
384
+ results = {}
385
+ walk_hash.each do |k, v|
386
+ oid = OID.new(k)
387
+ results[oid.index] ||= {}
388
+ results[oid.index][oid.node.label] = v
389
+ end
390
+ if block_given?
391
+ yield results
392
+ end
393
+ results
394
+ end
395
+
396
+ # table('ifEntry'). You must pass the direct parent entry. Calls columns with all
397
+ # columns in +table_name+
398
+ def table(table_name, &blk)
399
+ column_names = MIB::Node.get_node(table_name).children.collect {|c| c.oid }
400
+ results = columns(column_names)
401
+ if block_given?
402
+ yield results
403
+ end
404
+ results
405
+ end
406
+
407
+ # Send a PDU
408
+ # +pdu+ The Net::SNMP::PDU object to send. Usually created by Session.get, Session.getnext, etc.
409
+ # +callback+ An optional callback. It should take two parameters, status and response_pdu.
410
+ # If no +callback+ is given, the call will block until the response is available and will return
411
+ # the response pdu. If an error occurs, a Net::SNMP::Error will be thrown.
412
+ # If +callback+ is passed, the PDU will be sent and +send_pdu+ will return immediately. You must
413
+ # then call Session.select to invoke the callback. This is usually done in some sort of event loop.
414
+ # See Net::SNMP::Dispatcher.
415
+ #
416
+ # If you're running inside eventmachine and have fibers (ruby 1.9, jruby, etc), sychronous calls will
417
+ # actually run asynchronously behind the scenes. Just run Net::SNMP::Dispatcher.fiber_loop in your
418
+ # reactor.
419
+ #
420
+ # pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
421
+ # pdu.add_varbind(:oid => 'sysDescr.0')
422
+ # session.send_pdu(pdu) do |status, pdu|
423
+ # if status == :success
424
+ # pdu.print
425
+ # elsif status == :timeout
426
+ # puts "Timed Out"
427
+ # else
428
+ # puts "A problem occurred"
429
+ # end
430
+ # end
431
+ # session.select(false) #block until data is ready. Callback will be called.
432
+ # begin
433
+ # result = session.send_pdu(pdu)
434
+ # puts result.inspect
435
+ # rescue Net::SNMP::Error => e
436
+ # puts e.message
437
+ # end
438
+ def send_pdu(pdu, options = {}, &callback)
439
+ if options[:blocking]
440
+ return send_pdu_blocking(pdu)
441
+ end
442
+ if block_given?
443
+ @requests[pdu.reqid] = callback
444
+ debug "calling async_send"
445
+ if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
446
+ error("snmp_get async failed")
447
+ end
448
+ nil
449
+ else
450
+ if defined?(EM) && EM.reactor_running? && defined?(Fiber)
451
+ f = Fiber.current
452
+ send_pdu pdu do | op, response_pdu |
453
+ f.resume([op, response_pdu])
454
+ end
455
+ op, result = Fiber.yield
456
+ case op
457
+ when :timeout
458
+ raise TimeoutError.new, "timeout"
459
+ when :send_failed
460
+ error "send failed"
461
+ when :success
462
+ result
463
+ when :connect, :disconnect
464
+ nil #does this ever happen?
465
+ else
466
+ error "unknown operation #{op}"
467
+ end
468
+ else
469
+ send_pdu_blocking(pdu)
470
+ end
471
+ end
472
+ end
473
+
474
+ def send_pdu_blocking(pdu)
475
+ response_ptr = FFI::MemoryPointer.new(:pointer)
476
+ if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2, Constants::SNMP_MSG_RESPONSE].include?(pdu.command)
477
+ # Since we don't expect a response, the native net-snmp lib is going to free this
478
+ # pdu for us. Polite, though this may be, it causes intermittent segfaults when freeing
479
+ # memory malloc'ed by ruby. So, clone the pdu into a new memory buffer,
480
+ # and pass that along.
481
+ clone = Wrapper.snmp_clone_pdu(pdu.struct)
482
+ status = Wrapper.snmp_sess_send(@struct, clone)
483
+ if status == 0
484
+ error("snmp_sess_send")
485
+ end
486
+ :success
487
+ else
488
+ status = Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
489
+ unless status == Constants::STAT_SUCCESS
490
+ error("snmp_sess_synch_response", :status => status)
491
+ end
492
+ PDU.new(response_ptr.read_pointer)
493
+ end
494
+ end
495
+
496
+ def errno
497
+ get_error
498
+ @errno
499
+ end
500
+
501
+ # The SNMP Session error code
502
+ def snmp_err
503
+ get_error
504
+ @snmp_err
505
+ end
506
+
507
+ # The SNMP Session error message
508
+ def error_message
509
+ get_error
510
+ @snmp_msg
511
+ end
512
+
513
+ def print_errors
514
+ puts "errno: #{errno}, snmp_err: #{@snmp_err}, message: #{@snmp_msg}"
515
+ end
516
+
517
+ private
518
+
519
+ def sess_callback
520
+ @sess_callback ||= FFI::Function.new(:int, [:int, :pointer, :int, :pointer, :pointer]) do |operation, session, reqid, pdu_ptr, magic|
521
+ debug "in callback #{operation.inspect} #{session.inspect}"
522
+ op = case operation
523
+ when Constants::NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
524
+ :success
525
+ when Constants::NETSNMP_CALLBACK_OP_TIMED_OUT
526
+ :timeout
527
+ when Constants::NETSNMP_CALLBACK_OP_SEND_FAILED
528
+ :send_failed
529
+ when Constants::NETSNMP_CALLBACK_OP_CONNECT
530
+ :connect
531
+ when Constants::NETSNMP_CALLBACK_OP_DISCONNECT
532
+ :disconnect
533
+ else
534
+ error "Invalid PDU Operation"
535
+ end
536
+
537
+ if @requests[reqid]
538
+ pdu = PDU.new(pdu_ptr)
539
+ callback_return = @requests[reqid].call(op, pdu)
540
+ @requests.delete(reqid)
541
+ callback_return == false ? 0 : 1 #if callback returns false (failure), pass it on. otherwise return 1 (success)
542
+ else
543
+ 0 # Do what here? Can this happen? Maybe request timed out and was deleted?
544
+ end
545
+ end
546
+ end
547
+
548
+ def get_error
549
+ errno_ptr = FFI::MemoryPointer.new(:int)
550
+ snmp_err_ptr = FFI::MemoryPointer.new(:int)
551
+ msg_ptr = FFI::MemoryPointer.new(:pointer)
552
+ Wrapper.snmp_sess_error(@struct.pointer, errno_ptr, snmp_err_ptr, msg_ptr)
553
+ @errno = errno_ptr.null? ? nil : errno_ptr.read_int
554
+ @snmp_err = snmp_err_ptr.null? ? nil : snmp_err_ptr.read_int
555
+ @snmp_msg = (msg_ptr.null? || msg_ptr.read_pointer.null?) ? nil : msg_ptr.read_pointer.read_string
556
+ end
557
+
558
+ end
559
+ end
560
+ end