net-snmp 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,8 @@ It provides classes for sessions, pdus, varbinds, and more.
12
12
  * Supports sending of snmpv1 traps and snmpv2 traps/informs using TrapSession
13
13
  * Integrates well with eventmachine, or can be used standalone.
14
14
  * In Ruby 1.9, uses fibers behind the scenes to emulate synchronous calls asynchronously
15
-
15
+ * MIB support
16
+ * Convenience methods such as session.walk, session.get_columns, and session.table
16
17
 
17
18
  == USAGE
18
19
 
@@ -49,7 +50,7 @@ You must call Session.close when you're done with a session to avoid leaking mem
49
50
  In the synchronous versions, all session methods raise Net::SNMP:Error on any error. Timeouts raise Net::SNMP::TimeoutError.
50
51
  In the asynchronous versions, the first argument to your callback will be the return status. Possible values include :success, :timeout, and :send_error.
51
52
  If you need the underlying net-snmp session errors, you can call session.errno, session.snmp_err, and session.error_message.
52
-
53
+ PDU errors can be retreived with pdu.errstat, pdu.errindex giving the index of the offending varbind. See constants.rb for possible errors.
53
54
 
54
55
  == EXAMPLES
55
56
 
@@ -137,6 +138,13 @@ Net::SNMP::OID objects. For example
137
138
 
