SnmpKit v1.4.0 Release Notes
Release Date: September 2026 Version: 1.4.0 Previous Version: 1.3.24
Overview
v1.4.0 is the final 1.x line. It works through every issue filed against v1.3.24 (#4 through #27) that could be fixed without renaming or removing public modules and functions, rounds out the simulator's configuration API, and makes the code base lint-clean so a breaking 2.0 can start from a stable base. Where a fix changes observable behaviour, it does so to match the SNMP RFCs or to make an existing option work as documented.
Highlights
Protocol correctness (SnmpLib)
- OID BER encoding/decoding follows X.690 8.19 for the first two arcs, so
enterprise trees under
2.x(e.g.2.999) round-trip instead of being rejected or decoded as4.20(#4). - Malformed PDUs and varbinds decode to
{:error, {:pdu_parse_error, ...}}instead of an empty varbind list that walks interpreted as end-of-MIB. Well-formed values with an unknown tag are kept as{:unknown, {tag, bytes}}(#5). - Compiled MIB files are deserialised with
:safeand a size cap (#19).
SNMPv3 rewritten to RFC 3414 / 3826 / 7860 (#6, #7, #8, #9)
- The HMAC is computed over the entire transmitted message with a zeroed
msgAuthenticationParametersfield, and verified over the bytes actually received. SHA-2 MAC lengths are 16/24/32/48 per RFC 7860. - DES-CBC and AES-CFB128 use RFC-derived IVs (
engineBoots/engineTimeplus an 8-octet salt carried inmsgPrivacyParameters); CFB is no longer padded. - Password-to-key streams exactly 1 MiB; privacy keys are derived with the user's authentication hash and extended (Reeder, or Blumenthal on request) when the cipher needs more material. The RFC 3414 A.3 test vectors pass.
- The USM incoming path works against the real decoder,
synchronize_timereads realengineBoots/engineTime, and the RFC 3414 timeliness window is enforced.
Compatibility: snmpkit 1.3.x and 1.4.0 SNMPv3 traffic does not
interoperate; 1.4.0 interoperates with RFC-compliant stacks. DES privacy keys
are now 16 octets. SnmpKit.SnmpLib.Security.Keys.secure_wipe/1 is
deprecated (it was always a no-op).
Manager robustness (SnmpMgr)
Engineanswers each request exactly once, cancels timers, monitors callers, closes its sockets on shutdown, recycles pool connections and has an opt-in per-target circuit breaker. TheGenServer.calldeadline now exceeds the request timeout (they were equal) (#10, #11).Routerruns routed work in monitored workers so an engine crash no longer takes the router down, tracks real load, and never mints atoms from engine names (#12).Configand a service supervisor are part of the application tree; the concurrent manager services start supervised instead of linked to the first caller, which removes the intermittentSocketManager :noprocfailures.Metricsmonitors its subscribers (#13).CircuitBreaker.call/4runs the work in the caller, so one slow target no longer blocks the others; timeouts and crashes are reported separately (#14).SocketManagerhealth compares like units and sheds load past a configurable queue depth (#15).MultiV2fails only the requests whose own deadline passed;EngineV2drops registrations of dead callers;V2Walkno longer re-sends a PDU on a stray message (#16).- The
:portoption is honoured on every request path; several paths had been overriding it with 161.
Simulator (SnmpSim)
- New
SnmpKit.SnmpSimtop-level API: start device groups from a configuration map or JSON/YAML file, list, count and stop devices. Device groups pass their walk file through to the devices. - SNMPv1 and SNMPv2c end-of-MIB semantics are correct for each version
(noSuchName error vs. endOfMibView exception); the old code compared an
integer with an atom and ran the inverted branch for every request (#21).
As a consequence an SNMPv2c GET of an unknown object now yields
{:error, :no_such_object}rather than:no_such_name. - GETBULK honours
max-repetitionsexactly as requested on every path (the walk path used to clamp it to 50) and stops with a single endOfMibView instead of padding. - Per-packet workers are bounded, oversized datagrams are dropped, and handler crashes answer genErr (#17).
- No atoms are created from device types, type names or JSON keys; input
files go through
SnmpKit.SnmpSim.SafeFilewith a size cap and optional directory jail (:snmpkit, :input_roots) (#18, #20). sysUpTimeis real on every path, counters are monotonic and reproducible (deterministic per-device factors, exact Counter32/64 wrap), GETBULK sees the real device uptime, and noIO.inspectremains inlib/(#22, #24).SharedProfilesserves reads straight from ETS with an ordered OID index instead of aGenServer.callplus a full table sort per GETNEXT (#23).
Documentation, tests, tooling
SnmpMgr.Walkdocuments the enriched-map result shape, and the SNMPv1 walk path returns maps like the GETBULK path (#25).- Leftover
test/walk_*debug files were consolidated or removed; thelsof/kill -9sweep intest_helper.exsis gone; wall-clock performance assertions are opt-in via:performance(#26). .credo.exs, amix lintalias,@specs on the core SnmpMgr modules, complete HexDocs module groups, and a dialyzer baseline that passes (#27). CI checks formatting and compiles with warnings as errors.
Upgrade notes
- SNMPv3 users must run 1.4.0 on both ends or against RFC-compliant peers.
- Code matching
{:error, :no_such_name}from SNMPv2c GETs should also accept{:error, :no_such_object}. - Code consuming
SnmpKit.SnmpMgr.Walk.walk/3directly withversion: :v1now receives enriched maps. Engine.submit_request/3waits one second longer than the request:timeout; pass:call_timeoutto override.- Simulator GETBULK responses can now be as large as the manager asks for.
Deferred to 2.0
Canonical Engine/Multi/MIB/Config implementations, splitting the
oversized modules, EngineV2 owning the UDP socket, and removing the
deprecated Keys.secure_wipe/1 all rename or remove public API and are
scheduled for the 2.x branch.