netsnmp 0.2.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6bb62b9abd416188660e04be1c12cf009b1df0e307030b9c239ea4b2acd73cc
4
- data.tar.gz: ee88f7eb292f1a0077ab25e4677713290447e0e7755049bbe6db50f07d15d6a1
3
+ metadata.gz: b080e928ed8014ae935e3bafd2356c21304b60f1c29c2c5327f7694e2825567a
4
+ data.tar.gz: de052efcd0958285cf8ae5609fadca6519d94e48af2076f74d1a76886e6f5309
5
5
  SHA512:
6
- metadata.gz: fc90cc626d4be2a04d598ec267bcfc108e87aea8e8a585a7611033fc56cb6c174a222250bb9eeeb05b2d03d058983b106c92c30ba3d91e922b171ecfbc37041a
7
- data.tar.gz: '09dbbb277ae24d9b760c0de70738a02037da527d653d5e003632c1b41d808cc21c44034fb51bec63394a132fe3c30ffaac1adc846dc061ca483e13fc0719af97'
6
+ metadata.gz: 9ead6354c915b389219ef88a0227fe509347897b4980278cd4daf08d410014effeb1863c2d4aa36ddcc019c9dd018f9ec383771812e50de75ed3f6d29d35fd38
7
+ data.tar.gz: 323eb76b9ed6538704897d000b03d100c04b3443dd2b7c6b2313da67db065a0adca5f31431eef0b571eb9a29a4d9762fd6151fc90840c149cdf023634ab802d3
data/README.md CHANGED
@@ -55,6 +55,7 @@ All of these issues are resolved here.
55
55
 
56
56
  * Client Interface, which supports SNMP v3, v2c, and v1
57
57
  * Supports get, getnext, set and walk calls.
58
+ * MIB support.
58
59
  * Proxy IO object support (for eventmachine/celluloid-io)
59
60
  * Ruby >= 2.1 support (modern)
60
61
  * Pure Ruby (no FFI)
@@ -73,12 +74,11 @@ manager = NETSNMP::Client.new(host: "localhost", port: 33445, username: "simulat
73
74
  context: "a172334d7d97871b72241397f713fa12")
74
75
 
75
76
  # SNMP get
76
- # sysName.0
77
- manager.get(oid: "1.3.6.1.2.1.1.0") #=> 'tt'
77
+ manager.get(oid: "sysName.0") #=> 'tt'
78
78
 
79
79
  # SNMP walk
80
80
  # sysORDescr
81
- manager.walk(oid: "1.3.6.1.2.1.1.1").each do |oid_code, value|
81
+ manager.walk(oid: "sysORDescr").each do |oid_code, value|
82
82
  # do something with them
83
83
  puts "for #{oid_code}: #{value}"
84
84
  end
