- Zig 95.5%
- Nix 3.8%
- Python 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The released 0.17.0 is zig_0_17 in nixpkgs-unstable, so the devshell takes it as it is and the patched test runner goes: 0.17 fixed the type error it existed for. The dependencies move to their 0.17 releases -- zig-asn1, z46, zig-macaddress, zig-std-crypto-ext and zig-smi, all 0.2.0 -- and build.zig.zon.nix is regenerated with `zon2nix --17`. The port: b.args is gone for addPassthruArgs, findProgram takes an options struct and no longer fails, the docs server takes the emitted directory rather than an install path read while configuring, and array multiplication, which the language dropped, becomes @splat -- the one string repeated in tests/fuzz.zig is a splat of two-byte arrays bit-cast end to end. `zig fmt` rewrites @intFromEnum to @backingInt. A test is handed the environment now, as std.testing.environ, so the real-device tests read it from there instead of parsing /proc/self/environ, and stop being Linux-only. The test binaries are compiled with LLVM. The self-hosted backend emits no coverage instrumentation, and with LLVM `zig build fuzz --fuzz` runs with coverage feedback instead of stopping at an empty table of program counters. The loop in tools/fuzz.zig stays, as the run that is the same on every machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AJfjkPKSZVrYrkseQ1arKH |
||
| .forgejo/workflows | ||
| LICENSES | ||
| src | ||
| tests | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| flake.lock | ||
| flake.nix | ||
| package.nix | ||
| README.md | ||
| REUSE.toml | ||
zig-snmp
An SNMP manager for Zig: versions 1, 2c and 3 on the wire, with command line tools named after net-snmp's.
The API documentation is generated from the doc comments, which carry most of the explanation, and is published at https://jeff.jcollie.page/zig-snmp/.
$ git clone https://git.jcollie.dev/jeff/zig-snmp.git
$ cd zig-snmp
$ nix develop -c zig build test
It is written for Zig 0.17. The last release for Zig 0.16 is v0.1.0, and the
zig-0.16 branch holds it.
Where this lives
Two homes, with the same history in both.
- Forgejo, at https://git.jcollie.dev/jeff/zig-snmp, which is where the workflow runs and where the documentation is published from.
- Tangled, at https://tangled.org/jcollie.dev/zig-snmp.
What is here
| Module | What it is |
|---|---|
snmp.protocol |
The wire format: every value type, variable binding, PDU and message, encoded and decoded, both directions. No I/O anywhere in it, which is what lets the whole of it be fuzzed with no socket. |
snmp.Client |
A socket and the operations over one: get, getNext, getBulk, set and walk. The only file that does any I/O, and it takes its Io as a parameter rather than naming an implementation. |
snmp.Receiver |
A notification sink: traps and informs, with the acknowledgement an inform needs. |
snmp.usm |
The User-based Security Model: key derivation, the six authentication protocols, and DES, 3DES and AES-CFB privacy at all three key sizes. No I/O, so the whole of it is testable against the RFC's published vectors. |
It is built on two libraries of its own, both extracted rather than vendored:
zig-asn1— the Basic Encoding Rules and the OBJECT IDENTIFIER type.zig-std-crypto-ext— the ciphers and modesstd.cryptoleaves out, three of which SNMPv3 privacy needs: DES, AES-192, and CFB.
and on z46 for IP addresses and
zig-macaddress for the
PhysAddress textual convention.
State
SNMPv1, v2c and v3 all work against real agents. What is here:
-
The whole wire format. Every value type including the application-tagged ones (
Counter32,Gauge32,TimeTicks,IpAddress,Opaque,Counter64) and the SNMPv2 exception markers; variable bindings and lists; all eight PDUs in their three shapes; the community-based message wrapper. -
A UDP client with retries, a per-attempt deadline, and request-id matching that discards a late reply rather than answering with the previous question's answer.
-
A walk, over GetNext or GetBulk, that ends on
endOfMibView, on leaving the subtree, or on a v1 agent'snoSuchName— and refuses to loop when an agent fails to advance. -
Four command line tools:
snmpget,snmpwalk,snmpbulkwalkandsnmpset, which produce byte-identical output to net-snmp's given the same arguments, the same MIBs and the same agent. Verified againstsnmpdand against a Cisco Catalyst.The MIBs need saying because net-snmp has a compiled-in MIB directory and loads it whether or not
-Mis given, where these tools load nothing unless told --- so the two agree on identical arguments only once they have been told the same thing.MIBDIRSpointed at the same directory, orMIBS=emptied on net-snmp's side, is what makes the comparison a comparison. Both cases are in the virtual machine test. -
SNMPv3 with USM: engine discovery, the time window, all six authentication protocols (MD5, SHA-1 and RFC 7860's four SHA-2 ones) and five privacy protocols (DES-CBC, 3DES-EDE-CBC, and AES-CFB at 128, 192 and 256 bits). Verified against net-snmp and against Cisco IOS — two implementations that wrote their key derivation separately from each other and from this.
-
A trap and inform receiver, for all three versions. Traps are fire and forget; informs are acknowledged, which is what stops the sender retrying. Under v3 the receiver authenticates and decrypts notifications, and answers engine discovery — a v3 inform cannot be sent until it does, since the receiver is the authoritative engine for one. Driven by net-snmp's own
snmptrapin the tests: the one place our decoder is checked against net-snmp's encoder rather than the other way round. -
Five command line tools, the fifth being
snmptrapd. -
MIB support, through
zig-smi:-Mand-mspelled as net-snmp spells them, and both falling back to theMIBDIRSandMIBSenvironment variables, so the two programs can be run with one exported variable and identical arguments. An OID may be written any way a person would write one, and is printed in any of the four styles net-snmp's-Oflags select:$ export MIBDIRS=/path/to/mibs $ snmpget -v2c -c public localhost sysDescr.0 # or SNMPv2-MIB::sysDescr.0, SNMPv2-MIB::sysDescr.0 = STRING: Linux ... # or .1.3.6.1.2.1.1.1.0 $ snmpwalk -Of -v2c -c public localhost system | head -1 .iso.org.dod.internet.mgmt.mib-2.system.sysDescr.0 = STRING: Linux ...-mrestricts to the modules named and whatever they import, which is what net-snmp's does:-m IF-MIBagainst its own MIB directory loads five modules rather than sixty-five, so an OID that only HOST-RESOURCES-MIB names comes back numeric from both programs.What it costs, measured against net-snmp on the same files, ReleaseSafe against its release build:
time peak memory net-snmp's 65 base modules 0.06s 10 MB Cisco's 1,627 plus those 65, 80 MB of source 0.67s 392 MB net-snmp, the same 1,692 1.84s 75 MB So: faster than net-snmp and five times hungrier. The memory is the arena holding every module's source and parsed form, which is the shape this takes and not a leak; pointing
-Mat a whole vendor corpus is the extreme case, and-mnaming the two modules actually wanted is both quicker and smaller.Values are rendered through what the MIBs say about them too, which is most of what makes a walk readable: an enumeration's label, a
UNITSclause, and theDISPLAY-HINTof whatever textual convention the syntax names.$ snmpwalk -v2c -c public localhost ifPhysAddress | head -1 IF-MIB::ifPhysAddress.2 = STRING: 18:c0:4d:95:93:86 # PhysAddress, "1x:" $ snmpget -v2c -c public localhost ifOperStatus.2 hrStorageAllocationUnits.35 IF-MIB::ifOperStatus.2 = INTEGER: up(1) HOST-RESOURCES-MIB::hrStorageAllocationUnits.35 = INTEGER: 4096 BytesA walk of the whole of
mib-2against net-snmp's ownsnmpd--- 8,461 varbinds --- is byte-identical to net-snmp's, apart from the eight values that genuinely moved between the two runs: the uptime, the system clock, and the agent's own packet counters, which the two walks themselves increment.With no
-Mand noMIBDIRS, nothing is loaded and everything prints numerically --- which is exactly what these tools did before MIB support existed, so no script that predates it changes behaviour.
Which oracle covers what is not uniform, and worth knowing before relying on any of it:
| net-snmp | Cisco IOS | |
|---|---|---|
| v1, v2c, and the v3 message layer | yes | yes |
| MD5 and SHA-1 authentication | yes | yes |
| SHA-224/256/384/512 (RFC 7860) | yes | no — the device here has no SHA-2 |
| DES and AES-128 privacy | yes | yes |
| 3DES, AES-192, AES-256 privacy | no — its CLI refuses them | yes |
3DES, AES-192 and AES-256 come from drafts that expired without becoming
RFCs — draft-reeder-snmpv3-usm-3desede and draft-blumenthal-aes-usm —
and net-snmp 5.9's command line refuses all three, so Cisco firmware is the
only thing they have been checked against. The SHA-2 authentication protocols
are in the same position for the opposite reason: standardised, implemented by
net-snmp, and not by the device here.
All three need more localized key than their hash produces — 3DES wants 32
octets and MD5 gives 16 — so all three go through the same chained key
extension, which is defined in the first of those drafts and which net-snmp's
library implements as netsnmp_extend_kul.
Why net-snmp is everywhere in the tests
Correctness here is settled by agreement with the reference implementation rather than by reading the RFCs alone, because the RFCs leave room and the deployed base does not. Four tiers, cheapest first:
- Unit tests beside what they cover.
- Captured fixtures — net-snmp's own bytes, checked in, asserted by decode-then-re-encode identity. That sidesteps the random request-id entirely, since the id comes from the captured bytes rather than from us.
- A real agent, in an ordinary test.
snmpdruns unprivileged on a loopback high port givenSNMP_PERSISTENT_DIR, sotests/oracle.zigspawns one rather than booting a virtual machine. It answers v1, v2c and v3 with every auth and privacy protocol from there. - A NixOS guest, for only what genuinely needs privilege — the NixOS
module's own
snmpd, and a receiver on port 162 with net-snmp'ssnmptrapaimed at it with no:port, so the default is under test on both sides. Run bynix flake check, by hand and in the workflow alike.
There is a fifth, opt-in and never in CI: a real Cisco Catalyst.
tests/device-cisco.md has the IOS commands to configure a device for it and
to remove the configuration again. It is the
only oracle in the set that is another vendor's implementation rather than
net-snmp wearing two hats, and it has already earned its place by showing
three things snmpd never will — a sysDescr containing newlines, so one
varbind is not one line of output; sparse interface indices; and
ifHCInOctets returning a Counter64 under v2c and noSuchName under v1 from
the same agent at the same OID. See tests/device.zig for how it is gated.
Licence
MIT. See LICENSES/MIT.txt.