@@ -81,25 +81,11 @@ class SNMP::Client
8181 end
8282
8383 private def get (oid : String , sock : UDPSocket ) : SNMP ::Message
84- check_validation_probe(sock)
85-
86- message = session.get(oid)
87- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
88-
89- sock.write_bytes message
90- sock.flush
91- session.parse(sock.read_bytes(ASN1 ::BER ))
84+ request(sock) { session.get(oid) }
9285 end
9386
9487 private def get (oids : Enumerable (String ), sock : UDPSocket ) : SNMP ::Message
95- check_validation_probe(sock)
96-
97- message = session.get(oids)
98- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
99-
100- sock.write_bytes message
101- sock.flush
102- session.parse(sock.read_bytes(ASN1 ::BER ))
88+ request(sock) { session.get(oids) }
10389 end
10490
10591 def get_next (oid : String ) : SNMP ::Message
@@ -126,25 +112,11 @@ class SNMP::Client
126112 end
127113
128114 private def get_next (oid : String , sock : UDPSocket ) : SNMP ::Message
129- check_validation_probe(sock)
130-
131- message = session.get_next(oid)
132- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
133-
134- sock.write_bytes message
135- sock.flush
136- session.parse(sock.read_bytes(ASN1 ::BER ))
115+ request(sock) { session.get_next(oid) }
137116 end
138117
139118 private def get_next (oids : Enumerable (String ), sock : UDPSocket ) : SNMP ::Message
140- check_validation_probe(sock)
141-
142- message = session.get_next(oids)
143- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
144-
145- sock.write_bytes message
146- sock.flush
147- session.parse(sock.read_bytes(ASN1 ::BER ))
119+ request(sock) { session.get_next(oids) }
148120 end
149121
150122 # GetBulk: one round-trip returning up to *max_repetitions* successors per
@@ -160,14 +132,7 @@ class SNMP::Client
160132 end
161133
162134 private def get_bulk (oids : Enumerable (String ), sock : UDPSocket , non_repeaters, max_repetitions) : SNMP ::Message
163- check_validation_probe(sock)
164-
165- message = session.get_bulk(oids, non_repeaters, max_repetitions)
166- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
167-
168- sock.write_bytes message
169- sock.flush
170- session.parse(sock.read_bytes(ASN1 ::BER ))
135+ request(sock) { session.get_bulk(oids, non_repeaters, max_repetitions) }
171136 end
172137
173138 # Set a single OID to *value* (a typed SNMP value, a Crystal primitive, or a
@@ -194,25 +159,11 @@ class SNMP::Client
194159 end
195160
196161 private def set (oid : String , value , sock : UDPSocket ) : SNMP ::Message
197- check_validation_probe(sock)
198-
199- message = session.set(oid, value)
200- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
201-
202- sock.write_bytes message
203- sock.flush
204- session.parse(sock.read_bytes(ASN1 ::BER ))
162+ request(sock) { session.set(oid, value) }
205163 end
206164
207165 private def set (values : Hash (String , _ ), sock : UDPSocket ) : SNMP ::Message
208- check_validation_probe(sock)
209-
210- message = session.set(values)
211- message = session.prepare(message) if message.is_a?(SNMP ::V3 ::Message )
212-
213- sock.write_bytes message
214- sock.flush
215- session.parse(sock.read_bytes(ASN1 ::BER ))
166+ request(sock) { session.set(values) }
216167 end
217168
218169 def walk (oid : String ) : Array (SNMP ::Message )
@@ -290,4 +241,38 @@ class SNMP::Client
290241 session.validate sock.read_bytes(ASN1 ::BER )
291242 end
292243 end
244+
245+ # Build a fresh request, send it, and return the parsed response — transparently
246+ # recovering from a recoverable v3 usmStats Report (notInTimeWindow /
247+ # unknownEngineID) by resyncing the engine params and retrying exactly once.
248+ # A non-recoverable Report, or a Report that survives the retry, raises
249+ # `Security::ReportError`. The block is re-invoked on retry so the rebuilt
250+ # request carries the freshly synced engine boots/time/id.
251+ private def request (sock , & build : - > SNMP ::Message ) : SNMP ::Message
252+ check_validation_probe(sock)
253+ response = transceive(sock, build.call)
254+
255+ sess = session
256+ if response.is_a?(SNMP ::V3 ::Message ) && response.report? && sess.is_a?(SNMP ::V3 ::Session )
257+ stat = response.usm_stat
258+ if stat.try(& .resyncable?)
259+ sess.resync_from(response)
260+ response = transceive(sock, build.call)
261+ end
262+
263+ if response.is_a?(SNMP ::V3 ::Message ) && response.report?
264+ raise SNMP ::V3 ::Security ::ReportError .new(
265+ " agent returned a usmStats Report (#{ response.usm_stat || " unknown" } )" , response.usm_stat)
266+ end
267+ end
268+
269+ response
270+ end
271+
272+ private def transceive (sock , message : SNMP ::Message ) : SNMP ::Message
273+ payload = message.is_a?(SNMP ::V3 ::Message ) ? session.prepare(message) : message
274+ sock.write_bytes payload
275+ sock.flush
276+ session.parse(sock.read_bytes(ASN1 ::BER ))
277+ end
293278end
0 commit comments