net-snmp 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.rdoc +41 -20
- data/lib/net-snmp.rb +6 -2
- data/lib/net/snmp.rb +7 -4
- data/lib/net/snmp/constants.rb +73 -56
- data/lib/net/snmp/debug.rb +17 -0
- data/lib/net/snmp/dispatcher.rb +43 -65
- data/lib/net/snmp/error.rb +18 -10
- data/lib/net/snmp/inline.rb +4 -14
- data/lib/net/snmp/oid.rb +6 -2
- data/lib/net/snmp/pdu.rb +58 -19
- data/lib/net/snmp/session.rb +275 -195
- data/lib/net/snmp/trap_session.rb +64 -0
- data/lib/net/snmp/varbind.rb +28 -6
- data/lib/net/snmp/version.rb +1 -1
- data/lib/net/snmp/wrapper.rb +33 -8
- data/spec/async_spec.rb +78 -30
- data/spec/em_spec.rb +1 -1
- data/spec/error_spec.rb +36 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/sync_spec.rb +9 -1
- data/spec/thread_spec.rb +3 -1
- data/spec/trap_spec.rb +12 -10
- data/spec/wrapper_spec.rb +2 -2
- metadata +8 -4
data/lib/net/snmp/error.rb
CHANGED
@@ -1,22 +1,30 @@
|
|
1
1
|
module Net
|
2
2
|
module SNMP
|
3
3
|
class Error < RuntimeError
|
4
|
-
|
5
|
-
|
4
|
+
attr_accessor :status, :errno, :snmp_err, :snmp_msg
|
6
5
|
def initialize(opts = {})
|
7
6
|
@status = opts[:status]
|
7
|
+
@fiber = opts[:fiber]
|
8
8
|
if opts[:session]
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Wrapper.snmp_error(opts[:session].pointer, errno_ptr, snmp_err_ptr, msg_ptr)
|
13
|
-
|
14
|
-
@errno = errno_ptr.read_int
|
15
|
-
@snmp_err = snmp_err_ptr.read_int
|
16
|
-
@snmp_msg = msg_ptr.read_pointer.read_string
|
9
|
+
@errno = opts[:session].errno
|
10
|
+
@snmp_err = opts[:session].snmp_err
|
11
|
+
@snmp_msg = opts[:session].error_message
|
17
12
|
end
|
13
|
+
|
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}"
|
18
23
|
end
|
19
24
|
end
|
25
|
+
|
26
|
+
class TimeoutError < Error
|
27
|
+
end
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
data/lib/net/snmp/inline.rb
CHANGED
@@ -5,21 +5,11 @@ module Net
|
|
5
5
|
|
6
6
|
inline do |builder|
|
7
7
|
builder.include "sys/select.h"
|
8
|
-
builder.include "stdio.h"
|
9
|
-
builder.library "netsnmp"
|
8
|
+
#builder.include "stdio.h"
|
9
|
+
#builder.library "netsnmp"
|
10
10
|
builder.c %q{
|
11
|
-
int
|
12
|
-
|
13
|
-
fd_set fdset;
|
14
|
-
struct timeval timeout;
|
15
|
-
FD_ZERO(&fdset);
|
16
|
-
snmp_select_info(&fds, &fdset, &timeout, &block);
|
17
|
-
fds = select(fds, &fdset, 0,0, block ? 0 : &timeout);
|
18
|
-
if(fds) {
|
19
|
-
snmp_read(&fdset);
|
20
|
-
} else {
|
21
|
-
snmp_timeout();
|
22
|
-
}
|
11
|
+
int fd_setsize() {
|
12
|
+
return(FD_SETSIZE);
|
23
13
|
}
|
24
14
|
}
|
25
15
|
|
data/lib/net/snmp/oid.rb
CHANGED
@@ -2,12 +2,15 @@ module Net
|
|
2
2
|
module SNMP
|
3
3
|
class OID
|
4
4
|
attr_reader :oid, :pointer, :length_pointer
|
5
|
+
|
6
|
+
def from_pointer(ptr, len)
|
7
|
+
|
8
|
+
end
|
5
9
|
def initialize(oid)
|
6
10
|
@oid = oid
|
7
11
|
@pointer = FFI::MemoryPointer.new(:ulong, Constants::MAX_OID_LEN)
|
8
12
|
@length_pointer = FFI::MemoryPointer.new(:size_t)
|
9
13
|
@length_pointer.write_int(Constants::MAX_OID_LEN)
|
10
|
-
|
11
14
|
if @oid =~ /^[\d\.]*$/
|
12
15
|
if Wrapper.read_objid(@oid, @pointer, @length_pointer) == 0
|
13
16
|
Wrapper.snmp_perror(@oid)
|
@@ -22,8 +25,9 @@ module Net
|
|
22
25
|
end
|
23
26
|
|
24
27
|
def size
|
25
|
-
@length_pointer.read_int
|
28
|
+
@length_pointer.read_int * 8 # XXX 8 = sizeof(oid) on my system. Not sure if it's different on others
|
26
29
|
end
|
30
|
+
|
27
31
|
def oid
|
28
32
|
@oid
|
29
33
|
end
|
data/lib/net/snmp/pdu.rb
CHANGED
@@ -1,15 +1,25 @@
|
|
1
1
|
module Net
|
2
2
|
module SNMP
|
3
3
|
class PDU
|
4
|
+
# == Represents an SNMP PDU
|
5
|
+
#
|
6
|
+
# Wrapper around netsnmp_pdu.
|
7
|
+
# * +varbinds+ returns a list of variable bindings
|
8
|
+
# * +errstat+ returns the error code. See constants.rb
|
9
|
+
# * +errindex+ returns the index of the varbind causing the error.
|
4
10
|
extend Forwardable
|
5
|
-
|
11
|
+
include Net::SNMP::Debug
|
12
|
+
attr_accessor :struct, :varbinds, :callback, :command
|
6
13
|
def_delegators :struct, :pointer
|
7
14
|
|
8
|
-
|
15
|
+
# Create a new PDU object.
|
16
|
+
# +pdu_type+ The type of the PDU. For example, Net::SNMP::SNMP_MSG_GET. See constants.rb
|
17
|
+
def initialize(pdu_type)
|
9
18
|
@varbinds = []
|
10
|
-
case
|
19
|
+
case pdu_type
|
11
20
|
when FFI::Pointer
|
12
|
-
@struct = Wrapper::SnmpPdu.new(
|
21
|
+
@struct = Wrapper::SnmpPdu.new(pdu_type)
|
22
|
+
@command = @struct.command
|
13
23
|
v = @struct[:variables]
|
14
24
|
if v
|
15
25
|
@varbinds << Varbind.from_pointer(v)
|
@@ -19,9 +29,10 @@ module Net
|
|
19
29
|
@varbinds << Varbind.from_pointer(v)
|
20
30
|
end
|
21
31
|
when Fixnum
|
22
|
-
@struct = Wrapper.snmp_pdu_create(
|
32
|
+
@struct = Wrapper.snmp_pdu_create(pdu_type)
|
33
|
+
@command = pdu_type
|
23
34
|
else
|
24
|
-
raise "invalid type"
|
35
|
+
raise Error.new, "invalid pdu type"
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
@@ -65,12 +76,20 @@ module Net
|
|
65
76
|
end
|
66
77
|
end
|
67
78
|
|
79
|
+
# Adds a variable binding to the pdu.
|
80
|
+
# Options:
|
81
|
+
# * +oid+ The SNMP OID
|
82
|
+
# * +type+ The data type. Possible values include Net::SNMP::ASN_OCTET_STR, Net::SNMP::ASN_COUNTER, etc. See constants.rb
|
83
|
+
# * +value+ The value of the varbind. default is nil.
|
68
84
|
def add_varbind(options)
|
85
|
+
#puts "adding varbind #{options.inspect}"
|
69
86
|
options[:type] ||= case options[:value]
|
70
87
|
when String
|
71
88
|
Constants::ASN_OCTET_STR
|
72
89
|
when Fixnum
|
73
90
|
Constants::ASN_INTEGER
|
91
|
+
when Net::SNMP::OID
|
92
|
+
Constants::ASN_OBJECT_ID
|
74
93
|
when nil
|
75
94
|
Constants::ASN_NULL
|
76
95
|
else
|
@@ -78,39 +97,59 @@ module Net
|
|
78
97
|
end
|
79
98
|
|
80
99
|
value = options[:value]
|
81
|
-
value_len = case options[:
|
82
|
-
when
|
83
|
-
options[:value].length
|
84
|
-
when nil
|
100
|
+
value_len = case options[:type]
|
101
|
+
when Constants::ASN_NULL
|
85
102
|
0
|
86
103
|
else
|
87
104
|
options[:value].size
|
88
105
|
end
|
89
106
|
|
90
|
-
|
91
|
-
|
92
|
-
|
107
|
+
value = case options[:type]
|
108
|
+
when Constants::ASN_INTEGER, Constants::ASN_GAUGE, Constants::ASN_COUNTER, Constants::ASN_TIMETICKS, Constants::ASN_UNSIGNED
|
109
|
+
new_val = FFI::MemoryPointer.new(:long)
|
110
|
+
new_val.write_long(value)
|
111
|
+
new_val
|
112
|
+
when Constants::ASN_OCTET_STR, Constants::ASN_BIT_STR, Constants::ASN_OPAQUE
|
113
|
+
value
|
114
|
+
when Constants::ASN_IPADDRESS
|
115
|
+
# TODO
|
116
|
+
when Constants::ASN_OBJECT_ID
|
117
|
+
value.pointer
|
118
|
+
when Constants::ASN_NULL
|
119
|
+
nil
|
120
|
+
else
|
121
|
+
if value.respond_to?(:pointer)
|
122
|
+
value.pointer
|
123
|
+
else
|
124
|
+
raise Net::SNMP::Error.new, "Unknown variable type #{options[:type]}"
|
125
|
+
end
|
93
126
|
end
|
94
127
|
|
95
|
-
|
96
|
-
oid = Net::SNMP::OID.new(options[:oid])
|
128
|
+
oid = options[:oid].kind_of?(Net::SNMP::OID) ? options[:oid] : Net::SNMP::OID.new(options[:oid])
|
97
129
|
var_ptr = Wrapper.snmp_pdu_add_variable(@struct.pointer, oid.pointer, oid.length_pointer.read_int, options[:type], value, value_len)
|
98
130
|
varbind = Varbind.new(var_ptr)
|
99
131
|
#Wrapper.print_varbind(varbind.struct)
|
100
132
|
varbinds << varbind
|
101
133
|
end
|
102
|
-
|
134
|
+
|
135
|
+
# Returns true if pdu is in error
|
103
136
|
def error?
|
104
|
-
errstat != 0
|
137
|
+
self.errstat != 0
|
105
138
|
end
|
106
|
-
|
139
|
+
|
140
|
+
# A descriptive error message
|
107
141
|
def error_message
|
108
|
-
Wrapper::snmp_errstring(errstat)
|
142
|
+
Wrapper::snmp_errstring(self.errstat)
|
109
143
|
end
|
110
144
|
|
145
|
+
# Free the pdu
|
111
146
|
def free
|
112
147
|
Wrapper.snmp_free_pdu(@struct.pointer)
|
113
148
|
end
|
149
|
+
|
150
|
+
def print
|
151
|
+
puts pdu.inspect
|
152
|
+
end
|
114
153
|
end
|
115
154
|
end
|
116
155
|
end
|
data/lib/net/snmp/session.rb
CHANGED
@@ -4,7 +4,11 @@ require 'pp'
|
|
4
4
|
module Net
|
5
5
|
module SNMP
|
6
6
|
class Session
|
7
|
+
# == SNMP Session
|
8
|
+
#
|
9
|
+
# Provides API for interacting with a host with snmp
|
7
10
|
extend Forwardable
|
11
|
+
include Net::SNMP::Debug
|
8
12
|
attr_accessor :struct, :callback, :requests, :peername, :community
|
9
13
|
attr_reader :version
|
10
14
|
def_delegator :@struct, :pointer
|
@@ -14,17 +18,34 @@ module Net
|
|
14
18
|
|
15
19
|
class << self
|
16
20
|
attr_accessor :sessions, :lock
|
21
|
+
|
22
|
+
# Open a new session. Accepts a block which yields the session.
|
23
|
+
#
|
24
|
+
# Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'public') do |sess|
|
25
|
+
# pdu = sess.get(["sysDescr.0"])
|
26
|
+
# pdu.print
|
27
|
+
# end
|
28
|
+
# Options:
|
29
|
+
# * +peername+ - hostname
|
30
|
+
# * +community+ - snmp community string. Default is public
|
31
|
+
# * +version+ - snmp version. Possible values include 1, '2c', and 3. Default is 1.
|
32
|
+
# * +timeout+ - snmp timeout in seconds
|
33
|
+
# * +retries+ - snmp retries. default = 5
|
34
|
+
# Returns:
|
35
|
+
# Net::SNMP::Session
|
36
|
+
|
17
37
|
def open(options = {})
|
18
38
|
#puts "building session"
|
19
39
|
session = new(options)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
40
|
+
if Net::SNMP::thread_safe
|
41
|
+
Net::SNMP::Session.lock.synchronize {
|
42
|
+
Net::SNMP::Session.sessions[session.sessid] = session
|
43
|
+
}
|
44
|
+
else
|
45
|
+
Net::SNMP::Session.sessions[session.sessid] = session
|
46
|
+
end
|
24
47
|
if block_given?
|
25
|
-
#puts "calling block"
|
26
48
|
yield session
|
27
|
-
|
28
49
|
end
|
29
50
|
session
|
30
51
|
end
|
@@ -32,13 +53,15 @@ module Net
|
|
32
53
|
|
33
54
|
def initialize(options = {})
|
34
55
|
#puts "in initialize"
|
56
|
+
@timeout = options[:timeout] || 1
|
57
|
+
@retries = options[:retries] || 5
|
35
58
|
@requests = {}
|
36
59
|
@peername = options[:peername] || 'localhost'
|
60
|
+
@peername = "#{@peername}:#{options[:port]}" if options[:port]
|
37
61
|
@community = options[:community] || "public"
|
38
62
|
options[:community_len] = @community.length
|
39
63
|
@version = options[:version] || 1
|
40
64
|
options[:version] ||= Constants::SNMP_VERSION_1
|
41
|
-
@callback = options[:callback]
|
42
65
|
@version = options[:version] || 1
|
43
66
|
#self.class.sessions << self
|
44
67
|
@sess = Wrapper::SnmpSession.new(nil)
|
@@ -47,6 +70,7 @@ module Net
|
|
47
70
|
@sess.community = FFI::MemoryPointer.from_string(@community)
|
48
71
|
@sess.community_len = @community.length
|
49
72
|
@sess.peername = FFI::MemoryPointer.from_string(@peername)
|
73
|
+
#@sess.remote_port = options[:port] || 162
|
50
74
|
@sess.version = case options[:version].to_s
|
51
75
|
when '1'
|
52
76
|
Constants::SNMP_VERSION_1
|
@@ -57,23 +81,19 @@ module Net
|
|
57
81
|
else
|
58
82
|
Constants::SNMP_VERSION_1
|
59
83
|
end
|
84
|
+
debug "setting timeout = #{@timeout} retries = #{@retries}"
|
85
|
+
@sess.timeout = @timeout * 1000000
|
86
|
+
@sess.retries = @retries
|
60
87
|
|
61
|
-
if options[:timeout]
|
62
|
-
@sess.timeout = options[:timeout] * 1000000
|
63
|
-
end
|
64
|
-
if options[:retries]
|
65
|
-
@sess.retries = options[:retries]
|
66
|
-
end
|
67
88
|
if @sess.version == Constants::SNMP_VERSION_3
|
68
89
|
@sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
|
69
|
-
|
70
90
|
@sess.securityAuthProto = case options[:auth_protocol]
|
71
91
|
when :sha1
|
72
|
-
|
92
|
+
OID.new("1.3.6.1.6.3.10.1.1.3").pointer
|
73
93
|
when :md5
|
74
|
-
|
94
|
+
OID.new("1.3.6.1.6.3.10.1.1.2").pointer
|
75
95
|
when nil
|
76
|
-
|
96
|
+
OID.new("1.3.6.1.6.3.10.1.1.1").pointer
|
77
97
|
end
|
78
98
|
|
79
99
|
@sess.securityAuthProtoLen = 10
|
@@ -97,32 +117,28 @@ module Net
|
|
97
117
|
end
|
98
118
|
end
|
99
119
|
# General callback just takes the pdu, calls the session callback if any, then the request specific callback.
|
100
|
-
@sess.callback = lambda do |operation, session, reqid, pdu_ptr, magic|
|
101
|
-
#puts "callback is #{callback.inspect}"
|
102
|
-
#callback.call(operation, reqid, pdu, magic) if callback
|
103
120
|
|
104
|
-
if @requests[reqid]
|
105
|
-
pdu = Net::SNMP::PDU.new(pdu_ptr)
|
106
|
-
@requests[reqid].call(pdu)
|
107
|
-
@requests.delete(reqid)
|
108
|
-
end
|
109
|
-
0
|
110
|
-
end
|
111
121
|
@struct = Wrapper.snmp_sess_open(@sess.pointer)
|
112
|
-
#@handle = Wrapper.snmp_sess_open(@sess.pointer)
|
113
|
-
#@struct = Wrapper.snmp_sess_session(@handle)
|
114
122
|
end
|
115
123
|
|
116
124
|
|
125
|
+
# Close the snmp session and free associated resources.
|
117
126
|
def close
|
118
|
-
Net::SNMP
|
127
|
+
if Net::SNMP.thread_safe
|
128
|
+
self.class.lock.synchronize {
|
129
|
+
Wrapper.snmp_sess_close(@struct)
|
130
|
+
self.class.sessions.delete(self.sessid)
|
131
|
+
}
|
132
|
+
else
|
119
133
|
Wrapper.snmp_sess_close(@struct)
|
120
|
-
|
121
|
-
|
134
|
+
self.class.sessions.delete(self.sessid)
|
135
|
+
end
|
122
136
|
end
|
123
137
|
|
124
|
-
|
125
|
-
|
138
|
+
# Issue an SNMP GET Request.
|
139
|
+
# See #send_pdu
|
140
|
+
def get(oidlist, &block)
|
141
|
+
pdu = PDU.new(Constants::SNMP_MSG_GET)
|
126
142
|
oidlist = [oidlist] unless oidlist.kind_of?(Array)
|
127
143
|
oidlist.each do |oid|
|
128
144
|
pdu.add_varbind(:oid => oid)
|
@@ -130,8 +146,10 @@ module Net
|
|
130
146
|
send_pdu(pdu, &block)
|
131
147
|
end
|
132
148
|
|
133
|
-
|
134
|
-
|
149
|
+
# Issue an SNMP GETNEXT Request
|
150
|
+
# See #send_pdu
|
151
|
+
def get_next(oidlist, &block)
|
152
|
+
pdu = PDU.new(Constants::SNMP_MSG_GETNEXT)
|
135
153
|
oidlist = [oidlist] unless oidlist.kind_of?(Array)
|
136
154
|
oidlist.each do |oid|
|
137
155
|
pdu.add_varbind(:oid => oid)
|
@@ -139,8 +157,10 @@ module Net
|
|
139
157
|
send_pdu(pdu, &block)
|
140
158
|
end
|
141
159
|
|
160
|
+
# Issue an SNMP GETBULK Request
|
161
|
+
# See #send_pdu
|
142
162
|
def get_bulk(oidlist, options = {}, &block)
|
143
|
-
pdu =
|
163
|
+
pdu = PDU.new(Constants::SNMP_MSG_GETBULK)
|
144
164
|
oidlist = [oidlist] unless oidlist.kind_of?(Array)
|
145
165
|
oidlist.each do |oid|
|
146
166
|
pdu.add_varbind(:oid => oid)
|
@@ -151,9 +171,10 @@ module Net
|
|
151
171
|
end
|
152
172
|
|
153
173
|
|
154
|
-
|
155
|
-
|
156
|
-
|
174
|
+
# Issue an SNMP Set Request
|
175
|
+
# See #send_pdu
|
176
|
+
def set(oidlist, &block)
|
177
|
+
pdu = PDU.new(Constants::SNMP_MSG_SET)
|
157
178
|
oidlist.each do |oid|
|
158
179
|
pdu.add_varbind(:oid => oid[0], :type => oid[1], :value => oid[2])
|
159
180
|
end
|
@@ -169,187 +190,260 @@ module Net
|
|
169
190
|
end
|
170
191
|
|
171
192
|
|
172
|
-
# XXX This needs work. Need to use getbulk for speed, guess maxrepeaters, etc..
|
173
|
-
# Also need to figure out how we can tell column names from something like ifTable
|
174
|
-
# instead of ifEntry. Needs to handle errors, there are probably offset problems
|
175
|
-
# in cases of bad data, and various other problems. Also need to add async support.
|
176
|
-
# Maybe return a hash with index as key?
|
177
|
-
def get_table(table_name, options = {})
|
178
|
-
column_names = options[:columns] || Net::SNMP::MIB::Node.get_node(table_name).children.collect {|c| c.label }
|
179
|
-
results = []
|
180
|
-
|
181
|
-
first_result = get_next(column_names)
|
182
|
-
oidlist = []
|
183
|
-
good_column_names = []
|
184
|
-
row = {}
|
185
|
-
|
186
|
-
first_result.varbinds.each_with_index do |vb, idx|
|
187
|
-
oid = vb.oid
|
188
|
-
if oid.label[0..column_names[idx].length - 1] == column_names[idx]
|
189
|
-
oidlist << oid.label
|
190
|
-
good_column_names << column_names[idx]
|
191
|
-
row[column_names[idx]] = vb.value
|
192
|
-
end
|
193
|
-
end
|
194
|
-
results << row
|
195
193
|
|
196
|
-
catch :break_main_loop do
|
197
|
-
while(result = get_next(oidlist))
|
198
|
-
oidlist = []
|
199
|
-
row = {}
|
200
|
-
result.varbinds.each_with_index do |vb, idx|
|
201
|
-
#puts "got #{vb.oid.label} #{vb.value.inspect}, type = #{vb.object_type}"
|
202
|
-
row[good_column_names[idx]] = vb.value
|
203
|
-
oidlist << vb.oid.label
|
204
|
-
if vb.oid.label[0..good_column_names[idx].length - 1] != good_column_names[idx]
|
205
|
-
throw :break_main_loop
|
206
|
-
end
|
207
|
-
end
|
208
|
-
results << row
|
209
|
-
end
|
210
|
-
end
|
211
|
-
results
|
212
|
-
end
|
213
|
-
|
214
194
|
|
215
195
|
def default_max_repeaters
|
216
196
|
# We could do something based on transport here. 25 seems safe
|
217
197
|
25
|
218
198
|
end
|
219
199
|
|
220
|
-
def get_columns(columns, options = {})
|
221
|
-
column_oids = columns.map {|c| Net::SNMP::OID.new(c)}
|
222
|
-
options[:max_repetitions] ||= default_max_repeaters / columns.size
|
223
|
-
results = {}
|
224
|
-
if version == 1
|
225
|
-
else
|
226
|
-
while(result = get_bulk(columns, options))
|
227
|
-
result.varbinds.each do |vb|
|
228
|
-
match = column_oids.select {|c| vb.oid.oid =~ /^#{c.oid}/ }
|
229
|
-
end
|
230
|
-
end
|
231
200
|
|
232
|
-
end
|
233
|
-
end
|
234
201
|
|
235
|
-
|
236
|
-
|
237
|
-
Wrapper.snmp_sess_perror(msg, @sess.pointer)
|
238
|
-
|
239
|
-
#Wrapper.print_session(self.struct)
|
240
|
-
raise Net::SNMP::Error.new({:session => self}), msg
|
202
|
+
# Raise a NET::SNMP::Error with the session attached
|
203
|
+
def error(msg, options = {})
|
204
|
+
#Wrapper.snmp_sess_perror(msg, @sess.pointer)
|
205
|
+
raise Error.new({:session => self}.merge(options)), msg
|
241
206
|
end
|
242
207
|
|
243
208
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
|
256
|
-
def trap_v2(options = {})
|
257
|
-
pdu = PDU.new(Constants::SNMP_MSG_TRAP2)
|
258
|
-
build_trap_pdu(pdu, options)
|
259
|
-
send_pdu(pdu)
|
260
|
-
end
|
261
|
-
|
262
|
-
def inform(options = {}, &block)
|
263
|
-
pdu = PDU.new(Constants::SNMP_MSG_INFORM)
|
264
|
-
build_trap_pdu(pdu, options)
|
265
|
-
send_pdu(pdu, &block)
|
266
|
-
end
|
267
|
-
|
268
|
-
def poll(timeout = nil)
|
269
|
-
|
270
|
-
fdset = Net::SNMP::Wrapper.get_fd_set
|
209
|
+
# Check the session for SNMP responses from asynchronous SNMP requests
|
210
|
+
# This method will check for new responses and call the associated
|
211
|
+
# response callbacks.
|
212
|
+
# +timeout+ A timeout of nil indicates a poll and will return immediately.
|
213
|
+
# A value of false will block until data is available. Otherwise, pass
|
214
|
+
# the number of seconds to block.
|
215
|
+
# Returns the number of file descriptors handled.
|
216
|
+
def select(timeout = nil)
|
217
|
+
fdset = FFI::MemoryPointer.new(:pointer, Net::SNMP::Inline.fd_setsize / 8)
|
271
218
|
num_fds = FFI::MemoryPointer.new(:int)
|
272
219
|
tv_sec = timeout ? timeout.round : 0
|
273
220
|
tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
|
274
|
-
tval =
|
221
|
+
tval = Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
|
275
222
|
block = FFI::MemoryPointer.new(:int)
|
276
|
-
|
277
223
|
if timeout.nil?
|
278
224
|
block.write_int(0)
|
279
225
|
else
|
280
226
|
block.write_int(1)
|
281
227
|
end
|
282
|
-
|
283
|
-
|
284
|
-
num_ready = 0
|
285
|
-
#puts "block = #{block.read_int}"
|
286
|
-
|
287
|
-
#puts "numready = #{num_fds.read_int}"
|
288
|
-
#puts "tv = #{tval[:tv_sec]} #{tval[:tv_usec]}"
|
289
|
-
#puts "timeout = #{timeout}"
|
228
|
+
|
229
|
+
Wrapper.snmp_sess_select_info(@struct, num_fds, fdset, tval.pointer, block )
|
290
230
|
tv = (timeout == false ? nil : tval)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
#num_ready = RubyWrapper.rb_thread_select(num_fds.read_int, fdset, nil, nil, tv)
|
295
|
-
num_ready = Net::SNMP::Wrapper.select(num_fds.read_int, fdset, nil, nil, tv)
|
296
|
-
#puts "done select. num_ready = #{num_ready}"
|
231
|
+
debug "Calling select #{Time.now}"
|
232
|
+
num_ready = FFI::LibC.select(num_fds.read_int, fdset, nil, nil, tv)
|
233
|
+
debug "Done select #{Time.now}"
|
297
234
|
if num_ready > 0
|
298
|
-
|
235
|
+
Wrapper.snmp_sess_read(@struct, fdset)
|
299
236
|
elsif num_ready == 0
|
300
|
-
|
237
|
+
Wrapper.snmp_sess_timeout(@struct)
|
301
238
|
elsif num_ready == -1
|
302
239
|
# error. check snmp_error?
|
240
|
+
error("select")
|
303
241
|
else
|
304
|
-
|
242
|
+
error("wtf is wrong with select?")
|
305
243
|
end
|
306
|
-
#puts "done snmp_read"
|
307
244
|
num_ready
|
308
245
|
end
|
309
246
|
|
310
|
-
|
311
|
-
|
247
|
+
alias :poll :select
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
# Send a PDU
|
252
|
+
# +pdu+ The Net::SNMP::PDU object to send. Usually created by Session.get, Session.getnext, etc.
|
253
|
+
# +callback+ An optional callback. It should take two parameters, status and response_pdu.
|
254
|
+
# If no +callback+ is given, the call will block until the response is available and will return
|
255
|
+
# the response pdu. If an error occurs, a Net::SNMP::Error will be thrown.
|
256
|
+
# If +callback+ is passed, the PDU will be sent and +send_pdu+ will return immediately. You must
|
257
|
+
# then call Session.select to invoke the callback. This is usually done in some sort of event loop.
|
258
|
+
# See Net::SNMP::Dispatcher.
|
259
|
+
#
|
260
|
+
# If you're running inside eventmachine and have fibers (ruby 1.9, jruby, etc), sychronous calls will
|
261
|
+
# actually run asynchronously behind the scenes. Just run Net::SNMP::Dispatcher.fiber_loop in your
|
262
|
+
# reactor.
|
263
|
+
#
|
264
|
+
# pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
|
265
|
+
# pdu.add_varbind(:oid => 'sysDescr.0')
|
266
|
+
# session.send_pdu(pdu) do |status, pdu|
|
267
|
+
# if status == :success
|
268
|
+
# pdu.print
|
269
|
+
# elsif status == :timeout
|
270
|
+
# puts "Timed Out"
|
271
|
+
# else
|
272
|
+
# puts "A problem occurred"
|
273
|
+
# end
|
274
|
+
# end
|
275
|
+
# session.select(false) #block until data is ready. Callback will be called.
|
276
|
+
# begin
|
277
|
+
# result = session.send_pdu(pdu)
|
278
|
+
# puts result.inspect
|
279
|
+
# rescue Net::SNMP::Error => e
|
280
|
+
# puts e.message
|
281
|
+
# end
|
282
|
+
def send_pdu(pdu, &callback)
|
312
283
|
#puts "send_pdu #{Fiber.current.inspect}"
|
313
|
-
if
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
f.resume(response)
|
284
|
+
if block_given?
|
285
|
+
@requests[pdu.reqid] = callback
|
286
|
+
puts "calling async_send"
|
287
|
+
if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
|
288
|
+
error("snmp_get async failed")
|
319
289
|
end
|
320
|
-
|
290
|
+
#pdu.free
|
291
|
+
nil
|
321
292
|
else
|
322
|
-
if
|
323
|
-
|
324
|
-
|
325
|
-
|
293
|
+
if defined?(EM) && EM.reactor_running? && defined?(Fiber)
|
294
|
+
f = Fiber.current
|
295
|
+
send_pdu pdu do | op, response_pdu |
|
296
|
+
f.resume([op, response_pdu])
|
297
|
+
end
|
298
|
+
op, result = Fiber.yield
|
299
|
+
case op
|
300
|
+
when :timeout
|
301
|
+
raise TimeoutError.new, "timeout"
|
302
|
+
when :send_failed
|
303
|
+
error "send failed"
|
304
|
+
when :success
|
305
|
+
result
|
306
|
+
when :connect, :disconnect
|
307
|
+
nil #does this ever happen?
|
308
|
+
else
|
309
|
+
error "unknown operation #{op}"
|
326
310
|
end
|
327
|
-
#pdu.free
|
328
|
-
nil
|
329
311
|
else
|
330
312
|
response_ptr = FFI::MemoryPointer.new(:pointer)
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
status = Net::SNMP::Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
|
337
|
-
#end
|
338
|
-
#pdu.free #causing segfaults
|
339
|
-
if status != 0
|
340
|
-
error("snmp_get failed #{status}")
|
313
|
+
if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
|
314
|
+
status = Wrapper.snmp_sess_send(@struct, pdu.pointer)
|
315
|
+
if status == 0
|
316
|
+
error("snmp_sess_send")
|
317
|
+
end
|
341
318
|
else
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
# 1
|
347
|
-
#end
|
348
|
-
|
319
|
+
status = Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
|
320
|
+
unless status == Constants::STAT_SUCCESS
|
321
|
+
error("snmp_sess_synch_response", :status => status)
|
322
|
+
end
|
349
323
|
end
|
324
|
+
if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
|
325
|
+
1
|
326
|
+
else
|
327
|
+
PDU.new(response_ptr.read_pointer)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def errno
|
334
|
+
get_error
|
335
|
+
@errno
|
336
|
+
end
|
337
|
+
|
338
|
+
# The SNMP Session error code
|
339
|
+
def snmp_err
|
340
|
+
get_error
|
341
|
+
@snmp_err
|
342
|
+
end
|
343
|
+
|
344
|
+
# The SNMP Session error message
|
345
|
+
def error_message
|
346
|
+
get_error
|
347
|
+
@snmp_msg
|
348
|
+
end
|
349
|
+
|
350
|
+
private
|
351
|
+
def sess_callback
|
352
|
+
@sess_callback ||= FFI::Function.new(:int, [:int, :pointer, :int, :pointer, :pointer]) do |operation, session, reqid, pdu_ptr, magic|
|
353
|
+
#puts "in callback #{operation.inspect} #{session.inspect}"
|
354
|
+
op = case operation
|
355
|
+
when Constants::NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
|
356
|
+
:success
|
357
|
+
when Constants::NETSNMP_CALLBACK_OP_TIMED_OUT
|
358
|
+
:timeout
|
359
|
+
when Constants::NETSNMP_CALLBACK_OP_SEND_FAILED
|
360
|
+
:send_failed
|
361
|
+
when Constants::NETSNMP_CALLBACK_OP_CONNECT
|
362
|
+
:connect
|
363
|
+
when Constants::NETSNMP_CALLBACK_OP_DISCONNECT
|
364
|
+
:disconnect
|
365
|
+
else
|
366
|
+
error "Invalid PDU Operation"
|
367
|
+
end
|
368
|
+
|
369
|
+
if @requests[reqid]
|
370
|
+
pdu = PDU.new(pdu_ptr)
|
371
|
+
callback_return = @requests[reqid].call(op, pdu)
|
372
|
+
@requests.delete(reqid)
|
373
|
+
callback_return == false ? 0 : 1 #if callback returns false (failure), pass it on. otherwise return 1 (success)
|
374
|
+
else
|
375
|
+
0# Do what here? Can this happen? Maybe request timed out and was deleted?
|
350
376
|
end
|
351
377
|
end
|
352
378
|
end
|
379
|
+
def get_error
|
380
|
+
errno_ptr = FFI::MemoryPointer.new(:int)
|
381
|
+
snmp_err_ptr = FFI::MemoryPointer.new(:int)
|
382
|
+
msg_ptr = FFI::MemoryPointer.new(:pointer)
|
383
|
+
Wrapper.snmp_sess_error(@struct.pointer, errno_ptr, snmp_err_ptr, msg_ptr)
|
384
|
+
@errno = errno_ptr.read_int
|
385
|
+
@snmp_err = snmp_err_ptr.read_int
|
386
|
+
@snmp_msg = msg_ptr.read_pointer.read_string
|
387
|
+
end
|
388
|
+
|
389
|
+
public
|
390
|
+
# XXX This needs work. Need to use getbulk for speed, guess maxrepeaters, etc..
|
391
|
+
# Also need to figure out how we can tell column names from something like ifTable
|
392
|
+
# instead of ifEntry. Needs to handle errors, there are probably offset problems
|
393
|
+
# in cases of bad data, and various other problems. Also need to add async support.
|
394
|
+
# Maybe return a hash with index as key?
|
395
|
+
def get_table(table_name, options = {})
|
396
|
+
column_names = options[:columns] || MIB::Node.get_node(table_name).children.collect {|c| c.label }
|
397
|
+
results = []
|
398
|
+
|
399
|
+
# repeat_count = if @version.to_s == '1' || options[:no_getbulk]
|
400
|
+
# 1
|
401
|
+
# elsif options[:repeat_count]
|
402
|
+
# options[:repeat_count]
|
403
|
+
# else
|
404
|
+
# (1000 / 36 / (column_names.size + 1)).to_i
|
405
|
+
# end
|
406
|
+
#
|
407
|
+
# res = if @version.to_s != '1' && !options[:no_getbulk]
|
408
|
+
# get_bulk(column_names, :max_repetitions => repeat_count)
|
409
|
+
# else
|
410
|
+
# get_next(column_names)
|
411
|
+
# end
|
412
|
+
|
413
|
+
|
414
|
+
first_result = get_next(column_names)
|
415
|
+
oidlist = []
|
416
|
+
good_column_names = []
|
417
|
+
row = {}
|
418
|
+
|
419
|
+
first_result.varbinds.each_with_index do |vb, idx|
|
420
|
+
oid = vb.oid
|
421
|
+
if oid.label[0..column_names[idx].length - 1] == column_names[idx]
|
422
|
+
oidlist << oid.label
|
423
|
+
good_column_names << column_names[idx]
|
424
|
+
row[column_names[idx]] = vb.value
|
425
|
+
end
|
426
|
+
end
|
427
|
+
results << row
|
428
|
+
|
429
|
+
catch :break_main_loop do
|
430
|
+
while(result = get_next(oidlist))
|
431
|
+
oidlist = []
|
432
|
+
row = {}
|
433
|
+
result.varbinds.each_with_index do |vb, idx|
|
434
|
+
#puts "got #{vb.oid.label} #{vb.value.inspect}, type = #{vb.object_type}"
|
435
|
+
row[good_column_names[idx]] = vb.value
|
436
|
+
oidlist << vb.oid.label
|
437
|
+
if vb.oid.label[0..good_column_names[idx].length - 1] != good_column_names[idx]
|
438
|
+
throw :break_main_loop
|
439
|
+
end
|
440
|
+
end
|
441
|
+
results << row
|
442
|
+
end
|
443
|
+
end
|
444
|
+
results
|
445
|
+
end
|
446
|
+
|
353
447
|
|
354
448
|
# def get_entries_cb(pdu, columns, options)
|
355
449
|
# cache = {}
|
@@ -372,22 +466,8 @@ module Net
|
|
372
466
|
# end
|
373
467
|
# end
|
374
468
|
# end
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
def build_trap_pdu(pdu, options = {})
|
381
|
-
pdu.add_varbind(:oid => OID.new('sysUpTime'), :type => Constants::ASN_TIMETICKS, :value => 0)
|
382
|
-
pdu.add_varbind(:oid => OID.new('snmpTrapOID'), :type => Constants::ASN_OBJECT_ID, :value => options[:oid])
|
383
|
-
if options[:varbinds]
|
384
|
-
options[:varbinds].each do |vb|
|
385
|
-
pdu.add_varbind(vb)
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
|
391
469
|
end
|
392
470
|
end
|
393
471
|
end
|
472
|
+
|
473
|
+
|