netsnmp 0.1.8 → 0.4.1

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -27
  3. data/lib/netsnmp.rb +3 -21
  4. data/lib/netsnmp/client.rb +4 -5
  5. data/lib/netsnmp/encryption/aes.rb +1 -3
  6. data/lib/netsnmp/encryption/des.rb +0 -2
  7. data/lib/netsnmp/errors.rb +1 -0
  8. data/lib/netsnmp/extensions.rb +113 -0
  9. data/lib/netsnmp/loggable.rb +36 -0
  10. data/lib/netsnmp/message.rb +70 -28
  11. data/lib/netsnmp/mib.rb +172 -0
  12. data/lib/netsnmp/mib/parser.rb +750 -0
  13. data/lib/netsnmp/oid.rb +7 -12
  14. data/lib/netsnmp/pdu.rb +23 -12
  15. data/lib/netsnmp/scoped_pdu.rb +8 -2
  16. data/lib/netsnmp/security_parameters.rb +22 -14
  17. data/lib/netsnmp/session.rb +14 -16
  18. data/lib/netsnmp/v3_session.rb +21 -9
  19. data/lib/netsnmp/varbind.rb +27 -22
  20. data/lib/netsnmp/version.rb +1 -1
  21. data/sig/client.rbs +24 -0
  22. data/sig/loggable.rbs +16 -0
  23. data/sig/message.rbs +9 -0
  24. data/sig/mib.rbs +21 -0
  25. data/sig/mib/parser.rbs +7 -0
  26. data/sig/netsnmp.rbs +19 -0
  27. data/sig/oid.rbs +18 -0
  28. data/sig/openssl.rbs +20 -0
  29. data/sig/pdu.rbs +48 -0
  30. data/sig/scoped_pdu.rbs +15 -0
  31. data/sig/security_parameters.rbs +58 -0
  32. data/sig/session.rbs +38 -0
  33. data/sig/timeticks.rbs +7 -0
  34. data/sig/v3_session.rbs +21 -0
  35. data/sig/varbind.rbs +30 -0
  36. data/spec/client_spec.rb +26 -8
  37. data/spec/handlers/celluloid_spec.rb +4 -3
  38. data/spec/mib_spec.rb +13 -0
  39. data/spec/session_spec.rb +2 -2
  40. data/spec/spec_helper.rb +9 -5
  41. data/spec/support/request_examples.rb +2 -2
  42. data/spec/v3_session_spec.rb +4 -4
  43. data/spec/varbind_spec.rb +5 -3
  44. metadata +31 -71
  45. data/.coveralls.yml +0 -1
  46. data/.gitignore +0 -14
  47. data/.rspec +0 -2
  48. data/.rubocop.yml +0 -11
  49. data/.rubocop_todo.yml +0 -69
  50. data/.travis.yml +0 -28
  51. data/Gemfile +0 -22
  52. data/Rakefile +0 -30
  53. data/netsnmp.gemspec +0 -31
  54. data/spec/support/Dockerfile +0 -14
  55. data/spec/support/specs.sh +0 -51
  56. data/spec/support/stop_docker.sh +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63503f340ea989e7ea9f123bbb49901852322145256fe06ce44c0244dfeb9300
4
- data.tar.gz: f3981e9e148338f926428324836436c40ed38e4c84143a10ad0541052d302506
3
+ metadata.gz: c87bf65264474758aed741b992dadbd6bbb277aae52b925254da2e5fea56c5b3
4
+ data.tar.gz: cf7134f2e8b451c619aabdf9dcd6b931404ab51afd7953c785e4013a348cce02
5
5
  SHA512:
6
- metadata.gz: c0b078e30151385ab01d3825431debe83531cf70089536538a372574b3ee40deef8b53c99673a774553b3afed903b3773af2db57a782dffec7a403373cfeb06e
7
- data.tar.gz: ed3b9b066c6330e1bc4f9367e1c05bcd14f93c9874a492295cac1df29f1e48079aa76225fe16fb35bc7b975a0c66a5208fea0395715603e932dd8d80b5b6cd0c
6
+ metadata.gz: 1cade51302613f51f8da018351401b27c2d0fe08a43b915a79252ff420d5efdfe4a405ce29a60f10ade2d0f0f4a03dc2302117653043c451dd01683e8eb2c7a5
7
+ data.tar.gz: 0ce54aa2fb2cba42f47ccab2fd83e2536e8fd9e73d29d2f7a85aef4ab90d9e0c956f64f0f8fbe19f95c36a048ab2911492f55c4ca95ada0843941a896564b2cf
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # netsnmp
2
2
 
