From c94a9793ef263d2dc94300bba07683c2d7db642c Mon Sep 17 00:00:00 2001 From: Ruben Sanchez Diez Date: Thu, 2 Jul 2026 13:11:36 +0200 Subject: [PATCH] Fix SNMPv3 advance cached authoritative engineTime with the local clock The USM engineTime was frozen at the value learned during discovery and never advanced, so after ~150s of wall-clock drift the agent rejected requests with usmStatsNotInTimeWindows. The single per-request resync retry could not recover on agents with an erratic clock, leaving the session permanently broken until restart. Track the authoritative (engineBoots, engineTime) plus a monotonic timestamp when learned, and advance engineTime by the elapsed seconds on each outgoing v3 request (RFC 3414 section 2.3). Re-anchor from every authenticated message (Report and GetResponse), not just Reports, with a forward-only guard so reordered/delayed responses cannot rewind time. Handle the 31-bit engineTime rollover, and only anchor from authenticated sessions. --- index.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/index.js b/index.js index 8cf5fb9..dcc7ee2 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ const util = require ("util"); const crypto = require ("crypto"); const mibparser = require ("./lib/mib"); const Buffer = require('buffer').Buffer; +const { performance } = require ('perf_hooks'); var DEBUG = false; var STRICT_INT_RANGE_CHECKS = false; @@ -2439,6 +2440,7 @@ Session.prototype.onMsg = function (buffer) { msgAuthoritativeEngineBoots: message.msgSecurityParameters.msgAuthoritativeEngineBoots, msgAuthoritativeEngineTime: message.msgSecurityParameters.msgAuthoritativeEngineTime }; + this.setEngineTime (message); if ( this.proxy ) { this.msgSecurityParameters.msgUserName = this.proxy.user.name; this.msgSecurityParameters.msgAuthenticationParameters = ""; @@ -2459,6 +2461,7 @@ Session.prototype.onMsg = function (buffer) { } else if ( this.proxy ) { this.onProxyResponse (req, message); } else if (message.pdu.type == PduType.GetResponse) { + this.setEngineTime (message); req.onResponse (req, message); } else { req.responseCb (new ResponseInvalidError ("Unknown PDU type '" @@ -2974,7 +2977,43 @@ Session.prototype.walk = function () { return this; }; +Session.prototype.setEngineTime = function (message) { + var params = message.msgSecurityParameters; + if (!params) + return; + if (!this.user || this.user.level < SecurityLevel.authNoPriv) + return; + var boots = params.msgAuthoritativeEngineBoots; + if (this.engineTimeBoots != null && boots < this.engineTimeBoots) + return; + var time = params.msgAuthoritativeEngineTime; + var now = performance.now (); + var elapsedSeconds = Math.floor ((now - this.engineTimeReceivedAt) / 1000); + var estimate = this.engineTimeBase + elapsedSeconds; + if (this.engineTimeBoots != null && boots == this.engineTimeBoots && time <= estimate) + return; + this.engineTimeBase = time; + this.engineTimeBoots = boots; + this.engineTimeReceivedAt = now; +}; + +Session.prototype.advanceEngineTime = function () { + if (!this.msgSecurityParameters || this.engineTimeReceivedAt == null) + return; + var elapsedSeconds = Math.floor ((performance.now () - this.engineTimeReceivedAt) / 1000); + var totalSeconds = this.engineTimeBase + elapsedSeconds; + var boots = this.engineTimeBoots; + if (totalSeconds > MAX_SIGNED_INT32) { + var rollovers = Math.floor (totalSeconds / (MAX_SIGNED_INT32 + 1)); + totalSeconds = totalSeconds % (MAX_SIGNED_INT32 + 1); + boots = Math.min (boots + rollovers, MAX_SIGNED_INT32); + } + this.msgSecurityParameters.msgAuthoritativeEngineTime = totalSeconds; + this.msgSecurityParameters.msgAuthoritativeEngineBoots = boots; +}; + Session.prototype.sendV3Req = function (pdu, feedCb, responseCb, options, port, allowReport) { + this.advanceEngineTime (); var message = Message.createRequestV3 (this.user, this.msgSecurityParameters, pdu); var reqOptions = options || {}; var req = new Req (this, message, feedCb, responseCb, reqOptions);