@@ -135,6 +135,29 @@ manager.set("somecounteroid", value: 999999, type: 6)
135
135
  ```
136
136
  * Fork this library, extend support, write a test and submit a PR (the desired solution ;) )
137
137
 
138
+ ## MIB
139
+
140
+ `netsnmp` will load the default MIBs from known or advertised (via `MIBDIRS`) directories (provided that they're installed in the system). These will be used for the OID conversion.
141
+
142
+ Sometimes you'll need to load more, your own MIBs, in which case, you can use the following API:
143
+
144
+ ```ruby
145
+ require "netsnmp"
146
+
147
+ NETSNMP::MIB.load("MY-MIB")
148
+ # or, if it's not in any of the known locations
149
+ NETSNMP::MIB.load("/path/to/MY-MIB.txt")
150
+ ```
151
+
152
+ You can install common SNMP mibs by using your package manager:
153
+
154
+ ```
155
+ # using apt-get
156
+ > apt-get install snmp-mibs-downloader
157
+ # using apk
158
+ > apk --update add net-snmp-libs
159
+ ```
160
+
138
161
  ## Concurrency
139
162
 
140
163
  In ruby, you are usually adviced not to share IO objects across threads. The same principle applies here to `NETSNMP::Client`: provided you use it within a thread of execution, it should behave safely. So, something like this would be possible:
@@ -272,7 +295,6 @@ The job of the CI is:
272
295
 
273
296
  There are some features which this gem doesn't support. It was built to provide a client (or manager, in SNMP language) implementation only, and the requirements were fulfilled. However, these notable misses will stand-out:
274
297
 
275
- * No MIB support (you can only work with OIDs)
276
298
  * No server (Agent, in SNMP-ish) implementation.
277
299
  * No getbulk support.
278
300
 
@@ -34,6 +34,17 @@ rescue LoadError
34
34
  end
35
35
 
36
36
  module NETSNMP
37
+ module IsNumericExtensions
38
+ refine String do
39
+ def integer?
40
+ each_byte do |byte|
41
+ return false unless byte >= 48 && byte <= 57
42
+ end
43
+ true
44
+ end
45
+ end
46
+ end
47
+
37
48
  module StringExtensions
38
49
  # If you wonder why this is there: the oauth feature uses a refinement to enhance the
39
50
  # Regexp class locally with #match? , but this is never tested, because ActiveSupport
@@ -73,6 +84,7 @@ require "netsnmp/timeticks"
73
84
  require "netsnmp/oid"
74
85
  require "netsnmp/varbind"
75
86
  require "netsnmp/pdu"
87
+ require "netsnmp/mib"
76
88
  require "netsnmp/session"
77
89
 
78
90
  require "netsnmp/scoped_pdu"
@@ -77,7 +77,7 @@ module NETSNMP
77
77
  # @return [Enumerator] the enumerator-collection of the oid-value pairs
78
78
  #
79
79
  def walk(oid:)
80
- walkoid = oid
80
+ walkoid = OID.build(oid)
81
81
  Enumerator.new do |y|
82
82
  code = walkoid
83
83
  first_response_code = nil
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mib/parser"
4
+
5
+ module NETSNMP
6
+ module MIB
7
+ using IsNumericExtensions
8
+
9
+ OIDREGEX = /^[\d\.]*$/
10
+
11
+ module_function
12
+
13
+ MIBDIRS = ENV.fetch("MIBDIRS", File.join("/usr", "share", "snmp", "mibs")).split(":")
14
+ PARSER = Parser.new
15
+ @parser_mutex = Mutex.new
16
+ @modules_loaded = []
17
+ @object_identifiers = {}
18
+
19
+ # Translates na identifier, such as "sysDescr", into an OID
20
+ def oid(identifier)
21
+ prefix, *suffix = case identifier
22
+ when Array
23
+ identifier
24
+ else
25
+ identifier.split(".", 2)
26
+ end
27
+
28
+ # early exit if it's an OID already
29
+ unless prefix.integer?
30
+ load_defaults
31
+ # load module if need be
32
+ idx = prefix.index("::")
33
+ if idx
34
+ mod = prefix[0..(idx - 1)]
35
+ type = prefix[(idx + 2)..-1]
36
+ return unless load(mod)
37
+ else
38
+ type = prefix
39
+ end
40
+
41
+ return if type.nil? || type.empty?
42
+
43
+ prefix = @object_identifiers[type] ||
44
+ raise(Error, "can't convert #{type} to OID")
45
+
46
+ end
47
+
48
+ [prefix, *suffix].join(".")
49
+ end
50
+
51
+ # This is a helper function, do not rely on this functionality in future
52
+ # versions
53
+ def identifier(oid)
54
+ @object_identifiers.select do |_, full_oid|
55
+ full_oid.start_with?(oid)
56
+ end
57
+ end
58
+
59
+ #
60
+ # Loads a MIB. Can be called multiple times, as it'll load it once.
61
+ #
62
+ # Accepts the MIB name in several ways:
63
+ #
64
+ # MIB.load("SNMPv2-MIB")
65
+ # MIB.load("SNMPv2-MIB.txt")
66
+ # MIB.load("/path/to/SNMPv2-MIB.txt")
67
+ #
68
+ def load(mod)
69
+ unless File.file?(mod)
70
+ moddir = nil
71
+ MIBDIRS.each do |mibdir|
72
+ if File.exist?(File.join(mibdir, mod))
73
+ moddir = File.join(mibdir, mod)
74
+ break
75
+ elsif File.extname(mod).empty? && File.exist?(File.join(mibdir, "#{mod}.txt"))
76
+ moddir = File.join(mibdir, "#{mod}.txt")
77
+ break
78
+ end
79
+ end
80
+ return false unless moddir
81
+ mod = moddir
82
+ end
83
+ return true if @modules_loaded.include?(mod)
84
+ do_load(mod)
85
+ @modules_loaded << mod
86
+ true
87
+ end
88
+
89
+ TYPES = ["OBJECT-TYPE", "OBJECT IDENTIFIER", "MODULE-IDENTITY"].freeze
90
+
91
+ STATIC_MIB_TO_OID = {
92
+ "iso" => "1"
93
+ }.freeze
94
+
95
+ #
96
+ # Loads the MIB all the time, where +mod+ is the absolute path to the MIB.
97
+ #
98
+ def do_load(mod)
99
+ data = @parser_mutex.synchronize { PARSER.parse(File.read(mod)) }
100
+
101
+ imports = load_imports(data)
102
+
103
+ data[:declarations].each_with_object(@object_identifiers) do |dec, types|
104
+ next unless TYPES.include?(dec[:type])
105
+
106
+ oid = String(dec[:value]).split(/ +/).flat_map do |cp|
107
+ if cp.integer?
108
+ cp
109
+ else
110
+ STATIC_MIB_TO_OID[cp] || @object_identifiers[cp] || begin
111
+ imported_mod, = imports.find do |_, identifiers|
112
+ identifiers.include?(cp)
113
+ end
114
+
115
+ raise Error, "didn't find a module to import \"#{cp}\" from" unless imported_mod
116
+
117
+ load(imported_mod)
118
+
119
+ @object_identifiers[cp]
120
+ end
121
+ end
122
+ end.join(".")
123
+
124
+ types[String(dec[:name])] = oid
125
+ end
126
+ end
127
+
128
+ #
129
+ # Reformats the import lists into an hash indexed by module name, to a list of
130
+ # imported names
131
+ #
132
+ def load_imports(data)
133
+ return unless data[:imports]
134
+
135
+ data[:imports].each_with_object({}) do |import, imp|
136
+ imp[String(import[:name])] = case import[:ids]
137
+ when Hash
138
+ [String(import[:ids][:name])]
139
+ else
140
+ import[:ids].map { |id| String(id[:name]) }
141
+ end
142
+ end
143
+ end
144
+
145
+ def load_defaults
146
+ # loading the defaults MIBS
147
+ load("SNMPv2-MIB")
148
+ load("IF-MIB")
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,750 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module NETSNMP::MIB
6
+ class Parser < Parslet::Parser
7
+ root :mibfile
8
+
9
+ def spaced(character = nil)
10
+ if character.nil? && block_given?
11
+ yield >> space.repeat
12
+ else
13
+ str(character) >> space.repeat
14
+ end
15
+ end
16
+
17
+ def curly(atom)
18
+ str("{") >> space.repeat >> atom >> space.repeat >> str("}")
19
+ end
20
+
21
+ def bracketed(atom)
22
+ str("(") >> space.repeat >> atom >> space.repeat >> str(")")
23
+ end
24
+
25
+ def square_bracketed(atom)
26
+ str("[") >> space.repeat >> atom >> space.repeat >> str("]")
27
+ end
28
+
29
+ def with_separator(atom, separator = nil)
30
+ if separator
31
+ sep = if separator.is_a?(String)
32
+ space.repeat >> str(separator) >> space.repeat
33
+ else
34
+ separator
35
+ end
36
+
37
+ atom >> (sep >> atom).repeat
38
+ else
39
+ atom >> (space.repeat >> atom).repeat
40
+ end
41
+ end
42
+
43
+ rule(:mibfile) do
44
+ space.repeat >> modules.maybe
45
+ end
46
+
47
+ rule(:modules) do
48
+ with_separator(mod)
49
+ end
50
+
51
+ rule(:mod) do
52
+ spaced { module_name.as(:name) } >> module_oid >>
53
+ spaced("DEFINITIONS") >> colon_colon_part >>
54
+ spaced("BEGIN") >>
55
+ exports_part.as(:exports) >>
56
+ linkage_part.as(:imports) >>
57
+ declaration_part.as(:declarations) >>
58
+ spaced("END")
59
+ end
60
+
61
+ rule(:module_name) { uppercase_identifier }
62
+
63
+ rule(:module_oid) do
64
+ spaced { curly(object_identifier) }.maybe
65
+ end
66
+
67
+ rule(:declaration_part) do
68
+ spaced { declarations }.maybe
69
+ end
70
+
71
+ rule(:exports_part) do
72
+ spaced { exports_clause }.maybe
73
+ end
74
+
75
+ rule(:exports_clause) do
76
+ spaced("EXPORTS") >> spaced { import_identifiers } >> str(";")
77
+ end
78
+
79
+ rule(:linkage_part) do
80
+ spaced { linkage_clause }.maybe
81
+ end
82
+
83
+ rule(:linkage_clause) do
84
+ spaced("IMPORTS") >> spaced { import_part } >> str(";")
85
+ end
86
+
87
+ rule(:import_part) do
88
+ imports.maybe
89
+ end
90
+
91
+ rule(:imports) { with_separator(import) }
92
+
93
+ rule(:import) do
94
+ spaced { import_identifiers.as(:ids) } >> spaced("FROM") >> module_name.as(:name)
95
+ end
96
+
97
+ rule(:import_identifiers) { with_separator(import_identifier.as(:name), ",") }
98
+
99
+ rule(:import_identifier) do
100
+ lowercase_identifier | uppercase_identifier | imported_keyword
101
+ end
102
+
103
+ rule(:imported_keyword) do
104
+ imported_smi_keyword |
105
+ str("BITS") |
106
+ str("Integer32") |
107
+ str("IpAddress") |
108
+ str("MANDATORY-GROUPS") |
109
+ str("MODULE-COMPLIANCE") |
110
+ str("MODULE-IDENTITY") |
111
+ str("OBJECT-GROUP") |
112
+ str("OBJECT-IDENTITY") |
113
+ str("OBJECT-TYPE") |
114
+ str("Opaque") |
115
+ str("TEXTUAL-CONVENTION") |
116
+ str("TimeTicks") |
117
+ str("Unsigned32")
118
+ end
119
+
120
+ rule(:imported_smi_keyword) do
121
+ str("AGENT-CAPABILITIES") |
122
+ str("Counter32") |
123
+ str("Counter64") |
124
+ str("Gauge32") |
125
+ str("NOTIFICATION-GROUP") |
126
+ str("NOTIFICATION-TYPE") |
127
+ str("TRAP-TYPE")
128
+ end
129
+
130
+ rule(:declarations) do
131
+ with_separator(declaration)
132
+ end
133
+
134
+ rule(:declaration) do
135
+ type_declaration |
136
+ value_declaration |
137
+ object_identity_clause |
138
+ object_type_clause |
139
+ traptype_clause |
140
+ notification_type_clause |
141
+ module_identity_clause |
142
+ module_compliance_clause |
143
+ object_group_clause |
144
+ notification_group_clause |
145
+ agent_capabilities_clause |
146
+ macro_clause
147
+ end
148
+
149
+ rule(:macro_clause) do
150
+ spaced { macro_name.as(:name) } >> spaced { str("MACRO").as(:type) } >> colon_colon_part >>
151
+ spaced("BEGIN") >>
152
+ # ignoring macro clauses
153
+ match("^(?!END)").repeat >>
154
+ spaced("END")
155
+ end
156
+
157
+ rule(:macro_name) do
158
+ str("MODULE-IDENTITY") |
159
+ str("OBJECT-TYPE") |
160
+ str("TRAP-TYPE") |
161
+ str("NOTIFICATION-TYPE") |
162
+ str("OBJECT-IDENTITY") |
163
+ str("TEXTUAL-CONVENTION") |
164
+ str("OBJECT-GROUP") |
165
+ str("NOTIFICATION-GROUP") |
166
+ str("MODULE-COMPLIANCE") |
167
+ str("AGENT-CAPABILITIES")
168
+ end
169
+
170
+ rule(:agent_capabilities_clause) do
171
+ spaced { lowercase_identifier.as(:name) } >>
172
+ spaced { str("AGENT-CAPABILITIES").as(:type) } >>
173
+ spaced("PRODUCT-RELEASE") >> spaced { text } >>
174
+ spaced("STATUS") >> spaced { status } >>
175
+ spaced("DESCRIPTION") >> spaced { text } >>
176
+ spaced { refer_part }.maybe >>
177
+ spaced { module_part_capabilities }.maybe >>
178
+ colon_colon_part >> curly(object_identifier)
179
+ end
180
+
181
+ rule(:module_part_capabilities) do
182
+ modules_capabilities
183
+ end
184
+
185
+ rule(:modules_capabilities) do
186
+ with_separator(module_capabilities)
187
+ end
188
+
189
+ rule(:module_capabilities) do
190
+ spaced("SUPPORTS") >>
191
+ module_name_capabilities >>
192
+ spaced("INCLUDES") >> curly(capabilities_groups) >>
193
+ spaced { variation_part }.maybe
194
+ end
195
+
196
+ rule(:module_name_capabilities) do
197
+ spaced { uppercase_identifier } >> object_identifier | uppercase_identifier
198
+ end
199
+
200
+ rule(:capabilities_groups) do
201
+ with_separator(capabilities_group)
202
+ end
203
+
204
+ rule(:capabilities_group) { objectIdentifier }
205
+
206
+ rule(:variation_part) do
207
+ variations
208
+ end
209
+
210
+ rule(:variations) do
211
+ with_separator(variation)
212
+ end
213
+
214
+ rule(:variation) do
215
+ spaced("VARIATION") >> object_identifier >>
216
+ spaced { syntax_part }.maybe >>
217
+ spaced { write_syntax_part } >>
218
+ spaced { variation_access_part }.maybe >>
219
+ spaced { creation_part }.maybe >>
220
+ spaced { def_val_part }.maybe >>
221
+ spaced("DESCRIPTION") >> text
222
+ end
223
+
224
+ rule(:variation_access_part) do
225
+ spaced("ACCESS") >> variation_access
226
+ end
227
+
228
+ rule(:variation_access) { lowercase_identifier }
229
+
230
+ rule(:creation_part) do
231
+ spaced("CREATION-REQUIRES") >> curly(cells)
232
+ end
233
+
234
+ rule(:cells) { with_separator(cell, ",") }
235
+
236
+ rule(:cell) { object_identifier }
237
+
238
+ rule(:notification_group_clause) do
239
+ spaced { lowercase_identifier.as(:name) } >>
240
+ spaced { str("NOTIFICATION-GROUP").as(:type) } >>
241
+ spaced { notifications_part } >>
242
+ spaced("STATUS") >> spaced { status } >>
243
+ spaced("DESCRIPTION") >> spaced { text } >>
244
+ spaced { refer_part }.maybe >>
245
+ colon_colon_part >> curly(object_identifier)
246
+ end
247
+
248
+ rule(:notifications_part) do
249
+ spaced("NOTIFICATIONS") >> curly(notifications)
250
+ end
251
+
252
+ rule(:notifications) do
253
+ with_separator(notification, ",")
254
+ end
255
+
256
+ rule(:notification) do
257
+ notification_name
258
+ end
259
+
260
+ rule(:object_group_clause) do
261
+ spaced { lowercase_identifier.as(:name) } >>
262
+ spaced { str("OBJECT-GROUP").as(:type) } >>
263
+ spaced { object_group_objects_part } >>
264
+ spaced("STATUS") >> spaced { status } >>
265
+ spaced("DESCRIPTION") >> spaced { text } >>
266
+ spaced { refer_part }.maybe >>
267
+ colon_colon_part >> curly(object_identifier)
268
+ end
269
+
270
+ rule(:object_group_objects_part) do
271
+ spaced("OBJECTS") >> curly(objects)
272
+ end
273
+
274
+ rule(:module_compliance_clause) do
275
+ spaced { lowercase_identifier.as(:name) } >>
276
+ spaced { str("MODULE-COMPLIANCE").as(:type) } >>
277
+ spaced("STATUS") >> spaced { status } >>
278
+ spaced("DESCRIPTION") >> spaced { text } >>
279
+ spaced { refer_part }.maybe >>
280
+ spaced { compliance_modules } >>
281
+ colon_colon_part >> curly(object_identifier)
282
+ end
283
+
284
+ rule(:compliance_modules) do
285
+ with_separator(compliance_module)
286
+ end
287
+
288
+ rule(:compliance_module) do
289
+ spaced { str("MODULE") >> (space_in_line.repeat(1) >> compliance_module_name).maybe } >>
290
+ spaced { mandatory_part }.maybe >>
291
+ compliances.maybe
292
+ end
293
+
294
+ rule(:compliance_module_name) do
295
+ uppercase_identifier
296
+ end
297
+
298
+ rule(:mandatory_part) do
299
+ spaced("MANDATORY-GROUPS") >> curly(mandatory_groups)
300
+ end
301
+
302
+ rule(:compliances) do
303
+ with_separator(compliance).as(:compliances)
304
+ end
305
+
306
+ rule(:compliance) do
307
+ compliance_group | compliance_object
308
+ end
309
+
310
+ rule(:compliance_group) do
311
+ spaced { str("GROUP").as(:type) } >> spaced { object_identifier.as(:name) } >>
312
+ spaced("DESCRIPTION") >> text
313
+ end
314
+
315
+ rule(:compliance_object) do
316
+ spaced { str("OBJECT").as(:type) } >>
317
+ spaced { object_identifier.as(:name) } >>
318
+ spaced { syntax_part }.maybe >>
319
+ spaced { write_syntax_part }.maybe >>
320
+ spaced { access_part }.maybe >>
321
+ spaced("DESCRIPTION") >> text
322
+ end
323
+
324
+ rule(:syntax_part) do
325
+ spaced("SYNTAX") >> syntax.as(:syntax)
326
+ end
327
+
328
+ rule(:write_syntax_part) do
329
+ (spaced("WRITE-SYNTAX") >> spaced { syntax }).maybe
330
+ end
331
+
332
+ rule(:access_part) do
333
+ (spaced("MIN-ACCESS") >> spaced { access }).maybe
334
+ end
335
+
336
+ rule(:mandatory_groups) do
337
+ with_separator(mandatory_group, ",").as(:groups)
338
+ end
339
+
340
+ rule(:mandatory_group) { object_identifier.as(:name) }
341
+
342
+ rule(:module_identity_clause) do
343
+ spaced { lowercase_identifier.as(:name) } >>
344
+ spaced { str("MODULE-IDENTITY").as(:type) } >>
345
+ (spaced("SUBJECT-CATEGORIES") >> curly(category_ids)).maybe >>
346
+ spaced("LAST-UPDATED") >> spaced { ext_utc_time } >>
347
+ spaced("ORGANIZATION") >> spaced { text.as(:organization) } >>
348
+ spaced("CONTACT-INFO") >> spaced { text.as(:contact_info) } >>
349
+ spaced("DESCRIPTION") >> spaced { text } >>
350
+ spaced { revisions }.maybe >>
351
+ colon_colon_part >> curly(object_identifier.as(:value))
352
+ end
353
+
354
+ rule(:ext_utc_time) { text }
355
+
356
+ rule(:revisions) do
357
+ with_separator(revision)
358
+ end
359
+
360
+ rule(:revision) do
361
+ spaced("REVISION") >> spaced { ext_utc_time } >>
362
+ spaced("DESCRIPTION") >>
363
+ text
364
+ end
365
+
366
+ rule(:category_ids) do
367
+ with_separator(category_id, ",")
368
+ end
369
+
370
+ rule(:category_id) do
371
+ spaced { lowercase_identifier } >> bracketed(number) | lowercase_identifier
372
+ end
373
+
374
+ rule(:notification_type_clause) do
375
+ spaced { lowercase_identifier.as(:name) } >>
376
+ spaced { str("NOTIFICATION-TYPE").as(:type) } >>
377
+ spaced { notification_objects_part }.maybe >>
378
+ spaced("STATUS") >> spaced { status } >>
379
+ spaced("DESCRIPTION") >> spaced { text } >>
380
+ spaced { refer_part }.maybe >>
381
+ colon_colon_part >> curly(notification_name)
382
+ end
383
+
384
+ rule(:notification_objects_part) do
385
+ spaced("OBJECTS") >> curly(objects)
386
+ end
387
+
388
+ rule(:objects) do
389
+ with_separator(object, ",")
390
+ end
391
+
392
+ rule(:object) do
393
+ object_identifier
394
+ end
395
+
396
+ rule(:notification_name) do
397
+ object_identifier
398
+ end
399
+
400
+ rule(:traptype_clause) do
401
+ spaced { fuzzy_lowercase_identifier.as(:name) } >>
402
+ spaced { str("TRAP-TYPE").as(:type) } >> spaced { enterprise_part } >>
403
+ spaced { var_part }.maybe >>
404
+ spaced { descr_part }.maybe >>
405
+ spaced { refer_part }.maybe >>
406
+ colon_colon_part >> number
407
+ end
408
+
409
+ rule(:enterprise_part) do
410
+ spaced("ENTERPRISE") >> object_identifier |
411
+ spaced("ENTERPRISE") >> curly(object_identifier)
412
+ end
413
+
414
+ rule(:var_part) do
415
+ spaced("VARIABLES") >> curly(var_types)
416
+ end
417
+
418
+ rule(:var_types) do
419
+ with_separator(var_type, ",")
420
+ end
421
+
422
+ rule(:var_type) { object_identifier }
423
+
424
+ rule(:descr_part) do
425
+ spaced("DESCRIPTION") >> text
426
+ end
427
+
428
+ rule(:object_type_clause) do
429
+ spaced { lowercase_identifier.as(:name) } >>
430
+ spaced { str("OBJECT-TYPE").as(:type) } >>
431
+ spaced { syntax_part }.maybe >>
432
+ spaced { units_part }.maybe >>
433
+ spaced { max_access_part }.maybe >>
434
+ (spaced("STATUS") >> spaced { status }).maybe >>
435
+ spaced { description_clause }.maybe >>
436
+ spaced { refer_part }.maybe >>
437
+ spaced { index_part }.maybe >>
438
+ spaced { mib_index }.maybe >>
439
+ spaced { def_val_part }.maybe >>
440
+ colon_colon_part >> curly(object_identifier.as(:value))
441
+ end
442
+
443
+ rule(:object_identity_clause) do
444
+ spaced { lowercase_identifier.as(:name) } >>
445
+ spaced { str("OBJECT-IDENTITY").as(:type) } >>
446
+ spaced("STATUS") >> spaced { status } >>
447
+ spaced("DESCRIPTION") >> spaced { text } >>
448
+ spaced { refer_part }.maybe >>
449
+ colon_colon_part >> curly(object_identifier)
450
+ end
451
+
452
+ rule(:units_part) do
453
+ spaced("UNITS") >> text.as(:units)
454
+ end
455
+
456
+ rule(:max_access_part) do
457
+ spaced("MAX-ACCESS") >> access | spaced("ACCESS") >> access
458
+ end
459
+
460
+ rule(:access) { lowercase_identifier }
461
+
462
+ rule(:description_clause) do
463
+ spaced("DESCRIPTION") >> text
464
+ end
465
+
466
+ rule(:index_part) do
467
+ spaced("AUGMENTS") >> curly(entry)
468
+ end
469
+
470
+ rule(:mib_index) do
471
+ spaced("INDEX") >> curly(index_types)
472
+ end
473
+
474
+ rule(:def_val_part) do
475
+ spaced("DEFVAL") >> curly(valueof_simple_syntax)
476
+ end
477
+
478
+ rule(:valueof_simple_syntax) do
479
+ value | lowercase_identifier | text | curly(object_identifiers_defval)
480
+ end
481
+
482
+ rule(:object_identifiers_defval) do
483
+ with_separator(object_identifier_defval)
484
+ end
485
+
486
+ rule(:object_identifier_defval) do
487
+ spaced { lowercase_identifier } >> bracketed(number) |
488
+ number
489
+ end
490
+
491
+ rule(:index_types) do
492
+ with_separator(index_type, ",")
493
+ end
494
+
495
+ rule(:index_type) do
496
+ spaced("IMPLIED") >> idx | idx
497
+ end
498
+
499
+ rule(:idx) do
500
+ object_identifier
501
+ end
502
+
503
+ rule(:entry) do
504
+ object_identifier
505
+ end
506
+
507
+ rule(:value_declaration) do
508
+ spaced { fuzzy_lowercase_identifier.as(:name) } >>
509
+ spaced { str("OBJECT IDENTIFIER").as(:type) } >>
510
+ colon_colon_part >> curly(object_identifier.as(:value))
511
+ end
512
+
513
+ rule(:fuzzy_lowercase_identifier) do
514
+ lowercase_identifier | uppercase_identifier
515
+ end
516
+
517
+ rule(:object_identifier) do
518
+ sub_identifiers
519
+ end
520
+
521
+ rule(:sub_identifiers) do
522
+ with_separator(sub_identifier, space_in_line.repeat)
523
+ end
524
+
525
+ rule(:sub_identifier) do
526
+ fuzzy_lowercase_identifier |
527
+ number |
528
+ spaced { lowercase_identifier } >> bracketed(number)
529
+ end
530
+
531
+ rule(:type_declaration) do
532
+ spaced { type_name.as(:vartype) } >> colon_colon_part >> type_declaration_rhs
533
+ end
534
+
535
+ rule(:type_name) do
536
+ uppercase_identifier | type_smi
537
+ end
538
+
539
+ rule(:type_smi) do
540
+ type_smi_and_sppi | type_smi_only
541
+ end
542
+
543
+ rule(:type_declaration_rhs) do
544
+ spaced { choice_clause } |
545
+ spaced { str("TEXTUAL-CONVENTION") } >>
546
+ spaced { display_part }.maybe >>
547
+ spaced("STATUS") >> spaced { status } >>
548
+ spaced("DESCRIPTION") >> spaced { text } >>
549
+ spaced { refer_part }.maybe >>
550
+ spaced("SYNTAX") >> syntax |
551
+ syntax
552
+ end
553
+
554
+ rule(:refer_part) do
555
+ spaced("REFERENCE") >> text
556
+ end
557
+
558
+ rule(:choice_clause) do
559
+ # Ignoring choice syntax
560
+ spaced { str("CHOICE").as(:type) } >> curly(match("[^\}]").repeat)
561
+ end
562
+
563
+ rule(:syntax) do
564
+ object_syntax | spaced("BITS").as(:type) >> curly(named_bits)
565
+ end
566
+
567
+ rule(:display_part) do
568
+ spaced("DISPLAY-HINT") >> text
569
+ end
570
+
571
+ rule(:named_bits) do
572
+ with_separator(named_bit, ",")
573
+ end
574
+
575
+ rule(:named_bit) do
576
+ spaced { lowercase_identifier } >> bracketed(number)
577
+ end
578
+
579
+ rule(:object_syntax) do
580
+ conceptual_table |
581
+ entry_type |
582
+ simple_syntax |
583
+ application_syntax |
584
+ type_tag >> simple_syntax |
585
+ row.as(:value)
586
+ end
587
+
588
+ rule(:simple_syntax) do
589
+ spaced { str("INTEGER").as(:type) } >> (integer_subtype | enum_spec).maybe |
590
+ spaced { str("Integer32").as(:type) >> space } >> integer_subtype.maybe |
591
+ spaced { str("OCTET STRING").as(:type) } >> octetstring_subtype.maybe |
592
+ spaced { str("OBJECT IDENTIFIER").as(:type) } >> any_subtype |
593
+ spaced { uppercase_identifier.as(:type) } >> (integer_subtype | enum_spec | octetstring_subtype)
594
+ end
595
+
596
+ rule(:application_syntax) do
597
+ spaced { str("IpAddress").as(:type) >> space } >> any_subtype |
598
+ spaced { str("NetworkAddress").as(:type) >> space } >> any_subtype |
599
+ spaced { str("Counter32").as(:type) >> space } >> integer_subtype.maybe |
600
+ spaced { str("Gauge32").as(:type) >> space } >> integer_subtype.maybe |
601
+ spaced { str("Unsigned32").as(:type) >> space } >> integer_subtype.maybe |
602
+ spaced { str("TimeTicks").as(:type) >> space } >> any_subtype |
603
+ spaced { str("Opaque").as(:type) >> space } >> octetstring_subtype.maybe |
604
+ spaced { str("Counter64").as(:type) >> space } >> integer_subtype.maybe
605
+ end
606
+
607
+ rule(:conceptual_table) do
608
+ spaced { str("SEQUENCE OF").as(:type) } >> row.as(:value)
609
+ end
610
+
611
+ rule(:entry_type) do
612
+ spaced { str("SEQUENCE").as(:type) } >> curly(sequence_items)
613
+ end
614
+
615
+ rule(:type_tag) do
616
+ spaced { square_bracketed(spaced("APPLICATION") >> number.as(:application_type)) } >> spaced("IMPLICIT") |
617
+ spaced { square_bracketed(spaced("UNIVERSAL") >> number.as(:universal_type)) } >> spaced("IMPLICIT")
618
+ end
619
+
620
+ rule(:sequence_items) do
621
+ with_separator(sequence_item, ",")
622
+ end
623
+
624
+ rule(:sequence_item) do
625
+ spaced { lowercase_identifier } >> spaced { sequence_syntax }
626
+ end
627
+
628
+ rule(:sequence_syntax) do
629
+ str("BITS") |
630
+ sequence_object_syntax |
631
+ spaced { uppercase_identifier } >> any_subtype
632
+ end
633
+
634
+ rule(:sequence_object_syntax) do
635
+ sequence_simple_syntax | sequence_application_syntax
636
+ end
637
+
638
+ rule(:sequence_simple_syntax) do
639
+ spaced("INTEGER") >> any_subtype |
640
+ spaced("Integer32") >> any_subtype |
641
+ spaced("OCTET STRING") >> any_subtype |
642
+ spaced("OBJECT IDENTIFIER") >> any_subtype
643
+ end
644
+
645
+ rule(:sequence_application_syntax) do
646
+ spaced { str("IpAddress") >> space } >> any_subtype |
647
+ spaced { str("COUNTER32") } >> any_subtype |
648
+ spaced { str("Gauge32") >> space } >> any_subtype |
649
+ spaced { str("Unsigned32") >> space } >> any_subtype |
650
+ spaced { str("TimeTicks") >> space } >> any_subtype |
651
+ str("Opaque") |
652
+ spaced { str("Counter64") >> space } >> any_subtype
653
+ end
654
+
655
+ rule(:row) { uppercase_identifier }
656
+
657
+ rule(:integer_subtype) { bracketed(ranges) }
658
+
659
+ rule(:octetstring_subtype) do
660
+ bracketed(spaced("SIZE") >> bracketed(ranges))
661
+ end
662
+
663
+ rule(:any_subtype) do
664
+ (integer_subtype | octetstring_subtype | enum_spec).maybe
665
+ end
666
+
667
+ rule(:enum_spec) { curly(enum_items) }
668
+
669
+ rule(:enum_items) do
670
+ with_separator(enum_item.as(:enum), ",")
671
+ end
672
+
673
+ rule(:enum_item) do
674
+ fuzzy_lowercase_identifier.as(:name) >> space.repeat >> bracketed(number.as(:value))
675
+ end
676
+
677
+ rule(:ranges) do
678
+ with_separator(range.as(:range), "|")
679
+ end
680
+
681
+ rule(:range) do
682
+ value.as(:min) >> space.repeat >> (str("..") >> space.repeat >> value.as(:max)).maybe
683
+ end
684
+
685
+ rule(:value) do
686
+ number | hexstring | binstring
687
+ end
688
+
689
+ rule(:status) { lowercase_identifier }
690
+
691
+ rule(:uppercase_identifier) do
692
+ match("[A-Z]") >> match("[A-Za-z0-9\-]").repeat
693
+ end
694
+
695
+ rule(:lowercase_identifier) do
696
+ match("[a-z]") >> match("[A-Za-z0-9\-]").repeat
697
+ end
698
+
699
+ rule(:type_smi_and_sppi) do
700
+ str("IpAddress") | str("TimeTicks") | str("Opaque") | str("Integer32") | str("Unsigned32")
701
+ end
702
+
703
+ rule(:type_smi_only) do
704
+ str("Counter") | str("Gauge32") | str("Counter64")
705
+ end
706
+
707
+ rule(:colon_colon_part) { spaced("::=") }
708
+
709
+ rule(:space_in_line) { match('[ \t]').repeat(1) }
710
+ rule(:cr) { match("\n") }
711
+ rule(:space) do
712
+ # this rule match all not important text
713
+ (match('[ \t\r\n]') | comment_line).repeat(1)
714
+ end
715
+
716
+ rule(:comment_line) do
717
+ (match('\-\-') >> match('[^\n]').repeat >> match('\n'))
718
+ end
719
+
720
+ rule(:space?) { space.maybe }
721
+ rule(:digit) { match["0-9"] }
722
+ rule(:hexchar) { match("[0-9a-fA-F]") }
723
+ rule(:empty) { str("") }
724
+ rule(:number) do
725
+ (
726
+ str("-").maybe >> (
727
+ str("0") | (match("[1-9]") >> digit.repeat)
728
+ ) >> (
729
+ str(".") >> digit.repeat(1)
730
+ ).maybe >> (
731
+ match("[eE]") >> (str("+") | str("-")).maybe >> digit.repeat(1)
732
+ ).maybe
733
+ ).repeat(1)
734
+ end
735
+
736
+ rule(:hexstring) do
737
+ str("'") >> hexchar.repeat >> str("'") >> match("[hH]")
738
+ end
739
+
740
+ rule(:binstring) do
741
+ str("'") >> match["0-1"].repeat >> str("'")
742
+ end
743
+
744
+ rule(:text) do
745
+ str('"') >> (
746
+ str('\\') >> any | str('"').absent? >> any
747
+ ).repeat >> str('"')
748
+ end
749
+ end
750
+ end
@@ -10,16 +10,10 @@ module NETSNMP
10
10
 
11
11
  module_function
12
12
 
13
- def build(o)
14
- case o
15
- when Array
16
- o.join(".")
17
- when OIDREGEX
18
- o = o[1..-1] if o.start_with?(".")
19
- o
20
- # TODO: MIB to OID
21
- else raise Error, "can't convert #{o} to OID"
22
- end
13
+ def build(id)
14
+ oid = MIB.oid(id)
15
+ oid = oid[1..-1] if oid.start_with?(".")
16
+ oid
23
17
  end
24
18
 
25
19
  def to_asn(oid)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NETSNMP
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -0,0 +1,18 @@
1
+ module NETSNMP
2
+ module MIB
3
+ MIBDIRS: Array[String]
4
+ PARSER: Parser
5
+
6
+ @parser_mutex: Mutex
7
+ @modules_loaded: Array[String]
8
+ @object_identifiers: Hash[String, String]
9
+
10
+ def self?.oid: (String identifier) -> String?
11
+ | (Array[_ToS] identifier) -> String?
12
+
13
+ def self?.load: (String mod) -> void
14
+
15
+ def self?.load_imports: (?Hash[Symbol, untyped] data) -> Hash[String, Array[String]]?
16
+ def self?.load_defaults: () -> void
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module NETSNMP
2
+ module MIB
3
+ class Parser
4
+
5
+ end
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  module NETSNMP
2
- type oid = String | Array[Integer]
2
+ type oid = String | Array[_ToS]
3
3
 
4
4
  type oid_type = Integer | :ipaddress | :counter32 | :gauge | :timetick | :opaque | :nsap | :counter64 | :uinteger
5
5
 
@@ -22,7 +22,7 @@ RSpec.describe NETSNMP::Client do
22
22
  let(:get_oid) { "1.3.6.1.2.1.1.5.0" }
23
23
  let(:next_oid) { "1.3.6.1.2.1.1.6.0" }
24
24
  let(:walk_oid) { "1.3.6.1.2.1.1" }
25
- let(:set_oid) { "1.3.6.1.2.1.1.3.0" } # sysUpTimeInstance
25
+ let(:set_oid) { "sysUpTime.0" } # sysUpTimeInstance
26
26
  let(:get_result) { "DEVICE-192.168.1.1" }
27
27
  let(:next_result) { "The Cloud" }
28
28
  let(:walk_result) do
@@ -48,10 +48,10 @@ RSpec.describe NETSNMP::Client do
48
48
  community: "public"
49
49
  }
50
50
  end
51
- let(:get_oid) { "1.3.6.1.2.1.1.5.0" }
51
+ let(:get_oid) { "sysName.0" }
52
52
  let(:next_oid) { "1.3.6.1.2.1.1.6.0" }
53
- let(:walk_oid) { "1.3.6.1.2.1.1" }
54
- let(:set_oid) { "1.3.6.1.2.1.1.3.0" }
53
+ let(:walk_oid) { "system" }
54
+ let(:set_oid) { "sysUpTime.0" }
55
55
  let(:get_result) { "DEVICE-192.168.1.1" }
56
56
  let(:next_result) { "The Cloud" }
57
57
  let(:walk_result) do
@@ -78,9 +78,9 @@ RSpec.describe NETSNMP::Client do
78
78
  context: "a172334d7d97871b72241397f713fa12"
79
79
  }
80
80
  end
81
- let(:get_oid) { "1.3.6.1.2.1.1.5.0" }
81
+ let(:get_oid) { "sysName.0" }
82
82
  let(:next_oid) { "1.3.6.1.2.1.1.6.0" }
83
- let(:set_oid) { "1.3.6.1.2.1.1.3.0" } # sysUpTimeInstance
83
+ let(:set_oid) { "sysUpTime.0" } # sysUpTimeInstance
84
84
  let(:walk_oid) { "1.3.6.1.2.1.1.9.1.3" }
85
85
  let(:get_result) { "tt" }
86
86
  let(:next_result) { "KK12" }
@@ -4,7 +4,7 @@ require "celluloid/io"
4
4
  require_relative "../support/request_examples"
5
5
  require_relative "../support/celluloid"
6
6
 
7
- RSpec.describe "with cellulloid", type: :celluloid do
7
+ RSpec.describe "with cellulloid", type: :celluloid, if: RUBY_ENGINE == "truffleruby" do
8
8
  include CelluloidHelpers
9
9
  let(:user_options) do
10
10
  { username: "authprivmd5des", auth_password: "maplesyrup",
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe NETSNMP::MIB do
4
+ describe ".oid" do
5
+ it { expect(described_class.oid("1.2.3.4")).to eq("1.2.3.4") }
6
+ it { expect(described_class.oid("ifTable")).to eq("1.3.6.1.2.1.2.2") }
7
+ it { expect(described_class.oid("sysDescr.0")).to eq("1.3.6.1.2.1.1.1.0") }
8
+ it { expect(described_class.oid("ifTable.1.23")).to eq("1.3.6.1.2.1.2.2.1.23") }
9
+ it { expect(described_class.oid("IF-MIB::ifTable.1.23")).to eq("1.3.6.1.2.1.2.2.1.23") }
10
+ it { expect(described_class.oid("IFMIB::ifTable.1.23")).to be_nil }
11
+ it { expect(described_class.oid("IF-MIB::")).to be_nil }
12
+ end
13
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ GC.auto_compact = true if GC.respond_to?(:auto_compact=)
4
+
3
5
  if ENV.key?("CI")
4
6
  require "simplecov"
5
7
  SimpleCov.command_name "#{RUBY_ENGINE}-#{RUBY_VERSION}"
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: netsnmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-18 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-01-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: |2
14
28
  Wraps the net-snmp core usage into idiomatic ruby.
15
29
  It is designed to support as many environments and concurrency frameworks as possible.
@@ -28,6 +42,8 @@ files:
28
42
  - lib/netsnmp/encryption/none.rb
29
43
  - lib/netsnmp/errors.rb
30
44
  - lib/netsnmp/message.rb
45
+ - lib/netsnmp/mib.rb
46
+ - lib/netsnmp/mib/parser.rb
31
47
  - lib/netsnmp/oid.rb
32
48
  - lib/netsnmp/pdu.rb
33
49
  - lib/netsnmp/scoped_pdu.rb
@@ -39,6 +55,8 @@ files:
39
55
  - lib/netsnmp/version.rb
40
56
  - sig/client.rbs
41
57
  - sig/message.rbs
58
+ - sig/mib.rbs
59
+ - sig/mib/parser.rbs
42
60
  - sig/netsnmp.rbs
43
61
  - sig/oid.rbs
44
62
  - sig/openssl.rbs
@@ -51,6 +69,7 @@ files:
51
69
  - sig/varbind.rbs
52
70
  - spec/client_spec.rb
53
71
  - spec/handlers/celluloid_spec.rb
72
+ - spec/mib_spec.rb
54
73
  - spec/oid_spec.rb
55
74
  - spec/pdu_spec.rb
56
75
  - spec/security_parameters_spec.rb
@@ -91,6 +110,7 @@ test_files:
91
110
  - spec/spec_helper.rb
92
111
  - spec/session_spec.rb
93
112
  - spec/client_spec.rb
113
+ - spec/mib_spec.rb
94
114
  - spec/oid_spec.rb
95
115
  - spec/varbind_spec.rb
96
116
  - spec/support/request_examples.rb