netsnmp 0.0.0 → 0.0.2

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
  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: