netsnmp 0.2.0 → 0.5.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.
data/lib/netsnmp/oid.rb CHANGED
@@ -10,16 +10,10 @@ module NETSNMP
10
10
 
11
11
  module_function
12
12
 
13
- def build(o)
14
- case o
15
- when Array
16
- o.join(".")
17
- when OIDREGEX
18
- o = o[1..-1] if o.start_with?(".")
19
- o
20
- # TODO: MIB to OID
21
- else raise Error, "can't convert #{o} to OID"
22
- end
13
+ def build(id)
14
+ oid = MIB.oid(id)
15
+ oid = oid[1..-1] if oid.start_with?(".")
16
+ oid
23
17
  end
24
18
 
25
19
  def to_asn(oid)
data/lib/netsnmp/pdu.rb CHANGED
@@ -5,7 +5,11 @@ module NETSNMP
5
5
  # Abstracts the PDU base structure into a ruby object. It gives access to its varbinds.
6
6
  #
7
7
  class PDU
8
- MAXREQUESTID = 2147483647
8
+ using ASNExtensions
9
+
10
+ MAXREQUESTID = 0x7fffffff
11
+
12
+ using ASNExtensions
9
13
  class << self
10
14
  def decode(der)
11
15
  asn_tree = case der
@@ -53,6 +57,7 @@ module NETSNMP
53
57
  when :inform then 6
54
58
  when :trap then 7
55
59
  when :response then 2
60
+ when :report then 8
56
61
  else raise Error, "#{type} is not supported as type"
57
62
  end
58
63
  new(type: typ, **args)
@@ -85,6 +90,10 @@ module NETSNMP
85
90
  to_asn.to_der
86
91
  end
87
92
 
93
+ def to_hex
94
+ to_asn.to_hex
95
+ end
96
+
88
97
  # Adds a request varbind to the pdu
89
98
  #
90
99
  # @param [OID] oid a valid oid
@@ -96,25 +105,27 @@ module NETSNMP
96
105
  alias << add_varbind
97
106
 
98
107
  def to_asn
99
- request_id_asn = OpenSSL::ASN1::Integer.new(@request_id)
100
- error_asn = OpenSSL::ASN1::Integer.new(@error_status)
101
- error_index_asn = OpenSSL::ASN1::Integer.new(@error_index)
108
+ request_id_asn = OpenSSL::ASN1::Integer.new(@request_id).with_label(:request_id)
109
+ error_asn = OpenSSL::ASN1::Integer.new(@error_status).with_label(:error)
110
+ error_index_asn = OpenSSL::ASN1::Integer.new(@error_index).with_label(:error_index)
102
111
 
103
- varbind_asns = OpenSSL::ASN1::Sequence.new(@varbinds.map(&:to_asn))
112
+ varbind_asns = OpenSSL::ASN1::Sequence.new(@varbinds.map(&:to_asn)).with_label(:varbinds)
104
113
 
105
114
  request_asn = OpenSSL::ASN1::ASN1Data.new([request_id_asn,
106
115
  error_asn, error_index_asn,
107
116
  varbind_asns], @type,
108
- :CONTEXT_SPECIFIC)
117
+ :CONTEXT_SPECIFIC).with_label(:request)
109
118
 
110
- OpenSSL::ASN1::Sequence.new([*encode_headers_asn, request_asn])
119
+ OpenSSL::ASN1::Sequence.new([*encode_headers_asn, request_asn]).with_label(:pdu)
111
120
  end
112
121
 
113
122
  private
114
123
 
115
124
  def encode_headers_asn
116
- [OpenSSL::ASN1::Integer.new(@version),
117
- OpenSSL::ASN1::OctetString.new(@community)]
125
+ [
126
+ OpenSSL::ASN1::Integer.new(@version).with_label(:snmp_version),
127
+ OpenSSL::ASN1::OctetString.new(@community).with_label(:community)
128
+ ]
118
129
  end
119
130
 
