net-snmp2 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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