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.
@@ -1,22 +1,30 @@
1
1
  module Net
2
2
  module SNMP
3
3
  class Error < RuntimeError
4
- #attr :status, :errno, :snmp_err, :snmp_msg
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
- errno_ptr = FFI::MemoryPointer.new(:int)
10
- snmp_err_ptr = FFI::MemoryPointer.new(:int)
11
- msg_ptr = FFI::MemoryPointer.new(:pointer)
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
 
@@ -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 snmp_process_callbacks() {
12
- int fds = 0, block = 1;
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
- attr_accessor :struct, :varbinds, :callback
11
+ include Net::SNMP::Debug
12
+ attr_accessor :struct, :varbinds, :callback, :command
6
13
  def_delegators :struct, :pointer
7
14
 
8
- def initialize(arg)
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 arg
19
+ case pdu_type
11
20
  when FFI::Pointer
12
- @struct = Wrapper::SnmpPdu.new(arg)
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(arg)
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[:value]
82
- when String
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
- if value.respond_to?(:pointer)
92
- value = value.pointer
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
- #oid = options[:oid].kind_of?(Net::SNMP::OID) ? options[:oid] : Net::SNMP::OID.new(options[:oid])
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
@@ -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
- @lock.synchronize {
21
- @sessions[session.sessid] = session
22
- }
23
- #puts "done building"
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
- Net::SNMP::OID.new("1.3.6.1.6.3.10.1.1.3").pointer
92
+ OID.new("1.3.6.1.6.3.10.1.1.3").pointer
73
93
  when :md5
74
- Net::SNMP::OID.new("1.3.6.1.6.3.10.1.1.2").pointer
94
+ OID.new("1.3.6.1.6.3.10.1.1.2").pointer
75
95
  when nil
76
- Net::SNMP::OID.new("1.3.6.1.6.3.10.1.1.1").pointer
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::Session.lock.synchronize {
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
- Net::SNMP::Session.sessions.delete(self.sessid)
121
- }
134
+ self.class.sessions.delete(self.sessid)
135
+ end
122
136
  end
123
137
 
124
- def get(oidlist, options = {}, &block)
125
- pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
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
- def get_next(oidlist, options = {}, &block)
134
- pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GETNEXT)
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 = Net::SNMP::PDU.new(Constants::SNMP_MSG_GETBULK)
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
- def set(oidlist, options = {}, &block)
156
- pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_SET)
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
- def error(msg)
236
- Wrapper.snmp_perror(msg)
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
- def trap(options = {})
245
- pdu = PDU.new(Constants::SNMP_MSG_TRAP)
246
- options[:enterprise] ||= '1.3.6.1.4.1.3.1.1' # uh, just send netsnmp enterprise i guess
247
- pdu.enterprise = OID.new(options[:enterprise])
248
- pdu.trap_type = options[:trap_type] || 1 # need to check all these defaults
249
- pdu.specific_type = options[:specific_type] || 0
250
- pdu.time = 1 # put what here?
251
- send_pdu(pdu)
252
- true
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 = Net::SNMP::Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
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
- #puts "calling snmp_select_info"
283
- num = Net::SNMP::Wrapper.snmp_sess_select_info(@struct, num_fds, fdset, tval.pointer, block )
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
- #puts "calling select"
292
- #puts "tv = #{tv.inspect}"
293
- #puts "calling select with #{num_fds.read_int}"
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
- Net::SNMP::Wrapper.snmp_sess_read(@struct, fdset)
235
+ Wrapper.snmp_sess_read(@struct, fdset)
299
236
  elsif num_ready == 0
300
- # timeout. do something here? or just return 0?
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
- # uhhh
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
- private
311
- def send_pdu(pdu, &block)
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 defined?(EM) && EM.reactor_running? && !block_given?
314
- #puts "REACTORRUNNING"
315
- f = Fiber.current
316
-
317
- send_pdu pdu do | response |
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
- Fiber.yield
290
+ #pdu.free
291
+ nil
321
292
  else
322
- if block
323
- @requests[pdu.reqid] = block
324
- if (status = Net::SNMP::Wrapper.snmp_sess_send(@struct, pdu.pointer)) == 0
325
- error("snmp_get async failed")
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
- #Net::SNMP::Wrapper.print_session(@struct)
332
- #Net::SNMP::Wrapper.print_pdu(pdu.struct)
333
- #if pdu.command == Net::SNMP::Constants::SNMP_MSG_TRAP
334
- # status = Net::SNMP::Wrapper.snmp_sess_send(@struct, pdu.pointer) == 1 ? 0 : 1
335
- #else
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
- #Net::SNMP::Wrapper.print_pdu(Net::SNMP::Wrapper::SnmpPdu.new(response_ptr.read_pointer))
343
- #if pdu.command == Net::SNMP::Constants::SNMP_MSG_TRAP
344
- Net::SNMP::PDU.new(response_ptr.read_pointer)
345
- #else
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
+