net-snmp 0.2.0 → 0.2.1

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.
@@ -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