netsnmp 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01d3849f1d3acbab7fa9b57055aafd5150fd3865
4
- data.tar.gz: 444a3fae5b5d38df3873a4ee0c35cf16f3bf16a6
3
+ metadata.gz: 2a043cc79c90239f031b1873089c8a23f50dd5b8
4
+ data.tar.gz: 05b0baf81082f36cc1a870c3f4f5a87b40041a60
5
5
  SHA512:
6
- metadata.gz: 6cd620b0b6cc65ba8f58d6c0ec5168270f9f814f930b6bc56bd8f82e26cbc4bb5b146858bf328a27265cc149b5e73ca521655250d6a36e4ccec65b0cc64fa5fc
7
- data.tar.gz: 0be3b839d7992a719f4efa6a823899d9b1b98a206ef4a780bf6b044fbf655860daf800534aad976861dbbb39215ddb5ed537df0ff39e4bb0a68a22c94fbd1049
6
+ metadata.gz: 63c8a7195237c722b4fb7d0db9f00789ca475c7d080cc8d539f6e1f26adca2482c71d1064925fdd788f95f1f8cac9e89e33c06edc878c22b26be964948af0207
7
+ data.tar.gz: d30bb03a3a0fc19c34f9afd07512260ea0f4f4bc7f407b5b7c980e9bbb39e166730b34ec5be425b3435270b98e82e002d15b6992ae3834eeb1369bfae805bf19
@@ -72,23 +72,82 @@ module NETSNMP
72
72
  end
73
73
 
74
74
  # SNMP v3-relevant OIDs
75
+ class AuthOID < OID
76
+ def generate_key(session, user, pass)
77
+ raise Error, "no given Authorization User" unless user
78
+ raise Error, "no given Authorization Password" unless pass
75
79
 
76
- class MD5OID < OID
80
+ session[:securityAuthProto] = pointer
81
+ session[:securityName] = FFI::MemoryPointer.from_string(user)
82
+ session[:securityNameLen] = user.length
83
+
84
+ auth_len_ptr = FFI::MemoryPointer.new(:size_t)
85
+ auth_len_ptr.write_int(Core::Constants::USM_AUTH_KU_LEN)
86
+
87
+ auth_key_result = Core::LibSNMP.generate_Ku(pointer,
88
+ session[:securityAuthProtoLen],
89
+ pass,
90
+ pass.length,
91
+ session[:securityAuthKey],
92
+ auth_len_ptr)
93
+ unless auth_key_result == Core::Constants::SNMPERR_SUCCESS
94
+ raise AuthenticationFailed, "failed to authenticate #{auth_user} in #{@host}"
95
+ end
96
+ session[:securityAuthKeyLen] = auth_len_ptr.read_int
97
+ end
98
+ end
99
+
100
+ class PrivOID < OID
101
+
102
+ def generate_key(session, user, pass)
103
+ raise Error, "no given Priv User" unless user
104
+ raise Error, "no given Priv Password" unless pass
105
+
106
+ session[:securityPrivProto] = pointer
107
+
108
+ # other necessary lengths
109
+ priv_len_ptr = FFI::MemoryPointer.new(:size_t)
110
+ priv_len_ptr.write_int(Core::Constants::USM_PRIV_KU_LEN)
111
+
112
+ # NOTE I know this is handing off the AuthProto, but generates a proper
113
+ # key for encryption, and using PrivProto does not.
114
+ priv_key_result = Core::LibSNMP.generate_Ku(session[:securityAuthProto],
115
+ session[:securityAuthProtoLen],
116
+ pass,
117
+ pass.length,
118
+ session[:securityPrivKey],
119
+ priv_len_ptr)
120
+
121
+ unless priv_key_result == Core::Constants::SNMPERR_SUCCESS
122
+ raise AuthenticationFailed, "failed to authenticate #{auth_user} in #{@host}"
123
+ end
124
+ session[:securityPrivKeyLen] = priv_len_ptr.read_int
125
+
126
+ end
127
+ end
128
+
129
+ class MD5OID < AuthOID
77
130
  def initialize ; super("1.3.6.1.6.3.10.1.1.2") ; end
78
131
  end
