net-snmp2 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +20 -0
  6. data/README.md +28 -0
  7. data/Rakefile +24 -0
  8. data/TODO.md +6 -0
  9. data/bin/mib2rb +129 -0
  10. data/bin/net-snmp2 +64 -0
  11. data/examples/agent.rb +104 -0
  12. data/examples/manager.rb +11 -0
  13. data/examples/trap_handler.rb +46 -0
  14. data/examples/v1_trap_session.rb +24 -0
  15. data/examples/v2_trap_session.rb +21 -0
  16. data/lib/net-snmp2.rb +85 -0
  17. data/lib/net/snmp.rb +27 -0
  18. data/lib/net/snmp/agent/agent.rb +48 -0
  19. data/lib/net/snmp/agent/provider.rb +51 -0
  20. data/lib/net/snmp/agent/provider_dsl.rb +124 -0
  21. data/lib/net/snmp/agent/request_dispatcher.rb +38 -0
  22. data/lib/net/snmp/constants.rb +287 -0
  23. data/lib/net/snmp/debug.rb +54 -0
  24. data/lib/net/snmp/dispatcher.rb +108 -0
  25. data/lib/net/snmp/error.rb +29 -0
  26. data/lib/net/snmp/listener.rb +76 -0
  27. data/lib/net/snmp/message.rb +142 -0
  28. data/lib/net/snmp/mib/mib.rb +67 -0
  29. data/lib/net/snmp/mib/module.rb +39 -0
  30. data/lib/net/snmp/mib/node.rb +122 -0
  31. data/lib/net/snmp/mib/templates.rb +48 -0
  32. data/lib/net/snmp/oid.rb +134 -0
  33. data/lib/net/snmp/pdu.rb +235 -0
  34. data/lib/net/snmp/repl/manager_repl.rb +243 -0
  35. data/lib/net/snmp/session.rb +560 -0
  36. data/lib/net/snmp/trap_handler/trap_handler.rb +42 -0
  37. data/lib/net/snmp/trap_handler/v1_trap_dsl.rb +44 -0
  38. data/lib/net/snmp/trap_handler/v2_trap_dsl.rb +38 -0
  39. data/lib/net/snmp/trap_session.rb +92 -0
  40. data/lib/net/snmp/utility.rb +10 -0
  41. data/lib/net/snmp/varbind.rb +57 -0
  42. data/lib/net/snmp/version.rb +5 -0
  43. data/lib/net/snmp/wrapper.rb +450 -0
  44. data/net-snmp2.gemspec +30 -0
  45. data/spec/README.md +105 -0
  46. data/spec/async_spec.rb +123 -0
  47. data/spec/em_spec.rb +23 -0
  48. data/spec/error_spec.rb +34 -0
  49. data/spec/fiber_spec.rb +41 -0
  50. data/spec/mib_spec.rb +68 -0
  51. data/spec/net-snmp_spec.rb +18 -0
  52. data/spec/oid_spec.rb +21 -0
  53. data/spec/spec_helper.rb +10 -0
  54. data/spec/sync_spec.rb +132 -0
  55. data/spec/thread_spec.rb +19 -0
  56. data/spec/trap_spec.rb +45 -0
  57. data/spec/utility_spec.rb +10 -0
  58. data/spec/wrapper_spec.rb +69 -0
  59. 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