250 lines
8.1 KiB
Ruby
250 lines
8.1 KiB
Ruby
|
#
|
||
|
# Copyright (c) 2004 David R. Halliday
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# This SNMP library is free software. Redistribution is permitted under the
|
||
|
# same terms and conditions as the standard Ruby distribution. See the
|
||
|
# COPYING file in the Ruby distribution for details.
|
||
|
#
|
||
|
|
||
|
require 'snmp/varbind'
|
||
|
require 'fileutils'
|
||
|
require 'yaml'
|
||
|
|
||
|
module SNMP
|
||
|
|
||
|
class MIB
|
||
|
|
||
|
DEFAULT_MIB_PATH = File.expand_path(
|
||
|
File.join(File.dirname(__FILE__), "..", "..", "data", "snmp", "mibs")
|
||
|
)
|
||
|
|
||
|
#:startdoc:
|
||
|
|
||
|
MODULE_EXT = 'yaml'
|
||
|
|
||
|
class ModuleNotLoadedError < RuntimeError; end
|
||
|
|
||
|
class << self
|
||
|
##
|
||
|
# Import an SMIv2 MIB file for later loading. A module only needs to
|
||
|
# be installed once.
|
||
|
#
|
||
|
# module_file - the filename of the module to be imported
|
||
|
# mib_dir - the output directory for the serialized MIB data
|
||
|
#
|
||
|
# NOTE: This implementation requires that the 'smidump' tool is available
|
||
|
# in the PATH. This tool can be obtained from the libsmi website at
|
||
|
# http://http://www.ibr.cs.tu-bs.de/projects/libsmi/ .
|
||
|
#
|
||
|
# ALSO NOTE: The file format in future releases is subject to
|
||
|
# change. For now, it is a simple YAML hash with the MIB symbol
|
||
|
# as the key and the OID as the value. These files could be
|
||
|
# generated manually if 'smidump' is not available.
|
||
|
#
|
||
|
# Here is an example of the contents of an output file:
|
||
|
#
|
||
|
# ---
|
||
|
# ipDefaultTTL: 1.3.6.1.2.1.4.2
|
||
|
# ipForwDatagrams: 1.3.6.1.2.1.4.6
|
||
|
# ipOutRequests: 1.3.6.1.2.1.4.10
|
||
|
# ipOutNoRoutes: 1.3.6.1.2.1.4.12
|
||
|
# ipReasmTimeout: 1.3.6.1.2.1.4.13
|
||
|
# icmpInDestUnreachs: 1.3.6.1.2.1.5.3
|
||
|
#
|
||
|
def import_module(module_file, mib_dir=DEFAULT_MIB_PATH)
|
||
|
raise "smidump tool must be installed" unless import_supported?
|
||
|
FileUtils.makedirs mib_dir
|
||
|
mib_hash = `smidump -f python #{module_file}`
|
||
|
mib = eval_mib_data(mib_hash)
|
||
|
if mib
|
||
|
module_name = mib["moduleName"]
|
||
|
raise "#{module_file}: invalid file format; no module name" unless module_name
|
||
|
if mib["nodes"]
|
||
|
oid_hash = {}
|
||
|
mib["nodes"].each { |key, value| oid_hash[key] = value["oid"] }
|
||
|
if mib["notifications"]
|
||
|
mib["notifications"].each { |key, value| oid_hash[key] = value["oid"] }
|
||
|
end
|
||
|
File.open(module_file_name(module_name, mib_dir), 'w') do |file|
|
||
|
YAML.dump(oid_hash, file)
|
||
|
file.puts
|
||
|
end
|
||
|
module_name
|
||
|
else
|
||
|
warn "*** No nodes defined in: #{module_file} ***"
|
||
|
nil
|
||
|
end
|
||
|
else
|
||
|
warn "*** Import failed for: #{module_file} ***"
|
||
|
nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Returns the full filename of the imported MIB file for the given
|
||
|
# module name.
|
||
|
#
|
||
|
def module_file_name(module_name, mib_dir=DEFAULT_MIB_PATH)
|
||
|
File.join(mib_dir, module_name + "." + MODULE_EXT)
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# The MIB.import_module method is only supported if the external
|
||
|
# 'smidump' tool is available. This method returns true if a
|
||
|
# known version of the tool is available.
|
||
|
#
|
||
|
def import_supported?
|
||
|
`smidump --version` =~ /^smidump 0.4/ && $? == 0
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Returns a list of MIB modules that have been imported. All of
|
||
|
# the current IETF MIBs should be available from the default
|
||
|
# MIB directory.
|
||
|
#
|
||
|
# If a regex is provided, then the module names are matched
|
||
|
# against that pattern.
|
||
|
#
|
||
|
def list_imported(regex=//, mib_dir=DEFAULT_MIB_PATH)
|
||
|
list = []
|
||
|
Dir["#{mib_dir}/*.#{MODULE_EXT}"].each do |name|
|
||
|
module_name = File.basename(name, ".*")
|
||
|
list << module_name if module_name =~ regex
|
||
|
end
|
||
|
list
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def eval_mib_data(mib_hash)
|
||
|
ruby_hash = mib_hash.
|
||
|
gsub(':', '=>'). # fix hash syntax
|
||
|
gsub('(', '[').gsub(')', ']'). # fix tuple syntax
|
||
|
sub('FILENAME =', 'filename ='). # get rid of constants
|
||
|
sub('MIB =', 'mib =')
|
||
|
mib = nil
|
||
|
eval(ruby_hash)
|
||
|
mib
|
||
|
end
|
||
|
end # class methods
|
||
|
|
||
|
def initialize
|
||
|
@by_name = {}
|
||
|
@by_module_by_name = {}
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Loads a module into this MIB. The module must be imported before it
|
||
|
# can be loaded. See MIB.import_module .
|
||
|
#
|
||
|
def load_module(module_name, mib_dir=DEFAULT_MIB_PATH)
|
||
|
oid_hash = nil
|
||
|
File.open(MIB.module_file_name(module_name, mib_dir)) do |file|
|
||
|
oid_hash = YAML.load(file.read)
|
||
|
end
|
||
|
@by_name.merge!(oid_hash) do |key, old, value|
|
||
|
warn "warning: overwriting old MIB name '#{key}'"
|
||
|
end
|
||
|
@by_module_by_name[module_name] = {}
|
||
|
@by_module_by_name[module_name].merge!(oid_hash)
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Returns a VarBindList for the provided list of objects. If a
|
||
|
# string is provided it is interpretted as a symbolic OID.
|
||
|
#
|
||
|
# This method accepts many different kinds of objects:
|
||
|
# - single string object IDs e.g. "1.3.6.1" or "IF-MIB::ifTable.1.1"
|
||
|
# - single ObjectId
|
||
|
# - list of string object IDs
|
||
|
# - list of ObjectIds
|
||
|
# - list of VarBinds
|
||
|
#
|
||
|
def varbind_list(object_list, option=:KeepValue)
|
||
|
vb_list = VarBindList.new
|
||
|
if object_list.respond_to? :to_str
|
||
|
vb_list << oid(object_list).to_varbind
|
||
|
elsif object_list.respond_to? :to_varbind
|
||
|
vb_list << apply_option(object_list.to_varbind, option)
|
||
|
else
|
||
|
object_list.each do |item|
|
||
|
if item.respond_to? :to_str
|
||
|
varbind = oid(item).to_varbind
|
||
|
else
|
||
|
varbind = item.to_varbind
|
||
|
end
|
||
|
vb_list << apply_option(varbind, option)
|
||
|
end
|
||
|
end
|
||
|
vb_list
|
||
|
end
|
||
|
|
||
|
def apply_option(varbind, option)
|
||
|
if option == :NullValue
|
||
|
varbind.value = Null
|
||
|
elsif option != :KeepValue
|
||
|
raise ArgumentError, "invalid option: #{option.to_s}", caller
|
||
|
end
|
||
|
varbind
|
||
|
end
|
||
|
private :apply_option
|
||
|
|
||
|
##
|
||
|
# Returns a VarBind object for the given name and value. The name
|
||
|
# can be a String, ObjectId, or anything that responds to :to_varbind.
|
||
|
#
|
||
|
# String names are in the format <ModuleName>::<NodeName>.<Index> with
|
||
|
# ModuleName and Index being optional.
|
||
|
#
|
||
|
def varbind(name, value=Null)
|
||
|
if name.respond_to? :to_str
|
||
|
vb = VarBind.new(oid(name), value)
|
||
|
else
|
||
|
vb = name.to_varbind
|
||
|
vb.value = value
|
||
|
end
|
||
|
vb
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Returns an ObjectId for the given name. Names are in the format
|
||
|
# <ModuleName>::<NodeName>.<Index> with ModuleName and Index being
|
||
|
# optional.
|
||
|
#
|
||
|
def oid(name)
|
||
|
module_parts = name.to_str.split("::")
|
||
|
if module_parts.length == 1
|
||
|
parse_oid(@by_name, name.to_str)
|
||
|
elsif module_parts.length == 2
|
||
|
module_name = module_parts[0]
|
||
|
oid = module_parts[1]
|
||
|
module_hash = @by_module_by_name[module_name]
|
||
|
if module_hash
|
||
|
parse_oid(module_hash, oid)
|
||
|
else
|
||
|
raise ModuleNotLoadedError, "module '#{module_name}' not loaded"
|
||
|
end
|
||
|
else
|
||
|
raise ArgumentError, "invalid format: #{name.to_str}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def parse_oid(node_hash, name)
|
||
|
oid_parts = name.split(".")
|
||
|
first_part = oid_parts.shift
|
||
|
oid_string = node_hash[first_part]
|
||
|
if oid_string
|
||
|
oid_array = oid_string.split(".")
|
||
|
else
|
||
|
oid_array = [first_part]
|
||
|
end
|
||
|
oid_array.concat(oid_parts)
|
||
|
ObjectId.new(oid_array)
|
||
|
end
|
||
|
private :parse_oid
|
||
|
|
||
|
end
|
||
|
|
||
|
end # module SNMP
|