138
139
  For more complex MIB parsing needs, see smi-ffi[http://github.com/mixtli/smi-ffi]
139
140
 
141
+ == EXPERIMENTAL THREAD SUPPORT
142
+ If you intent to use threads, you should set:
143
+
144
+ Net::SNMP.thread_safe = true
145
+
146
+ Thread support is experimental and may be dropped. Let me know if you're using this feature.
147
+
140
148
  == CAVEATS/DISCLAIMER
141
149
 
142
150
  THIS GEM COULD CRASH YOUR SYSTEM AND EAT YOUR CHILDREN!
@@ -166,7 +174,8 @@ break this gem. Please let me know if you find bugs or missing features. Or be
166
174
  * Commit, do not mess with rakefile, version, or history.
167
175
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
168
176
  * Send me a pull request. Bonus points for topic branches.
177
+ * Bug reports including a failing rspec get priority treatment
169
178
 
170
- == Copyright
179
+ == Copyright
171
180
 
172
181
  Copyright (c) 2010 mixtli. See LICENSE for details.
@@ -49,7 +49,7 @@ module Net
49
49
  # Exception values for SNMPv2 and SNMPv3
50
50
  SNMP_NOSUCHOBJECT = (ASN_CONTEXT | ASN_PRIMITIVE | 0x0)
51
51
  SNMP_NOSUCHINSTANCE = (ASN_CONTEXT | ASN_PRIMITIVE | 0x1)
52
- SNMP_ENDOFVIEW = (ASN_CONTEXT | ASN_PRIMITIVE | 0x2)
52
+ SNMP_ENDOFMIBVIEW = (ASN_CONTEXT | ASN_PRIMITIVE | 0x2)
53
53
 
54
54
 
55
55
  # PDU types
@@ -71,6 +71,35 @@ module Net
71
71
  NETSNMP_CALLBACK_OP_CONNECT = 4
72
72
  NETSNMP_CALLBACK_OP_DISCONNECT = 5
73
73
 
74
+
75
+ # Error codes (the value of the field error-status in PDUs)
76
+
77
+ # in SNMPv1, SNMPsec, SNMPv2p, SNMPv2c, SNMPv2u, SNMPv2*, and SNMPv3 PDUs
78
+ SNMP_ERR_NOERROR = (0) # XXX Used only for PDUs?
79
+ SNMP_ERR_TOOBIG = (1)
80
+ SNMP_ERR_NOSUCHNAME = (2)
81
+ SNMP_ERR_BADVALUE = (3)
82
+ SNMP_ERR_READONLY = (4)
83
+ SNMP_ERR_GENERR = (5)
84
+
85
+ # in SNMPv2p, SNMPv2c, SNMPv2u, SNMPv2*, and SNMPv3 PDUs
86
+ SNMP_ERR_NOACCESS = (6)
87
+ SNMP_ERR_WRONGTYPE = (7)
88
+ SNMP_ERR_WRONGLENGTH = (8)
89
+ SNMP_ERR_WRONGENCODING = (9)
90
+ SNMP_ERR_WRONGVALUE = (10)
91
+ SNMP_ERR_NOCREATION = (11)
92
+ SNMP_ERR_INCONSISTENTVALUE = (12)
93
+ SNMP_ERR_RESOURCEUNAVAILABLE = (13)
94
+ SNMP_ERR_COMMITFAILED = (14)
95
+ SNMP_ERR_UNDOFAILED = (15)
96
+ SNMP_ERR_AUTHORIZATIONERROR = (16)
97
+ SNMP_ERR_NOTWRITABLE = (17)
98
+
99
+ # in SNMPv2c, SNMPv2u, SNMPv2*, and SNMPv3 PDUs
100
+ SNMP_ERR_INCONSISTENTNAME = (18)
101
+
102
+
74
103
  # SNMP Errors
75
104
  SNMPERR_SUCCESS = (0)
76
105
  SNMPERR_GENERR = (-1)
@@ -8,7 +8,7 @@ module Net::SNMP
8
8
  class << self
9
9
  def get_node(oid)
10
10
  if oid.kind_of?(String)
11
- oid = Net::SNMP::OID.new(oid)
11
+ oid = OID.new(oid)
12
12
  end
13
13
  struct = Wrapper.get_tree(oid.pointer, oid.length_pointer.read_int, Wrapper.get_tree_head().pointer)
14
14
  new(struct.pointer)
@@ -30,7 +30,7 @@ module Net::SNMP
30
30
 
31
31
  def oid
32
32
  return @oid if @oid
33
- @oid = Net::SNMP::OID.new(label)
33
+ @oid = OID.new(label)
34
34
  end
35
35
 
36
36
  # actually seems like list is linked backward, so this will retrieve the previous oid numerically
@@ -52,6 +52,22 @@ module Net
52
52
  node.label + "." + index
53
53
  end
54
54
 
55
+ def <=>(o)
56
+ a = self._packed
57
+ b = o._packed
58
+ a <=> b
59
+ end
60
+
61
+ def _packed
62
+ i = self.to_s.dip
63
+ i.sub!(/^\./,'')
64
+ i.gsub!(/ /, '.0')
65
+ i.replace(i.split('.').map(&:to_i).pack('N*'))
66
+ end
67
+
68
+ def parent_of?(o)
69
+ o.to_s =~ /^#{self.to_s}\./
70
+ end
55
71
  end
56
72
  end
57
73
  end
@@ -82,7 +82,6 @@ module Net
82
82
  # * +type+ The data type. Possible values include Net::SNMP::ASN_OCTET_STR, Net::SNMP::ASN_COUNTER, etc. See constants.rb
83
83
  # * +value+ The value of the varbind. default is nil.
84
84
  def add_varbind(options)
85
- #puts "adding varbind #{options.inspect}"
86
85
  options[:type] ||= case options[:value]
87
86
  when String
88
87
  Constants::ASN_OCTET_STR
@@ -125,7 +124,7 @@ module Net
125
124
  end
126
125
  end
127
126
 
128
- oid = options[:oid].kind_of?(Net::SNMP::OID) ? options[:oid] : Net::SNMP::OID.new(options[:oid])
127
+ oid = options[:oid].kind_of?(OID) ? options[:oid] : OID.new(options[:oid])
129
128
  var_ptr = Wrapper.snmp_pdu_add_variable(@struct.pointer, oid.pointer, oid.length_pointer.read_int, options[:type], value, value_len)
130
129
  varbind = Varbind.new(var_ptr)
131
130
  #Wrapper.print_varbind(varbind.struct)
@@ -142,6 +141,9 @@ module Net
142
141
  Wrapper::snmp_errstring(self.errstat)
143
142
  end
144
143
 
144
+ def print_errors
145
+ puts "errstat = #{self.errstat}, index = #{self.errindex}, message = #{self.error_message}"
146
+ end
145
147
  # Free the pdu
146
148
  def free
147
149
  Wrapper.snmp_free_pdu(@struct.pointer)
@@ -33,9 +33,7 @@ module Net
33
33
  # * +retries+ - snmp retries. default = 5
34
34
  # Returns:
35
35
  # Net::SNMP::Session
36
-
37
36
  def open(options = {})
38
- #puts "building session"
39
37
  session = new(options)
40
38
  if Net::SNMP::thread_safe
41
39
  Net::SNMP::Session.lock.synchronize {
@@ -52,7 +50,6 @@ module Net
52
50
  end
53
51
 
54
52
  def initialize(options = {})
55
- #puts "in initialize"
56
53
  @timeout = options[:timeout] || 1
57
54
  @retries = options[:retries] || 5
58
55
  @requests = {}
@@ -189,20 +186,17 @@ module Net
189
186
  end
190
187
  end
191
188
 
192
-
193
-
194
-
195
189
  def default_max_repeaters
196
190
  # We could do something based on transport here. 25 seems safe
197
191
  25
198
192
  end
199
193
 
200
-
201
-
202
194
  # Raise a NET::SNMP::Error with the session attached
203
195
  def error(msg, options = {})
204
196
  #Wrapper.snmp_sess_perror(msg, @sess.pointer)
205
- raise Error.new({:session => self}.merge(options)), msg
197
+ err = Error.new({:session => self}.merge(options))
198
+ err.print
199
+ raise err, msg
206
200
  end
207
201
 
208
202
 
@@ -247,6 +241,90 @@ module Net
247
241
  alias :poll :select
248
242
 
249
243
 
244
+ # Issue repeated getnext requests on each oid passed in until
245
+ # the result is no longer a child. Returns a hash with the numeric
246
+ # oid strings as keys.
247
+ # XXX work in progress. only works synchronously (except with EM + fibers).
248
+ # Need to do better error checking and use getbulk when avaiable.
249
+ def walk(oidlist)
250
+ oidlist = [oidlist] unless oidlist.kind_of?(Array)
251
+ oidlist = oidlist.map {|o| o.kind_of?(OID) ? o : OID.new(o)}
252
+ all_results = {}
253
+ base_list = oidlist
254
+ while(!oidlist.empty? && pdu = get_next(oidlist))
255
+ debug "==============================================================="
256
+ debug "base_list = #{base_list}"
257
+ prev_base = base_list.dup
258
+ oidlist = []
259
+ print_errors
260
+ pdu.print_errors
261
+ pdu.varbinds.each_with_index do |vb, i|
262
+ if prev_base[i].parent_of?(vb.oid) && vb.object_type != Constants::SNMP_ENDOFMIBVIEW
263
+ # Still in subtree. Store results and add next oid to list
264
+ debug "adding #{vb.oid} to oidlist"
265
+ all_results[vb.oid.to_s] = vb.value
266
+ oidlist << vb.oid
267
+ else
268
+ # End of subtree. Don't add to list or results
269
+ debug "End of subtree"
270
+ base_list.delete_at(i)
271
+ debug "not adding #{vb.oid}"
272
+ end
273
+ # If get a pdu error, we can only tell the first failing varbind,
274
+ # So we remove it and resend all the rest
275
+ if pdu.error? && pdu.errindex == i + 1
276
+ oidlist.pop # remove the bad oid
277
+ debug "caught error"
278
+ if pdu.varbinds.size > i+1
279
+ # recram rest of oids on list
280
+ ((i+1)..pdu.varbinds.size).each do |j|
281
+ debug "j = #{j}"
282
+ debug "adding #{j} = #{prev_list[j]}"
283
+ oidlist << prev_list[j]
284
+ end
285
+ # delete failing oid from base_list
286
+ base_list.delete_at(i)
287
+ end
288
+ break
289
+ end
290
+ end
291
+ end
292
+ if block_given?
293
+ yield all_results
294
+ end
295
+ all_results
296
+ end
297
+
298
+
299
+ # Given a list of columns (e.g ['ifIndex', 'ifDescr'], will return a hash with
300
+ # the indexes as keys and hashes as values.
301
+ # puts sess.get_columns(['ifIndex', 'ifDescr']).inspect
302
+ # {'1' => {'ifIndex' => '1', 'ifDescr' => 'lo0'}, '2' => {'ifIndex' => '2', 'ifDescr' => 'en0'}}
303
+ def columns(columns)
304
+ columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)}
305
+ walk_hash = walk(columns)
306
+ results = {}
307
+ walk_hash.each do |k, v|
308
+ oid = OID.new(k)
309
+ results[oid.index] ||= {}
310
+ results[oid.index][oid.node.label] = v
311
+ end
312
+ if block_given?
313
+ yield results
314
+ end
315
+ results
316
+ end
317
+
318
+ # table('ifEntry'). You must pass the direct parent entry. Calls columns with all
319
+ # columns in +table_name+
320
+ def table(table_name, &blk)
321
+ column_names = MIB::Node.get_node(table_name).children.collect {|c| c.oid }
322
+ results = columns(column_names)
323
+ if block_given?
324
+ yield results
325
+ end
326
+ results
327
+ end
250
328
 
