snmp 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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)