netsnmp 0.1.5 → 0.2.0

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
  SHA256:
3
- metadata.gz: 48d02a452a0dee151b936f9e8ff6af2c0c305f6a235a42a23ce5426a5dcd33f2
4
- data.tar.gz: 05b2efdcbac79cba8d4e749dca163446fab1214c355a609c3e855762af1018a5
3
+ metadata.gz: d6bb62b9abd416188660e04be1c12cf009b1df0e307030b9c239ea4b2acd73cc
4
+ data.tar.gz: ee88f7eb292f1a0077ab25e4677713290447e0e7755049bbe6db50f07d15d6a1
5
5
  SHA512:
6
- metadata.gz: a26c9601d225cb1a3008a11890955acf1372fafba39e07ea4eed8f70969fd22ebdacd655ddafbf62681ba4dd1d95159e2b348fd627dc8063e9c6c8f5f244579a
7
- data.tar.gz: 3d0a6cfac763709d76cfc83b3d3d38261be9cb84d144a58267b3135a46b98418dd1cf882b5ff0866fabd1c74b59a82f641bbaad49387cbfc3a9d49242ef323b1
6
+ metadata.gz: fc90cc626d4be2a04d598ec267bcfc108e87aea8e8a585a7611033fc56cb6c174a222250bb9eeeb05b2d03d058983b106c92c30ba3d91e922b171ecfbc37041a
7
+ data.tar.gz: '09dbbb277ae24d9b760c0de70738a02037da527d653d5e003632c1b41d808cc21c44034fb51bec63394a132fe3c30ffaac1adc846dc061ca483e13fc0719af97'
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
 
@@ -123,6 +122,8 @@ If you create an `IPAddr` object (ruby standard library `ipaddr`) and pass it to
123
122
  The `NETSNMP::Timeticks` type is internal to this library, but it is a ruby `Numeric` type. You are safe to use it "as a numeric", that is, perform calculations.
124
123
 
125
124
 
125
+ Counter32 and Counter64 types will map to plain integers.
126
+
126
127
  You can find usage examples [here](https://github.com/swisscom/ruby-netsnmp/blob/master/spec/varbind_spec.rb). If you need support to a missing type, you have the following options:
127
128
 
128
129
  * Use the `:type` parameter in `#set` calls:
@@ -221,6 +222,7 @@ All encoding/decoding/encryption/decryption/digests are done using `openssl`, wh
221
222
 
222
223
  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).
223
224
 
225
+
224
226
  ### RSpec
225
227
 
226
228
  You can run all tests by typing:
@@ -232,37 +234,31 @@ You can run all tests by typing:
232
234
  ...
233
235
  ```
234
236
 
235
- ### SNMP Simulator
236
237
 
237
- 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.
238
+ ### Docker
238
239
 
239
- #### Docker
240
-
241
- The preferred way to use the snmp simulator is by using docker.
242
-
243
- In order to start the simulator container, run:
240
+ 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:
244
241
 
245
242
  ```