251
329
  # Send a PDU
252
330
  # +pdu+ The Net::SNMP::PDU object to send. Usually created by Session.get, Session.getnext, etc.
@@ -280,10 +358,9 @@ module Net
280
358
  # puts e.message
281
359
  # end
282
360
  def send_pdu(pdu, &callback)
283
- #puts "send_pdu #{Fiber.current.inspect}"
284
361
  if block_given?
285
362
  @requests[pdu.reqid] = callback
286
- puts "calling async_send"
363
+ debug "calling async_send"
287
364
  if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
288
365
  error("snmp_get async failed")
289
366
  end
@@ -346,11 +423,14 @@ module Net
346
423
  get_error
347
424
  @snmp_msg
348
425
  end
349
-
426
+
427
+ def print_errors
428
+ puts "errno: #{errno}, snmp_err: #{@snmp_err}, message: #{@snmp_msg}"
429
+ end
350
430
  private
351
431
  def sess_callback
352
432
  @sess_callback ||= FFI::Function.new(:int, [:int, :pointer, :int, :pointer, :pointer]) do |operation, session, reqid, pdu_ptr, magic|
353
- #puts "in callback #{operation.inspect} #{session.inspect}"
433
+ debug "in callback #{operation.inspect} #{session.inspect}"
354
434
  op = case operation
