netsnmp 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.travis.yml +4 -4
  4. data/Gemfile +5 -1
  5. data/README.md +124 -63
  6. data/lib/netsnmp.rb +66 -10
  7. data/lib/netsnmp/client.rb +93 -75
  8. data/lib/netsnmp/encryption/aes.rb +84 -0
  9. data/lib/netsnmp/encryption/des.rb +80 -0
  10. data/lib/netsnmp/encryption/none.rb +17 -0
  11. data/lib/netsnmp/errors.rb +1 -3
  12. data/lib/netsnmp/message.rb +81 -0
  13. data/lib/netsnmp/oid.rb +18 -137
  14. data/lib/netsnmp/pdu.rb +106 -64
  15. data/lib/netsnmp/scoped_pdu.rb +23 -0
  16. data/lib/netsnmp/security_parameters.rb +198 -0
  17. data/lib/netsnmp/session.rb +84 -275
  18. data/lib/netsnmp/v3_session.rb +81 -0
  19. data/lib/netsnmp/varbind.rb +65 -156
  20. data/lib/netsnmp/version.rb +2 -1
  21. data/netsnmp.gemspec +2 -8
  22. data/spec/client_spec.rb +147 -99
  23. data/spec/handlers/celluloid_spec.rb +33 -20
  24. data/spec/oid_spec.rb +11 -5
  25. data/spec/pdu_spec.rb +22 -22
  26. data/spec/security_parameters_spec.rb +40 -0
  27. data/spec/session_spec.rb +0 -23
  28. data/spec/support/celluloid.rb +24 -0
  29. data/spec/support/request_examples.rb +36 -0
  30. data/spec/support/start_docker.sh +15 -1
  31. data/spec/v3_session_spec.rb +21 -0
  32. data/spec/varbind_spec.rb +2 -51
  33. metadata +30 -76
  34. data/lib/netsnmp/core.rb +0 -12
  35. data/lib/netsnmp/core/client.rb +0 -15
  36. data/lib/netsnmp/core/constants.rb +0 -153
  37. data/lib/netsnmp/core/inline.rb +0 -20
  38. data/lib/netsnmp/core/libc.rb +0 -48
  39. data/lib/netsnmp/core/libsnmp.rb +0 -44
  40. data/lib/netsnmp/core/structures.rb +0 -167
  41. data/lib/netsnmp/core/utilities.rb +0 -13
  42. data/lib/netsnmp/handlers/celluloid.rb +0 -27
  43. data/lib/netsnmp/handlers/em.rb +0 -56
  44. data/spec/core/libc_spec.rb +0 -2
  45. data/spec/core/libsnmp_spec.rb +0 -32
  46. data/spec/core/structures_spec.rb +0 -54
  47. data/spec/handlers/em_client_spec.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a043cc79c90239f031b1873089c8a23f50dd5b8
4
- data.tar.gz: 05b0baf81082f36cc1a870c3f4f5a87b40041a60
3
+ metadata.gz: 35229e0d1dd73c4a43a778b6d0e724bbca5ecc12
4
+ data.tar.gz: f16c14549b5df1408842b8da935b61180751c997
5
5
  SHA512:
6
- metadata.gz: 63c8a7195237c722b4fb7d0db9f00789ca475c7d080cc8d539f6e1f26adca2482c71d1064925fdd788f95f1f8cac9e89e33c06edc878c22b26be964948af0207
7
- data.tar.gz: d30bb03a3a0fc19c34f9afd07512260ea0f4f4bc7f407b5b7c980e9bbb39e166730b34ec5be425b3435270b98e82e002d15b6992ae3834eeb1369bfae805bf19
6
+ metadata.gz: 45139e881aa85c3350408c9ed59ded3c1ef1c2a418b29fab7953af0a50e056ef235d63280a4958ad537f4b5545e2b5ab0d79c2521b04e0f41ab6a9653f35c47e
7
+ data.tar.gz: 7621a13aece7a4abcd5b7ec11652a8a39d25ab07ef6206263e8ad378e13aa1d4c397c58fce27e366ab32a845b11e7f7fd043ac2f9a594cf47258fcfc29c94262
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.travis.yml CHANGED
@@ -25,12 +25,12 @@ script:
25
25
 
26
26
  language: ruby
27
27
  rvm:
28
- - 2.0
29
28
  - 2.1
30
29
  - 2.2
