Repository navigation
Conversation
ilyam8
added this pull request to stack #38
October 9, 2026 12:00
A fake authoritative SNMPv3 engine answers the client in the engine harness. It reads each request with its own parser, checks it with the library's digests and ciphers in the order of RFC 3414 section 3.2 (engine ID, user, security level, digest, time window, decryption) and answers with a GetResponse at the request's security level or with a Report at the level the RFC gives it. Its engine time runs on the bubble's clock; it can reboot or change its engine ID, and scenarios can script any answer. testdata/engine/v3.golden pins, known bugs included: discovery and the request at each security level, a known engine ID with and without its time, the context engine ID and name, an agent that reboots or changes its engine ID, requests after 150 s of silence, a wrong passphrase, unknown users, an unsupported level, every Report counter for a noAuthNoPriv and an authNoPriv user, and answers that do not match the request (request ID, msgID, security level, privacy key, security model, an empty identity). Because the agent verifies request digests with keys localized to its own engine ID, the net catches a client that does not re-key after discovery, does not adopt the context engine ID, or checks a reply with the wrong parameters. parseV3Message, factored out of splitV3Message, gives the agent and the transcripts the same independent reading of a message.
The fake SNMPv3 agent now behaves like net-snmp's snmpd where the client's engine depends on it: - it drops what RFC 3412 section 7.2 discards before USM (another version or security model, a msgMaxSize outside 484..2^31-1, privacy without authentication) and a scoped PDU that does not decode, so a wrong privacy passphrase gets no answer instead of a decryptionError Report; - a GetResponse carries the request's context (the agent's engine ID when the request has none) and a Report the agent's engine ID and the default context (RFC 3412 section 7.1 step 3 d); - the digest is checked only on minimally encoded messages, the only ones it re-serializes exactly. The transcripts show what mutants of the engine changed without a trace: the salt of every message, a security model or msgMaxSize that differs from the library's, the flags an answer was actually sent with, and the engine ID the client's keys are localized to after each scenario. New scenarios: a resynchronization answered with notInTimeWindow again and an unknownEngineID Report that repeats (both returned with a nil error), an unanswered retransmission after unknownEngineID, an engine-ID change for a noAuth user (the context engine ID keeps the old engine's), a GetResponse from the new engine to a request for the old one (accepted and adopted), error Reports 10 s after discovery (authenticated, and unauthenticated from another engine ID), a wrong privacy passphrase (AES and DES), discovery Reports with another security model, another or no context engine ID, replies in another context, for another user, encrypted to an authNoPriv request, or with an empty msgFlags (accepted with the request's flags), and an error Report with two varbinds. Notes: an authNoPriv reply to an authPriv request is accepted (RFC 3412 section 7.2 step 12 b); discarding an unauthenticated notInTimeWindow Report conforms to RFC 3414 section 3.2 step 7 and is no longer named a bug; a Report for another message keeps both IDs stale, as RFC 3412 correlates Reports by msgID; the time-window note cites RFC 3414 section 3.1 step 6 a.
The fake SNMPv3 agent follows snmpd in the cases the second review compared: - the digest is checked over the message as received, with the digest field zeroed (RFC 3414 section 3.2 step 6), so non-minimal encodings are checked instead of dropped; - it decrypts only when the privacy flag is set, answers decryptionErrors to a privacy flag without a ciphertext or an 8-octet salt, and drops a ciphertext without the flag; - it sends no Report to a request that is not reportable and whose PDU it cannot read (RFC 3412 section 7.1 step 3 b), and drops a msgID outside 0..2^31-1. New scenarios, each pinning a change a mutant or a variant of the REPORT resync port made without a trace: authenticated Reports with a wrong digest (discarded), an authPriv client answered from a new engine ID (privacy key re-derived), every Report counter unauthenticated to an authNoPriv client, authentic replies with an older engine time or lower boots (adopted), a Report with another security model after discovery (acted on), a discovery Report with an empty msgFlags (accepted), and an AES client talking to a DES user (the unauthenticated decryptionErrors Report is discarded). The scripted Reports are ones an agent can send: the authenticated error Report 10 s after discovery is snmpUnknownContexts, and the unknownEngineID Reports and the late unauthenticated Report come from the other engine ID in its context.
stelfrag
approved these changes
Oct 11, 2026
vkalintiris
approved these changes
Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second safety net for the request engine: what it does with an SNMPv3 agent, known bugs included, so the decomposition of
sendOneRequest/send/v3.goand the later port of the REPORT resynchronization change are measured against it. Test files only, no library change.engine_v3_agent_test.go:fakeV3Agent, an authoritative SNMPv3 engine on the harness of the previous PR. It reads requests with its own parser, drops what RFC 3412 discards (another version or security model, a msgID or msgMaxSize out of range, privacy without authentication, a scoped PDU that does not decode, so a wrong privacy passphrase gets no answer as with net-snmp's snmpd, and Reports to unreadable requests that are not reportable), and checks the rest with the library's digests and ciphers in RFC 3414 section 3.2 order: the digest over the message as received, decryption when the privacy flag is set. It answers with a GetResponse at the request's security level and in its context, or with a Report at the RFC's level in the default context. Its engine time follows the test clock; it can reboot, change its engine ID and script any answer.engine_v3_test.go->testdata/engine/v3.golden(90 scenarios): discovery at every security level (no answer, another Report, the unknownUserNames device workaround, preset context engine ID, context name, Reports with another model, another or no context engine ID, an empty msgFlags), a known engine ID, reboots, engine-ID changes (noAuth, authNoPriv and authPriv users, replies from the new engine), the 150 s time window, older engine time and lower boots, resynchronizations that fail again, wrong authentication and privacy passphrases, a privacy protocol mismatch, unknown users, unsupported levels, every Report counter unauthenticated and authenticated, Reports after discovery, forged Reports, and mismatched answers (stale msgID or request ID, unauthenticated, unencrypted, encrypted with another key or to an authNoPriv request, another context, user or security model, an empty msgFlags, Reports with two varbinds).Known bugs pinned with
known bug:notes include: unauthenticated Reports (unknownEngineID, wrongDigest, unknownUserName, unsupportedSecLevel) discarded under an authenticated session, so the caller never sees those errors; a notInTimeWindow or unknownEngineID Report that repeats after the retransmission returned with a nil error; retransmission errors replaced byErrNotInTimeWindow/ErrUnknownEngineID; the engine time of the last message sent instead of the current one (RFC 3414 section 3.1 step 6 a); the reply's msgID, context, user, security level and engine ID not compared with the request's (RFC 3412 section 7.2 step 12); the context engine ID kept after an engine-ID change; the engine time of an authenticated error Report not stored; an older engine time or lower boots adopted (RFC 3414 section 3.2 step 7 b); replies and Reports with another security model or an empty msgFlags acted on instead of discarded; a Report with two varbinds returned as a success.Testing
go test ./...on darwin/arm64 andGOARCH=386;-race;-shuffle=on; 100 consecutive runs and-cpu 1,8,64identical; golangci-lint v2.14.0: 0 issues.sendOneRequest91.1% -> 99.4%,send64.0% -> 92.0%,negotiateInitialSecurityParameters71.4% -> 85.7%,storeSecurityParameters71.4% -> 85.7%; package 85.2% -> 87.1%.