netsnmp 0.1.5 → 0.2.0

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