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,39 @@
1
+ # Ruby api to the Wrapper::Module class (represents netsnmp's `struct module`)
2
+
3
+ module Net::SNMP
4
+ class Module
5
+ extend Forwardable
6
+ def_delegators :struct, :modid, :no_imports
7
+
8
+ # Gets a module node by its id
9
+ def self.find(module_id)
10
+ if module_id < 0
11
+ nil
12
+ else
13
+ new(Wrapper.find_module(module_id))
14
+ end
15
+ end
16
+
17
+ def initialize(struct)
18
+ raise "Tried to initialize null module" if struct.null?
19
+ @struct = struct
20
+ end
21
+
22
+ def name
23
+ @struct.name.read_string
24
+ end
25
+
26
+ def file
27
+ @struct.file.read_string
28
+ end
29
+
30
+ def next
31
+ if @struct.next.null?
32
+ nil
33
+ else
34
+ self.class.new(@struct.next)
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,122 @@
1
+ # Ruby api to the Wrapper::Tree class (represents netsnmp's `struct tree`)
2
+
3
+ module Net::SNMP
4
+ module MIB
5
+ class Node
6
+ include Debug
7
+ extend Forwardable
8
+ attr_accessor :struct, :is_in_mib
9
+ def_delegators :struct, :label, :type, :access, :status, :modid, :subid
10
+
11
+ class << self
12
+ include Debug
13
+ def get_node(oid)
14
+ if oid.kind_of?(String)
15
+ oid = OID.new(oid)
16
+ end
17
+ struct = Wrapper.get_tree(oid.pointer, oid.length_pointer.read_int, Wrapper.get_tree_head().pointer)
18
+ node = new(struct.pointer)
19
+ warn "OID #{oid.to_s} not found in MIB" unless node.in_mib?
20
+ node
21
+ end
22
+ end
23
+
24
+ def initialize(arg)
25
+ @oid = nil
26
+ @module = nil
27
+ case arg
28
+ when Wrapper::Tree
29
+ @struct = arg
30
+ when FFI::Pointer
31
+ @struct = Wrapper::Tree.new(arg)
32
+ else
33
+ raise "invalid type"
34
+ end
35
+ end
36
+
37
+ def exists_in_mib?
38
+ # The structure can be created without the oid actually
39
+ # existing in the mib, but the parent will be null.
40
+ # Of course, the parent is null for the root node as well,
41
+ # so ignore that case.
42
+ (!@struct.parent.null?) || oid.to_s == '1'
43
+ end
44
+ alias in_mib? exists_in_mib?
45
+
46
+ def module
47
+ unless @module
48
+ @module = Module.find(modid)
49
+ end
50
+ @module
51
+ end
52
+
53
+ def description
54
+ if @struct.description.null?
55
+ nil
56
+ else
57
+ @struct.description.read_string
58
+ end
59
+ end
60
+
61
+ def oid
62
+ return @oid if @oid
63
+ @oid = OID.new(label)
64
+ end
65
+
66
+ # actually seems like list is linked backward, so this will retrieve the previous oid numerically
67
+ def next
68
+ return nil if @struct.next.null?
69
+ self.class.new(@struct.next)
70
+ end
71
+
72
+ def next_peer
73
+ return nil if @struct.next_peer.null?
74
+ self.class.new(@struct.next_peer)
75
+ end
76
+
77
+ def parent
78
+ return nil if @struct.parent.null?
79
+ self.class.new(@struct.parent)
80
+ end
81
+
82
+ def children
83
+ return to_enum __method__ unless block_given?
84
+ return if @struct.child_list.null?
85
+ child = self.class.new(@struct.child_list)
86
+ yield child
87
+ while child = child.next_peer
88
+ yield child
89
+ end
90
+ end
91
+
92
+ # Depth-first traversal of all descendants
93
+ def descendants(&block)
94
+ return to_enum __method__ unless block_given?
95
+ return if @struct.child_list.null?
96
+ child = self.class.new(@struct.child_list)
97
+ block[child]
98
+ child.descendants(&block)
99
+ while child = child.next_peer
100
+ block[child]
101
+ child.descendants(&block)
102
+ end
103
+ end
104
+
105
+ def peers
106
+ return [] if oid.to_s == '1'
107
+ parent.children.reject { |n| n.oid.to_s == oid.to_s }
108
+ end
109
+ alias siblings peers
110
+
111
+ def enums
112
+ return to_enum __method__ unless block_given?
113
+ enum = struct.enums
114
+ while !enum.null?
115
+ yield({value: enum.value, label: enum.label.read_string})
116
+ enum = enum.next
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,48 @@
1
+ module Net::SNMP::MIB
2
+ module Templates
3
+ DESCRIBE = '
4
+ <% nodes.each do |node| -%>
5
+ <%= node.module.nil? ? "" : "#{node.module.name}::" %><%= node.label %>
6
+ - oid: <%= node.oid %>
7
+ - type: <%= node.type %>
8
+ - file: <%= node.module.file unless node.module.nil? %>
9
+ - descr: <%= "\"#{node.description}\"" %>
10
+ - enums: <%=
11
+ if !(node.enums.count == 0)
12
+ node.enums.map { |e| "#{e[:label]}(#{e[:value]})" }.join(", ")
13
+ else
14
+ "NONE"
15
+ end
16
+ %>
17
+ - parent: <%= "#{node.parent.label}(#{node.parent.oid})" unless node.parent.nil? %>
18
+ - peers: <%= node.peers.map { |n| "#{n.label}(#{n.subid})"}.join(", ") %>
19
+ - next: <%= node.next.oid unless node.next.nil? %>
20
+ - next_peer: <%= node.next_peer.oid unless node.next_peer.nil? %>
21
+ - children: <%=
22
+ if node.children.count > 0
23
+ node.children.map { |n| "#{n.label}(#{n.subid})"}.join(", ")
24
+ else
25
+ "NONE"
26
+ end
27
+ %>
28
+ <% end -%>
29
+ '.sub!("\n", "") # Remove leading newline
30
+
31
+ JSON = json_template = '
32
+ [
33
+ <% nodes.each_with_index { |node, index| -%>
34
+ <%= ",\n" if index > 0 -%>
35
+ {
36
+ "name": "<%= node.module.nil? ? "" : "#{node.module.name}::" %><%= node.label %>",
37
+ "oid": "<%= node.oid %>",
38
+ "type": <%= node.type %>,
39
+ <% if node.enums && node.enums.count > 0 -%>
40
+ "enums": { <%= node.enums.map { |enum| "\"#{enum[:label]}\": #{enum[:value]}" }.join(", ") %> },
41
+ <% end -%>
42
+ "parent": <%= node.parent ? %Q["#{node.parent.oid}"] : "undefined" %>
43
+ }<% } -%>
44
+
45
+ ]
46
+ '.sub!("\n", "")
47
+ end
48
+ end
@@ -0,0 +1,134 @@
1
+ module Net
2
+ module SNMP
3
+ class OID
4
+ extend Debug
5
+ include Debug
6
+
7
+ attr_reader :oid, :pointer, :length_pointer
8
+ @oid_size = nil
9
+ @sub_id_bit_width = nil
10
+
11
+ def self.from_pointer(ptr, sub_id_count)
12
+ OID.new(OID.read_pointer(ptr, sub_id_count))
13
+ end
14
+
15
+ def self.oid_size
16
+ unless @oid_size
17
+ determine_oid_size
18
+ end
19
+ @oid_size
20
+ end
21
+
22
+ def self.determine_oid_size
23
+ if Net::SNMP.initialized?
24
+ oid_ptr = FFI::MemoryPointer.new(:ulong, 8)
25
+ length_ptr = FFI::MemoryPointer.new(:size_t, 1)
26
+ length_ptr.write_int(oid_ptr.total)
27
+
28
+ Wrapper.read_objid('1.1', oid_ptr, length_ptr)
29
+ oid_str = oid_ptr.read_array_of_uint8(oid_ptr.total).map{|byte| byte.to_s(2).rjust(8, '0') }.join('')
30
+
31
+ @oid_size = (oid_str[/10*1/].length - 1) / 8
32
+ else
33
+ @oid_size = FFI::MemoryPointer.new(:ulong).total
34
+ warn "SNMP not initialized\n" + <<-WARNING
35
+ Cannot determine OID sub-id size, assuming common case of sizeof(ulong)
36
+ On this platform, sizeof(ulong) = #{@oid_size} bytes
37
+ To avoid this warning, call `Net::SNMP.init`
38
+ WARNING
39
+ end
40
+ end
41
+
42
+ def self.read_pointer(pointer, sub_id_count)
43
+ unless @sub_id_bit_width
44
+ @sub_id_bit_width = OID.oid_size * 8
45
+ end
46
+ pointer.send("read_array_of_uint#{@sub_id_bit_width}", sub_id_count).join('.')
47
+ end
48
+
49
+ def write_to_buffer(buffer)
50
+ unless @sub_id_bit_width
51
+ @sub_id_bit_width = OID.oid_size * 8
52
+ end
53
+ buffer.send("write_array_of_uint#{@sub_id_bit_width}", self.to_s.split('.').map{ |subid| subid.to_i })
54
+ end
55
+
56
+ def initialize(oid)
57
+ @oid = oid
58
+ @pointer = FFI::MemoryPointer.new(Net::SNMP::OID.oid_size * Constants::MAX_OID_LEN)
59
+ @length_pointer = FFI::MemoryPointer.new(:size_t)
60
+ @length_pointer.write_int(Constants::MAX_OID_LEN)
61
+ if @oid =~ /^[\d\.]*$/
62
+ if Wrapper.read_objid(@oid, @pointer, @length_pointer) == 0
63
+ Wrapper.snmp_perror(@oid)
64
+ end
65
+ else
66
+ if Wrapper.get_node(@oid, @pointer, @length_pointer) == 0
67
+ raise "No such node: #{oid}"
68
+ end
69
+ @oid = to_s
70
+ end
71
+ end
72
+
73
+ def size
74
+ @length_pointer.read_int * Net::SNMP::OID.oid_size
75
+ end
76
+
77
+ def length
78
+ @length_pointer.read_int
79
+ end
80
+
81
+ def oid
82
+ @oid
83
+ end
84
+
85
+ def name
86
+ @oid
87
+ end
88
+
89
+ def to_s
90
+ OID.read_pointer(@pointer, length)
91
+ end
92
+
93
+ # Gets the MIB node corresponding to this OID.
94
+ # Note that if this OID represents a MIB node, like '1.3',
95
+ # then `oid.to_s == oid.node.oid.to_s`. However, if there
96
+ # are indices on this oid, then `oid.to_s.start_with?(oid.node.oid.to_s)`
97
+ def node
98
+ MIB::Node.get_node(oid)
99
+ end
100
+
101
+ # The instance index string
102
+ # Note that some MIB nodes specify the instance implicitly,
103
+ # like sysUpTimeInstance. So, this may be an empty string.
104
+ def index
105
+ # Strip the name
106
+ index = oid.sub(node.oid.name, '')
107
+ # Strip any leading dot
108
+ index.sub(/^\./, '')
109
+ end
110
+
111
+ # The label for the this OID. Includes any instance indexes.
112
+ def label
113
+ "#{node.label}#{index.length > 0 ? "." + index : ""}"
114
+ end
115
+
116
+ def <=>(o)
117
+ a = self._packed
118
+ b = o._packed
119
+ a <=> b
120
+ end
121
+
122
+ def _packed
123
+ i = self.to_s.dup
124
+ i.sub!(/^\./,'')
125
+ i.gsub!(/ /, '.0')
126
+ i.replace(i.split('.').map(&:to_i).pack('N*'))
127
+ end
128
+
129
+ def parent_of?(o)
130
+ o.to_s =~ /^#{self.to_s}\./
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,235 @@
1
+ module Net
2
+ module SNMP
3
+ # Wrapper around netsnmp_pdu.
4
+ class PDU
5
+ extend Forwardable
6
+ include Net::SNMP::Debug
7
+ attr_accessor :struct, :varbinds, :callback, :command
8
+ def_delegators :struct, :pointer
9
+
10
+ # Create a new PDU object.
11
+ # +pdu_type+ The type of the PDU. For example, Net::SNMP::SNMP_MSG_GET. See constants.rb
12
+ def initialize(pdu_type)
13
+ @varbinds = []
14
+ case pdu_type
15
+ when FFI::Pointer
16
+ @struct = Wrapper::SnmpPdu.new(pdu_type)
17
+ @command = @struct.command
18
+ v = @struct.variables
19
+ unless v.null?
20
+ @varbinds << Varbind.from_pointer(v)
21
+ while( !(v = v.next_variable).null? )
22
+ @varbinds << Varbind.from_pointer(v)
23
+ end
24
+ end
25
+ when Fixnum
26
+ @struct = Wrapper.snmp_pdu_create(pdu_type)
27
+ @command = pdu_type
28
+ else
29
+ raise Error.new, "invalid pdu type"
30
+ end
31
+ end
32
+
33
+ # Specifies the number of non-repeating, regular objects at the start of
34
+ # the variable list in the request.
35
+ # (For getbulk requests only, non-repeaters is stored in errstat location)
36
+ def non_repeaters
37
+ @struct.errstat
38
+ end
39
+
40
+ def non_repeaters=(nr)
41
+ @struct.errstat = nr
42
+ end
43
+
44
+ # The number of iterations in the table to be read for the repeating
45
+ # objects that follow the non-repeating objects.
46
+ # (For getbulk requests only, max-repititions are stored in errindex location)
47
+ def max_repetitions=(mr)
48
+ @struct.errindex = mr
49
+ end
50
+
51
+ def max_repetitions
52
+ @struct.errindex
53
+ end
54
+
55
+ # Sets the enterprise OID of this PDU
56
+ # (Valid for SNMPv1 traps only)
57
+ def enterprise=(oid)
58
+ @i_own_enterprise = true
59
+ oid = OID.new(oid) if oid.kind_of?(String)
60
+ @struct.enterprise = FFI::LibC.calloc(oid.length, OID.oid_size)
61
+ oid.write_to_buffer(@struct.enterprise)
62
+ @struct.enterprise_length = oid.length
63
+ end
64
+
65
+ # The enterprise OID of this PDU
66
+ # (Valid for SNMPv1 traps only)
67
+ def enterprise
68
+ OID.from_pointer(@struct.enterprise, @struct.enterprise_length)
69
+ end
70
+
71
+ # Sets the address of the agent that sent this PDU
72
+ # (Valid for SNMPv1 traps only)
73
+ def agent_addr=(addr)
74
+ # @struct.agent_addr is a binary array of 4 characters,
75
+ # so pack the provided string into four bytes and we can assign it
76
+ @struct.agent_addr = addr.split('.').map{ |octet| octet.to_i }.pack("CCCC")
77
+ end
78
+
79
+ # The address of the agent that sent this PDU
80
+ # (Valid for SNMPv1 traps only)
81
+ def agent_addr
82
+ # @struct.agent_addr is a binary array of 4 characters,
83
+ # to_a converts this to a ruby array of Integers, then join get's us
84
+ # back to the string form
85
+ @struct.agent_addr.to_a.join('.')
86
+ end
87
+
88
+ # The uptime for the PDU
89
+ # (Only valid for SNMPv1 traps)
90
+ def uptime
91
+ @struct.time
92
+ end
93
+
94
+ # The uptime for the PDU
95
+ # (Only valid for SNMPv1 traps)
96
+ def uptime=(value)
97
+ @struct.time = value.to_i
98
+ end
99
+
100
+ # Returns true if pdu is in error
101
+ def error?
102
+ self.errstat != 0
103
+ end
104
+
105
+ # Sets the pdu errstat
106
+ def error=(value)
107
+ @struct.errstat = value
108
+ end
109
+ alias errstat= error=
110
+ alias error_status= error=
111
+
112
+ # Sets the error index
113
+ def error_index=(index)
114
+ @struct.errindex = index
115
+ end
116
+ alias errindex= error_index=
117
+
118
+ # A descriptive error message
119
+ def error_message
120
+ Wrapper::snmp_errstring(self.errstat)
121
+ end
122
+
123
+ # Tries to delegate missing methods to the underlying Wrapper::SnmpPdu object.
124
+ # If it does not respond to the method, calls super.
125
+ def method_missing(m, *args)
126
+ if @struct.respond_to?(m)
127
+ @struct.send(m, *args)
128
+ else
129
+ super
130
+ end
131
+ end
132
+
133
+ # Adds a variable binding to the pdu.
134
+ # Options:
135
+ # * +oid+ The SNMP OID
136
+ # * +type+ The data type. Possible values include Net::SNMP::ASN_OCTET_STR, Net::SNMP::ASN_COUNTER, etc. See constants.rb
137
+ # * +value+ The value of the varbind. default is nil.
138
+ def add_varbind(options)
139
+ options[:type] ||= case options[:value]
140
+ when String
141
+ Constants::ASN_OCTET_STR
142
+ when Fixnum
143
+ Constants::ASN_INTEGER
144
+ when Net::SNMP::OID
145
+ Constants::ASN_OBJECT_ID
146
+ when nil
147
+ Constants::ASN_NULL
148
+ else
149
+ raise "Unknown value type"
150
+ end
151
+
152
+ value = options[:value]
153
+ value_len = case options[:type]
154
+ when Constants::ASN_NULL,
155
+ Constants::SNMP_NOSUCHOBJECT,
156
+ Constants::SNMP_NOSUCHINSTANCE,
157
+ Constants::SNMP_ENDOFMIBVIEW
158
+ 0
159
+ else
160
+ options[:value].size
161
+ end
162
+
163
+ value = case options[:type]
164
+ when Constants::ASN_INTEGER,
165
+ Constants::ASN_GAUGE,
166
+ Constants::ASN_COUNTER,
167
+ Constants::ASN_TIMETICKS,
168
+ Constants::ASN_UNSIGNED
169
+ new_val = FFI::MemoryPointer.new(:long)
170
+ new_val.write_long(value)
171
+ new_val
172
+ when Constants::ASN_OCTET_STR,
173
+ Constants::ASN_BIT_STR,
174
+ Constants::ASN_OPAQUE
175
+ value
176
+ when Constants::ASN_IPADDRESS
177
+ # TODO
178
+ when Constants::ASN_OBJECT_ID
179
+ value.pointer
180
+ when Constants::ASN_NULL,
181
+ Constants::SNMP_NOSUCHOBJECT,
182
+ Constants::SNMP_NOSUCHINSTANCE,
183
+ Constants::SNMP_ENDOFMIBVIEW
184
+ nil
185
+ else
186
+ if value.respond_to?(:pointer)
187
+ value.pointer
188
+ else
189
+ raise Net::SNMP::Error.new, "Unknown variable type #{options[:type]}"
190
+ end
191
+ end
192
+
193
+ oid = options[:oid].kind_of?(OID) ? options[:oid] : OID.new(options[:oid])
194
+ var_ptr = Wrapper.snmp_pdu_add_variable(@struct.pointer, oid.pointer, oid.length_pointer.read_int, options[:type], value, value_len)
195
+ varbind = Varbind.new(var_ptr)
196
+ varbinds << varbind
197
+ end
198
+
199
+ def print_errors
200
+ puts "errstat = #{self.errstat}, index = #{self.errindex}, message = #{self.error_message}"
201
+ end
202
+
203
+ # Free the pdu
204
+ # Segfaults at the moment - most of the time, anyway.
205
+ # This is troublesome...
206
+ def free
207
+ # HACK
208
+ # snmp_free_pdu segfaults intermittently when freeing the enterprise
209
+ # oid if we've allocated it. Can't figure out why. For now, freeing it manually
210
+ # before calling snmp_free_pdu does the trick
211
+ if @i_own_enterprise
212
+ FFI::LibC.free(@struct.enterprise) unless @struct.enterprise.null?
213
+ @struct.enterprise = FFI::Pointer::NULL
214
+ end
215
+ Wrapper.snmp_free_pdu(@struct.pointer)
216
+ end
217
+
218
+ def print
219
+ puts "PDU"
220
+ if command == Constants::SNMP_MSG_TRAP
221
+ puts " - Enterprise: #{enterprise}"
222
+ puts " - Trap Type: #{trap_type}"
223
+ puts " - Specific Type: #{specific_type}"
224
+ puts " - Agent Addr: #{agent_addr}"
225
+ end
226
+
227
+ puts " - Varbinds:"
228
+ varbinds.each do |v|
229
+ puts " + #{MIB.translate(v.oid.to_s)}(#{v.oid}) = #{v.value}"
230
+ end
231
+ end
232
+
233
+ end
234
+ end
235
+ end