Skip to main content

SNMP reference

nl6 answers SNMP v2c and v3 queries on UDP port 161 (override with -snmp-port) for every simulated device. The stack is implemented in go/nl6/snmp*.go — see Architecture for the component map.

Protocol coverage

  • SNMP v2cGET, GETNEXT, GETBULK against the full per-device OID table. Community string is public by default.
  • SNMPv3 — enable with -snmpv3-engine-id. Auth protocols: none, md5, sha1. Privacy protocols: none, des, aes128. Auth and priv are implemented in snmpv3.go / snmpv3_crypto.go.

SNMPv3 auth / priv matrix

AuthPrivSecurity level
nonenonenoAuthNoPriv
md5noneauthNoPriv
sha1noneauthNoPriv
md5desauthPriv
md5aes128authPriv
sha1desauthPriv
sha1aes128authPriv

Per-device SNMPv3 credentials can be supplied when creating devices via the REST API — see Web API → Create devices.

OID lookup internals

OIDs are stored per-device in a sync.Map for lock-free O(1) reads under concurrent load. Pre-computed next-OID mappings avoid scanning the table for GETNEXT / GETBULK — each OID has a direct pointer to its lexicographic successor. Request buffers come from a shared pool to reduce GC pressure on SNMP-heavy workloads.

OIDs in resource files may be written with or without a leading dot — the loader normalises them to the net-snmp convention (.1.3.6.1…) at startup.

Dynamic IF-MIB counters

Every per-interface counter listed below is generated dynamically by go/nl6/if_counters.go:IfCounterCycler:

ifXTable Counter64 HC columns (.1.3.6.1.2.1.31.1.1.1.X):

ColumnOID columnDerivation
ifHCInOctets.6master dial (sine wave, 60 – 100 % of ifHighSpeed / ifSpeed, 1 h period)
ifHCInUcastPkts.7baseInUcast + (inDeltaOctets / pktSizeIn) × ucastRatioIn
ifHCInMulticastPkts.8same shape, mcastRatioIn
ifHCInBroadcastPkts.9same shape, bcastRatioIn
ifHCOutOctets.10outbound master dial
ifHCOutUcastPkts.11baseOutUcast + (outDeltaOctets / pktSizeOut) × ucastRatioOut
ifHCOutMulticastPkts.12same shape, mcastRatioOut
ifHCOutBroadcastPkts.13same shape, bcastRatioOut

ifXTable Counter32 shadow columns — always equal to uint32(HC_value & 0xFFFFFFFF):

ColumnOID columnShadow of
ifInMulticastPkts.2ifHCInMulticastPkts (.8)
ifInBroadcastPkts.3ifHCInBroadcastPkts (.9)
ifOutMulticastPkts.4ifHCOutMulticastPkts (.12)
ifOutBroadcastPkts.5ifHCOutBroadcastPkts (.13)

ifTable Counter32 columns (.1.3.6.1.2.1.2.2.1.X):

ColumnOID columnDerivation
ifInUcastPkts.11shadow of ifHCInUcastPkts (.7)
ifInDiscards.13baseInDisc + inDeltaPkts × discPpmIn / 1e6
ifInErrors.14baseInErr + inDeltaPkts × errPpmIn / 1e6
ifOutUcastPkts.17shadow of ifHCOutUcastPkts (.11)
ifOutDiscards.19baseOutDisc + outDeltaPkts × discPpmOut / 1e6
ifOutErrors.20baseOutErr + outDeltaPkts × errPpmOut / 1e6

Properties common to every dynamic counter:

  • Monotonic. The underlying octet integral never decreases (rate floor is 60 % of ifSpeed), and every derivation is base-plus-growth, so Counter64 columns are strictly increasing. Counter32 shadow columns wrap naturally at 2³²; ifCounterDiscontinuityTime stays at 0 — wrap is inherent, not a discontinuity.
  • Pre-seeded. Each counter starts at a base derived from ~24 h of traffic, ratios, and the active error scenario (see below) so a fresh device doesn't look unrealistically pristine.
  • Per-interface variance. Packet-size divisor jitters ±20 % around 500 B; mix ratios jitter ±3 % around 85 / 10 / 5 (in) and 90 / 8 / 2 (out); error / discard ppm values are drawn once from the scenario band — all deterministic from the device seed.
  • Sine-driven correlation. All derived counters share the master octet sine wave, so when a link is "quiet" (60 % of ifSpeed) the full counter family slows together — matching how real hardware behaves under reduced traffic.
  • SNMP ↔ sFlow agreement. Both read paths resolve the same IfCounterCycler dispatcher, so concurrent SNMP GETs and sFlow counter_sample bodies carry matching values at the same instant.
  • Zero-goroutine cost. Every counter is computed on-demand from the current time against analytic formulas — no per-interface goroutine, no polling loop.
  • Values are visible on both GET and GETNEXT / GETBULK.

Counter32 wrap guidance. At 10 Gbps / 80 % util / 500 B avg packet size, ifInUcastPkts wraps every ~26 minutes. At 100 Gbps the same column wraps every ~2.6 minutes. Collectors handle the wrap via the existing delta-modulo convention; but when your link is ≥1 Gbps you should prefer the Counter64 HC columns (ifHCInUcastPkts etc.) to avoid missing a wrap on a slow poll cycle.

Per-device error scenario

The ifInErrors / ifOutErrors / ifInDiscards / ifOutDiscards rates are driven by a per-device scenario carried in DeviceSimulator.IfErrorScenario:

