Class: Net::SNMP::Session

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Debug
Defined in:
lib/net/snmp/session.rb

Overview

Provides an API for managing SNMP agents

Direct Known Subclasses

TrapSession

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Debug

#debug, #fatal, #info, #print_packet, #time, #warn

Constructor Details

#initialize(options = {}) ⇒ Session

Returns a new instance of Session.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/net/snmp/session.rb', line 59

def initialize(options = {})
  options = {:peername => options} if options.kind_of?(String)
  @timeout = options[:timeout] || 1
  @retries = options[:retries] || 5
  @requests = {}
  @peername = options[:peername] || 'localhost'
  # If the port is supplied in the peername, don't
  # worry about the port option (avoids appending two port numbers)
  unless @peername[':']
    options[:port] ||= 161
    @port = options[:port]
    @peername = "#{@peername}:#{options[:port]}"
  end
  @community = options[:community] || "public"
  options[:community_len] = @community.length
  options[:version] ||= Constants::SNMP_VERSION_2c
  @version = options[:version]
  @sess = Wrapper::SnmpSession.new(nil)
  Wrapper.snmp_sess_init(@sess.pointer)
  @sess.community = FFI::MemoryPointer.from_string(@community)
  @sess.community_len = @community.length
  @sess.peername = FFI::MemoryPointer.from_string(@peername)
  @sess.version = case @version.to_s
  when '1'
    Constants::SNMP_VERSION_1
  when '2', '2c'
    Constants::SNMP_VERSION_2c
  when '3'
    Constants::SNMP_VERSION_3
  else
    Constants::SNMP_VERSION_1
  end
  debug "setting timeout = #{@timeout} retries = #{@retries}"
  @sess.timeout = @timeout * 1000000
  @sess.retries = @retries

  if @sess.version == Constants::SNMP_VERSION_3
    @sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
    @sess.securityAuthProto = case options[:auth_protocol]
        when :sha1
          OID.new("1.3.6.1.6.3.10.1.1.3").pointer
        when :md5
          OID.new("1.3.6.1.6.3.10.1.1.2").pointer
        when nil
          OID.new("1.3.6.1.6.3.10.1.1.1").pointer
    end
    @sess.securityPrivProto = case options[:priv_protocol]
        when :aes
          OID.new("1.3.6.1.6.3.10.1.2.4").pointer
        when :des
          OID.new("1.3.6.1.6.3.10.1.2.2").pointer
        when nil
          OID.new("1.3.6.1.6.3.10.1.2.1").pointer
    end

    @sess.securityAuthProtoLen = 10
    @sess.securityAuthKeyLen = Constants::USM_AUTH_KU_LEN

    @sess.securityPrivProtoLen = 10
    @sess.securityPrivKeyLen = Constants::USM_PRIV_KU_LEN


    if options[:context]
      @sess.contextName = FFI::MemoryPointer.from_string(options[:context])
      @sess.contextNameLen = options[:context].length
    end

    # Do not generate_Ku, unless we're Auth or AuthPriv
    unless @sess.securityLevel == Constants::SNMP_SEC_LEVEL_NOAUTH
      options[:auth_password] ||= options[:password]  # backward compatability
      if options[:username].nil? or options[:auth_password].nil?
        raise Net::SNMP::Error.new "SecurityLevel requires username and password"
      end
      if options[:username]
        @sess.securityName = FFI::MemoryPointer.from_string(options[:username])
        @sess.securityNameLen = options[:username].length
      end

      auth_len_ptr = FFI::MemoryPointer.new(:size_t)
      auth_len_ptr.write_int(Constants::USM_AUTH_KU_LEN)
      auth_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                       @sess.securityAuthProtoLen,
                                       options[:auth_password],
                                       options[:auth_password].length,
                                       @sess.securityAuthKey,
                                       auth_len_ptr)
      @sess.securityAuthKeyLen = auth_len_ptr.read_int

      if @sess.securityLevel == Constants::SNMP_SEC_LEVEL_AUTHPRIV
        priv_len_ptr = FFI::MemoryPointer.new(:size_t)
        priv_len_ptr.write_int(Constants::USM_PRIV_KU_LEN)

        # NOTE I know this is handing off the AuthProto, but generates a proper
        # key for encryption, and using PrivProto does not.
        priv_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                         @sess.securityAuthProtoLen,
                                         options[:priv_password],
                                         options[:priv_password].length,
                                         @sess.securityPrivKey,
                                         priv_len_ptr)
        @sess.securityPrivKeyLen = priv_len_ptr.read_int
      end

      unless auth_key_result == Constants::SNMPERR_SUCCESS and priv_key_result == Constants::SNMPERR_SUCCESS
        Wrapper.snmp_perror("netsnmp")
      end
    end
  end
  # General callback just takes the pdu, calls the session callback if any, then the request specific callback.

  @struct = Wrapper.snmp_sess_open(@sess.pointer)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args) ⇒ Object

