net-snmp2 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +24 -0
- data/TODO.md +6 -0
- data/bin/mib2rb +129 -0
- data/bin/net-snmp2 +64 -0
- data/examples/agent.rb +104 -0
- data/examples/manager.rb +11 -0
- data/examples/trap_handler.rb +46 -0
- data/examples/v1_trap_session.rb +24 -0
- data/examples/v2_trap_session.rb +21 -0
- data/lib/net-snmp2.rb +85 -0
- data/lib/net/snmp.rb +27 -0
- data/lib/net/snmp/agent/agent.rb +48 -0
- data/lib/net/snmp/agent/provider.rb +51 -0
- data/lib/net/snmp/agent/provider_dsl.rb +124 -0
- data/lib/net/snmp/agent/request_dispatcher.rb +38 -0
- data/lib/net/snmp/constants.rb +287 -0
- data/lib/net/snmp/debug.rb +54 -0
- data/lib/net/snmp/dispatcher.rb +108 -0
- data/lib/net/snmp/error.rb +29 -0
- data/lib/net/snmp/listener.rb +76 -0
- data/lib/net/snmp/message.rb +142 -0
- data/lib/net/snmp/mib/mib.rb +67 -0
- data/lib/net/snmp/mib/module.rb +39 -0
- data/lib/net/snmp/mib/node.rb +122 -0
- data/lib/net/snmp/mib/templates.rb +48 -0
- data/lib/net/snmp/oid.rb +134 -0
- data/lib/net/snmp/pdu.rb +235 -0
- data/lib/net/snmp/repl/manager_repl.rb +243 -0
- data/lib/net/snmp/session.rb +560 -0
- data/lib/net/snmp/trap_handler/trap_handler.rb +42 -0
- data/lib/net/snmp/trap_handler/v1_trap_dsl.rb +44 -0
- data/lib/net/snmp/trap_handler/v2_trap_dsl.rb +38 -0
- data/lib/net/snmp/trap_session.rb +92 -0
- data/lib/net/snmp/utility.rb +10 -0
- data/lib/net/snmp/varbind.rb +57 -0
- data/lib/net/snmp/version.rb +5 -0
- data/lib/net/snmp/wrapper.rb +450 -0
- data/net-snmp2.gemspec +30 -0
- data/spec/README.md +105 -0
- data/spec/async_spec.rb +123 -0
- data/spec/em_spec.rb +23 -0
- data/spec/error_spec.rb +34 -0
- data/spec/fiber_spec.rb +41 -0
- data/spec/mib_spec.rb +68 -0
- data/spec/net-snmp_spec.rb +18 -0
- data/spec/oid_spec.rb +21 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/sync_spec.rb +132 -0
- data/spec/thread_spec.rb +19 -0
- data/spec/trap_spec.rb +45 -0
- data/spec/utility_spec.rb +10 -0
- data/spec/wrapper_spec.rb +69 -0
- metadata +166 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module Net
|
2
|
+
module SNMP
|
3
|
+
module Debug
|
4
|
+
class << self
|
5
|
+
attr_accessor :logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def debug(msg)
|
9
|
+
Debug.logger.debug msg if Debug.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(msg)
|
13
|
+
Debug.logger.info msg if Debug.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def warn(msg)
|
17
|
+
Debug.logger.warn msg if Debug.logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(msg)
|
21
|
+
Debug.logger.error msg if Debug.logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def fatal(msg)
|
25
|
+
Debug.logger.fatal msg if Debug.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def time(label, &block)
|
29
|
+
t_start = Time.now
|
30
|
+
block[]
|
31
|
+
t_end = Time.now
|
32
|
+
info "#{label}: #{(t_end - t_start)*1000}ms"
|
33
|
+
end
|
34
|
+
|
35
|
+
def print_packet(packet)
|
36
|
+
byte_string = (packet.kind_of?(Array) ? packet[0] : packet)
|
37
|
+
|
38
|
+
byte_array = byte_string.unpack("C*")
|
39
|
+
binary = byte_array.map{|n| n.to_s(2).rjust(8, '0')}
|
40
|
+
|
41
|
+
puts " # | Decimal | Hex | Binary | Character"
|
42
|
+
puts "-------------------------------------------"
|
43
|
+
|
44
|
+
i = 0
|
45
|
+
prev = 0
|
46
|
+
byte_array.zip(binary).each do |byte, binary_string|
|
47
|
+
puts "#{i.to_s.ljust(5)}#{byte.to_s.ljust(10)}0x#{byte.to_s(16).ljust(6)}#{binary_string.ljust(11)}#{byte.chr} #{'Sequence Length' if byte == 130 && prev == 48}"
|
48
|
+
prev = byte
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class Net::SNMP::Dispatcher
|
2
|
+
# A class with convenience methods for polling multiple open sessions
|
3
|
+
class << self
|
4
|
+
# Loop through all sessions, calling select on each.
|
5
|
+
def select(timeout = nil)
|
6
|
+
total = 0
|
7
|
+
t = timeout
|
8
|
+
t = nil if t == false
|
9
|
+
catch :got_data do
|
10
|
+
loop do
|
11
|
+
if Net::SNMP.thread_safe
|
12
|
+
Net::SNMP::Session.lock.synchronize {
|
13
|
+
Net::SNMP::Session.sessions.each do |k, sess|
|
14
|
+
total += sess.select(t)
|
15
|
+
end
|
16
|
+
}
|
17
|
+
else
|
18
|
+
Net::SNMP::Session.sessions.each do |k, sess|
|
19
|
+
total += sess.select(t)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
throw :got_data if total > 0
|
24
|
+
throw :got_data unless timeout == false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
total
|
28
|
+
end
|
29
|
+
alias :poll :select
|
30
|
+
|
31
|
+
# Start a poller loop. Behavior depends upon whether
|
32
|
+
# you are running under eventmachine and whether fibers
|
33
|
+
# are available.
|
34
|
+
# +options+
|
35
|
+
# * :timeout Number of seconds to block on select. nil effects a poll. false blocks forever (probably not what you want).
|
36
|
+
# * :sleep Number of seconds to sleep if no data is available. Gives other fibers/threads a chance to run.
|
37
|
+
def run_loop(options = {})
|
38
|
+
if defined?(EM) && EM.reactor_running?
|
39
|
+
if defined?(Fiber)
|
40
|
+
fiber_loop(options)
|
41
|
+
else
|
42
|
+
em_loop(options)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
thread_loop(options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Start a poller loop in a seperate thread. You
|
50
|
+
# should first call Net::SNMP.thread_safe = true.
|
51
|
+
# +options+
|
52
|
+
# * :timeout Number of seconds to block on select. Will not block other threads.
|
53
|
+
# * :sleep Number of seconds to sleep if no data is available. Allows other threads to run. Default 0.2
|
54
|
+
def thread_loop(options = {})
|
55
|
+
timeout = options[:timeout] || 0.2
|
56
|
+
sleep_time = options[:sleep] || 0.2
|
57
|
+
Thread.new do
|
58
|
+
loop do
|
59
|
+
num_ready = select(timeout)
|
60
|
+
if num_ready == 0
|
61
|
+
sleep(sleep_time) if sleep_time
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Start a loop in eventmachine (no fibers)
|
68
|
+
# +options+
|
69
|
+
# * :sleep Number of seconds to sleep if no data available. So we don't peg the reactor. Default 0.2
|
70
|
+
def em_loop(options = {})
|
71
|
+
timeout = options[:timeout]
|
72
|
+
sleep_time = options[:sleep] || 0.2
|
73
|
+
myproc = Proc.new do
|
74
|
+
EM.next_tick do
|
75
|
+
while(true) do
|
76
|
+
num_ready = select(timeout)
|
77
|
+
break if num_ready == 0
|
78
|
+
end
|
79
|
+
EM.add_timer(sleep_time) do
|
80
|
+
myproc.call
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
myproc.call
|
85
|
+
end
|
86
|
+
|
87
|
+
# Start a loop using eventmachine and fibers
|
88
|
+
# +options+
|
89
|
+
# * :sleep Number of seconds to sleep if no data available, so we don't peg the reactor. Default 0.2
|
90
|
+
def fiber_loop(options = {})
|
91
|
+
timeout = options[:timeout]
|
92
|
+
sleep_time = options[:sleep] || 0.2
|
93
|
+
fib = Fiber.new {
|
94
|
+
loop do
|
95
|
+
num_handled = poll(timeout)
|
96
|
+
if num_handled == 0
|
97
|
+
f = Fiber.current
|
98
|
+
EM.add_timer(sleep_time) do
|
99
|
+
f.resume
|
100
|
+
end
|
101
|
+
Fiber.yield
|
102
|
+
end
|
103
|
+
end
|
104
|
+
}
|
105
|
+
fib.resume
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Net
|
2
|
+
module SNMP
|
3
|
+
class Error < RuntimeError
|
4
|
+
attr_accessor :status, :errno, :snmp_err, :snmp_msg
|
5
|
+
def initialize(opts = {})
|
6
|
+
@status = opts[:status]
|
7
|
+
@fiber = opts[:fiber]
|
8
|
+
if opts[:session]
|
9
|
+
@errno = opts[:session].errno
|
10
|
+
@snmp_err = opts[:session].snmp_err
|
11
|
+
@snmp_msg = opts[:session].error_message
|
12
|
+
end
|
13
|
+
print
|
14
|
+
end
|
15
|
+
|
16
|
+
def print
|
17
|
+
puts "SNMP Error: #{self.class.to_s}"
|
18
|
+
puts "message = #{message}"
|
19
|
+
puts "status = #{@status}"
|
20
|
+
puts "errno = #{@errno}"
|
21
|
+
puts "snmp_err = #{@snmp_err}"
|
22
|
+
puts "snmp_msg = #{@snmp_msg}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TimeoutError < Error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Net::SNMP
|
4
|
+
|
5
|
+
# Implements a generic listener for incoming SNMP packets
|
6
|
+
|
7
|
+
class Listener
|
8
|
+
include Debug
|
9
|
+
|
10
|
+
attr_accessor :port, :socket, :packet, :callback
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
# Set by `stop` (probably in an INT signal handler) to
|
14
|
+
# indicate that the agent should stop
|
15
|
+
@killed = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Starts the listener's run loop
|
19
|
+
def start(port = 161, interval = 2, max_packet_size = 65_000)
|
20
|
+
@interval = interval
|
21
|
+
@socket = UDPSocket.new
|
22
|
+
@socket.bind("127.0.0.1", port)
|
23
|
+
@max_packet_size = max_packet_size
|
24
|
+
info "Listening on port #{port}"
|
25
|
+
run_loop
|
26
|
+
end
|
27
|
+
alias listen start
|
28
|
+
alias run start
|
29
|
+
|
30
|
+
# Stops the listener's run loop
|
31
|
+
def stop
|
32
|
+
@killed = true
|
33
|
+
end
|
34
|
+
alias kill stop
|
35
|
+
|
36
|
+
# Sets the handler for all incoming messages.
|
37
|
+
#
|
38
|
+
# The block provided will be called back for each message as follows:
|
39
|
+
#
|
40
|
+
# block[message, from_address, from_port]
|
41
|
+
#
|
42
|
+
# Where
|
43
|
+
#
|
44
|
+
# - `message` is the parsed Net::SNMP::Message object
|
45
|
+
# - `from_address` is a string representing the address of the host sending the request
|
46
|
+
# - `from_port` is the port the host sent the request from
|
47
|
+
def on_message(&block)
|
48
|
+
@callback = block
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def run_loop
|
54
|
+
packet = nil
|
55
|
+
loop {
|
56
|
+
begin
|
57
|
+
return if @killed
|
58
|
+
# TODO: Not exactly the most efficient solution...
|
59
|
+
timeout(@interval) do
|
60
|
+
@packet = @socket.recvfrom(@max_packet_size)
|
61
|
+
end
|
62
|
+
return if @killed
|
63
|
+
time "Message Processing" do
|
64
|
+
message = Message.parse(@packet)
|
65
|
+
@callback[message, @packet[1][3], @packet[1][1]] if @callback
|
66
|
+
end
|
67
|
+
rescue Timeout::Error => timeout
|
68
|
+
next
|
69
|
+
rescue StandardError => ex
|
70
|
+
error "Error in listener.\n#{ex}\n #{ex.backtrace.join("\n ")}"
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Net
|
2
|
+
module SNMP
|
3
|
+
class Message
|
4
|
+
include SNMP::Debug
|
5
|
+
|
6
|
+
def self.parse(packet)
|
7
|
+
Message.new(packet)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Could have been an instance method, but a response pdu
|
11
|
+
# isn't really an intrinsic property of all messages. So,
|
12
|
+
# going with class method instead.
|
13
|
+
def self.response_pdu_for(message)
|
14
|
+
response_pdu = PDU.new(Constants::SNMP_MSG_RESPONSE)
|
15
|
+
response_pdu.reqid = message.pdu.reqid
|
16
|
+
response_pdu.version = message.version
|
17
|
+
response_pdu.community = message.pdu.community
|
18
|
+
response_pdu
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :version, :community, :pdu, :version_ptr, :community_ptr
|
22
|
+
|
23
|
+
def version_name
|
24
|
+
case @version
|
25
|
+
when Constants::SNMP_VERSION_1
|
26
|
+
'1'
|
27
|
+
when Constants::SNMP_VERSION_2c
|
28
|
+
'2c'
|
29
|
+
when Constants::SNMP_VERSION_3
|
30
|
+
'3'
|
31
|
+
else
|
32
|
+
raise "Invalid SNMP version: #{@version}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_accessor :type,
|
39
|
+
:length,
|
40
|
+
:data,
|
41
|
+
:cursor,
|
42
|
+
:bytes_remaining
|
43
|
+
|
44
|
+
def initialize(packet)
|
45
|
+
@version = nil
|
46
|
+
@version_ptr = FFI::MemoryPointer.new(:long, 1)
|
47
|
+
@community_ptr = FFI::MemoryPointer.new(:uchar, 100)
|
48
|
+
@packet = packet
|
49
|
+
@packet_length = packet[0].length
|
50
|
+
@type_ptr = FFI::MemoryPointer.new(:int, 1)
|
51
|
+
@data_ptr = FFI::MemoryPointer.new(:char, @packet_length)
|
52
|
+
@data_ptr.write_bytes(packet[0])
|
53
|
+
@cursor_ptr = @data_ptr
|
54
|
+
@bytes_remaining_ptr = FFI::MemoryPointer.new(:int, 1)
|
55
|
+
@bytes_remaining_ptr.write_bytes([@packet_length].pack("L"))
|
56
|
+
debug "MESSAGE INITIALIZED\n#{self}"
|
57
|
+
parse
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse
|
61
|
+
parse_length
|
62
|
+
parse_version
|
63
|
+
parse_community
|
64
|
+
parse_pdu
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_length
|
69
|
+
@cursor_ptr = Net::SNMP::Wrapper.asn_parse_header(@data_ptr, @bytes_remaining_ptr, @type_ptr)
|
70
|
+
unless @type_ptr.read_int == 48
|
71
|
+
raise "Invalid SNMP packet. Message should start with a sequence declaration"
|
72
|
+
end
|
73
|
+
debug "MESSAGE SEQUENCE HEADER PARSED\n#{self}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_version
|
77
|
+
@cursor_ptr = Net::SNMP::Wrapper.asn_parse_int(
|
78
|
+
@cursor_ptr,
|
79
|
+
@bytes_remaining_ptr,
|
80
|
+
@type_ptr,
|
81
|
+
@version_ptr,
|
82
|
+
@version_ptr.total)
|
83
|
+
|
84
|
+
@version = @version_ptr.read_long
|
85
|
+
debug "VERSION NUMBER PARSED\n#{self}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_community
|
89
|
+
community_length_ptr = FFI::MemoryPointer.new(:size_t, 1)
|
90
|
+
community_length_ptr.write_int(@community_ptr.total)
|
91
|
+
@cursor_ptr = Net::SNMP::Wrapper.asn_parse_string(
|
92
|
+
@cursor_ptr,
|
93
|
+
@bytes_remaining_ptr,
|
94
|
+
@type_ptr,
|
95
|
+
@community_ptr,
|
96
|
+
community_length_ptr)
|
97
|
+
|
98
|
+
@community = @community_ptr.read_string
|
99
|
+
debug "COMMUNITY PARSED\n#{self}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_pdu
|
103
|
+
###########################################
|
104
|
+
# Don't do this...
|
105
|
+
#
|
106
|
+
# pdu_struct_ptr = Wrapper::SnmpPdu.new
|
107
|
+
#
|
108
|
+
# We do not want to own this pointer, or we can't call `snmp_free_pdu` on it,
|
109
|
+
# which happens in PDU#free. Instead, let the native library allocate it.
|
110
|
+
# Note that if we allocate any members for this pdu (like the enterprise oid string),
|
111
|
+
# We will have to free those ourselves before calling `snmp_free_pdu`.
|
112
|
+
#
|
113
|
+
# If the above is not done, segfaults start happening when one side tries
|
114
|
+
# to free memory malloc'ed by the other side. Possibly because the netsnmp.dll
|
115
|
+
# links to a different C Runtime library, which may have differences in malloc/free.
|
116
|
+
pdu_struct_ptr = Wrapper.snmp_pdu_create(0)
|
117
|
+
###########################################
|
118
|
+
|
119
|
+
Net::SNMP::Wrapper.snmp_pdu_parse(pdu_struct_ptr, @cursor_ptr, @bytes_remaining_ptr)
|
120
|
+
@pdu = Net::SNMP::PDU.new(pdu_struct_ptr.pointer)
|
121
|
+
debug "PDU PARSED\n#{self}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_s
|
125
|
+
<<-EOF
|
126
|
+
version(#{@version})
|
127
|
+
community(#{@community})
|
128
|
+
pdu
|
129
|
+
command(#{@pdu.command if @pdu})
|
130
|
+
varbinds (#{@pdu.varbinds.map{|v| "\n #{v.oid.to_s} => #{v.value}" }.join('') if @pdu})
|
131
|
+
type(#{@type_ptr.read_int})
|
132
|
+
bytes_remaining(#{@bytes_remaining_ptr.read_int})
|
133
|
+
cursor @ #{@cursor_ptr.address}
|
134
|
+
Byte: #{indices = []; (@bytes_remaining_ptr.read_int.times {|i| indices.push((i+1).to_s.rjust(2))}; indices.join ' ')}
|
135
|
+
Value: #{@cursor_ptr.get_bytes(0, @bytes_remaining_ptr.read_int).each_byte.map {|b| b.to_s(16).rjust(2, '0') }.join(' ')}
|
136
|
+
data @ #{@data_ptr.address}
|
137
|
+
#{@data_ptr.get_bytes(0, @packet_length).each_byte.map {|b| b.to_s(16).rjust(2, '0') }.join(' ')}
|
138
|
+
EOF
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Net
|
2
|
+
module SNMP
|
3
|
+
module MIB
|
4
|
+
|
5
|
+
# Configures the MIB directory search path (using add_mibdir ), sets up the internal
|
6
|
+
# MIB framework, and then loads the appropriate MIB modules (using netsnmp_read_module
|
7
|
+
# and read_mib). It should be called before any other routine that manipulates
|
8
|
+
# or accesses the MIB tree (but after any additional add_mibdir calls).
|
9
|
+
def self.init
|
10
|
+
Wrapper.netsnmp_init_mib
|
11
|
+
end
|
12
|
+
|
13
|
+
# Read in all the MIB modules found on the MIB directory search list
|
14
|
+
def self.read_all_mibs
|
15
|
+
Wrapper.read_all_mibs
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get an OID object representing the MIB node containing `oid`
|
19
|
+
# - `oid` argument may be a numerical oid, or the MIB name
|
20
|
+
def self.get_node(oid)
|
21
|
+
Node.get_node(oid)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get an OID object representing the MIB node containing `oid`
|
25
|
+
# - `oid` argument may be a numerical oid, or the MIB name
|
26
|
+
def self.[](oid)
|
27
|
+
Node.get_node(oid)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Translates a numerical oid to it's MIB name, or a name to numerical oid
|
31
|
+
def self.translate(oid)
|
32
|
+
node = Node.get_node(oid)
|
33
|
+
if oid =~ /^[0-9.]*$/
|
34
|
+
# Node label + instance indexes from argument
|
35
|
+
"#{node.label}#{oid.sub(node.oid.to_s, "")}"
|
36
|
+
else
|
37
|
+
# Node OID + instance indexes from argument
|
38
|
+
"#{node.oid.to_s}#{oid.sub(node.label.to_s, "")}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add the specified directory to the path of locations which are searched
|
43
|
+
# for files containing MIB modules. Note that this does not actually load
|
44
|
+
# the MIB modules located in that directory
|
45
|
+
def self.add_mibdir(dirname)
|
46
|
+
Wrapper.add_mibdir(dirname)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Takes the name of a MIB module (which need not be the same as the name
|
50
|
+
# of the file that contains the module), locates this within the configured
|
51
|
+
# list of MIB directories, and loads the definitions from the module into
|
52
|
+
# the active MIB tree. It also loads any MIB modules listed in the IMPORTS
|
53
|
+
# clause of this module.
|
54
|
+
def self.read_module(name)
|
55
|
+
Wrapper.netsnmp_read_module(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Similar to read_module, but takes the name of the file containing the MIB
|
59
|
+
# module. Note that this file need not be located within the MIB directory
|
60
|
+
# search list (although any modules listed in the IMPORTS clause do).
|
61
|
+
def self.read_mib(filename)
|
62
|
+
Wrapper.read_mib(filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|