ScenarioerrPpmdiscPpmTypical dashboard appearance
clean (default)00Flat line at the baseline
typical10 – 10020 – 200Faint steady slope (good production gear)
degraded1 000 – 10 0002 000 – 20 000Visible error-rate alert candidates (0.1 – 1 %)
failing10 000 – 100 00020 000 – 200 000Link-flap / bad-cable alarms (1 – 10 %)

Set for the auto-start batch via the CLI flag -if-error-scenario <name>, or per-device via if_error_scenario in the POST /api/v1/devices body. See CLI flags reference and Web API reference.

Example walks

# Walk ifXTable — covers all HC counters, Counter32 shadows, and ifHighSpeed
snmpwalk -v2c -c public 192.168.100.1 1.3.6.1.2.1.31.1.1

# Walk ifTable — covers ifInUcastPkts, ifInDiscards, ifInErrors,
# ifOutUcastPkts, ifOutDiscards, ifOutErrors
snmpwalk -v2c -c public 192.168.100.1 1.3.6.1.2.1.2.2.1

# Fetch HC in/out for interface 1 directly
snmpget -v2c -c public 192.168.100.1 \
1.3.6.1.2.1.31.1.1.1.6.1 \
1.3.6.1.2.1.31.1.1.1.10.1

# Continuous rate monitoring (poll every 10 s)
watch -n 10 "snmpget -v2c -c public 192.168.100.1 \
1.3.6.1.2.1.31.1.1.1.6.1 1.3.6.1.2.1.31.1.1.1.10.1"

# Watch error / discard growth on a device deployed with -if-error-scenario failing
watch -n 5 "snmpget -v2c -c public 192.168.100.1 \
1.3.6.1.2.1.2.2.1.14.1 1.3.6.1.2.1.2.2.1.13.1"

Dynamic CPU / memory / temperature metrics

CPU, memory, and temperature OIDs cycle through a 100-point pre-generated sine-wave pattern per device, driven by metrics_cycler.go. Per-category device profiles define the baseline ranges and spike amplitudes; see device_profiles.go. GPU servers add per-GPU metric cycling on top of this — see GPU simulation.

Interface-state scenarios

The -if-scenario flag controls the initial ifAdminStatus / ifOperStatus values reported across every simulated interface. Scenario 4 uses a deterministic ifIndex % 100 < n rule so results are reproducible across restarts.

# Spot-check admin status (should all be "1" in scenarios 2/3/4)
snmpwalk -v2c -c public 192.168.100.1 1.3.6.1.2.1.2.2.1.7

# Verify oper status after scenario 3 (all-failure)
snmpwalk -v2c -c public 192.168.100.1 1.3.6.1.2.1.2.2.1.8

Dynamic state engine (post-v0.8.0). ifOperStatus.<N> (.8), ifAdminStatus.<N> (.7), and ifLastChange.<N> (.9) are now served live from the per-device interface state engine — not from the cached JSON value. Two mutation sources update them at runtime:

  • Flap scheduler-if-flap-scenario {clean|rare|typical|aggressive} drives Poisson-distributed link flaps per (device, ifIndex). See the interface state engine reference.
  • REST control planePOST /api/v1/devices/{ip}/interfaces/{N}/{oper,admin}-status flips state for test-harness use.

Cross-protocol consistency: SNMP ifOperStatus.<N> and gNMI /interfaces/interface[name=*]/state/oper-status read from the same slot table and agree byte-for-byte at every instant. The trap and syslog catalog firings remain decoupled (Tier C follow-up).

RFC 2863 note on ifLastChange. Strict reading of RFC 2863 specifies ifLastChange as "value of sysUpTime at the time the interface entered its current operational state". The simulator computes the value relative to the per-device state-engine construction time, not the SNMP agent's sysUpTime. Today these epochs coincide (state engine constructs once, during device boot, guarded against re-init by a panic). If a future "reload scenario" feature is added, this divergence must be re-examined.

SNMP / gNMI timestamp resolution divergence on ifLastChange. SNMP encodes ifLastChange as TimeTicks (centiseconds of sysUpTime), so two transitions less than 10 ms apart collide to the same wire value. The gNMI state/last-change leaf exports the engine's nanosecond timestamp directly, so the same two transitions are distinguishable there. Under the aggressive flap scenario (mean ≈1 min) the collision is statistically irrelevant, but a custom test harness driving back-to-back REST POSTs against the same (device, ifIndex) can observe the divergence: SNMP ifLastChange returns the same TimeTicks value across both transitions while the gNMI leaf separates them by their actual nanosecond delta.

Entity MIB and vendor OIDs

Every network device ships with a properly aligned Entity MIB: chassis, line cards, power supplies, fans, and temperature sensors — plus the entAliasMappingTable linking physical ports to logical interfaces. Vendor-specific OIDs (Cisco, Juniper, Arista, NVIDIA, etc.) are provided per device type under go/nl6/resources/<device>/. See Resource files for the JSON schema and Device types for the catalog.

Notifications (trap / INFORM)

SNMP defines both a request/response path — the GET / GETNEXT / GETBULK / SET operations documented above — and a push path where a device initiates a notification to a monitoring collector. nl6 implements the push path for SNMPv2c only: fire-and-forget TRAPs (PDU 0xA7) and acknowledged INFORMs (PDU 0xA6). SNMPv1 traps and SNMPv3 notifications are deferred.

See SNMP trap reference for wire format, the JSON catalog schema, and the HTTP endpoints, and SNMP trap / INFORM export (operator guide) for enabling the feature and the snmptrapd smoke test.