79
- class SHA1OID < OID
132
+ class SHA1OID < AuthOID
80
133
  def initialize ; super("1.3.6.1.6.3.10.1.1.3") ; end
81
134
  end
82
- class NoAuthOID < OID
135
+ class NoAuthOID < AuthOID
83
136
  def initialize ; super("1.3.6.1.6.3.10.1.1.1") ; end
137
+ def generate_key(session, *)
138
+ session[:securityAuthProto] = pointer
139
+ end
84
140
  end
85
- class AESOID < OID
141
+ class AESOID < PrivOID
86
142
  def initialize ; super("1.3.6.1.6.3.10.1.2.4") ; end
87
143
  end
88
- class DESOID < OID
144
+ class DESOID < PrivOID
89
145
  def initialize ; super("1.3.6.1.6.3.10.1.2.2") ; end
90
146
  end
91
- class NoPrivOID < OID
147
+ class NoPrivOID < PrivOID
92
148
  def initialize ; super("1.3.6.1.6.3.10.1.2.1") ; end
149
+ def generate_key(session, *)
150
+ session[:securityPrivProto] = pointer
151
+ end
93
152
  end
94
153
  end
@@ -22,12 +22,17 @@ module NETSNMP
22
22
  #
23
23
  def initialize(host, opts)
24
24
  @host = host
25
+ # this is because other evented clients might discover IP first, but hostnames
26
+ # give you better trackability of errors. Give the opportunity to the users to
27
+ # pass it, by setting the hostname explicitly. If not, fallback to the host.
28
+ @hostname = opts.fetch(:hostname, @host)
25
29
  @options = opts
30
+ @logged_at = nil
26
31
  @request = nil
27
32
  # For now, let's eager load the signature
28
33
  @signature = build_signature(@options)
29
34
  if @signature.null?
30
- raise ConnectionFailed, "could not connect to #{host}"
35
+ raise ConnectionFailed, "could not build signature for #@hostname"
31
36
  end
32
37
  @requests ||= {}
33
38
  end
@@ -44,7 +49,7 @@ module NETSNMP
44
49
  transport.close rescue nil
45
50
  end
46
51
  if Core::LibSNMP.snmp_sess_close(@signature) == 0
47
- raise Error, "#@host: Couldn't clean up session properly"
52
+ raise Error, "#@hostname: Couldn't clean up session properly"
48
53
  end
49
54
  end
50
55
 
@@ -60,6 +65,26 @@ module NETSNMP
60
65
 
61
66
  private
62
67
 
68
+ LoggedInTimeout = Class.new(Timeout::Error)
69
+ def try_login
70
+ return yield unless @logged_at.nil?
71
+
72
+ begin
73
+ # problem for snmp is, there is no login process.
74
+ # signature is sent on first packet. As we are not using the synch interface, this will not
75
+ # be handled by the core library. Which sucks. But even core library would just retry and timeout
76
+ # at some point.
77
+ # we do something similar: before any PDU succeeds, after first PDU is async-sent, we wait for reads, but we can't block. If
78
+ # the socket hasn't anything to read, we can assume it was the wrong USERNAME (?).
79
+ # TODO: research if this is true.
80
+ Timeout.timeout(@timeout, LoggedInTimeout) do
81
+ yield
82
+ end
83
+ rescue LoggedInTimeout
84
+ raise ConnectionFailed, "failed to login to #@hostname"
85
+ end
86
+ end
87
+
63
88
  def transport
64
89
  @transport ||= fetch_transport
65
90
  end
@@ -73,31 +98,40 @@ module NETSNMP
73
98
  if ( @reqid = Core::LibSNMP.snmp_sess_async_send(@signature, pdu.pointer, session_callback, nil) ) == 0
74
99
  # it's interesting, pdu's are only fred if the async send is successful... netsnmp 1 - me 0
75
100
  Core::LibSNMP.snmp_free_pdu(pdu.pointer)
76
- raise SendError, "#@host: Failed to send pdu"
101
+ # if it's the first time we're passing here and send fails, we can (?) assume that
102
+ # AUTH_PASSWORD is wrong
103
+ if @logged_at.nil?
104
+ raise ConnectionFailed, "failed to login to #@hostname"
105
+ else
106
+ raise SendError, "#@hostname: Failed to send pdu"
107
+ end
77
108
  end