120
131
  # http://www.tcpipguide.com/free/t_SNMPVersion2SNMPv2MessageFormats-5.htm#Table_219
@@ -2,8 +2,12 @@
2
2
 
3
3
  module NETSNMP
4
4
  class ScopedPDU < PDU
5
+ using ASNExtensions
6
+
5
7
  attr_reader :engine_id
6
8
 
9
+ attr_accessor :security_level, :auth_param
10
+
7
11
  def initialize(type:, headers:, **options)
8
12
  @engine_id, @context = headers
9
13
  super(type: type, headers: [3, nil], **options)
@@ -12,8 +16,10 @@ module NETSNMP
12
16
  private
13
17
 
14
18
  def encode_headers_asn
15
- [OpenSSL::ASN1::OctetString.new(@engine_id || ""),
16
- OpenSSL::ASN1::OctetString.new(@context || "")]
19
+ [
20
+ OpenSSL::ASN1::OctetString.new(@engine_id || "").with_label(:engine_id),
21
+ OpenSSL::ASN1::OctetString.new(@context || "").with_label(:context)
22
+ ]
17
23
  end
18
24
  end
19
25
  end
@@ -8,6 +8,9 @@ module NETSNMP
8
8
  # It also provides validation of the security options passed with a client is initialized in v3 mode.
9
9
  class SecurityParameters
10
10
  using StringExtensions
11
+ using ASNExtensions
12
+
13
+ prepend Loggable
11
14
 
12
15
  IPAD = "\x36" * 64
13
16
  OPAD = "\x5c" * 64
@@ -72,7 +75,10 @@ module NETSNMP
72
75
  if encryption
73
76
  encrypted_pdu, salt = encryption.encrypt(pdu.to_der, engine_boots: engine_boots,
74
77
  engine_time: engine_time)
75
- [OpenSSL::ASN1::OctetString.new(encrypted_pdu), OpenSSL::ASN1::OctetString.new(salt)]
78
+ [
79
+ OpenSSL::ASN1::OctetString.new(encrypted_pdu).with_label(:encrypted_pdu),
80
+ OpenSSL::ASN1::OctetString.new(salt).with_label(:salt)
81
+ ]
76
82
  else
77
83
  [pdu.to_asn, salt]
78
84
  end
@@ -82,15 +88,16 @@ module NETSNMP
82
88
  # @param [String] salt the salt from the incoming der
83
89
  # @param [Integer] engine_time the reported engine time
84
90
  # @param [Integer] engine_boots the reported engine boots
85
- def decode(der, salt:, engine_time:, engine_boots:)
91
+ def decode(der, salt:, engine_time:, engine_boots:, security_level: @security_level)
86
92
  asn = OpenSSL::ASN1.decode(der)
87
- if encryption
88
- encrypted_pdu = asn.value
89
- pdu_der = encryption.decrypt(encrypted_pdu, salt: salt, engine_time: engine_time, engine_boots: engine_boots)
90
- OpenSSL::ASN1.decode(pdu_der)
91
- else
92
- asn
93
- end
93
+ return asn if security_level < 3
94
+
95
+ return asn unless encryption
96
+
97
+ encrypted_pdu = asn.value
98
+ pdu_der = encryption.decrypt(encrypted_pdu, salt: salt, engine_time: engine_time, engine_boots: engine_boots)
99
+ log(level: 2) { "message has been decrypted" }
100
+ OpenSSL::ASN1.decode(pdu_der)
94
101
  end
95
102
 
96
103
  # @param [String] message the already encoded snmp v3 message
@@ -120,10 +127,11 @@ module NETSNMP
120
127
  # @param [String] salt the incoming payload''s salt
121
128
  #
122
129
  # @raise [NETSNMP::Error] if the message's integration has been violated
123
- def verify(stream, salt)
124
- return if @security_level < 1
130
+ def verify(stream, salt, security_level: @security_level)
131
+ return if security_level < 1
125
132
  verisalt = sign(stream)