355
435
  when Constants::NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
356
436
  :success
@@ -386,86 +466,7 @@ module Net
386
466
  @snmp_msg = msg_ptr.read_pointer.read_string
387
467
  end
388
468
 
389
- public
390
- # XXX This needs work. Need to use getbulk for speed, guess maxrepeaters, etc..
391
- # Also need to figure out how we can tell column names from something like ifTable
392
- # instead of ifEntry. Needs to handle errors, there are probably offset problems
393
- # in cases of bad data, and various other problems. Also need to add async support.
394
- # Maybe return a hash with index as key?
395
- def get_table(table_name, options = {})
396
- column_names = options[:columns] || MIB::Node.get_node(table_name).children.collect {|c| c.label }
397
- results = []
398
-
399
- # repeat_count = if @version.to_s == '1' || options[:no_getbulk]
400
- # 1
401
- # elsif options[:repeat_count]
402
- # options[:repeat_count]
403
- # else
404
- # (1000 / 36 / (column_names.size + 1)).to_i
405
- # end
406
- #
407
- # res = if @version.to_s != '1' && !options[:no_getbulk]
408
- # get_bulk(column_names, :max_repetitions => repeat_count)
409
- # else
410
- # get_next(column_names)
411
- # end
412
-
413
-
414
- first_result = get_next(column_names)
415
- oidlist = []
416
- good_column_names = []
417
- row = {}
418
-
419
- first_result.varbinds.each_with_index do |vb, idx|
420
- oid = vb.oid
421
- if oid.label[0..column_names[idx].length - 1] == column_names[idx]
422
- oidlist << oid.label
423
- good_column_names << column_names[idx]
424
- row[column_names[idx]] = vb.value
425
- end
426
- end
427
- results << row
428
-
429
- catch :break_main_loop do
430
- while(result = get_next(oidlist))
431
- oidlist = []
432
- row = {}
433
- result.varbinds.each_with_index do |vb, idx|
434
- #puts "got #{vb.oid.label} #{vb.value.inspect}, type = #{vb.object_type}"
435
- row[good_column_names[idx]] = vb.value
436
- oidlist << vb.oid.label
437
- if vb.oid.label[0..good_column_names[idx].length - 1] != good_column_names[idx]
438
- throw :break_main_loop
439
- end
440
- end
441
- results << row
442
- end
443
- end
444
- results
445
- end
446
-
447
469
 