Proxy getters to the C struct representing the session



255
256
257
258
259
260
261
# File 'lib/net/snmp/session.rb', line 255

def method_missing(m, *args)
  if @struct.respond_to?(m)
    @struct.send(m, *args)
  else
    super
  end
end

Class Attribute Details

.lockObject

Returns the value of attribute lock.



17
18
19
# File 'lib/net/snmp/session.rb', line 17

def lock
  @lock
end

.sessionsObject

Returns the value of attribute sessions.



17
18
19
# File 'lib/net/snmp/session.rb', line 17

def sessions
  @sessions
end

Instance Attribute Details

#callbackObject

Returns the value of attribute callback.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def callback
  @callback
end

#communityObject

Returns the value of attribute community.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def community
  @community
end

#peernameObject

Returns the value of attribute peername.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def peername
  @peername
end

#portObject

Returns the value of attribute port.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def port
  @port
end

#requestsObject

Returns the value of attribute requests.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def requests
  @requests
end

#structObject

Returns the value of attribute struct.



10
11
12
# File 'lib/net/snmp/session.rb', line 10

def struct
  @struct
end

#versionObject (readonly)

Returns the value of attribute version.



11
12
13
# File 'lib/net/snmp/session.rb', line 11

def version
  @version
end

Class Method Details

.open(options = {}) ⇒ Object

Open a new session. Accepts a block which yields the session.

Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'public') do |sess|
  pdu = sess.get(["sysDescr.0"])
  pdu.print
end

Arguments

  • options: A Hash or String object + As a Hash, supports the following keys

    - peername: hostname
    - community: snmp community string.  Default is public
    - version: snmp version.  Possible values include 1, '2c', and 3. Default is 1.
    - timeout: snmp timeout in seconds
    - retries: snmp retries.  default = 5
    - security_level: SNMPv3 only. default = Net::SNMP::Constants::SNMP_SEC_LEVEL_NOAUTH
    - auth_protocol: SNMPv3 only. default is nil (usmNoAuthProtocol). Possible values include :md5, :sha1, and nil
    - priv_protocol: SNMPv3 only. default is nil (usmNoPrivProtocol). Possible values include :des, :aes, and nil
    - context: SNMPv3 only.
    - username: SNMPv3 only.
    - auth_password: SNMPv3 only.
    - priv_password: SNMPv3 only.
    

Returns a new Net::SNMP::Session



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/net/snmp/session.rb', line 43

def open(options = {})
  session = new(options)
  if Net::SNMP::thread_safe
    Net::SNMP::Session.lock.synchronize {
      Net::SNMP::Session.sessions[session.sessid] = session
    }
  else
    Net::SNMP::Session.sessions[session.sessid] = session
  end
  if block_given?
    yield session
  end
  session
end

Instance Method Details

#closeObject

Close the snmp session and free associated resources.



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/net/snmp/session.rb', line 173

def close
  if Net::SNMP.thread_safe
    self.class.lock.synchronize {
      Wrapper.snmp_sess_close(@struct)
      self.class.sessions.delete(self.sessid)
    }
  else
    Wrapper.snmp_sess_close(@struct)
    self.class.sessions.delete(self.sessid)
  end
end

#columns(columns, options = {}) ⇒ Object

Given a list of columns (e.g [‘ifIndex’, ‘ifDescr’], will return a hash with the indexes as keys and hashes as values.

puts sess.get_columns(['ifIndex', 'ifDescr']).inspect
{'1' => {'ifIndex' => '1', 'ifDescr' => 'lo0'}, '2' => {'ifIndex' => '2', 'ifDescr' => 'en0'}}


381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/net/snmp/session.rb', line 381

def columns(columns, options = {})
  columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)}
  walk_hash = walk(columns, options)
  results = {}
  walk_hash.each do |k, v|
    oid = OID.new(k)
    results[oid.index] ||= {}
    results[oid.index][oid.node.label] = v
  end
  if block_given?
    yield results
  end
  results
end

#default_max_repeatersObject



263
264
265
266
# File 'lib/net/snmp/session.rb', line 263

def default_max_repeaters
  # We could do something based on transport here.  25 seems safe
  25
end

#errnoObject



496
497
498
499
# File 'lib/net/snmp/session.rb', line 496

def errno
  get_error
  @errno
end

#error(msg, options = {}) ⇒ Object

Raise a NET::SNMP::Error with the session attached

Raises:

  • (err)


269
270
271
272
273
# File 'lib/net/snmp/session.rb', line 269

def error(msg, options = {})
  #Wrapper.snmp_sess_perror(msg, @sess.pointer)
  err =  Error.new({:session => self}.merge(options))
  raise err, msg
end

#error_messageObject

The SNMP Session error message



508
509
510
511
# File 'lib/net/snmp/session.rb', line 508

def error_message
  get_error
  @snmp_msg
end

#get(oidlist, options = {}, &block) ⇒ Object

Issue an SNMP GET Request. See #send_pdu



187
188
189
190
191
192
193
194
# File 'lib/net/snmp/session.rb', line 187

def get(oidlist, options = {}, &block)
  pdu = PDU.new(Constants::SNMP_MSG_GET)
  oidlist = [oidlist] unless oidlist.kind_of?(Array)
  oidlist.each do |oid|
    pdu.add_varbind(:oid => oid)
  end
  send_pdu(pdu, options, &block)
end

#get_bulk(oidlist, options = {}, &block) ⇒ Object

Issue an SNMP GETBULK Request Supports typical options, plus

- `:non_repeaters` The number of non-repeated oids in the request
- `:max_repititions` The maximum repititions to return for all repeaters

Note that the non-repeating varbinds must be added first. See #send_pdu



213
214
215
216
217
218
219
220
221
222
# File 'lib/net/snmp/session.rb', line 213

def get_bulk(oidlist, options = {}, &block)
  pdu = PDU.new(Constants::SNMP_MSG_GETBULK)
  oidlist = [oidlist] unless oidlist.kind_of?(Array)
  oidlist.each do |oid|
    pdu.add_varbind(:oid => oid)
  end
  pdu.non_repeaters = options[:non_repeaters] || 0
  pdu.max_repetitions = options[:max_repetitions] || 10
  send_pdu(pdu, options, &block)
end

#get_next(oidlist, options = {}, &block) ⇒ Object

Issue an SNMP GETNEXT Request See #send_pdu



198
199
200
201
202
203
204
205
# File 'lib/net/snmp/session.rb', line 198

def get_next(oidlist, options = {}, &block)
  pdu = PDU.new(Constants::SNMP_MSG_GETNEXT)
  oidlist = [oidlist] unless oidlist.kind_of?(Array)
  oidlist.each do |oid|
    pdu.add_varbind(:oid => oid)
  end
  send_pdu(pdu, options, &block)
end


513
514
515
# File 'lib/net/snmp/session.rb', line 513

def print_errors
  puts "errno: #{errno}, snmp_err: #{@snmp_err}, message: #{@snmp_msg}"
end

#select(timeout = nil) ⇒ Object Also known as: poll

Check the session for SNMP responses from asynchronous SNMP requests This method will check for new responses and call the associated response callbacks. timeout A timeout of nil indicates a poll and will return immediately. A value of false will block until data is available. Otherwise, pass the number of seconds to block. Returns the number of file descriptors handled.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/net/snmp/session.rb', line 283