126
133
  raise Error, "invalid message authentication salt" unless verisalt == salt
134
+ log(level: 2) { "message has been verified" }
127
135
  end
128
136
 
129
137
  def must_revalidate?
@@ -4,6 +4,8 @@ module NETSNMP
4
4
  # Let's just remind that there is no session in snmp, this is just an abstraction.
5
5
  #
6
6
  class Session
7
+ prepend Loggable
8
+
7
9
  TIMEOUT = 2
8
10
 
9
11
  # @param [Hash] opts the options set
@@ -36,9 +38,16 @@ module NETSNMP
36
38
  # @return [NETSNMP::PDU] the response pdu
37
39
  #
38
40
  def send(pdu)
41
+ log { "sending request..." }
42
+ log(level: 2) { pdu.to_hex }
39
43
  encoded_request = pdu.to_der
44
+ log { Hexdump.dump(encoded_request) }
40
45
  encoded_response = @transport.send(encoded_request)
41
- PDU.decode(encoded_response)
46
+ log { "received response" }
47
+ log { Hexdump.dump(encoded_response) }
48
+ response_pdu = PDU.decode(encoded_response)
49
+ log(level: 2) { response_pdu.to_hex }
50
+ response_pdu
42
51
  end
43
52
 
44
53
  private
@@ -66,7 +75,7 @@ module NETSNMP
66
75
 
67
76
  def initialize(host, port, timeout:)
68
77
  @socket = UDPSocket.new
69
- @socket.connect(host, port)
78
+ @destaddr = Socket.sockaddr_in(port, host)
70
79
  @timeout = timeout
71
80
  end
72
81
 
@@ -81,13 +90,13 @@ module NETSNMP
81
90
 
82
91
  def write(payload)
83
92
  perform_io do
84
- @socket.send(payload, 0)
93
+ @socket.sendmsg(payload, Socket::MSG_DONTWAIT | Socket::MSG_NOSIGNAL, @destaddr)
85
94
  end
86
95
  end
87
96
 
88
97
  def recv(bytesize = MAXPDUSIZE)
89
98
  perform_io do
90
- datagram, = @socket.recvfrom_nonblock(bytesize)
99
+ datagram, = @socket.recvmsg_nonblock(bytesize, Socket::MSG_DONTWAIT)
91
100
  datagram
92
101
  end
93
102
  end
@@ -8,6 +8,7 @@ module NETSNMP
8
8
  @context = context
9
9
  @security_parameters = opts.delete(:security_parameters)
10
10
  super
11
+ @message_serializer = Message.new(**opts)
11
12
  end
12
13
 
13
14
  # @see {NETSNMP::Session#build_pdu}
@@ -20,10 +21,11 @@ module NETSNMP
20
21
 
21
22
  # @see {NETSNMP::Session#send}
22
23
  def send(pdu)
24
+ log { "sending request..." }
23
25
  encoded_request = encode(pdu)
24
26
  encoded_response = @transport.send(encoded_request)
25
- pdu, = decode(encoded_response)
26
- pdu
27
+ response_pdu, * = decode(encoded_response)
28
+ response_pdu
27
29
  end
28
30
 
29
31
  private
@@ -60,7 +62,8 @@ module NETSNMP
60
62
  report_sec_params = SecurityParameters.new(security_level: 0,
61
63
  username: @security_parameters.username)
62
64
  pdu = ScopedPDU.build(:get, headers: [])
63
- encoded_report_pdu = Message.encode(pdu, security_parameters: report_sec_params)
65
+ log { "sending probe..." }
66
+ encoded_report_pdu = @message_serializer.encode(pdu, security_parameters: report_sec_params)
64
67
 
65
68
  encoded_response_pdu = @transport.send(encoded_report_pdu)
66
69
 
@@ -69,13 +72,39 @@ module NETSNMP
69
72
  end
70
73
 
71
74
  def encode(pdu)
