Class: Net::SNMP::Session
- Inherits:
-
Object
- Object
- Net::SNMP::Session
- Extended by:
- Forwardable
- Includes:
- Debug
- Defined in:
- lib/net/snmp/session.rb
Overview
Provides an API for managing SNMP agents
Direct Known Subclasses
Class Attribute Summary collapse
-
.lock ⇒ Object
Returns the value of attribute lock.
-
.sessions ⇒ Object
Returns the value of attribute sessions.
Instance Attribute Summary collapse
-
#callback ⇒ Object
Returns the value of attribute callback.
-
#community ⇒ Object
Returns the value of attribute community.
-
#peername ⇒ Object
Returns the value of attribute peername.
-
#port ⇒ Object
Returns the value of attribute port.
-
#requests ⇒ Object
Returns the value of attribute requests.
-
#struct ⇒ Object
Returns the value of attribute struct.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.open(options = {}) ⇒ Object
Open a new session.
Instance Method Summary collapse
-
#close ⇒ Object
Close the snmp session and free associated resources.
-
#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 => {‘ifIndex’ => ‘1’, ‘ifDescr’ => ‘lo0’, ‘2’ => => ‘2’, ‘ifDescr’ => ‘en0’}.
- #default_max_repeaters ⇒ Object
- #errno ⇒ Object
-
#error(msg, options = {}) ⇒ Object
Raise a NET::SNMP::Error with the session attached.
-
#error_message ⇒ Object
The SNMP Session error message.
-
#get(oidlist, options = {}, &block) ⇒ Object
Issue an SNMP GET Request.
-
#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.
-
#get_next(oidlist, options = {}, &block) ⇒ Object
Issue an SNMP GETNEXT Request See #send_pdu.
-
#initialize(options = {}) ⇒ Session
constructor
A new instance of Session.
-
#method_missing(m, *args) ⇒ Object
Proxy getters to the C struct representing the session.
- #print_errors ⇒ Object
-
#select(timeout = nil) ⇒ Object
(also: #poll)
Check the session for SNMP responses from asynchronous SNMP requests This method will check for new responses and call the associated response callbacks.
-
#send_pdu(pdu, options = {}, &callback) ⇒ Object
Send a PDU
pdu
The Net::SNMP::PDU object to send. - #send_pdu_blocking(pdu) ⇒ Object
-
#set(vb_list, options = {}, &block) ⇒ Object
Issue an SNMP Set Request.
-
#snmp_err ⇒ Object
The SNMP Session error code.
-
#table(table_name, &blk) ⇒ Object
table(‘ifEntry’).
-
#walk(oidlist, options = {}) ⇒ Object
Issue repeated getnext requests on each oid passed in until the result is no longer a child.
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( = {}) = {:peername => } if .kind_of?(String) @timeout = [:timeout] || 1 @retries = [:retries] || 5 @requests = {} @peername = [:peername] || 'localhost' # If the port is supplied in the peername, don't # worry about the port option (avoids appending two port numbers) unless @peername[':'] [:port] ||= 161 @port = [:port] @peername = "#{@peername}:#{[:port]}" end @community = [:community] || "public" [:community_len] = @community.length [:version] ||= Constants::SNMP_VERSION_2c @version = [: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 = [:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH @sess.securityAuthProto = case [: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 [: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 [:context] @sess.contextName = FFI::MemoryPointer.from_string([:context]) @sess.contextNameLen = [:context].length end # Do not generate_Ku, unless we're Auth or AuthPriv unless @sess.securityLevel == Constants::SNMP_SEC_LEVEL_NOAUTH [:auth_password] ||= [:password] # backward compatability if [:username].nil? or [:auth_password].nil? raise Net::SNMP::Error.new "SecurityLevel requires username and password" end if [:username] @sess.securityName = FFI::MemoryPointer.from_string([:username]) @sess.securityNameLen = [: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, [:auth_password], [: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, [:priv_password], [: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
.lock ⇒ Object
Returns the value of attribute lock.
17 18 19 |
# File 'lib/net/snmp/session.rb', line 17 def lock @lock end |
.sessions ⇒ Object
Returns the value of attribute sessions.
17 18 19 |
# File 'lib/net/snmp/session.rb', line 17 def sessions @sessions end |
Instance Attribute Details
#callback ⇒ Object
Returns the value of attribute callback.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def callback @callback end |
#community ⇒ Object
Returns the value of attribute community.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def community @community end |
#peername ⇒ Object
Returns the value of attribute peername.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def peername @peername end |
#port ⇒ Object
Returns the value of attribute port.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def port @port end |
#requests ⇒ Object
Returns the value of attribute requests.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def requests @requests end |
#struct ⇒ Object
Returns the value of attribute struct.
10 11 12 |
# File 'lib/net/snmp/session.rb', line 10 def struct @struct end |
#version ⇒ Object (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( = {}) session = new() 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
#close ⇒ Object
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, = {}) columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)} walk_hash = walk(columns, ) 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_repeaters ⇒ Object
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 |
#errno ⇒ Object
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
269 270 271 272 273 |
# File 'lib/net/snmp/session.rb', line 269 def error(msg, = {}) #Wrapper.snmp_sess_perror(msg, @sess.pointer) err = Error.new({:session => self}.merge()) raise err, msg end |
#error_message ⇒ Object
The SNMP Session error message
508 509 510 511 |
# File 'lib/net/snmp/session.rb', line 508 def 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, = {}, &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, , &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, = {}, &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 = [:non_repeaters] || 0 pdu.max_repetitions = [:max_repetitions] || 10 send_pdu(pdu, , &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, = {}, &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, , &block) end |
#print_errors ⇒ Object
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.
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, = {}, &callback) if [: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, = {}, &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, , &block) end |
#snmp_err ⇒ Object
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, = {}) 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, )) 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 |