448
- # def get_entries_cb(pdu, columns, options)
449
- # cache = {}
450
- # row_index = nil
451
- # varbinds = pdu.varbinds.dup
452
- # while(varbinds.size > 0)
453
- # row = {}
454
- # columns.each do |column|
455
- # vb = varbinds.shift
456
- # if vb.oid.to_s =~ /#{column.to_s}\.(\d+(:?\.\d+)*)/
457
- # index = $1
458
- # else
459
- # last_entry = true
460
- # next
461
- # end
462
- # row_index = index unless row_index
463
- # index_cmp = Net::SNMP.oid_lex_cmp(index, row_index)
464
- # if(index_cmp == 0)
465
- # end
466
- # end
467
- # end
468
- # end
469
470
  end
470
471
  end
471
472
  end
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  module SNMP
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'pp'
2
3
 
3
4
  describe "synchronous calls" do
4
5
  # To test sets, you have to have a local snmpd running with write permissions
@@ -65,29 +66,39 @@ describe "synchronous calls" do
65
66
  end
66
67
  end
67
68
 
68
- it "get_table should work with multiple columns" do
69
- #pending
70
- session = Net::SNMP::Session.open(:peername => "localhost", :version => '1')
71
- table = session.get_table("ifTable", :columns => ["ifIndex", "ifDescr", "ifName"])
72
- table[0]['ifName'].should eql("lo0")
73
- table[1]['ifName'].should eql("gif0")
74
- end
75
-
76
69
  it "get_table should work" do
77
- pending "not yet implemented"
70
+ #pending "not yet implemented"
78
71
  session = Net::SNMP::Session.open(:peername => "localhost", :version => '1')
79
- table = session.get_table("ifTable", :columns => ['ifIndex', 'ifDescr'])
80
- table[0]['ifIndex'].should eql(1)
81
- table[1]['ifIndex'].should eql(2)
72
+ table = session.table("ifEntry")
73
+ table['1']['ifIndex'].should eql(1)
74
+ table['2']['ifIndex'].should eql(2)
82
75
  end
83
76
 
84
77
  it "walk should work" do
85
- pending "not yet implemented"
86
- session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 1)
78
+ #pending "not yet implemented"
79
+ session = Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :version => 1, :community => 'demopublic')
87
80
  results = session.walk("system")
88
81
  results['1.3.6.1.2.1.1.1.0'].should match(/test.net-snmp.org/)
89
82
  end
90
83
 
84
+ it "walk should work with multiple oids" do
85
+ Net::SNMP::Session.open(:peername => 'localhost', :version => 1) do |sess|
86
+ sess.walk(['system', 'ifTable']) do |results|
87
+ pp results
88
+ results['1.3.6.1.2.1.1.1.0'].should match(/Darwin/)
89
+ results['1.3.6.1.2.1.2.2.1.1.2'].should eql(2)
90
+ results.size.should eql(186)
91
+ end
92
+ end
93
+ end
94
+
95
+ it "get_columns should work" do
96
+ Net::SNMP::Session.open(:peername => 'localhost') do |sess|
97
+ table = sess.columns(['ifIndex', 'ifDescr', 'ifType'])
98
+ table['1']['ifIndex'].should eql(1)
99
+ table['2']['ifDescr'].should eql('gif0')
100
+ end
101
+ end
91
102
  end
92
103
 
93
104
  context "version 2" do
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ron McClain
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-04-30 00:00:00 -05:00
17
+ date: 2011-05-01 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency