netsnmp 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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