3
- [![Build Status](https://travis-ci.org/swisscom/ruby-netsnmp.svg?branch=master)](https://travis-ci.org/swisscom/ruby-netsnmp)
4
- [![Coverage Status](https://coveralls.io/repos/github/swisscom/ruby-netsnmp/badge.svg?branch=master)](https://coveralls.io/github/swisscom/ruby-netsnmp?branch=master)
3
+ ![Tests](https://github.com/swisscom/ruby-netsnmp/workflows/Tests/badge.svg)
5
4
  [![Code Climate](https://codeclimate.com/github/swisscom/ruby-netsnmp/badges/gpa.svg)](https://codeclimate.com/github/swisscom/ruby-netsnmp)
6
5
  [![Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/swisscom/ruby-netsnmp/master)
7
6
 
@@ -33,7 +32,7 @@ This gem provides:
33
32
 
34
33
  * Implementation in ruby of the SNMP Protocol for v3, v2c and v1 (most notable the rfc3414 and 3826).
35
34
  * Client/Manager API with simple interface for get, genext, set and walk.
36
- * No dependencies.
35
+ * Pure Ruby.
37
36
  * Support for concurrency and evented I/O.
38
37
 
39
38
  ## Why?
@@ -55,10 +54,12 @@ All of these issues are resolved here.
55
54
  ## Features
56
55
 
57
56
  * Client Interface, which supports SNMP v3, v2c, and v1
58
- * Supports get, getnext, set and walk calls.
57
+ * Supports get, getnext, set and walk calls
58
+ * MIB support
59
59
  * Proxy IO object support (for eventmachine/celluloid-io)
60
60
  * Ruby >= 2.1 support (modern)
61
61
  * Pure Ruby (no FFI)
62
+ * Easy PDU debugging
62
63
 
63
64
  ## Examples
64
65
 
@@ -74,12 +75,11 @@ manager = NETSNMP::Client.new(host: "localhost", port: 33445, username: "simulat
74
75
  context: "a172334d7d97871b72241397f713fa12")
75
76
 
76
77
  # SNMP get
77
- # sysName.0
78
- manager.get(oid: "1.3.6.1.2.1.1.0") #=> 'tt'
78
+ manager.get(oid: "sysName.0") #=> 'tt'
79
79
 
80
80
  # SNMP walk
81
81
  # sysORDescr
82
- manager.walk(oid: "1.3.6.1.2.1.1.1").each do |oid_code, value|
82
+ manager.walk(oid: "sysORDescr").each do |oid_code, value|
83
83
  # do something with them
84
84
  puts "for #{oid_code}: #{value}"
85
85
  end
@@ -136,6 +136,29 @@ manager.set("somecounteroid", value: 999999, type: 6)
136
136
  ```
137
137
  * Fork this library, extend support, write a test and submit a PR (the desired solution ;) )
138
138
 
139
+ ## MIB
140
+
141
+ `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.
142
+
143
+ Sometimes you'll need to load more, your own MIBs, in which case, you can use the following API:
144
+
145
+ ```ruby
146
+ require "netsnmp"
147
+
148
+ NETSNMP::MIB.load("MY-MIB")
149
+ # or, if it's not in any of the known locations
150
+ NETSNMP::MIB.load("/path/to/MY-MIB.txt")
151
+ ```
152
+
153
+ You can install common SNMP mibs by using your package manager:
154
+
155
+ ```
156
+ # using apt-get
157
+ > apt-get install snmp-mibs-downloader
158
+ # using apk
159
+ > apk --update add net-snmp-libs
160
+ ```
161
+
139
162
  ## Concurrency
140
163
 
141
164
  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:
@@ -214,15 +237,32 @@ NETSNMP::Client.new(share_options.merge(proxy: router_proxy, security_parameters
214
237
  end
215
238
  ```
216
239
 
240
+ ## Compatibility
241
+
242
+ This library supports and is tested against ruby versions 2.1 or more recent, including ruby 3. It also supports and tests against Truffleruby.
243
+
217
244
  ## OpenSSL
218
245
 
219
246
  All encoding/decoding/encryption/decryption/digests are done using `openssl`, which is (still) a part of the standard library. If at some point `openssl` is removed and not specifically distributed, you'll have to install it yourself. Hopefully this will never happen.
220
247
 
248
+ It also uses the `openssl` ASN.1 API to encode/decode BERs, which is known to be strict, and [may not be able to decode PDUs if not compliant with the supported RFC](https://github.com/swisscom/ruby-netsnmp/issues/47).
249
+
250
+ ## Debugging
251
+
252
+ You can either set the `NETSNMP_DEBUG` to the desided debug level (currently, 1 and 2). The logs will be written to stderr.
253
+
254
+ You can also set it for a specific client:
255
+
256
+ ```ruby
257
+ manager2 = NETSNMP::Client.new(debug: $stderr, debug_level: 2, ....)
258
+ ```
259
+
221
260
 
222
261
  ## Tests
223
262
 
224
263
  This library uses RSpec. The client specs are "integration" tests, in that we communicate with an [snmpsim-built snmp agent simulator](https://github.com/etingof/snmpsim).
225
264
 
265
+
226
266
  ### RSpec
227
267
 
228
268
  You can run all tests by typing:
@@ -234,37 +274,31 @@ You can run all tests by typing:
234
274
  ...
235
275
  ```
236
276
 
237
- ### SNMP Simulator
238
-
239
- You can install the package yourself (ex: `pip install snmpsim`) and run the server locally, and then set the `SNMP_PORT` environment variable, where the snmp simulator is running.
240
-
241
- #### Docker
242
277
 
243
- The preferred way to use the snmp simulator is by using docker.
278
+ ### Docker
244
279
 
245
- In order to start the simulator container, run:
280
+ The most straightforward way of running the tests is by using the `docker-compose` setup (which is also what's used in the CI). Run it against the ruby version you're targeting:
246
281
 
247
282
  ```
248
- > spec/support/spec.sh start
283
+ > docker-compose -f docker-compose.yml -f docker-compose-ruby-${RUBY_MAJOR_VERSION}.${RUBY_MAJOR_VERSION}.yml run netsnmp
249
284
  ```
250
285
 
251
- after, you just need to set the `SNMP_PORT` variable to the port found typing:
286
+ The CI runs the tests against all supported ruby versions. If changes break a specific version of ruby, make sure you commit appropriate changes addressing the edge case, or let me know in the issues board, so I can help.
252
287
 
253
- ```
254
- > docker port test-snmp 1161/udp
255
- ```
288
+ ### SNMP Simulator
256
289
 
257
- and run the tests.
290
+ The SNMP simulator runs in its own container in the `docker` setup.
258
291
 
259
- #### CI
292
+ You can install the package yourself (ex: `pip install snmpsim`) and run the server locally, and then set the `SNMP_PORT` environment variable, where the snmp simulator is running.
260
293
 
261
- If you want to replicate what the CI is doing, just do:
294
+ #### CI
262
295
 
263
- ```
264
- > spec/support/spec.sh run
265
- ```
296
+ The job of the CI is:
266
297
 
267
- Which should: build and run the simulator container, run the tests, run rubocop, and remove all artifacts.
298
+ * Run all the tests;
299
+ * Make sure the tests cover an appropriate surface of the code;
300
+ * Lint the code;
301
+ * (for ruby 3.0) type check the code;
268
302
 
269
303
 
270
304
  ## Contributing
@@ -278,7 +312,6 @@ Which should: build and run the simulator container, run the tests, run rubocop,
278
312
 
279
313
  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:
280
314
 
281
- * No MIB support (you can only work with OIDs)
282
315
  * No server (Agent, in SNMP-ish) implementation.
283
316
  * No getbulk support.
284
317
 
data/lib/netsnmp.rb CHANGED
@@ -33,34 +33,16 @@ rescue LoadError
33
33
  end
34
34
  end
35
35
 
36
- module NETSNMP
37
- def self.debug=(io)
38
- @debug_output = io
39
- end
40
-
41
- def self.debug(&blk)
42
- @debug_output << blk.call + "\n" if @debug_output
43
- end
44
-
45
- unless defined?(Hexdump) # support the hexdump gem
46
- module Hexdump
47
- def self.dump(data, width: 8)
48
- pairs = data.unpack("H*").first.scan(/.{4}/)
49
- pairs.each_slice(width).map do |row|
50
- row.join(" ")
51
- end.join("\n")
52
- end
53
- end
54
- end
55
- end
56
-
57
36
  require "netsnmp/errors"
37
+ require "netsnmp/extensions"
38
+ require "netsnmp/loggable"
58
39
 
59
40
  require "netsnmp/timeticks"
60
41
 
61
42
  require "netsnmp/oid"
62
43
  require "netsnmp/varbind"
63
44
  require "netsnmp/pdu"
45
+ require "netsnmp/mib"
64
46
  require "netsnmp/session"
65
47
 
66
48
  require "netsnmp/scoped_pdu"
@@ -23,8 +23,7 @@ module NETSNMP
23
23
  # puts client.get(oid: "1.3.6.1.2.1.1.5.0")
24
24
  # end
25
25
  #
26
- def initialize(**options)
27
- version = options[:version]
26
+ def initialize(version: nil, **options)
28
27
  version = case version
29
28
  when Integer then version # assume the use know what he's doing
30
29
  when /v?1/ then 0
@@ -33,7 +32,7 @@ module NETSNMP
33
32
  end
34
33
 
35
34
  @retries = options.fetch(:retries, RETRIES)
36
- @session ||= version == 3 ? V3Session.new(options) : Session.new(options)
35
+ @session ||= version == 3 ? V3Session.new(**options) : Session.new(version: version, **options)
37
36
  return unless block_given?
38
37
  begin
39
38
  yield self
@@ -78,7 +77,7 @@ module NETSNMP
78
77
  # @return [Enumerator] the enumerator-collection of the oid-value pairs
79
78
  #
80
79
  def walk(oid:)
81
- walkoid = oid
80
+ walkoid = OID.build(oid)
82
81
  Enumerator.new do |y|
83
82
  code = walkoid
84
83
  first_response_code = nil
@@ -154,7 +153,7 @@ module NETSNMP
154
153
  retries = @retries
155
154
  begin
156
155
  yield
157
- rescue Timeout::Error => e
156
+ rescue Timeout::Error, IdNotInTimeWindowError => e
158
157
  raise e if retries.zero?
159
158
  retries -= 1
160
159
  retry
@@ -22,13 +22,12 @@ module NETSNMP
22
22
  end
23
23
 
24
24
  encrypted_data = cipher.update(decrypted_data) + cipher.final
25
- NETSNMP.debug { "encrypted:\n#{Hexdump.dump(encrypted_data)}" }
26
25
 
27
26
  [encrypted_data, salt]
28
27
  end
29
28
 
30
29
  def decrypt(encrypted_data, salt:, engine_boots:, engine_time:)
31
- raise Error, "invalid priv salt received" unless (salt.length % 8).zero?
30
+ raise Error, "invalid priv salt received" unless !salt.empty? && (salt.length % 8).zero?
32
31
 
33
32
  cipher = OpenSSL::Cipher::AES128.new(:CFB)
34
33
  cipher.padding = 0
@@ -39,7 +38,6 @@ module NETSNMP
39
38
  cipher.key = aes_key
40
39
  cipher.iv = iv
41
40
  decrypted_data = cipher.update(encrypted_data) + cipher.final
42
- NETSNMP.debug { "decrypted:\n#{Hexdump.dump(decrypted_data)}" }
43
41
 
44
42
  hlen, bodylen = OpenSSL::ASN1.traverse(decrypted_data) { |_, _, x, y, *| break x, y }
45
43
  decrypted_data.byteslice(0, hlen + bodylen)
@@ -24,7 +24,6 @@ module NETSNMP
24
24
  end
25
25
 
26
26
  encrypted_data = cipher.update(decrypted_data) + cipher.final
27
- NETSNMP.debug { "encrypted:\n#{Hexdump.dump(encrypted_data)}" }
28
27
  [encrypted_data, salt]
29
28
  end
30
29
 
@@ -41,7 +40,6 @@ module NETSNMP
41
40
  cipher.key = des_key
42
41
  cipher.iv = iv
43
42
  decrypted_data = cipher.update(encrypted_data) + cipher.final
44
- NETSNMP.debug { "decrypted:\n#{Hexdump.dump(decrypted_data)}" }
45
43
 
46
44
  hlen, bodylen = OpenSSL::ASN1.traverse(decrypted_data) { |_, _, x, y, *| break x, y }
47
45
  decrypted_data.byteslice(0, hlen + bodylen)
@@ -4,4 +4,5 @@ module NETSNMP
4
4
  Error = Class.new(StandardError)
5
5
  ConnectionFailed = Class.new(Error)
6
6
  AuthenticationFailed = Class.new(Error)
7
+ IdNotInTimeWindowError = Class.new(Error)
7
8
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NETSNMP
4
+ module IsNumericExtensions
5
+ refine String do
6
+ def integer?
7
+ each_byte do |byte|
8
+ return false unless byte >= 48 && byte <= 57
9
+ end
10
+ true
11
+ end
12
+ end
13
+ end
14
+
15
+ module StringExtensions
16
+ refine(String) do
17
+ unless String.method_defined?(:match?)
18
+ def match?(*args)
19
+ !match(*args).nil?
20
+ end
21
+ end
22
+
23
+ unless String.method_defined?(:unpack1)
24
+ def unpack1(format)
25
+ unpack(format).first
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module ASNExtensions
32
+ ASN_COLORS = {
33
+ OpenSSL::ASN1::Sequence => 34, # blue
34
+ OpenSSL::ASN1::OctetString => 32, # green
35
+ OpenSSL::ASN1::Integer => 33, # yellow
36
+ OpenSSL::ASN1::ObjectId => 35, # magenta
37
+ OpenSSL::ASN1::ASN1Data => 36 # cyan
38
+ }.freeze
39
+
40
+ # basic types
41
+ ASN_COLORS.each_key do |klass|
42
+ refine(klass) do
43
+ def to_hex
44
+ "#{colorize_hex} (#{value.to_s.inspect})"
45
+ end
46
+ end
47
+ end
48
+
49
+ # composite types
50
+ refine(OpenSSL::ASN1::Sequence) do
51
+ def to_hex
52
+ values = value.map(&:to_der).join
53
+ hex_values = value.map(&:to_hex).map { |s| s.gsub(/(\t+)/) { "\t#{Regexp.last_match(1)}" } }.map { |s| "\n\t#{s}" }.join
54
+ der = to_der
55
+ der = der.sub(values, "")
56
+
57
+ "#{colorize_hex(der)}#{hex_values}"
58
+ end
59
+ end
60
+
61
+ refine(OpenSSL::ASN1::ASN1Data) do
62
+ attr_reader :label
63
+
64
+ def with_label(label)
65
+ @label = label
66
+ self
67
+ end
68
+
69
+ def to_hex
70
+ case value
71
+ when Array
72
+ values = value.map(&:to_der).join
73
+ hex_values = value.map(&:to_hex)
74
+ .map { |s| s.gsub(/(\t+)/) { "\t#{Regexp.last_match(1)}" } }
75
+ .map { |s| "\n\t#{s}" }.join
76
+ der = to_der
77
+ der = der.sub(values, "")
78
+ else
79
+ der = to_der
80
+ hex_values = nil
81
+ end
82
+
83
+ "#{colorize_hex(der)}#{hex_values}"
84
+ end
85
+
86
+ private
87
+
88
+ def colorize_hex(der = to_der)
89
+ hex = Hexdump.dump(der, separator: " ")
90
+ lbl = @label || self.class.name.split("::").last
91
+ "#{lbl}: \e[#{ASN_COLORS[self.class]}m#{hex}\e[0m"
92
+ end
93
+ end
94
+ end
95
+
96
+ module Hexdump
97
+ using StringExtensions
98
+
99
+ def self.dump(data, width: 8, in_groups_of: 4, separator: "\n")
100
+ pairs = data.unpack1("H*").scan(/.{#{in_groups_of}}/)
101
+ pairs.each_slice(width).map do |row|
102
+ row.join(" ")
103
+ end.join(separator)
104
+ end
105
+ end
106
+
107
+ # Like a string, but it prints an hex-string version of itself
108
+ class HexString < String
109
+ def inspect
110
+ Hexdump.dump(self, in_groups_of: 2, separator: " ")
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NETSNMP
4
+ module Loggable
5
+ DEBUG = ENV.key?("NETSNMP_DEBUG") ? $stderr : nil
6
+ DEBUG_LEVEL = (ENV["NETSNMP_DEBUG"] || 1).to_i
7
+
8
+ def initialize(debug: DEBUG, debug_level: DEBUG_LEVEL, **opts)
9
+ super(**opts)
10
+ @debug = debug
11
+ @debug_level = debug_level
12
+ end
13
+
14
+ private
15
+
16
+ COLORS = {
17
+ black: 30,
18
+ red: 31,
19
+ green: 32,
20
+ yellow: 33,
21
+ blue: 34,
22
+ magenta: 35,
23
+ cyan: 36,
24
+ white: 37
25
+ }.freeze
26
+
27
+ def log(level: @debug_level)
28
+ return unless @debug
29
+ return unless @debug_level >= level
30
+
31
+ debug_stream = @debug
32
+
33
+ debug_stream << (+"\n" << yield << "\n")
34
+ end
35
+ end
36
+ end