netsnmp 0.1.8 → 0.4.1

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