72
- Message.encode(pdu, security_parameters: @security_parameters,
73
- engine_boots: @engine_boots,
74
- engine_time: @engine_time)
75
+ @message_serializer.encode(pdu, security_parameters: @security_parameters,
76
+ engine_boots: @engine_boots,
77
+ engine_time: @engine_time)
75
78
  end
76
79
 
77
80
  def decode(stream, security_parameters: @security_parameters)
78
- Message.decode(stream, security_parameters: security_parameters)
81
+ return_pdu = @message_serializer.decode(stream, security_parameters: security_parameters)
82
+
83
+ pdu, *args = return_pdu
84
+
85
+ # usmStats: http://oidref.com/1.3.6.1.6.3.15.1.1
86
+ if pdu.type == 8
87
+ case pdu.varbinds.first.oid
88
+ when "1.3.6.1.6.3.15.1.1.1.0" # usmStatsUnsupportedSecLevels
89
+ raise Error, "Unsupported security level"
90
+ when "1.3.6.1.6.3.15.1.1.2.0" # usmStatsNotInTimeWindows
91
+ _, @engine_boots, @engine_time = args
92
+ raise IdNotInTimeWindowError, "Not in time window"
93
+ when "1.3.6.1.6.3.15.1.1.3.0" # usmStatsUnknownUserNames
94
+ raise Error, "Unknown user name"
95
+ when "1.3.6.1.6.3.15.1.1.4.0" # usmStatsUnknownEngineIDs
96
+ raise Error, "Unknown engine ID" unless @security_parameters.must_revalidate?
97
+ when "1.3.6.1.6.3.15.1.1.5.0" # usmStatsWrongDigests
98
+ raise Error, "Authentication failure (incorrect password, community or key)"
99
+ when "1.3.6.1.6.3.15.1.1.6.0" # usmStatsDecryptionErrors
100
+ raise Error, "Decryption error"
101
+ end
102
+ end
103
+
104
+ # validate_authentication
105
+ @message_serializer.verify(stream, pdu.auth_param, pdu.security_level, security_parameters: @security_parameters)
106
+
107
+ return_pdu
79
108
  end
80
109
  end
81
110
  end
@@ -4,6 +4,8 @@ module NETSNMP
4
4
  # Abstracts the PDU variable structure into a ruby object
5
5
  #
6
6
  class Varbind
7
+ using StringExtensions
8
+
7
9
  attr_reader :oid, :value
8
10
 
9
11
  def initialize(oid, value: nil, type: nil)
@@ -48,18 +50,15 @@ module NETSNMP
48
50
  def convert_val(asn_value)
49
51
  case asn_value
50
52
  when OpenSSL::ASN1::OctetString
51
- # yes, we are forcing all output to UTF-8
53
+ val = asn_value.value
54
+
52
55
  # it's kind of common in snmp, some stuff can't be converted,
53
56
  # like Hexa Strings. Parse them into a readable format a la netsnmp
54
- val = asn_value.value
55
- begin
56
- val.encode("UTF-8")
57
- rescue Encoding::UndefinedConversionError,
58
- Encoding::ConverterNotFoundError,
59
- Encoding::InvalidByteSequenceError
60
- # hexdump me!
61
- val.unpack("H*")[0].upcase.scan(/../).join(" ")
62
- end
57
+ # https://github.com/net-snmp/net-snmp/blob/ed90aaaaea0d9cc6c5c5533f1863bae598d3b820/snmplib/mib.c#L650
58
+ is_hex_string = val.each_char.any? { |c| !c.match?(/[[:print:]]/) && !c.match?(/[[:space:]]/) }
59
+
60
+ val = HexString.new(val) if is_hex_string
61
+ val
63
62
  when OpenSSL::ASN1::Primitive
64
63
  val = asn_value.value
65
64
  val = val.to_i if val.is_a?(OpenSSL::BN)
@@ -81,11 +80,11 @@ module NETSNMP
81
80
  when :ipaddress then 0
82
81
  when :counter32
