snmp 0.5.1 → 0.6.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/README CHANGED
@@ -9,16 +9,13 @@ that Ruby can run.
9
9
 
10
10
  See snmplib.rubyforge.org[http://snmplib.rubyforge.org/] for more info.
11
11
 
12
- Version 0.5.1 fixes bugs #2054 and #2164. Responses were ignored if they did
13
- not come back from the same host that the request was sent to. This behavior
14
- causes problems for some multi-homed hosts.
12
+ Version 0.6.0 of this software supports the following:
15
13
 
16
- Version 0.5.0 of this software supports the following:
17
-
18
- * The GetRequest, GetNextRequest, GetBulkRequest, SetRequest, and Response
19
- PDUs
14
+ * The GetRequest, GetNextRequest, GetBulkRequest, SetRequest, Response
15
+ SNMPv1_Trap, SNMPv2_Trap, and Inform PDUs
20
16
  * All of the ASN.1 data types defined by SNMPv1 and SNMPv2c
21
- * Trap handling for both v1 and v2 traps
17
+ * Sending informs and v1 and v2 traps
18
+ * Trap handling for v1 and v2 traps
22
19
  * Symbolic OID values (ie. "ifTable" instead of "1.3.6.1.2.1.2.2") as
23
20
  parameters to the SNMP.Manager API
24
21
  * Includes symbol data files for all current IETF MIBs
@@ -28,8 +25,26 @@ examples below for more details.
28
25
 
29
26
  == Changes
30
27
 
31
- Changes from version 0.4.0:
32
-
28
+ Changes for version 0.6.0:
29
+
30
+ * Added support for sending informs and traps for SNMPv2c and traps for
31
+ SNMPv1
32
+ * Improved SNMP::Manager#walk so that it can handle missing varbinds
33
+ when reading tables. The indexes for a table row read by #walk are now
34
+ guaranteed to match.
35
+ * Added to_oid methods to SNMP::OctetString, SNMP::Integer, and
36
+ SNMP::IpAddress (feature request #2486)
37
+ * Fixed some problems with retrying requests that caused annoying "Request ID
38
+ mismatch" warnings and caused too few retries to be attempted
39
+ * Thanks to Mark Cotner, Dan Hamlin, Tim Howe, Diana Eichert, Ery Lee,
40
+ Jan �ge Johnsen, and Jeff Foster for their help and suggestions (and
41
+ apologies in advance if I have forgotten someone).
42
+
43
+ Changes for version 0.5.1:
44
+
45
+ * Fixed bugs #2054 and #2164. Responses were ignored if they did
46
+ not come back from the same host that the request was sent to. This
47
+ behavior causes problems for some multi-homed hosts.
33
48
  * Fixed bugs #1679 (NameError in Manager.get_response()) and #1736
34
49
  (Notifications not included in YAML output).
35
50
  * Added Manager.get_value convenience method to get a list of values for a
@@ -40,7 +55,7 @@ Changes from version 0.4.0:
40
55
  * Break out of Manager.walk instead of looping forever if OIDs returned by the
41
56
  remote host are not in ascending order.
42
57
 
43
- Changes from version 0.3.0:
58
+ Changes for version 0.4.0:
44
59
 
45
60
  * Added support for loading MIBs and using symbolic OID values.
46
61
  * Enhanced Manager.walk
@@ -50,7 +65,7 @@ Changes from version 0.3.0:
50
65
  processing of results.
51
66
  * Some minor improvements to code structure and error messages.
52
67
 
53
- Changes from version 0.2.0:
68
+ Changes for version 0.3.0:
54
69
 
55
70
  * Added SNMPv1_Trap and SNMPv2_Trap classes
56
71
  * Added TrapListener class for receiving v1 and v2 traps
@@ -59,7 +74,7 @@ Changes from version 0.2.0:
59
74
  * Defined IpAddress.== and IpAddress.eql? so that IpAddress objects can be
60
75
  compared by value
61
76
 
62
- Changes from version 0.1.0:
77
+ Changes for version 0.2.0:
63
78
 
64
79
  * Added GetBulkRequest
65
80
  * Added open and close methods to the Manager class to ensure that
@@ -82,22 +97,21 @@ formats.
82
97
  From the .gem file you can install using
83
98
  RubyGems[http://rubyforge.org/projects/rubygems].
84
99
 
85
- gem install snmp-0.5.0.gem
100
+ gem install snmp-0.6.0.gem
86
101
 
87
102
  From the .tgz or .zip file you can install using
88
103
  setup.rb[http://i.loveruby.net/en/prog/setup.html]. Uncompress the archive
89
104
  and then run setup.
90
105
 
91
- cd snmp-0.5.0
106
+ cd snmp-0.6.0
92
107
  ruby setup.rb (may require root privilege)
93
108
 
94
109
  == Testing
95
110
 
96
111
  This library has received limited testing:
97
- * The unit tests have been executed with Ruby 1.8.2 on Mac OS X 10.4 and
98
- Windows XP.
112
+ * The unit tests have been executed with Ruby 1.8.3 on Mac OS X 10.4.
99
113
  * Basic interoperability testing has been done with the
100
- net-snmp[http://www.net-snmp.org/] tools, but not much beyond that.
114
+ net-snmp[http://www.net-snmp.org/] tools.
101
115
 
102
116
  I'm very interested in hearing about successes or failures on other platforms.
103
117
 
data/Rakefile CHANGED
@@ -9,13 +9,14 @@ Rake::TestTask.new do |test|
9
9
  end
10
10
 
11
11
  # package target
12
- PKG_VERSION = '0.5.1'
12
+ PKG_VERSION = '0.6.0'
13
13
  PKG_FILES = FileList[
14
14
  'Rakefile',
15
15
  'README',
16
16
  'setup.rb',
17
17
  'lib/**/*.rb',
18
18
  'test/**/test*.rb',
19
+ 'test/**/*.yaml',
19
20
  'examples/*.rb',
20
21
  'data/**/*.yaml']
21
22
 
@@ -39,7 +40,7 @@ end
39
40
 
40
41
  Rake::GemPackageTask.new(spec) do |package|
41
42
  package.need_zip = true
42
- package.need_tar = true
43
+ # package.need_tar = true
43
44
  end
44
45
 
45
46
  # rdoc, clobber_rdoc, rerdoc targets
@@ -22,18 +22,16 @@ class RequestTimeout < RuntimeError; end
22
22
  # using other transport types (e.g. TCP)
23
23
  #
24
24
  class UDPTransport
25
- def initialize(host, port)
25
+ def initialize
26
26
  @socket = UDPSocket.open
27
- @host = host
28
- @port = port
29
27
  end
30
28
 
31
29
  def close
32
30
  @socket.close
33
31
  end
34
32
 
35
- def send(data)
36
- @socket.send(data, 0, @host, @port)
33
+ def send(data, host, port)
34
+ @socket.send(data, 0, host, port)
37
35
  end
38
36
 
39
37
  def recv(max_bytes)
@@ -94,9 +92,9 @@ end
94
92
  #
95
93
  # See MIB.varbind_list for a description of valid parameter formats.
96
94
  #
97
- # The following modules are loaded by default: "SNMPv2-MIB", "IF-MIB",
98
- # "IP-MIB", "TCP-MIB", "UDP-MIB". All of the current IETF MIBs have been
99
- # imported and are available for loading.
95
+ # The following modules are loaded by default: "SNMPv2-SMI", "SNMPv2-MIB",
96
+ # "IF-MIB", "IP-MIB", "TCP-MIB", "UDP-MIB". All of the current IETF MIBs
97
+ # have been imported and are available for loading.
100
98
  #
101
99
  # Additional modules may be imported using the MIB class. The
102
100
  # current implementation of the importing code requires that the
@@ -125,6 +123,7 @@ class Manager
125
123
  DefaultConfig = {
126
124
  :Host => 'localhost',
127
125
  :Port => 161,
126
+ :TrapPort => 162,
128
127
  :Community => 'public',
129
128
  :WriteCommunity => nil,
130
129
  :Version => :SNMPv2c,
@@ -133,7 +132,7 @@ class Manager
133
132
  :Transport => UDPTransport,
134
133
  :MaxReceiveBytes => 8000,
135
134
  :MibDir => MIB::DEFAULT_MIB_PATH,
136
- :MibModules => ["SNMPv2-MIB", "IF-MIB", "IP-MIB", "TCP-MIB", "UDP-MIB"]}
135
+ :MibModules => ["SNMPv2-SMI", "SNMPv2-MIB", "IF-MIB", "IP-MIB", "TCP-MIB", "UDP-MIB"]}
137
136
 
138
137
  @@request_id = RequestId.new
139
138
 
@@ -153,13 +152,16 @@ class Manager
153
152
  end
154
153
  @config = DefaultConfig.merge(config)
155
154
  @config[:WriteCommunity] = @config[:WriteCommunity] || @config[:Community]
155
+ @host = @config[:Host]
156
+ @port = @config[:Port]
157
+ @trap_port = @config[:TrapPort]
156
158
  @community = @config[:Community]
157
159
  @write_community = @config[:WriteCommunity]
158
160
  @snmp_version = @config[:Version]
159
- @max_bytes = @config[:MaxReceiveBytes]
160
161
  @timeout = @config[:Timeout]
161
162
  @retries = @config[:Retries]
162
- @transport = @config[:Transport].new(@config[:Host], @config[:Port])
163
+ @transport = @config[:Transport].new
164
+ @max_bytes = @config[:MaxReceiveBytes]
163
165
  @mib = MIB.new
164
166
  load_modules(@config[:MibModules], @config[:MibDir])
165
167
  end
@@ -264,6 +266,93 @@ class Manager
264
266
  try_request(request, @write_community)
265
267
  end
266
268
 
269
+ ##
270
+ # Sends an SNMPv1 style trap.
271
+ #
272
+ # enterprise: The enterprise OID from the IANA assigned numbers
273
+ # (http://www.iana.org/assignments/enterprise-numbers) as a String or
274
+ # an ObjectId.
275
+ #
276
+ # agent_addr: The IP address of the SNMP agent as a String or IpAddress.
277
+ #
278
+ # generic_trap: The generic trap identifier. One of :coldStart,
279
+ # :warmStart, :linkDown, :linkUp, :authenticationFailure,
280
+ # :egpNeighborLoss, or :enterpriseSpecific
281
+ #
282
+ # specific_trap: An integer representing the specific trap type for
283
+ # an enterprise-specific trap.
284
+ #
285
+ # timestamp: An integer respresenting the number of hundredths of
286
+ # a second that this system has been up.
287
+ #
288
+ # object_list: A list of additional varbinds to send with the trap.
289
+ #
290
+ # For example:
291
+ #
292
+ # Manager.open(:Version => :SNMPv1) do |snmp|
293
+ # snmp.trap_v1(
294
+ # "enterprises.9",
295
+ # "10.1.2.3",
296
+ # :enterpriseSpecific,
297
+ # 42,
298
+ # 12345,
299
+ # [VarBind.new("1.3.6.1.2.3.4", Integer.new(1))])
300
+ # end
301
+ #
302
+ def trap_v1(enterprise, agent_addr, generic_trap, specific_trap, timestamp, object_list=[])
303
+ vb_list = @mib.varbind_list(object_list, :KeepValue)
304
+ ent_oid = @mib.oid(enterprise)
305
+ agent_ip = IpAddress.new(agent_addr)
306
+ specific_int = Integer(specific_trap)
307
+ ticks = TimeTicks.new(timestamp)
308
+ trap = SNMPv1_Trap.new(ent_oid, agent_ip, generic_trap, specific_int, ticks, vb_list)
309
+ send_request(trap, @community, @host, @trap_port)
310
+ end
311
+
312
+ ##
313
+ # Sends an SNMPv2c style trap.
314
+ #
315
+ # sys_up_time: An integer respresenting the number of hundredths of
316
+ # a second that this system has been up.
317
+ #
318
+ # trap_oid: An ObjectId or String with the OID identifier for this
319
+ # trap.
320
+ #
321
+ # object_list: A list of additional varbinds to send with the trap.
322
+ #
323
+ def trap_v2(sys_up_time, trap_oid, object_list=[])
324
+ vb_list = create_trap_vb_list(sys_up_time, trap_oid, object_list)
325
+ trap = SNMPv2_Trap.new(@@request_id.next, vb_list)
326
+ send_request(trap, @community, @host, @trap_port)
327
+ end
328
+
329
+ ##
330
+ # Sends an inform request using the supplied varbind list.
331
+ #
332
+ # sys_up_time: An integer respresenting the number of hundredths of
333
+ # a second that this system has been up.
334
+ #
335
+ # trap_oid: An ObjectId or String with the OID identifier for this
336
+ # inform request.
337
+ #
338
+ # object_list: A list of additional varbinds to send with the inform.
339
+ #
340
+ def inform(sys_up_time, trap_oid, object_list=[])
341
+ vb_list = create_trap_vb_list(sys_up_time, trap_oid, object_list)
342
+ request = InformRequest.new(@@request_id.next, vb_list)
343
+ try_request(request, @community, @host, @trap_port)
344
+ end
345
+
346
+ ##
347
+ # Helper method for building VarBindList for trap and inform requests.
348
+ #
349
+ def create_trap_vb_list(sys_up_time, trap_oid, object_list)
350
+ vb_args = @mib.varbind_list(object_list, :KeepValue)
351
+ uptime_vb = VarBind.new(SNMP::SYS_UP_TIME_OID, TimeTicks.new(sys_up_time.to_int))
352
+ trap_vb = VarBind.new(SNMP::SNMP_TRAP_OID_OID, @mib.oid(trap_oid))
353
+ VarBindList.new([uptime_vb, trap_vb, *vb_args])
354
+ end
355
+
267
356
  ##
268
357
  # Walks a list of ObjectId or VarBind objects using get_next until
269
358
  # the response to the first OID in the list reaches the end of its
@@ -288,31 +377,65 @@ class Manager
288
377
  # end
289
378
  # end
290
379
  #
291
- def walk(object_list)
380
+ # The index_column identifies the column that will provide the index
381
+ # for each row. This information is used to deal with "holes" in a
382
+ # table (when a row is missing a varbind for one column). A missing
383
+ # varbind is replaced with a varbind with the value NoSuchInstance.
384
+ #
385
+ # Note: If you are getting back rows where all columns have a value of
386
+ # NoSuchInstance then your index column is probably missing one of the
387
+ # rows. Choose an index column that includes all indexes for the table.
388
+ #
389
+ def walk(object_list, index_column=0)
292
390
  raise ArgumentError, "expected a block to be given" unless block_given?
293
- varbind_list = @mib.varbind_list(object_list, :NullValue)
294
- start_oid = varbind_list.first.name
391
+ vb_list = @mib.varbind_list(object_list, :NullValue)
392
+ raise ArgumentError, "index_column is past end of varbind list" if index_column >= vb_list.length
393
+ is_single_vb = object_list.respond_to?(:to_str) ||
394
+ object_list.respond_to?(:to_varbind)
395
+ start_list = vb_list
396
+ start_oid = vb_list[index_column].name
295
397
  last_oid = start_oid
296
398
  loop do
297
- varbind_list = get_next(varbind_list).varbind_list
298
- first_vb = varbind_list.first
299
- break if EndOfMibView == first_vb.value
300
- stop_oid = first_vb.name
399
+ vb_list = get_next(vb_list).vb_list
400
+ index_vb = vb_list[index_column]
401
+ break if EndOfMibView == index_vb.value
402
+ stop_oid = index_vb.name
301
403
  if stop_oid <= last_oid
302
404
  warn "OIDs are not increasing, #{last_oid} followed by #{stop_oid}"
303
405
  break
304
406
  end
305
407
  break unless stop_oid.subtree_of?(start_oid)
306
408
  last_oid = stop_oid
307
- if object_list.respond_to?(:to_str) ||
308
- object_list.respond_to?(:to_varbind)
309
- then
310
- yield first_vb
409
+ if is_single_vb
410
+ yield index_vb
311
411
  else
312
- yield varbind_list
412
+ vb_list = validate_row(vb_list, start_list, index_column)
413
+ yield vb_list
414
+ end
415
+ end
416
+ end
417
+
418
+ ##
419
+ # Helper method for walk. Checks all of the VarBinds in vb_list to
420
+ # make sure that the row indices match. If the row index does not
421
+ # match the index column, then that varbind is replaced with a varbind
422
+ # with a value of NoSuchInstance.
423
+ #
424
+ def validate_row(vb_list, start_list, index_column)
425
+ start_vb = start_list[index_column]
426
+ index_vb = vb_list[index_column]
427
+ row_index = index_vb.name.index(start_vb.name)
428
+ vb_list.each_index do |i|
429
+ if i != index_column
430
+ expected_oid = start_list[i].name + row_index
431
+ if vb_list[i].name != expected_oid
432
+ vb_list[i] = VarBind.new(expected_oid, NoSuchInstance)
433
+ end
313
434
  end
314
435
  end
436
+ vb_list
315
437
  end
438
+ private :validate_row
316
439
 
317
440
  ##
318
441
  # Set the next request-id instead of letting it be generated
@@ -334,10 +457,10 @@ class Manager
334
457
  module_list.each { |m| @mib.load_module(m, mib_dir) }
335
458
  end
336
459
 
337
- def try_request(request, community=@community)
338
- @retries.times do |n|
460
+ def try_request(request, community=@community, host=@host, port=@port)
461
+ (@retries + 1).times do |n|
462
+ send_request(request, community, host, port)
339
463
  begin
340
- send_request(request, community)
341
464
  timeout(@timeout) do
342
465
  return get_response(request)
343
466
  end
@@ -350,18 +473,22 @@ class Manager
350
473
  raise RequestTimeout, "host #{@config[:Host]} not responding", caller
351
474
  end
352
475
 
353
- def send_request(request, community)
476
+ def send_request(request, community, host, port)
354
477
  message = Message.new(@snmp_version, community, request)
355
- @transport.send(message.encode)
478
+ @transport.send(message.encode, host, port)
356
479
  end
357
480
 
481
+ ##
482
+ # Wait until response arrives. Ignore responses with mismatched IDs;
483
+ # these responses are typically from previous requests that timed out
484
+ # or almost timed out.
485
+ #
358
486
  def get_response(request)
359
- data = @transport.recv(@max_bytes)
360
- message = Message.decode(data)
361
- response = message.pdu
362
- if (request.request_id != response.request_id)
363
- raise RuntimeError, "Request ID mismatch: expected #{request.request_id}, got #{response.request_id}", caller
364
- end
487
+ begin
488
+ data = @transport.recv(@max_bytes)
489
+ message = Message.decode(data)
490
+ response = message.pdu
491
+ end until request.request_id == response.request_id
365
492
  response
366
493
  end
367
494
  end
@@ -23,6 +23,9 @@ class InvalidErrorStatus < RuntimeError; end
23
23
  class InvalidTrapVarbind < RuntimeError; end
24
24
  class InvalidGenericTrap < RuntimeError; end
25
25
 
26
+ SYS_UP_TIME_OID = ObjectId.new("1.3.6.1.2.1.1.3.0")
27
+ SNMP_TRAP_OID_OID = ObjectId.new("1.3.6.1.6.3.1.1.4.1.0")
28
+
26
29
  class Message
27
30
  attr_reader :version
28
31
  attr_reader :community
@@ -68,6 +71,9 @@ class Message
68
71
  when GetBulkRequest_PDU_TAG
69
72
  raise InvalidPduTag, "get-bulk not valid for #{version.to_s}" if version != :SNMPv2c
70
73
  pdu = PDU.decode(GetBulkRequest, pdu_data)
74
+ when InformRequest_PDU_TAG
75
+ raise InvalidPduTag, "inform not valid for #{version.to_s}" if version != :SNMPv2c
76
+ pdu = PDU.decode(InformRequest, pdu_data)
71
77
  when SNMPv2_Trap_PDU_TAG
72
78
  raise InvalidPduTag, "SNMPv2c-trap not valid for #{version.to_s}" if version != :SNMPv2c
73
79
  pdu = PDU.decode(SNMPv2_Trap, pdu_data)
@@ -253,9 +259,8 @@ class SNMPv2_Trap < PDU
253
259
  # Throws InvalidTrapVarbind if the sysUpTime varbind is not present.
254
260
  #
255
261
  def sys_up_time
256
- sys_up_time_oid = ObjectId.new("1.3.6.1.2.1.1.3.0")
257
262
  varbind = @varbind_list[0]
258
- if varbind && (varbind.name == sys_up_time_oid)
263
+ if varbind && (varbind.name == SYS_UP_TIME_OID)
259
264
  return varbind.value
260
265
  else
261
266
  raise InvalidTrapVarbind, "Expected sysUpTime.0, found " + varbind.to_s
@@ -268,9 +273,8 @@ class SNMPv2_Trap < PDU
268
273
  # Throws InvalidTrapVarbind if the snmpTrapOID varbind is not present.
269
274
  #
270
275
  def trap_oid
271
- snmp_trap_oid_oid = ObjectId.new("1.3.6.1.6.3.1.1.4.1.0")
272
276
  varbind = @varbind_list[1]
273
- if varbind && (varbind.name == snmp_trap_oid_oid)
277
+ if varbind && (varbind.name == SNMP_TRAP_OID_OID)
274
278
  return varbind.value
275
279
  else
276
280
  raise InvalidTrapVarbind, "Expected snmpTrapOID.0, found " + varbind.to_s
@@ -278,6 +282,16 @@ class SNMPv2_Trap < PDU
278
282
  end
279
283
  end
280
284
 
285
+ ##
286
+ # The PDU class for SNMPv2 Inform notifications. This class is identical
287
+ # to SNMPv2_Trap.
288
+ #
289
+ class InformRequest < SNMPv2_Trap
290
+ def encode
291
+ encode_pdu(InformRequest_PDU_TAG)
292
+ end
293
+ end
294
+
281
295
  ##
282
296
  # The PDU class for traps in SNMPv1.
283
297
  #
@@ -294,6 +308,8 @@ class SNMPv1_Trap
294
308
  attr_accessor :timestamp
295
309
  attr_accessor :varbind_list
296
310
 
311
+ alias :vb_list :varbind_list
312
+
297
313
  def self.decode(pdu_data)
298
314
  oid_data, remainder = decode_object_id(pdu_data)
299
315
  enterprise = ObjectId.new(oid_data)