246
- > spec/support/spec.sh start
243
+ > docker-compose -f docker-compose.yml -f docker-compose-ruby-${RUBY_MAJOR_VERSION}.${RUBY_MAJOR_VERSION}.yml run netsnmp
247
244
  ```
248
245
 
249
- after, you just need to set the `SNMP_PORT` variable to the port found typing:
246
+ 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.
250
247
 
251
- ```
252
- > docker port test-snmp 1161/udp
253
- ```
248
+ ### SNMP Simulator
254
249
 
255
- and run the tests.
250
+ The SNMP simulator runs in its own container in the `docker` setup.
256
251
 
257
- #### CI
252
+ 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.
258
253
 
259
- If you want to replicate what the CI is doing, just do:
254
+ #### CI
260
255
 
261
- ```
262
- > spec/support/spec.sh run
263
- ```
256
+ The job of the CI is:
264
257
 
265
- Which should: build and run the simulator container, run the tests, run rubocop, and remove all artifacts.
258
+ * Run all the tests;
259
+ * Make sure the tests cover an appropriate surface of the code;
260
+ * Lint the code;
261
+ * (for ruby 3.0) type check the code;
266
262
 
267
263
 
268
264
  ## Contributing
@@ -34,6 +34,18 @@ rescue LoadError
34
34
  end
35
35
 
36
36
  module NETSNMP
37
+ module StringExtensions
38
+ # If you wonder why this is there: the oauth feature uses a refinement to enhance the
39
+ # Regexp class locally with #match? , but this is never tested, because ActiveSupport
40
+ # monkey-patches the same method... Please ActiveSupport, stop being so intrusive!
41
+ # :nocov:
42
+ refine(String) do
43
+ def match?(*args)
44
+ !match(*args).nil?
45
+ end
46
+ end
47
+ end
48
+
37
49
  def self.debug=(io)
38
50
  @debug_output = io
39
51
  end
@@ -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
@@ -4,13 +4,14 @@ module NETSNMP
4
4
  # Abstracts the OID structure
5
5
  #
6
6
  module OID
7
+ using StringExtensions unless String.method_defined?(:match?)
8
+
7
9
  OIDREGEX = /^[\d\.]*$/
8
10
 
9
11
  module_function
10
12
 
11
13
  def build(o)
12
14
  case o
13
- when OID then o
14
15
  when Array
15
16
  o.join(".")
16
17
  when OIDREGEX
@@ -29,7 +30,7 @@ module NETSNMP
29
30
  # @return [true, false] whether the given OID belongs to the sub-tree
30
31
  #
31
32
  def parent?(parent_oid, child_oid)
32
- child_oid.match(/\A#{parent_oid}\./)
33
+ child_oid.match?(/\A#{parent_oid}\./)
33
34
  end
34
35
  end
35
36
  end
@@ -55,7 +55,7 @@ module NETSNMP
55
55
  when :response then 2
56
56
  else raise Error, "#{type} is not supported as type"
57
57
  end
58
- new(args.merge(type: typ))
58
+ new(type: typ, **args)
59
59
  end
60
60
  end
61
61
 
@@ -64,7 +64,7 @@ module NETSNMP
64
64
  attr_reader :version, :community, :request_id
65
65
 
66
66
  def initialize(type:, headers:,
67
- request_id: nil,
67
+ request_id: SecureRandom.random_number(MAXREQUESTID),
68
68
  error_status: 0,
69
69
  error_index: 0,
70
70
  varbinds: [])
@@ -75,9 +75,9 @@ module NETSNMP
75
75
  @type = type
76
76
  @varbinds = []
77
77
  varbinds.each do |varbind|
78
- add_varbind(varbind)
78
+ add_varbind(**varbind)
79
79
  end
80
- @request_id = request_id || SecureRandom.random_number(MAXREQUESTID)
80
+ @request_id = request_id
81
81
  check_error_status(@error_status)
82
82
  end
83
83
 
@@ -9,6 +9,8 @@ module NETSNMP
9
9
  super(type: type, headers: [3, nil], **options)
10
10
  end
11
11
 
12
+ private
13
+
12
14
  def encode_headers_asn
13
15
  [OpenSSL::ASN1::OctetString.new(@engine_id || ""),
14
16
  OpenSSL::ASN1::OctetString.new(@context || "")]
@@ -24,7 +24,7 @@ module NETSNMP
24
24
 
25
25
  # @param [String] username the snmp v3 username
26
26
  # @param [String] engine_id the device engine id (initialized to '' for report)
27
- # @param [Symbol, integer] security_level allowed snmp v3 security level (:auth_priv, :auh_no_priv, etc)
27
+ # @param [Symbol, integer] security_level allowed snmp v3 security level (:auth_priv, :auth_no_priv, etc)
28
28
  # @param [Symbol, nil] auth_protocol a supported authentication protocol (currently supported: :md5, :sha)
29
29
  # @param [Symbol, nil] priv_protocol a supported privacy protocol (currently supported: :des, :aes)
30
30
  # @param [String, nil] auth_password the authentication password
@@ -99,7 +99,7 @@ module NETSNMP
99
99
  # @note this method is used in the process of authenticating a message
100
100
  def sign(message)
101
101
  # don't sign unless you have to
102
- return nil unless @auth_protocol
102
+ return unless @auth_protocol
103
103
 
104
104
  key = auth_key.dup
105
105
 
@@ -211,7 +211,7 @@ module NETSNMP
211
211
  end
212
212
 
213
213
  def authorizable?
214
- @auth_protocol && @auth_protocol != :none
214
+ @auth_protocol != :none
215
215
  end
216
216
  end
217
217
  end
@@ -10,7 +10,7 @@ module NETSNMP
10
10
  def initialize(version: 1, community: "public", **options)
11
11
  @version = version
12
12
  @community = community
13
- validate(options)
13
+ validate(**options)
14
14
  end
15
15
 
16
16
  # Closes the session
@@ -36,23 +36,20 @@ module NETSNMP
36
36
  # @return [NETSNMP::PDU] the response pdu
37
37
  #
38
38
  def send(pdu)
39
- encoded_request = encode(pdu)
39
+ encoded_request = pdu.to_der
40
40
  encoded_response = @transport.send(encoded_request)
41
- decode(encoded_response)
41
+ PDU.decode(encoded_response)
42
42
  end
43
43
 
44
44
  private
45
45
 
46
- def validate(**options)
47
- proxy = options[:proxy]
46
+ def validate(host: nil, port: 161, proxy: nil, timeout: TIMEOUT, **)
48
47
  if proxy
49
48
  @proxy = true
50
49
  @transport = proxy
51
50
  else
52
- host, port = options.values_at(:host, :port)
53
51
  raise "you must provide an hostname/ip under :host" unless host
54
- port ||= 161 # default snmp port
55
- @transport = Transport.new(host, port.to_i, timeout: options.fetch(:timeout, TIMEOUT))
52
+ @transport = Transport.new(host, port.to_i, timeout: timeout)
56
53
  end
57
54
  @version = case @version
58
55
  when Integer then @version # assume the use know what he's doing
@@ -64,14 +61,6 @@ module NETSNMP
64
61
  end
65
62
  end
66
63
 
67
- def encode(pdu)
68
- pdu.to_der
69
- end
70
-
71
- def decode(stream)
72
- PDU.decode(stream)
73
- end
74
-
75
64
  class Transport
76
65
  MAXPDUSIZE = 0xffff + 1
77
66
 
@@ -4,7 +4,7 @@ module NETSNMP
4
4
  # Abstraction for the v3 semantics.
5
5
  class V3Session < Session
6
6
  # @param [String, Integer] version SNMP version (always 3)
7
- def initialize(version: 3, context: "", **opts)
7
+ def initialize(context: "", **opts)
8
8
  @context = context
9
9
  @security_parameters = opts.delete(:security_parameters)
10
10
  super
@@ -19,8 +19,10 @@ module NETSNMP
19
19
  end
20
20
 
21
21
  # @see {NETSNMP::Session#send}
22
- def send(*)
23
- pdu, = super
22
+ def send(pdu)
23
+ encoded_request = encode(pdu)
24
+ encoded_response = @transport.send(encoded_request)
25
+ pdu, = decode(encoded_response)
24
26
  pdu
25
27
  end
26
28
 
@@ -6,7 +6,7 @@ module NETSNMP
6
6
  class Varbind
7
7
  attr_reader :oid, :value
8
8
 
9
- def initialize(oid, value: nil, type: nil, **_opts)
9
+ def initialize(oid, value: nil, type: nil)
10
10
  @oid = OID.build(oid)
11
11
  @type = type
12
12
  @value = convert_val(value) if value
@@ -81,17 +81,25 @@ module NETSNMP
81
81
  when :ipaddress then 0
82
82
  when :counter32
83
83
  asn_val = [value].pack("N*")
84
- asn_val = asn_val[1..-1] while asn_val.start_with?("\x00")
84
+ asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack("B").first != "1"
85
85
  1
86
86
  when :gauge
87
87
  asn_val = [value].pack("N*")
88
- asn_val = asn_val[1..-1] while asn_val.start_with?("\x00")
88
+ asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack("B").first != "1"
89
89
  2
90
90
  when :timetick
91
91
  return Timetick.new(value).to_asn
92
92
  when :opaque then 4
93
93
  when :nsap then 5
94
- when :counter64 then 6
94
+ when :counter64
95
+ asn_val = [
96
+ (value >> 96) & 0xFFFFFFFF,
97
+ (value >> 64) & 0xFFFFFFFF,
98
+ (value >> 32) & 0xFFFFFFFF,
99
+ value & 0xFFFFFFFF
100
+ ].pack("NNNN")
101
+ asn_val = asn_val[1..-1] while asn_val.start_with?("\x00")
102
+ 6
95
103
  when :uinteger then 7
96
104
  else
97
105
  raise Error, "#{typ}: unsupported application type"
@@ -106,16 +114,27 @@ module NETSNMP
106
114
  IPAddr.new_ntoh(asn.value)
107
115
  when 1, # ASN counter 32
108
116
  2 # gauge
109
- val = asn.value
110
- val.prepend("\x00") while val.bytesize < 4
111
- asn.value.unpack("N*")[0] || 0
117
+ unpack_32bit_integer(asn.value)
112
118
  when 3 # timeticks
113
- Timetick.new(asn.value.unpack("N*")[0] || 0)
119
+ Timetick.new(unpack_32bit_integer(asn.value))
114
120
  # when 4 # opaque
115
121
  # when 5 # NSAP
116
- # when 6 # ASN Counter 64
122
+ when 6 # ASN Counter 64
123
+ unpack_64bit_integer(asn.value)
117
124
  # when 7 # ASN UInteger
118
125
  end
119
126
  end
127
+
128
+ private
129
+
130
+ def unpack_32bit_integer(payload)
131
+ payload.prepend("\x00") until (payload.bytesize % 4).zero?
132
+ payload.unpack("N*")[-1] || 0
133
+ end
134
+
135
+ def unpack_64bit_integer(payload)
136
+ payload.prepend("\x00") until (payload.bytesize % 16).zero?
137
+ payload.unpack("NNNN").reduce(0) { |sum, elem| (sum << 32) + elem }
138
+ end
120
139
  end
121
140
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NETSNMP
4
- VERSION = "0.1.5"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,24 @@
1
+ module NETSNMP
2
+ class Client
3
+
4
+ def get: (*untyped) -> untyped
5
+ | (*untyped) { (PDU) -> void } -> untyped
6
+
7
+ def get_next: (*untyped) -> untyped
8
+ | (*untyped) { (PDU) -> void } -> untyped
9
+
10
+ def set: (*untyped) -> untyped
11
+ | (*untyped) { (PDU) -> void } -> untyped
12
+
13
+ def inform: (*untyped) -> untyped
14
+ | (*untyped) { (PDU) -> void } -> untyped
15
+
16
+ def walk: (oid: oid) -> _Each[oid_type, oid_value]
17
+
18
+ def close: () -> void
19
+
20
+ private
21
+
22
+ def initialize: (?version?: snmp_version, **untyped) -> untyped
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module NETSNMP
2
+ module Message
3
+ def self?.decode: (String stream, security_parameters: SecurityParameters) -> [ScopedPDU, String, Integer, Integer]
4
+
5
+ def self?.encode: (ScopedPDU pdu, security_parameters: SecurityParameters, ?engine_boots: Integer, ?engine_time: Integer) -> String
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module NETSNMP
2
+ interface _Logger
3
+ def <<: (string) -> void
4
+ end
5
+
6
+ interface _Authenticate
7
+ def reset: () -> void
8
+ def <<: (string) -> void
9
+ def digest: () -> String
10
+ end
11
+
12
+ interface _Encrypt
13
+ def encrypt: (String payload, engine_boots: Integer, engine_time: Integer) -> String
14
+
15
+ def decrypt: (String payload, salt: String, engine_boots: Integer, engine_time: Integer) -> String
16
+ end
17
+
18
+ type snmp_version = 0 | 1 | 3 | :v1 | :v2c | :v3 | nil
19
+
20
+ def self.debug=: (_Logger) -> void
21
+
22
+ def self.debug: { () -> string } -> void
23
+ end
@@ -0,0 +1,18 @@
1
+ module NETSNMP
2
+ type oid = String | Array[Integer]
3
+
4
+ type oid_type = Integer | :ipaddress | :counter32 | :gauge | :timetick | :opaque | :nsap | :counter64 | :uinteger
5
+
6
+ type oid_value = String | Integer | Timetick | true | false | nil | IPAddr
7
+
8
+ # type oid_options = {}
9
+
10
+ module OID
11
+
12
+ def self?.build: (oid) -> String
13
+
14
+ def self?.to_asn: (String oid) -> OpenSSL::ASN1::ObjectId
15
+
16
+ def self?.parent?: (String oid, String oid) -> bool
17
+ end
18
+ end