Hi,
I've been using Arduino_SNMP in ESP8266/ESP-01 projects for years now and i started to get deep into the code along the way. I've been maintaining an improved version in my fork and wanted to give you a heads-up. If you're interested, I'm happy to open a pull request — one big cumulative PR. And no pressure at all: if upstream would rather stay as-is, the fork lives on independently and is there for anyone who wants it:
https://github.com/syntax1269/Arduino_SNMP/tree/v3.3.1
This is a fully-compliant SNMPv2c Agent built for Arduino's, but will work on any OS, providing API code is written for packet serialization (See tests/mock.cpp for an example)
Current Version: 3.3.1
New since v3.1.5: a hardware-validated reliability campaign (v3.1.23–v3.1.25: pool double-release fix, loud tooBig rejection of over-cap GetBulk, pool-baseline freeze, 17 KB slot right-size), v3.2.0 derived pool sizing (the ASN pool auto-sizes at compile time from the number of handlers your sketch registers — no more one-size-fits-all arena), and v3.3.0 boot-time lock-in (the arena is claimed in the SNMPAgent constructor, before setup() and WiFi, so the heap can never fragment it).
Features
- Full SNMPv2c Data Type support:
- INTEGER
int
- STRING
char[] / const char* (C-style strings, no std::string or Arduino String)
- NULLTYPE
- OIDTYPE
const char* (dotted-decimal, e.g. ".1.3.6.1.4.1.5.0")
- Complex data type support:
- NETWORK ADDRESS
- COUNTER32
uint32_t
- GAUGE32
uint32_t
- TIMESTAMP
uint32_t
- OPAQUE
uint8_t*
- COUNTER64
uint64_t
- SNMP PDU Support
- GetRequest
- GetNextRequest
- GetResponse (For SNMPv2c INFORM Responses only for now)
- SetRequest
- SNMPv2 Trap
- GetBulkRequest
- InformRequest
- Deterministic memory (v3.2.0+)
- Compile-time derived pool sizing — the arena scales with the handlers you register
- Boot-time lock-in — one contiguous arena claimed before
setup()/WiFi; zero per-packet heap traffic
- Loud failure modes — over-cap GetBulk answers an RFC 3416
tooBig error PDU instead of silently truncating
- Hardware-validated: ESP-01 30-minute soak campaigns, 0 pool alarms / 0 reboots / flat heap
It was designed and tested around an ESP32, but will work with any Arduino-based device that has a UDP object available. Optimized for ESP-01 (ESP8266) and other memory-constrained embedded targets — the library auto-tunes a reduced "TINY" profile on ESP8266 and was soak-tested on a 1 MB ESP-01 against upstream v2.1.0 (same sketch: flat heap, zero allocation failures, bounded deterministic RAM).
The example goes into detail around how to use, or look at src/SNMP_Agent.h for the API.
If you're coming from v1, most, but not all APIs are drop-in replaceable.
Some of the API's, especially around strings have changed. Look in SNMP_Agent.h for details.
If you're upgrading from v2.0/v2.1, note the 2.2.0 string model change below.
If you're upgrading from v2.2, v3.0.0 is source-compatible (no API changes) but fixes several critical BER TLV encoding/decoding bugs. Mandatory upgrade if you use GetBulk, large responses (length ≥ 128 bytes, especially exactly 256 bytes), or snmpbulkwalk.
If you're upgrading from v3.0.0 / v3.0.6 to v3.1.0: 100% source + wire compatible, zero API changes, zero breaking changes. v3.1.0 closes out the 4-phase zero-heap refactor (eliminates all remaining std::vector / std::deque / std::list from library source; replaces last make_shared temp-allocations with pool-allocated raw BER objects; drops 3 dead standard-container includes + 1 dead inline method that was pulling shared_ptr machinery per-TU). Flash is slightly smaller on every target (−0.71% average vs v3.0.0 baseline; largest win PlatformIO esp32dev −1.3% = −9.6 KB), BSS is deterministic +48.9 KB (linker-reported, no mid-packet fragmentation, tuneable down via SNMP_POOL_ASN_OBJECTS if you're on esp01_1m). Mandatory upgrade if you've ever seen ESP-01 heap-fragmentation panics after 30+ days of SNMP polling.
The headline: a real memory-corruption bug (fixed)
There's a use-after-free in the request path that can silently corrupt responses — and eventually crash the agent after days of uptime:
handlePacket() explicitly called request.~SNMPPacket() and the destructor ran again at scope exit — every parsed request object was destroyed twice.
ASNPool::release() had no double-release guard — the second release re-ran the destructor and decremented the pool's usedCount twice.
- With the counter under-reported,
rawAlloc() could eventually hand a still-occupied pool slot to a new live object — silent data corruption with no error logs.
The visible symptom: GetBulk responses degrading to a single varbind when trap traffic overlaps request handling (the pool's transient slots are where request parse objects live). Both defects are fixed; the fix is proven on real hardware (details below).
Also in v3.1.23
- Pool diagnostics:
usedCountPeak high-water telemetry plus a one-shot DEBUG alarm that fires on any double release (it caught the bug live: DOUBLE RELEASE of slot 21 — the first transient slot, exactly where request parse objects live)
- CI workflow (
.github/workflows/test.yml): arduino-lint (specification mode), host Catch2 test suite, and a 4-way example-compile matrix (ESP8266 d1_mini + ESP32 × both examples) — all green
- GCC
-Wpedantic cleanups: removed extra ; after inline member functions (Ubuntu GCC treats these as errors with -Werror=pedantic; Apple clang doesn't flag them, which is how they slipped through)
- Host test suite: 101 assertions / 10 test cases, all passing (the same UAF used to SIGSEGV the suite)
- Static pool option (
SNMP_POOLS_IN_BSS) and assorted RAM/flash footprint work for 1 MB parts like the ESP-01
- Cumulative improvements from v2.2.0 onward (this is a large delta — upstream is at 2.1.0 and the fork has evolved through many tagged releases since; every tagged version in the fork has its own release notes)
How the fork got here — The Roadmap
Every step is tagged in the fork with its own release notes; the phases below summarize how v2.1.0 became v3.1.23:
| Phase |
Versions |
Theme |
What landed |
| Baseline |
v3.1.5 |
Compliance |
arduino-lint clean (demos → extras/demos/); zero functional changes from upstream — the measured starting line |
| Safety (P0) |
v3.1.6 – v3.1.8 |
Stop the crashes |
callbacksCount overflow guard + virtual ValueCallback destructor; ASNPool zero-heap hot path with explicit nullptr error paths (3 silent NULL-deref/Exception-28 causes closed); trap OID ownership made explicit (no more new OIDType leaks in the trap API) |
| RAM (P1) |
v3.1.9 |
Smaller footprint |
SortableOIDType shrunk, pool slot size recalculated → +4 KB heap headroom (and −4 KB BSS with the optional SNMP_POOLS_IN_BSS static pool) |
| Flash (P2) |
v3.1.10 – v3.1.12 |
Smaller code |
duplicate PDU encode paths merged into one shared envelope builder; compile-time OID-literal strlen via template ctor; sort_oids() rewritten to lockstep BER decode → −320 B library ROM and 3–5× faster sort/walk with −232 B stack per comparator call |
| Hardening + DevX (P3) |
v3.1.13 – v3.1.14 |
Correct-by-construction API |
printAllOIDsTo(Print&) roster helper; SNMP community truncation auth-bypass closed (len > cap now rejects instead of silently truncating); UDP.begin() return-value checked; Counter setOccurred only on real value change |
| Interim |
(v3.1.15 – v3.1.22 internal) |
Build ID + hardware campaign |
LIBRARY_VERSION + SNMPAgent::getVersion() so sketches/sysDescr always report the exact build; example banner updates; extensive ESP-01 hardware validation (details in the table below). These intermediate numbers were used during on-hardware testing but were never tagged — the first public tag after v3.1.14 is v3.1.23 |
| Correctness (this release) |
v3.1.23 |
Fix the latent UAF |
the double-destroy + double-release fix described above, plus pool telemetry and full CI |
Net effect since the v3.1.5 baseline: +4 KB free heap, 0 trap-path leaks (worst case was O(N × ~170 B)), 3/3 Exception-28 causes closed, auth bypass closed, 100% of polymorphic-delete UB eliminated, −320 B library ROM, ~3–5× faster OID sort/walk, and the native suite holding at 101/101 with zero regressions across all eleven releases.
Verification (not just claimed — measured)
| Check |
Result |
Host suite (make ci-test, GCC, -Werror) |
101/101 assertions, 10/10 cases |
| Example compiles (ESP8266 d1_mini + ESP32 × 2 examples) |
4/4 green |
| ESP-01 on hardware, full RFC1213 system group |
8/8 PASS |
| ESP-01 GETBULK across trap boundaries |
14/14 varbinds + clean EOM (was: single varbind) |
| 39-minute soak (7,664 ops: GET/GETNEXT/GETBULK/SET loops) |
0 alarms, 0 leaks (heap flat), 0 reboots, pool peak 76/76 recovering cleanly |
| Flash footprint (ESP-01, 1 MB) |
28% — well within budget |
If you'd like a PR
Say the word and i will pull one cumulative PR that includes all the fixes and enhancements. If you'd rather not, that's completely fine — the fork stays available at the link above for anyone that wants to use this new version.
Either way, thanks for the excellent library — it's been the backbone of several projects over the years.
— syntax1269
Hi,
I've been using Arduino_SNMP in ESP8266/ESP-01 projects for years now and i started to get deep into the code along the way. I've been maintaining an improved version in my fork and wanted to give you a heads-up. If you're interested, I'm happy to open a pull request — one big cumulative PR. And no pressure at all: if upstream would rather stay as-is, the fork lives on independently and is there for anyone who wants it:
https://github.com/syntax1269/Arduino_SNMP/tree/v3.3.1
This is a fully-compliant SNMPv2c Agent built for Arduino's, but will work on any OS, providing API code is written for packet serialization (See tests/mock.cpp for an example)
Current Version: 3.3.1
Features
intchar[]/const char*(C-style strings, nostd::stringor ArduinoString)const char*(dotted-decimal, e.g. ".1.3.6.1.4.1.5.0")uint32_tuint32_tuint32_tuint8_t*uint64_tsetup()/WiFi; zero per-packet heap traffictooBigerror PDU instead of silently truncatingIt was designed and tested around an ESP32, but will work with any Arduino-based device that has a UDP object available. Optimized for ESP-01 (ESP8266) and other memory-constrained embedded targets — the library auto-tunes a reduced "TINY" profile on ESP8266 and was soak-tested on a 1 MB ESP-01 against upstream v2.1.0 (same sketch: flat heap, zero allocation failures, bounded deterministic RAM).
The example goes into detail around how to use, or look at
src/SNMP_Agent.hfor the API.If you're coming from v1, most, but not all APIs are drop-in replaceable.
Some of the API's, especially around strings have changed. Look in
SNMP_Agent.hfor details.If you're upgrading from v2.0/v2.1, note the 2.2.0 string model change below.
If you're upgrading from v2.2, v3.0.0 is source-compatible (no API changes) but fixes several critical BER TLV encoding/decoding bugs. Mandatory upgrade if you use GetBulk, large responses (length ≥ 128 bytes, especially exactly 256 bytes), or snmpbulkwalk.
If you're upgrading from v3.0.0 / v3.0.6 to v3.1.0: 100% source + wire compatible, zero API changes, zero breaking changes. v3.1.0 closes out the 4-phase zero-heap refactor (eliminates all remaining
std::vector/std::deque/std::listfrom library source; replaces lastmake_sharedtemp-allocations with pool-allocated raw BER objects; drops 3 dead standard-container includes + 1 dead inline method that was pulling shared_ptr machinery per-TU). Flash is slightly smaller on every target (−0.71% average vs v3.0.0 baseline; largest win PlatformIO esp32dev −1.3% = −9.6 KB), BSS is deterministic +48.9 KB (linker-reported, no mid-packet fragmentation, tuneable down viaSNMP_POOL_ASN_OBJECTSif you're on esp01_1m). Mandatory upgrade if you've ever seen ESP-01 heap-fragmentation panics after 30+ days of SNMP polling.The headline: a real memory-corruption bug (fixed)
There's a use-after-free in the request path that can silently corrupt responses — and eventually crash the agent after days of uptime:
handlePacket()explicitly calledrequest.~SNMPPacket()and the destructor ran again at scope exit — every parsed request object was destroyed twice.ASNPool::release()had no double-release guard — the second release re-ran the destructor and decremented the pool'susedCounttwice.rawAlloc()could eventually hand a still-occupied pool slot to a new live object — silent data corruption with no error logs.The visible symptom: GetBulk responses degrading to a single varbind when trap traffic overlaps request handling (the pool's transient slots are where request parse objects live). Both defects are fixed; the fix is proven on real hardware (details below).
Also in v3.1.23
usedCountPeakhigh-water telemetry plus a one-shotDEBUGalarm that fires on any double release (it caught the bug live:DOUBLE RELEASE of slot 21— the first transient slot, exactly where request parse objects live).github/workflows/test.yml): arduino-lint (specification mode), host Catch2 test suite, and a 4-way example-compile matrix (ESP8266 d1_mini + ESP32 × both examples) — all green-Wpedanticcleanups: removed extra;after inline member functions (Ubuntu GCC treats these as errors with-Werror=pedantic; Apple clang doesn't flag them, which is how they slipped through)SNMP_POOLS_IN_BSS) and assorted RAM/flash footprint work for 1 MB parts like the ESP-01How the fork got here — The Roadmap
Every step is tagged in the fork with its own release notes; the phases below summarize how v2.1.0 became v3.1.23:
extras/demos/); zero functional changes from upstream — the measured starting linecallbacksCountoverflow guard + virtualValueCallbackdestructor; ASNPool zero-heap hot path with explicit nullptr error paths (3 silent NULL-deref/Exception-28 causes closed); trap OID ownership made explicit (no morenew OIDTypeleaks in the trap API)SortableOIDTypeshrunk, pool slot size recalculated → +4 KB heap headroom (and −4 KB BSS with the optionalSNMP_POOLS_IN_BSSstatic pool)strlenvia template ctor;sort_oids()rewritten to lockstep BER decode → −320 B library ROM and 3–5× faster sort/walk with −232 B stack per comparator callprintAllOIDsTo(Print&)roster helper; SNMP community truncation auth-bypass closed (len > cap now rejects instead of silently truncating);UDP.begin()return-value checked; CountersetOccurredonly on real value changeLIBRARY_VERSION+SNMPAgent::getVersion()so sketches/sysDescr always report the exact build; example banner updates; extensive ESP-01 hardware validation (details in the table below). These intermediate numbers were used during on-hardware testing but were never tagged — the first public tag after v3.1.14 is v3.1.23Net effect since the v3.1.5 baseline: +4 KB free heap, 0 trap-path leaks (worst case was O(N × ~170 B)), 3/3 Exception-28 causes closed, auth bypass closed, 100% of polymorphic-delete UB eliminated, −320 B library ROM, ~3–5× faster OID sort/walk, and the native suite holding at 101/101 with zero regressions across all eleven releases.
Verification (not just claimed — measured)
make ci-test, GCC,-Werror)If you'd like a PR
Say the word and i will pull one cumulative PR that includes all the fixes and enhancements. If you'd rather not, that's completely fine — the fork stays available at the link above for anyone that wants to use this new version.
Either way, thanks for the excellent library — it's been the backbone of several projects over the years.
— syntax1269