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 as 4.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 :safe and 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 msgAuthenticationParameters field, 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/engineTime plus an 8-octet salt carried in msgPrivacyParameters); 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_time reads real engineBoots/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)

  • Engine answers 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. The GenServer.call deadline now exceeds the request timeout (they were equal) (#10, #11).
  • Router runs 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).
  • Config and 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 intermittent SocketManager :noproc failures. Metrics monitors its subscribers (#13).
  • CircuitBreaker.call/4 runs the work in the caller, so one slow target no longer blocks the others; timeouts and crashes are reported separately (#14).
  • SocketManager health compares like units and sheds load past a configurable queue depth (#15).
  • MultiV2 fails only the requests whose own deadline passed; EngineV2 drops registrations of dead callers; V2Walk no longer re-sends a PDU on a stray message (#16).
  • The :port option is honoured on every request path; several paths had been overriding it with 161.

Simulator (SnmpSim)

  • New SnmpKit.SnmpSim top-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-repetitions exactly 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.SafeFile with a size cap and optional directory jail (:snmpkit, :input_roots) (#18, #20).
  • sysUpTime is real on every path, counters are monotonic and reproducible (deterministic per-device factors, exact Counter32/64 wrap), GETBULK sees the real device uptime, and no IO.inspect remains in lib/ (#22, #24).
  • SharedProfiles serves reads straight from ETS with an ordered OID index instead of a GenServer.call plus a full table sort per GETNEXT (#23).

Documentation, tests, tooling

  • SnmpMgr.Walk documents 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; the lsof/kill -9 sweep in test_helper.exs is gone; wall-clock performance assertions are opt-in via :performance (#26).
  • .credo.exs, a mix lint alias, @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/3 directly with version: :v1 now receives enriched maps.
  • Engine.submit_request/3 waits one second longer than the request :timeout; pass :call_timeout to 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.