78
109
  end
79
110
 
80
111
  def read
81
112
  receive # trigger callback ahead of time and wait for it
113
+ # Sounds a bit unreasonable, but only after we arrived here we know for sure that the credentials are proper.
114
+ # So we can set this variable, so further errors can be safely ignored.
115
+ @logged_at ||= Time.now
82
116
  handle_response
83
117
  end
84
118
 
85
119
  def handle_response
86
120
  operation, response_pdu = @requests.delete(@reqid)
87
121
  case operation
88
- when :send_failed
89
- raise ReceiveError, "#@host: Failed to receive pdu"
90
- when :timeout
91
- raise Timeout::Error, "#@host: timed out while waiting for pdu response"
92
122
  when :success
93
123
  response_pdu
124
+ when :send_failed
125
+ raise ReceiveError, "#@hostname: Failed to receive pdu"
126
+ when :timeout
127
+ raise Timeout::Error, "#@hostname: timed out while waiting for pdu response"
94
128
  else
95
- raise Error, "#@host: unrecognized operation for request #{@reqid}: #{operation} for #{response_pdu}"
129
+ raise Error, "#@hostname: unrecognized operation for request #{@reqid}: #{operation} for #{response_pdu}"
96
130
  end
97
131
  end
98
132
 
99
133
  def receive
100
- readers, _ = wait_readable
134
+ readers, _ = try_login { wait_readable }
101
135
  case readers.size
102
136
  when 1..Float::INFINITY
103
137
  # triggers callback
@@ -105,13 +139,19 @@ module NETSNMP
105
139
  when 0
106
140
  Core::LibSNMP.snmp_sess_timeout(@signature)
107
141
  else
108
- raise ReceiveError, "#@host: error receiving data"
142
+ raise ReceiveError, "#@hostname: error receiving data"
109
143
  end
110
144
  end
111
145
 
112
146
  def async_read
113
147
  if Core::LibSNMP.snmp_sess_read(@signature, get_selectable_sockets.pointer) != 0
114
- raise ReceiveError, "#@host: Failed to receive pdu response"
148
+ # if it's the first time we're passing here and send fails, we can (?) assume that
149
+ # PRIV_PASSWORD is wrong
150
+ if @logged_at.nil?
151
+ raise ConnectionFailed, "failed to login to #@hostname"
152
+ else
153
+ raise ReceiveError, "#@hostname: Failed to receive pdu response"
154
+ end
115
155
  end
116
156
  end
117
157
 
@@ -164,79 +204,52 @@ module NETSNMP
164
204
  return unless session[:version] == Core::Constants::SNMP_VERSION_3
165
205
 
166
206
 
207
+ session[:securityAuthProtoLen] = 10
208
+ session[:securityAuthKeyLen] = Core::Constants::USM_AUTH_KU_LEN
209
+ session[:securityPrivProtoLen] = 10
210
+ session[:securityPrivKeyLen] = Core::Constants::USM_PRIV_KU_LEN
211
+
167
212
  # Security Authorization
168
- session[:securityLevel] = options[:security_level] || Core::Constants::SNMP_SEC_LEVEL_AUTHPRIV
213
+ session[:securityLevel] = case options[:security_level]
214
+ when /noauth/ then Core::Constants::SNMP_SEC_LEVEL_NOAUTH
215
+ when /auth_?no_?priv/ then Core::Constants::SNMP_SEC_LEVEL_AUTHNOPRIV
216
+ when /auth_?priv/ then Core::Constants::SNMP_SEC_LEVEL_AUTHPRIV
217
+ when Integer
218
+ options[:security_level]
219
+ else Core::Constants::SNMP_SEC_LEVEL_AUTHPRIV
220
+ end
221
+
169
222
  auth_protocol_oid = case options[:auth_protocol]
170
223
  when :md5 then MD5OID.new
171
224
  when :sha1 then SHA1OID.new
172
225
  when nil then NoAuthOID.new
173
- else raise Error, "#@host: #{options[:auth_protocol]} is an unsupported authorization protocol"
226
+ else raise Error, "#@hostname: #{options[:auth_protocol]} is an unsupported authorization protocol"
174
227
  end
175
228
 