83
82
  asn_val = [value].pack("N*")
84
- asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack("B").first != "1"
83
+ asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack1("B") != "1"
85
84
  1
86
85
  when :gauge
87
86
  asn_val = [value].pack("N*")
88
- asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack("B").first != "1"
87
+ asn_val = asn_val[1..-1] while asn_val[0] == "\x00".b && asn_val[1].unpack1("B") != "1"
89
88
  2
90
89
  when :timetick
91
90
  return Timetick.new(value).to_asn
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NETSNMP
4
- VERSION = "0.2.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/sig/loggable.rbs ADDED
@@ -0,0 +1,16 @@
1
+ module NETSNMP
2
+ module Loggable
3
+ interface _Debugger
4
+ def <<: (string) -> void
5
+ end
6
+
7
+ DEBUG_LEVEL: Integer
8
+ DEBUG: _Debugger
9
+
10
+ private
11
+
12
+ def initialize: (?debug: _DEBUGGER, ?debug_level: Integer, **untyped) -> void
13
+
14
+ def log: (?level: Integer) { () -> String } -> void
15
+ end
16
+ end
data/sig/message.rbs CHANGED
@@ -1,7 +1,11 @@
1
1
  module NETSNMP
2
- module Message
3
- def self?.decode: (String stream, security_parameters: SecurityParameters) -> [ScopedPDU, String, Integer, Integer]
2
+ class Message
3
+ prepend Loggable
4
4
 
5
- def self?.encode: (ScopedPDU pdu, security_parameters: SecurityParameters, ?engine_boots: Integer, ?engine_time: Integer) -> String
5
+ def verify: (String stream, String auth_param, Integer? security_level, security_parameters: SecurityParameters) -> void
6
+
7
+ def decode: (String stream, security_parameters: SecurityParameters) -> [ScopedPDU, String, Integer, Integer]
8
+
9
+ def encode: (ScopedPDU pdu, security_parameters: SecurityParameters, ?engine_boots: Integer, ?engine_time: Integer) -> String
6
10
  end
7
11
  end
data/sig/mib.rbs ADDED
@@ -0,0 +1,21 @@
1
+ module NETSNMP
2
+ module MIB
3
+ type import = {ids: Array[{name: string}], name: string} | {ids: {name: string}, name: string}
4
+
5
+
6
+ MIBDIRS: Array[String]
7
+ PARSER: Parser
8
+
9
+ @parser_mutex: Mutex
10
+ @modules_loaded: Array[String]
11
+ @object_identifiers: Hash[String, String]
12
+
13
+ def self?.oid: (String identifier) -> String?
14
+ | (Array[_ToS] identifier) -> String?
15
+
16
+ def self?.load: (String mod) -> void
17
+
18
+ def self?.load_imports: ((Array[import] | import)? data) -> Hash[String, Array[String]]?
19
+ def self?.load_defaults: () -> void
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module NETSNMP
2
+ module MIB
3
+ class Parser
4
+
5
+ end
6
+ end
7
+ end
data/sig/netsnmp.rbs CHANGED
@@ -16,8 +16,4 @@ module NETSNMP
16
16
  end
17
17
 
18
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
19
  end
data/sig/oid.rbs CHANGED
@@ -1,5 +1,5 @@
1
1
  module NETSNMP
2
- type oid = String | Array[Integer]
2
+ type oid = String | Array[_ToS]
3
3
 
4
4
  type oid_type = Integer | :ipaddress | :counter32 | :gauge | :timetick | :opaque | :nsap | :counter64 | :uinteger
5
5
 
data/sig/pdu.rbs CHANGED
@@ -12,7 +12,7 @@ module NETSNMP
12
12
  # }
13
13
 
14
14
  attr_reader varbinds: Array[Varbind]
15
- attr_reader type: pdu_type
15
+ attr_reader type: Integer
16
16
  attr_reader version: snmp_version
17
17
  attr_reader community: String
18
18
  attr_reader request_id: Integer