def select(timeout = nil)
    if @fdset
      # Re-use the same fd set buffer to avoid
      # multiple allocation overhead.
      @fdset.clear
    else
      # 8K should be plenty of space
      @fdset = FFI::MemoryPointer.new(1024 * 8)
    end

    num_fds = FFI::MemoryPointer.new(:int)
    tv_sec = timeout ? timeout.round : 0
    tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
    tval = Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
    block = FFI::MemoryPointer.new(:int)
    if timeout.nil?
      block.write_int(0)
    else
      block.write_int(1)
    end

    Wrapper.snmp_sess_select_info(@struct, num_fds, @fdset, tval.pointer, block )
    tv = (timeout == false ? nil : tval)
    #debug "Calling select #{Time.now}"
    num_ready = FFI::LibC.select(num_fds.read_int, @fdset, nil, nil, tv)
    #debug "Done select #{Time.now}"
    if num_ready > 0
      Wrapper.snmp_sess_read(@struct, @fdset)
    elsif num_ready == 0
      Wrapper.snmp_sess_timeout(@struct)
    elsif num_ready == -1
      # error.  check snmp_error?
      error("select")
    else
      error("wtf is wrong with select?")
    end
    num_ready
end

#send_pdu(pdu, options = {}, &callback) ⇒ Object

Send a PDU pdu The Net::SNMP::PDU object to send. Usually created by Session.get, Session.getnext, etc. callback An optional callback. It should take two parameters, status and response_pdu. If no callback is given, the call will block until the response is available and will return the response pdu. If an error occurs, a Net::SNMP::Error will be thrown. If callback is passed, the PDU will be sent and send_pdu will return immediately. You must then call Session.select to invoke the callback. This is usually done in some sort of event loop. See Net::SNMP::Dispatcher.

If you’re running inside eventmachine and have fibers (ruby 1.9, jruby, etc), sychronous calls will actually run asynchronously behind the scenes. Just run Net::SNMP::Dispatcher.fiber_loop in your reactor.

pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
pdu.add_varbind(:oid => 'sysDescr.0')
session.send_pdu(pdu) do |status, pdu|
  if status == :success
    pdu.print
  elsif status == :timeout
    puts "Timed Out"
  else
    puts "A problem occurred"
  end
end
session.select(false)  #block until data is ready.  Callback will be called.
begin
  result = session.send_pdu(pdu)
  puts result.inspect
rescue Net::SNMP::Error => e
  puts e.message
end


438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/net/snmp/session.rb', line 438

def send_pdu(pdu, options = {},  &callback)
  if options[:blocking]
    return send_pdu_blocking(pdu)
  end
  if block_given?
    @requests[pdu.reqid] = callback
    debug "calling async_send"
    if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
      error("snmp_get async failed")
    end
    nil
  else
    if defined?(EM) && EM.reactor_running? && defined?(Fiber)
      f = Fiber.current
      send_pdu pdu do | op, response_pdu |
        f.resume([op, response_pdu])
      end
      op, result = Fiber.yield
      case op
        when :timeout
          raise TimeoutError.new, "timeout"
        when :send_failed
          error "send failed"
        when :success
          result
        when :connect, :disconnect
          nil   #does this ever happen?
        else
          error "unknown operation #{op}"
      end
    else
      send_pdu_blocking(pdu)
    end
  end
end

#send_pdu_blocking(pdu) ⇒ Object



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/net/snmp/session.rb', line 474

def send_pdu_blocking(pdu)
  response_ptr = FFI::MemoryPointer.new(:pointer)
  if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2, Constants::SNMP_MSG_RESPONSE].include?(pdu.command)
    # Since we don't expect a response, the native net-snmp lib is going to free this
    # pdu for us. Polite, though this may be, it causes intermittent segfaults when freeing
    # memory malloc'ed by ruby. So, clone the pdu into a new memory buffer,
    # and pass that along.
    clone = Wrapper.snmp_clone_pdu(pdu.struct)
    status = Wrapper.snmp_sess_send(@struct, clone)
    if status == 0
      error("snmp_sess_send")
    end
    :success
  else
    status = Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
    unless status == Constants::STAT_SUCCESS
      error("snmp_sess_synch_response", :status => status)
    end
    PDU.new(response_ptr.read_pointer)
  end