176
- session[:securityAuthProto] = auth_protocol_oid.pointer
177
-
178
- # Priv Protocol
229
+ # Priv Protocol
179
230
  priv_protocol_oid = case options[:priv_protocol]
180
231
  when :aes then AESOID.new
181
232
  when :des then DESOID.new
182
233
  when nil then NoPrivOID.new
183
- else raise Error, "#@host: #{options[:priv_protocol]} is an unsupported privacy protocol"
234
+ else raise Error, "#@hostname: #{options[:priv_protocol]} is an unsupported privacy protocol"
184
235
  end
185
- session[:securityPrivProto] = priv_protocol_oid.pointer
186
-
187
- # other necessary lengths
188
- session[:securityAuthProtoLen] = 10
189
- session[:securityAuthKeyLen] = Core::Constants::USM_AUTH_KU_LEN
190
- session[:securityPrivProtoLen] = 10
191
- session[:securityPrivKeyLen] = Core::Constants::USM_PRIV_KU_LEN
192
-
236
+
237
+ user, auth_pass, priv_pass = options.values_at(:username, :auth_password, :priv_password)
238
+ auth_protocol_oid.generate_key(session, user, auth_pass)
239
+ priv_protocol_oid.generate_key(session, user, priv_pass )
193
240
 
194
241
  if options[:context]
195
242
  session[:contextName] = FFI::MemoryPointer.from_string(options[:context])
196
243
  session[:contextNameLen] = options[:context].length
197
244
  end
198
245
 
199
- # Authentication
200
- # Do not generate_Ku, unless we're Auth or AuthPriv
201
- auth_user, auth_pass = options.values_at(:username, :auth_password)
202
- raise Error, "#@host: no given Authorization User" unless auth_user
203
- session[:securityName] = FFI::MemoryPointer.from_string(auth_user)
204
- session[:securityNameLen] = auth_user.length
205
-
206
- auth_len_ptr = FFI::MemoryPointer.new(:size_t)
207
- auth_len_ptr.write_int(Core::Constants::USM_AUTH_KU_LEN)
208
- auth_key_result = Core::LibSNMP.generate_Ku(session[:securityAuthProto],
209
- session[:securityAuthProtoLen],
210
- auth_pass,
211
- auth_pass.length,
212
- session[:securityAuthKey],
213
- auth_len_ptr)
214
- session[:securityAuthKeyLen] = auth_len_ptr.read_int
215
-
216
- priv_len_ptr = FFI::MemoryPointer.new(:size_t)
217
- priv_len_ptr.write_int(Core::Constants::USM_PRIV_KU_LEN)
218
-
219
- priv_pass = options[:priv_password]
220
- # NOTE I know this is handing off the AuthProto, but generates a proper
221
- # key for encryption, and using PrivProto does not.
222
- priv_key_result = Core::LibSNMP.generate_Ku(session[:securityAuthProto],
223
- session[:securityAuthProtoLen],
224
- priv_pass,
225
- priv_pass.length,
226
- session[:securityPrivKey],
227
- priv_len_ptr)
228
- session[:securityPrivKeyLen] = priv_len_ptr.read_int
229
-
230
- unless auth_key_result == Core::Constants::SNMPERR_SUCCESS and
231
- priv_key_result == Core::Constants::SNMPERR_SUCCESS
232
- raise AuthenticationFailed, "failed to authenticate #{auth_user} in #{@host}"
233
- end
246
+
234
247
  end
235
248
 
236
249
 
237
250
  # @param [Hash] options options to open the net-snmp session
238
251
  # @option options [String] :community the snmp community string (defaults to public)
239
- # @option options [Integer] :timeout number of millisecs until first timeout
252
+ # @option options [Integer] :timeout number of sec until first timeout
240
253
  # @option options [Integer] :retries number of retries before timeout
241
254
  # @return [FFI::Pointer] a pointer to the validated session signature, which will therefore be used in all _sess_ methods from libnetsnmp
242
255
  def build_signature(options)
@@ -259,9 +272,9 @@ module NETSNMP
259
272
 
260
273
  session[:peername] = FFI::MemoryPointer.from_string(peername)
261
274
 
