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.
- checksums.yaml +4 -4
- data/README.md +45 -6
- data/lib/netsnmp.rb +3 -33
- data/lib/netsnmp/client.rb +2 -2
- data/lib/netsnmp/encryption/aes.rb +1 -3
- data/lib/netsnmp/encryption/des.rb +0 -2
- data/lib/netsnmp/errors.rb +1 -0
- data/lib/netsnmp/extensions.rb +113 -0
- data/lib/netsnmp/loggable.rb +36 -0
- data/lib/netsnmp/message.rb +78 -30
- data/lib/netsnmp/mib.rb +172 -0
- data/lib/netsnmp/mib/parser.rb +750 -0
- data/lib/netsnmp/oid.rb +4 -10
- data/lib/netsnmp/pdu.rb +20 -9
- data/lib/netsnmp/scoped_pdu.rb +8 -2
- data/lib/netsnmp/security_parameters.rb +19 -11
- data/lib/netsnmp/session.rb +13 -4
- data/lib/netsnmp/v3_session.rb +36 -7
- data/lib/netsnmp/varbind.rb +11 -12
- data/lib/netsnmp/version.rb +1 -1
- data/sig/loggable.rbs +16 -0
- data/sig/message.rbs +7 -3
- data/sig/mib.rbs +21 -0
- data/sig/mib/parser.rbs +7 -0
- data/sig/netsnmp.rbs +0 -4
- data/sig/oid.rbs +1 -1
- data/sig/pdu.rbs +1 -1
- data/sig/scoped_pdu.rbs +2 -0
- data/sig/security_parameters.rbs +4 -2
- data/sig/session.rbs +2 -0
- data/spec/client_spec.rb +36 -6
- data/spec/mib_spec.rb +13 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/celluloid.rb +11 -7
- data/spec/varbind_spec.rb +5 -3
- metadata +34 -11
data/lib/netsnmp/oid.rb
CHANGED
@@ -10,16 +10,10 @@ module NETSNMP
|
|
10
10
|
|
11
11
|
module_function
|
12
12
|
|
13
|
-
def build(
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
[
|
117
|
-
|
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
|
data/lib/netsnmp/scoped_pdu.rb
CHANGED
@@ -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
|
-
[
|
16
|
-
|
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
|
-
[
|
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
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
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?
|
data/lib/netsnmp/session.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
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.
|
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.
|
99
|
+
datagram, = @socket.recvmsg_nonblock(bytesize, Socket::MSG_DONTWAIT)
|
91
100
|
datagram
|
92
101
|
end
|
93
102
|
end
|
data/lib/netsnmp/v3_session.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
data/lib/netsnmp/varbind.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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].
|
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].
|
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
|
data/lib/netsnmp/version.rb
CHANGED
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
|
-
|
3
|
-
|
2
|
+
class Message
|
3
|
+
prepend Loggable
|
4
4
|
|
5
|
-
def
|
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
|
data/sig/mib/parser.rbs
ADDED
data/sig/netsnmp.rbs
CHANGED
data/sig/oid.rbs
CHANGED