end

#set(vb_list, options = {}, &block) ⇒ Object

Issue an SNMP Set Request.

  • vb_list: An single varbind, or an array of varbinds, each of which may be + An Array of length 3 ‘[oid, type, value]` + An Array of length 2 `[oid, value]` + Or a Hash `oid, type: type, value: value`

    * Hash syntax is the same as supported by PDU.add_varbind
    * If type is not supplied, it is infered by the value
    

See #send_pdu



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/net/snmp/session.rb', line 232

def set(vb_list, options = {}, &block)
  pdu = PDU.new(Constants::SNMP_MSG_SET)

  # Normalize input to an array if a single varbind is supplied
  if vb_list.kind_of?(Hash) || (vb_list.kind_of?(Enumerable) && !vb_list.first.kind_of?(Enumerable))
    vb_list = [vb_list]
  end

  vb_list.each do |vb|
    if vb.kind_of?(Hash)
      pdu.add_varbind(vb)
    elsif vb.kind_of?(Enumerable) && vb.length == 3
      pdu.add_varbind(:oid => vb[0], :type => vb[1], :value => vb[2])
    elsif vb.kind_of?(Enumerable) && vb.length == 2
      pdu.add_varbind(:oid => vb[0], :value => vb[1])
    else
      raise "Invalid varbind: #{vb}"
    end
  end
  send_pdu(pdu, options, &block)
end

#snmp_errObject

The SNMP Session error code



502
503
504
505
# File 'lib/net/snmp/session.rb', line 502

def snmp_err
  get_error
  @snmp_err
end

#table(table_name, &blk) ⇒ Object

table(‘ifEntry’). You must pass the direct parent entry. Calls columns with all columns in table_name



398
399
400
401
402
403
404
405
# File 'lib/net/snmp/session.rb', line 398

def table(table_name, &blk)
  column_names = MIB::Node.get_node(table_name).children.collect {|c| c.oid }
  results = columns(column_names)
  if block_given?
    yield results
  end
  results
end

#walk(oidlist, options = {}) ⇒ Object

Issue repeated getnext requests on each oid passed in until the result is no longer a child. Returns a hash with the numeric oid strings as keys. XXX work in progress. only works synchronously (except with EM + fibers). Need to do better error checking and use getbulk when avaiable.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/net/snmp/session.rb', line 329

def walk(oidlist, options = {})
  oidlist = [oidlist] unless oidlist.kind_of?(Array)
  oidlist = oidlist.map {|o| o.kind_of?(OID) ? o : OID.new(o)}
  all_results = {}
  base_list = oidlist
  while(!oidlist.empty? && pdu = get_next(oidlist, options))
    debug "================ Walk: Get Next ====================="
    debug "base_list: \n#{base_list.map { |o| "   - #{o.to_s}" }.join("\n")}"
    prev_base = base_list.dup
    oidlist = []
    pdu.varbinds.each_with_index do |vb, i|
      if prev_base[i].parent_of?(vb.oid) && vb.object_type != Constants::SNMP_ENDOFMIBVIEW
        # Still in subtree.  Store results and add next oid to list
        debug "adding #{vb.oid} to oidlist"
        all_results[vb.oid.to_s] = vb.value
        oidlist << vb.oid
      else
        # End of subtree.  Don't add to list or results
        debug "End of subtree"
        base_list.delete_at(i)
        debug "not adding #{vb.oid}"
      end
      # If get a pdu error, we can only tell the first failing varbind,
      # So we remove it and resend all the rest
      if pdu.error? && pdu.errindex == i + 1
        oidlist.pop  # remove the bad oid
        debug "caught error"
        if pdu.varbinds.size > i+1
          # recram rest of oids on list
          ((i+1)..pdu.varbinds.size).each do |j|
            debug "j = #{j}"
            debug "adding #{j} = #{prev_list[j]}"
            oidlist << prev_list[j]
          end
          # delete failing oid from base_list
          base_list.delete_at(i)
        end
        break
      end
    end
  end
  if block_given?
    yield all_results
  end
  all_results
end