31
- - 2.3.1
30
+ - 2.3
31
+ - 2.4
32
32
  - ruby-head
33
- - jruby-9.1.1.0
33
+ - jruby-9.1.6.0
34
34
  - jruby-head
35
35
  - rbx-2
36
36
  matrix:
@@ -38,5 +38,5 @@ matrix:
38
38
  - rvm: ruby-head
39
39
  - rvm: jruby-head
40
40
  # to figure out later
41
- - rvm: jruby-9.1.1.0
41
+ - rvm: jruby-9.1.6.0
42
42
  - rvm: rbx-2
data/Gemfile CHANGED
@@ -6,7 +6,11 @@ gem 'coveralls', require: false
6
6
 
7
7
  group :development do
8
8
  gem 'pry'
9
- gem 'pry-byebug'
10
9
  end
11
10
 
11
+ platforms :mri do
12
+ gem "pry-byebug", require: false
13
+ gem "stackprof", require: false
14
+ end
12
15
 
16
+ gem "xorcist"
data/README.md CHANGED
@@ -5,9 +5,7 @@
5
5
  [![Code Climate](https://codeclimate.com/github/swisscom/ruby-netsnmp/badges/gpa.svg)](https://codeclimate.com/github/swisscom/ruby-netsnmp)
6
6
  [![Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/swisscom/ruby-netsnmp/master)
7
7
 
8
- The netsnmp gem provides a ruby DSL to handle SNMP queries. It currently uses the net-snmp C library using the FFI interface.
9
-
10
- This gem started as a cleanup from [net-snmp](https://github.com/mixtli/net-snmp) and its follow-up [net-snmp2](https://github.com/jbreeden/net-snmp2), both of which have been mostly inactive for the last year(s).
8
+ The `netsnmp` gem provides a ruby native implementation of the SNMP protocol (v1/2c abd v3).
11
9
 
12
10
  ## Installation
13
11
 
@@ -29,33 +27,38 @@ Or install it yourself as:
29
27
  $ gem install netsnmp
30
28
  ```
31
29
 
32
- ## Why?
30
+ ## Features
33
31
 
34
- You may ask, why not just use the aforementioned? I'll try to sum up the reasons.
32
+ This gem provides:
35
33
 
36
- * Lack of support for some specific net-snmp versions.
37
- * Memory Leaks (both leave the responsibility of cleaning pdus to the user, and this usually creates memory leak when the responses fail).
38
- * Lack of support for some ruby > 2.1 GC directives, which makes the VM crash under certain circumstances.
39
- * Iffy EventMachine support (they basically send the request pdu, and schedule the reads to the reactor, and not really reacting on the socket handle).
40
- * Lack of support for NIO4r/Celluloid-IO (the other event loop, besides EM, that counts in the ruby world).
41
- * The default sync request calls block the whole VM, making multi-threading a non-factor, forcing you to resort to multiprocess for concurrency.
34
+ * Implementation in ruby of the SNMP Protocol for v3, v2c and v1 (most notable the rfc3414 and 3826).
35
+ * Client/Manager API with simple interface for get, genext, set and walk.
36
+ * No dependencies.
37
+ * Support for concurrency and evented I/O.
42
38
 
39
+ ## Why?
43
40
 
44
- ## Philosophy
41
+ If you look for snmp gems in ruby toolbox, you'll find a bunch.
42
+ You may ask, why not just use one of them?
45
43
 
46
- The main motto of the gem is: API economy. As less moving parts as possible. New features can be discussed and integrated, but they all must abide to this philosophy.
44
+ Most of them only implement v1 and v2, so if your requirement is to use v3, you're left with only 2 choices: [net-snmp](https://github.com/mixtli/net-snmp) (unmantained since 2013) and its follow-up [net-snmp2](https://github.com/jbreeden/net-snmp2), which started as a fork to fix some bugs left unattended. Both libraries wrap the C netsnmp library using FFI, which leaves them vulnerable to the following bugs (experienced in both libraries):
47
45
 
48
- The main purpose of this gem is: SNMP v3 support. Why? Because it's the only one with authentication and security features integrated. SNMP has been a protocol which historically ignored security for many years, and even when it embraced it, its choices are by today's standards considered half-baked (MD5, SHA-1, shrug). Still, some-security is better than no-security. Also, support for v1 and v2 is something that you can get from other ruby gems (most of them refuse to support v3 altogether).
46
+ * Dependency of specific versions of netsnmp C package.
47
+ * Memory Leaks.
48
+ * Doesn't work reliable in ruby > 2.0.0-p576, crashing the VM.
49
+ * Network I/O done by the library, thereby blocking the GVL, thereby making all snmp calls block the whole ruby VM.
50
+ * This means, multi-threading is impossible.
51
+ * This means, evented I/O is impossible.
49
52
 
50
- Do one thing and do it well. There's only an interface to interact with an SNMP Agent. Other features like credentials encryption or concurrency are out of the scope of this library, which only guarantees that an SNMP Client is thread-safe and performs ruby-VM-compatible IO.
53
+ All of these issues are resolved here.
51
54
 
52
55
  ## Features
53
56
 
54
- * Client Interface, which supports SNMP 3, 2c, and 1
55
- * Supports get, set, walk and bulk calls.
56
- * Wrappers for eventmachine and celluloid-io
57
- * Ruby >= 2.0 support
58
- * net-snmp (C library) >= 5.5 support
57
+ * Client Interface, which supports SNMP v3, v2c, and v1
58
+ * Supports get, getnext, set and walk calls.
59
+ * Proxy IO object support (for eventmachine/celluloid-io)
60
+ * Ruby >= 2.1 support
61
+ * Pure Ruby (no FFI)
59
62
 
60
63
  ## Examples
61
64
 
@@ -64,13 +67,14 @@ You can use the docker container provided under spec/support to test against the
64
67
  ```ruby
65
68
  require 'netsnmp'
66
69
 
67
- manager = NETSNMP::Client.new("localhost", port: 33445, username: "simulator",
68
- auth_password: "auctoritas", auth_protocol: :md5,
69
- priv_password: "privatus", priv_protocol: :des,
70
- context: "a172334d7d97871b72241397f713fa12")
70
+ # example you can test against the docker simulator provided. port attribute might be different.
71
+ manager = NETSNMP::Client.new(host: "localhost", port: 33445, username: "simulator",
72
+ auth_password: "auctoritas", auth_protocol: :md5,
73
+ priv_password: "privatus", priv_protocol: :des,
74
+ context: "a172334d7d97871b72241397f713fa12")
71
75
 
72
76
  # SNMP get
73
- manager.get("sysName.0") #=> 'tt'
77
+ manager.get(oid: "sysName.0") #=> 'tt'
74
78
 
75
79
  # SNMP walk
76
80
  manager.walk("sysORDescr") do |oid_code, value|
@@ -78,19 +82,13 @@ manager.walk("sysORDescr") do |oid_code, value|
78
82
  puts "for #{oid_code}: #{value}"
79
83
  end
80
84
 
81
- # SNMP get_bulk
82
- manager.get_bulk("sysOrDescr") do |oid_code, value|
83
- # do something with them
84
- puts "for #{oid_code}: #{value}"
85
- end
86
-
87
85
  manager.close
88
86
 
89
87
  # SNMP set
90
- manager2 = NETSNMP::Client.new("localhost", port: 33445, username: "simulator",
91
- auth_password: "auctoritas", auth_protocol: :md5,
92
- priv_password: "privatus", priv_protocol: :des,
93
- context: "0886e1397d572377c17c15036a1e6c66")
88
+ manager2 = NETSNMP::Client.new(host: "localhost", port: 33445, username: "simulator",
89
+ auth_password: "auctoritas", auth_protocol: :md5,
90
+ priv_password: "privatus", priv_protocol: :des,
91
+ context: "0886e1397d572377c17c15036a1e6c66")
94
92
 
95
93
  # setting to 43, becos yes
96
94
  manager2.set("sysUpTimeInstance", 43)
@@ -98,54 +96,113 @@ manager2.set("sysUpTimeInstance", 43)
98
96
  manager2.close
99
97
  ```
100
98
 
101
- ## Tests
99
+ SNMP v2/v1 examples will be similar (beware of the differences in the initialization attributes).
102
100
 
103
- This library uses RSpec. The client specs are "integration" tests, in that we communicate with an snmp agent simulator.
104
101
 
105
- To start the simulator locally, you'll need docker 1.9 or higher (Why 1.9? ```--build-arg``` parameter support was needed for our builds in the CI. You could use a lower version by providing the proxy environment variables in the Dockerfile directly, provided you don't merge these changes to master, thereby exposing your proxy).
102
+ ## Concurrency
106
103
 
107
- ```
108
- > spec/support/start_docker.sh
109
- ```
104
+ 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:
110
105
 
111
- this builds and starts the docker image in deamonized mode. You can afterwards run your specs:
106
+ ```ruby
107
+ general_options = { auth_protocol: ....
108
+ routers.map do |r|
109
+ Thread.start do
110
+ NETSNMP::Client.new(general_options.merge(host: r)) do |cl|
111
+ cli.get(oid: "1.6.3.......
112
112
 
113
- ```
114
- > bundle exec rspec
113
+ end
114
+ end
115
+ end.each(&:join)
115
116
  ```
116
117
 
117
- To stop the image, you can just:
118
+ Evented IO is also supported, in that you can pass a `:proxy` object as an already opened channel of communication to the client. Very important: you have to take care of the lifecycle, as the client will not connect and will not close the object, it will assume no control over it.
119
+
120
+ When passing a proxy object, you can omit the `:host` parameter.
121
+
122
+ The proxy object will have to be a duck-type implementing `#send`, which is a method receiving the sending PDU payload, and return the payload of the receiving PDU.
123
+
124
+ Here is a small pseudo-code example:
125
+
126
+ ```ruby
127
+ # beware, we are inside a warp-speed loop!!!
128
+ general_options = { auth_protocol: ....
129
+ proxy = SpecialUDPImplementation.new(host: router)
130
+ NETSNMP::Client.new(general_options.merge(proxy: proxy)) do |cl|
131
+ # this get call will eventually #send to the proxy...
132
+ cli.get(oid: "1.6.3.......
118
133
 
134
+ end
135
+ # client isn't usable anymore, but now we must close to proxy
136
+ proxy.close
119
137
  ```
120
- > spec/supoprt/stop_docker.sh
138
+
139
+ For more information about this subject, the specs test this feature against celluloid-io. An eventmachine could be added, if someone would be kind enough to provide an implementation.
140
+
141
+ ## Performance
142
+
143
+
144
+ ### XOR
145
+
146
+ This library has some workarounds to some missing features in the ruby language, namely the inexistence of a byte array structure. The closest we have is a byte stream presented as a String with ASCII encoding. A method was added to the String class called `#xor` for some operations needed internally. To prevent needless monkey-patches, Refinements have been employed.
147
+
148
+ If `#xor` becomes at some point the bottleneck of your usage, this gem has also support for [xorcist](https://github.com/fny/xorcist/). You just have to add it to your Gemfile (or install it in the system):
149
+
121
150
  ```
151
+ # Gemfile
152
+
153
+ gem 'netsnmp'
154
+
155
+ # or, in the command line
156
+
157
+ $ gem install netsnmp
158
+ ```
159
+
160
+ and `netsnmp` will automatically pick it up.
122
161
 
123
- ## Notes
162
+ ## Auth/Priv Key
124
163
 
125
- This library provides support for C net-snmp 5.5 or higher, as this has been considered the most stable SNMP v3 implementation.
164
+ If you'll use this gem often with SNMP v3 and auth/priv security level enabled, you'll have that funny feeling that everything could be a bit faster. Well, this is basically because the true performance bottleneck of this gem is the generation of the auth and pass keys used for authorization and encryption. Although this is a one-time thing for each client, its lag will be noticeable if you're running on > 100 hosts.
126
165
 
127
- To install it in your environment, just use your package manager:
166
+ There is a recommended work-around, but this is only usable **if you are using the same user/authpass/privpass on all the hosts!!!**. Use this with care, then:
128
167
 
168
+ ```ruby
169
+ $shared_security_parameters = NETSNMP::SecurityParameters.new(security_level: :authpriv, username: "mustermann",
170
+ auth_protocol: :md5, priv_protocol: :aes, ....
171
+ # this will eager-load the auth/priv_key
172
+ ...
173
+
174
+ # over 9000 routers are running on this event loop!!! this is just one!
175
+ NETSNMP::Client.new(share_options.merge(proxy: router_proxy, security_parameters: $shared_security_parameters.dup).new do |cl|
176
+ cli.get(oid: .....
177
+ end
129
178
  ```
130
- # on OSX
131
- > brew install net-snmp
132
179
 
133
- # Linux
134
- # centOS
135
- > sudo yum install net-snmp
136
- # Ubuntu
137
- > sudo apt-get install net-snmp
138
- > sudo apt-get install libsnmp-dev
180
+ ## OpenSSL
181
+
182
+ 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.
183
+
184
+
185
+ ## Tests
186
+
187
+ This library uses RSpec. The client specs are "integration" tests, in that we communicate with an snmp agent simulator.
188
+
189
+ To start the simulator locally, you'll need docker 1.9 or higher (Why 1.9? ```--build-arg``` parameter support was needed for our builds in the CI. You could use a lower version by providing the proxy environment variables in the Dockerfile directly, provided you don't merge these changes to master, thereby exposing your proxy).
190
+
191
+ ```
192
+ > spec/support/start_docker.sh
139
193
  ```
140
194
 
141
- Ubuntu doesn't come with the default mibs, you might have to download the mibs downloader package:
195
+ this builds and starts the docker image in deamonized mode. You can afterwards run your specs:
142
196
 
143
197
  ```
144
- > sudo apt-get install -y snmp-mibs-downloader
145
- > sudo download-mibs
198
+ > bundle exec rspec
146
199
  ```
147
200
 
148
- TODO: on Windows(?)
201
+ To stop the image, you can just:
202
+
203
+ ```
204
+ > spec/supoprt/stop_docker.sh
205
+ ```
149
206
 
150
207
  ## Contributing
151
208
 
@@ -156,7 +213,11 @@ TODO: on Windows(?)
156
213
 
157
214
  ## TODO
158
215
 
159
- There are a few features still to be added, like:
216
+ 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:
217
+
218
+ * No MIB support (you can only work with OIDs)
219
+ * No server (Agent, in SNMP-ish) implementation.
220
+ * No getbulk support.
160
221
 
161
- * [JRuby Support](https://github.com/celluloid/nio4r/issues/94)
222
+ So if you like the gem, but would rather have these features implemented, please help by sending us a PR and we'll gladly review it.
162
223
 
data/lib/netsnmp.rb CHANGED
@@ -1,16 +1,72 @@
1
- require 'netsnmp/version'
1
+ # frozen_string_literal: true
2
+ require "netsnmp/version"
3
+ require "openssl"
4
+ require "io/wait"
5
+ require "securerandom"
6
+
2
7
 
3
8
  # core structures
4
- require 'netsnmp/core'
9
+
10
+ begin
11
+ require "xorcist"
12
+ require "xorcist/refinements"
13
+ NETSNMP::StringExtensions = Xorcist::Refinements
14
+ rescue LoadError
15
+ # "no xorcist"
16
+ module NETSNMP
17
+ module StringExtensions
18
+ refine String do
19
+ # Bitwise XOR operator for the String class
20
+ def xor( other )
21
+ b1 = self.unpack("C*")
22
+ return b1 if !other
23
+
24
+ b2 = other.unpack("C*")
25
+ longest = [b1.length,b2.length].max
26
+ b1 = [0]*(longest-b1.length) + b1
27
+ b2 = [0]*(longest-b2.length) + b2
28
+ b1.zip(b2).map{ |a,b| a^b }.pack("C*")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
5
35
 
6
36
  module NETSNMP
7
- # @return [String] the version of the netsnmp C library
8
- def self.version ; Core.version ; end
37
+ def self.set_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
9
55
  end
10
56
 
11
- require 'netsnmp/errors'
12
- require 'netsnmp/varbind'
13
- require 'netsnmp/oid'
14
- require 'netsnmp/pdu'
15
- require 'netsnmp/session'
16
- require 'netsnmp/client'
57
+
58
+ require "netsnmp/errors"
59
+
60
+ require "netsnmp/oid"
61
+ require "netsnmp/varbind"
62
+ require "netsnmp/pdu"
63
+ require "netsnmp/session"
64
+
65
+ require "netsnmp/scoped_pdu"
66
+ require "netsnmp/v3_session"
67
+ require "netsnmp/security_parameters"
68
+ require "netsnmp/message"
69
+ require "netsnmp/encryption/des"
70
+ require "netsnmp/encryption/aes"
71
+
72
+ require "netsnmp/client"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module NETSNMP
2
3
  # Main Entity, provides the user-facing API to communicate with SNMP Agents
3
4
  #
@@ -7,83 +8,90 @@ module NETSNMP
7
8
  #
8
9
  #
9
10
  class Client
10
-
11
- # @param [String] hostname the hostname of the agent
12
- # @param [Hash] options the set of options to open the session.
11
+ RETRIES = 5
12
+
13
+ # @param [Hash] options the options to needed to enable the SNMP client.
14
+ # @option options [String, Integer, nil] :version the version of the protocol (defaults to 3).
15
+ # also accepts common known declarations like :v3, "v2c", etc
16
+ # @option options [Integer] :retries number of retries for each failed PDU (after which it raise timeout error. Defaults to {RETRIES} retries)
17
+ # @yield [client] the instantiated client, after which it closes it for use.
18
+ # @example Yielding a clinet
19
+ # NETSNMP::Client.new(host: "241.232.22.12") do |client|
20
+ # puts client.get(oid: "1.3.6.1.2.1.1.5.0")
21
+ # end
13
22
  #
14
- # @see Session#initialize
15
- def initialize(hostname, options)
16
- @session ||= Session.new(hostname, options)
23
+ def initialize(**options)
24
+ version = options[:version]
25
+ version = case version
26
+ when Integer then version # assume the use know what he's doing
27
+ when /v?1/ then 0
28
+ when /v?2c?/ then 1
29
+ when /v?3/, nil then 3
30
+ end
31
+
32
+ @retries = options.fetch(:retries, RETRIES)
33
+ @session ||= version == 3 ? V3Session.new(options) : Session.new(options)
34
+ if block_given?
35
+ begin
36
+ yield self
37
+ ensure
38
+ close
39
+ end
40
+ end
17
41
  end
18
42
 
19
- # @see Session#close
43
+ # Closes the inner section
20
44
  def close
21
45
  @session.close
22
46
  end
23
47
 
24
48
  # Performs an SNMP GET Request
25
- #
26
- # @param [OID, String] oid_code the oid to get
27
- # @param [Hash] options the varbind options (see Varbind)
28
- # @option options [true, false] :response_pdu if true, the method returns a PDU
29
49
  #
30
- # @return [String] the value for the oid
50
+ # @see {NETSNMP::Varbind#new}
31
51
  #
32
- def get(oid_code, **options)
33
- request_pdu = RequestPDU.build(:get)
34
- oid = oid_code.is_a?(OID) ? oid_code : OID.new(oid_code)
35
- request_pdu.add_varbind(oid, options)
36
- yield request_pdu if block_given?
37
- response_pdu = @session.send(request_pdu)
38
- case options[:response_type]
39
- when :pdu then response_pdu
40
- else response_pdu.value
41
- end
52
+ def get(oid_opts)
53
+ request = @session.build_pdu(:get, oid_opts)
54
+ response = handle_retries { @session.send(request) }
55
+ yield response if block_given?
56
+ response.varbinds.first.value
42
57
  end
43
58
 
44
59
  # Performs an SNMP GETNEXT Request
45
60
  #
46
- # @param [OID, String] oid_code the oid to get
47
- # @param [Hash] options the varbind options (see Varbind)
48
- # @option options [true, false] :response_pdu if true, the method returns a PDU
49
- #
50
- # @return [String] the value for the next oid
51
- #
52
- # @note this method is used as a sub-routine for the walk
61
+ # @see {NETSNMP::Varbind#new}
53
62
  #
54
- def get_next(oid_code, **options)
55
- request_pdu = RequestPDU.build(:getnext)
56
- oid = oid_code.is_a?(OID) ? oid_code : OID.new(oid_code)
57
- request_pdu.add_varbind(oid, options)
58
- yield request_pdu if block_given?
59
- response_pdu = @session.send(request_pdu)
60
- case options[:response_type]
61
- when :pdu then response_pdu
62
- else response_pdu.value
63
- end
63
+ def get_next(oid_opts)
64
+ request = @session.build_pdu(:getnext, oid_opts)
65
+ response = handle_retries { @session.send(request) }
66
+ yield response if block_given?
67
+ varbind = response.varbinds.first
68
+ [varbind.oid, varbind.value]
64
69
  end
65
70
 
66
71
  # Perform a SNMP Walk (issues multiple subsequent GENEXT requests within the subtree rooted on an OID)
67
72
  #
68
- # @param [OID, String] oid_code the root oid from the subtree
69
- # @param [Hash] options the varbind options
73
+ # @param [String] oid the root oid from the subtree
70
74
  #
71
75
  # @return [Enumerator] the enumerator-collection of the oid-value pairs
72
76
  #
73
- def walk(oid_code, **options)
74
- options[:response_type] = :pdu
75
- walkoid = oid_code.is_a?(OID) ? oid_code : OID.new(oid_code)
77
+ def walk(oid: )
78
+ walkoid = oid
76
79
  Enumerator.new do |y|
77
- code = walkoid.code
80
+ code = walkoid
81
+ first_response_code = nil
78
82
  catch(:walk) do
79
83
  loop do
80
- response_pdu = get_next(code, options)
81
- response_pdu.varbinds.each do |varbind|
82
- code = varbind.oid_code
83
- if !walkoid.parent_of?(code) or varbind.value.eql?(:endofmibview)
84
- throw(:walk)
85
- else
86
- y << [code, varbind.value]
84
+ get_next(oid: code) do |response|
85
+ response.varbinds.each do |varbind|
86
+ code = varbind.oid
87
+ if !OID.parent?(walkoid, code) or
88
+ varbind.value.eql?(:endofmibview) or
89
+ code == first_response_code
90
+ throw(:walk)
91
+ else
92
+ y << [code, varbind.value]
93
+ end
94
+ first_response_code ||= code
87
95
  end
88
96
  end
89
97
  end
@@ -93,39 +101,49 @@ module NETSNMP
93
101
 
94
102
  # Perform a SNMP GETBULK Request (performs multiple GETNEXT)
95
103
  #
96
- # @param [OID, String] oid_code the first oid
104
+ # @param [String] oid the first oid
97
105
  # @param [Hash] options the varbind options
98
106
  # @option options [Integer] :errstat sets the number of objects expected for the getnext instance
99
107
  # @option options [Integer] :errindex number of objects repeating for all the repeating IODs.
100
108
  #
101
109
  # @return [Enumerator] the enumerator-collection of the oid-value pairs
102
110
  #
103
- def get_bulk(oid_code, **options)
104
- request_pdu = RequestPDU.build(:getbulk)
105
- request_pdu[:errstat] = options.delete(:non_repeaters) || 0
106
- request_pdu[:errindex] = options.delete(:max_repetitions) || 10
107
- request_pdu.add_varbind(OID.new(oid_code), options)
108
- yield request_pdu if block_given?
109
- response_pdu = @session.send(request_pdu)
110
- Enumerator.new do |y|
111
- response_pdu.varbinds.each do |varbind|
112
- y << [ varbind.oid_code, varbind.value ]
113
- end
114
- end
115
- end
111
+ #def get_bulk(oid)
112
+ # request = @session.build_pdu(:getbulk, *oids)
113
+ # request[:error_status] = options.delete(:non_repeaters) || 0
114
+ # request[:error_index] = options.delete(:max_repetitions) || 10
115
+ # response = @session.send(request)
116
+ # Enumerator.new do |y|
117
+ # response.varbinds.each do |varbind|
118
+ # y << [ varbind.oid, varbind.value ]
119
+ # end
120
+ # end
121
+ #end
116
122
 
117
123
  # Perform a SNMP SET Request
118
124
  #
119
- # @param [OID, String] oid_code the oid to update
120
- # @param [Hash] options the varbind options
121
- # @option options [Object] :value value to update the oid with.
125
+ # @see {NETSNMP::Varbind#new}
122
126
  #
123
- def set(oid_code, **options)
124
- request_pdu = PDU.build(:set)
125
- request_pdu.add_varbind(OID.new(oid_code), options)
126
- yield request_pdu if block_given?
127
- response_pdu = @session.send(request_pdu)
128
- response_pdu.value
127
+ def set(oid_opts)
128
+ request = @session.build_pdu(:set, oid_opts)
129
+ response = handle_retries { @session.send(request) }
130
+ yield response if block_given?
131
+ response.varbinds.map(&:value)
132
+ end
133
+
134
+
135
+ private
136
+
137
+ # Handles timeout errors by reissuing the same pdu until it runs out or retries.
138
+ def handle_retries
139
+ retries = @retries
140
+ begin
141
+ yield
142
+ rescue Timeout::Error => e
143
+ raise e if retries == 0
144
+ retries -= 1
145
+ retry
146
+ end
129
147
  end
130
148
  end
131
149
  end