262
- session[:timeout] = options[:timeout] if options.has_key?(:timeout)
263
- session[:retries] = options[:retries] if options.has_key?(:retries)
264
-
275
+ @timeout = options[:timeout] || 10
276
+ session[:timeout] = @timeout * 1000000
277
+ session[:retries] = options[:retries] || 5
265
278
  session_authorization(session, options)
266
279
  Core::LibSNMP.snmp_sess_open(session.pointer)
267
280
  end
@@ -289,14 +302,14 @@ module NETSNMP
289
302
 
290
303
  # TODO: pass exception in case of failure
291
304
 
305
+ response_pdu = ResponsePDU.new(pdu_ptr)
306
+ @requests[@reqid] = [op, response_pdu]
292
307
  if reqid == @reqid
293
- response_pdu = ResponsePDU.new(pdu_ptr)
294
308
  # probably pass the result as a yield from a fiber
295
- @requests[@reqid] = [op, response_pdu]
296
-
297
309
  op.eql?(:unrecognized_operation) ? 0 : 1
298
310
  else
299
- puts "wow, unexpected #{op}.... #{reqid} different than #{@reqid}"
311
+ # this is happening when user is unknown(????)
312
+ #puts "wow, unexpected #{op}.... #{reqid} different than #{@reqid}"
300
313
  0
301
314
  end
302
315
  end
@@ -1,3 +1,3 @@
1
1
  module NETSNMP
2
- VERSION = '0.0.0'.freeze
2
+ VERSION = '0.0.2'.freeze
3
3
  end
@@ -15,6 +15,7 @@ DESC
15
15
  gem.homepage = ""
16
16
  gem.platform = Gem::Platform::RUBY
17
17
  gem.required_ruby_version = '>=2.0.0'
18
+ gem.metadata["allowed_push_hosts"] = "https://rubygems.org/"
18
19
 
19
20
  # Manifest
20
21
  gem.files = `git ls-files`.split("\n") - Dir['tmp/**/*']
@@ -12,6 +12,35 @@ RSpec.describe NETSNMP::Client do
12
12
 
13
13
  subject { described_class.new(host, options) }
14
14
 
15
+ describe "establishing session" do
16
+ let(:oid) { "1.3.6.1.2.1.1.5.0" } # sysName.0
17
+ context "with a wrong auth password" do
18
+ let(:options) { host_options.merge( auth_password: "auctoritas2", timeout: 5) }
19
+ it {
20
+ expect {
21
+ subject.get(oid)
22
+ }.to raise_error(NETSNMP::ConnectionFailed)
23
+ }
24
+ end
25
+ context "with a wrong priv password" do
26
+ let(:options) { host_options.merge( priv_password: "privatus2", timeout: 5) }
27
+ it {
28
+ expect {
29
+ subject.get(oid)
30
+ }.to raise_error(NETSNMP::ConnectionFailed)
31
+ }
32
+ end
33
+
34
+ context "with an unexisting user" do
35
+ let(:options) { host_options.merge(username: "simulata", timeout: 5) }
36
+ it {
37
+ expect {
38
+ subject.get(oid)
39
+ }.to raise_error(NETSNMP::ConnectionFailed)
40
+ }
41
+ end
42
+ end
43
+
15
44
  describe "#get" do
16
45
  let(:oid) { "1.3.6.1.2.1.1.5.0" } # sysName.0
17
46
  let(:value) { subject.get(oid) }
@@ -1,4 +1,5 @@
1
1
  sudo docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy -t snmp-server-emulator -f spec/support/Dockerfile .
2
2
  sudo docker run -d -p :1161/udp --name test-snmp-emulator snmp-server-emulator --agent-udpv4-endpoint=0.0.0.0:1161 --agent-udpv6-endpoint='[::0]:1161'
3
+ sleep 20 # give some time for the simulator to boot
3
4
 
4
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: netsnmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-07 00:00:00.000000000 Z
11
+ date: 2016-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -147,7 +147,8 @@ files:
147
147
  homepage: ''
148
148
  licenses:
149
149
  - Apache-2.0
150
- metadata: {}
150
+ metadata:
151
+ allowed_push_hosts: https://rubygems.org/
151
152
  post_install_message:
152
153
  rdoc_options: []
153
154
  require_paths: