Collect from an SNMP-only device
When you need this
Section titled “When you need this”- “Our gNMI rollout hasn’t reached the 1U closet switches yet — but they still need CAM visibility.”
- “This is a vendor-X switch that doesn’t ship gNMI at all. SNMPv2c is the only telemetry it supports.”
- “We want a backstop in case gNMI streaming goes silent.”
SNMP is the universal backbone. Every managed switch built in the last
two decades speaks it. l2trace’s SNMP collector polls the Q-BRIDGE-MIB
(VLAN-aware FDB) and IF-MIB (port names) on a configurable cadence,
joins the two tables, and emits the same MacLearned envelopes the
gNMI collector emits.
How the walk works
Section titled “How the walk works”The collector does three sequential GETBULK walks per poll cycle:
| OID | Purpose |
|---|---|
1.3.6.1.2.1.17.7.1.2.2.1.2 (dot1qTpFdbPort) | VLAN-indexed MAC table |
1.3.6.1.2.1.17.1.4.1.2 (dot1dBasePortIfIndex) | Bridge port → ifIndex map |
1.3.6.1.2.1.31.1.1.1.1 (ifName) | ifIndex → port name (with ifDescr fallback for older devices) |
dot1qTpFdbPort’s OID index is the load-bearing tricky bit:
<vlan>.<mac-as-6-dotted-octets>. The varBind name
1.3.6.1.2.1.17.7.1.2.2.1.2.10.0.27.131.176.55.18 decodes to VLAN
10, MAC 00:1b:83:b0:37:12.
Sequential walks (not parallel) because most switches throttle parallel SNMP queries. Total walk volume on a 48-port access switch is ~5 KB per subtree; the round-trip cost is dominated by per-walk latency, not by data volume.
Configuration
Section titled “Configuration”The collector takes a CollectorConfig with these fields:
from l2trace.collectors.base import CollectorConfigfrom l2trace.collectors.snmp import SnmpCollectorfrom l2trace.events.schema import Source
cfg = CollectorConfig( device_id=42, hostname="sw-access-7", mgmt_ip="10.0.0.7", source=Source.SNMP, auth={"community": "your-read-community"}, extras={"snmp_port": 161, "snmp_timeout_seconds": 5.0},)
collector = SnmpCollector(cfg, emit=publish_to_nats, poll_interval_seconds=60.0)await collector.run() # long-running looppoll_interval_seconds defaults to 60s. Tune by device count + churn:
- High-churn access tier (lots of laptop comings/goings): 30s
- Stable data center fabric: 120s
- Bandwidth-constrained WAN: 300s
The compactor’s SNMP_POLL_INTERVAL_SECONDS=120 default means rows
stay “live” for at least 2× the poll interval, so a missed poll
doesn’t immediately age a MAC out.
Trying it against the mock agent
Section titled “Trying it against the mock agent”The test suite ships an in-process SNMPv2c mock agent
(tests/fixtures/mock_snmp_agent.py) — a real pysnmp CommandResponder
serving a static MIB tree. Useful for development without a real switch:
import asynciofrom tests.fixtures.mock_snmp_agent import MockSnmpAgent, build_canonical_walk
async def main(): walk = build_canonical_walk( fdb=[(10, "00:1a:a1:11:22:33", 1, 3)], # (vlan, mac, port, status) bridge_to_ifindex={1: 1001}, ifname={1001: "Gi0/1"}, ) async with MockSnmpAgent(oid_values=walk) as agent: # The agent is listening on (agent.host, agent.port). # Point the SnmpCollector at it. ...
asyncio.run(main())The mock validates the full wire round trip — pysnmp GETBULK over UDP → varBind decode → parser → envelope — without needing a real device or external simulator.
What SNMP can’t do (vs gNMI)
Section titled “What SNMP can’t do (vs gNMI)”| Capability | gNMI | SNMP |
|---|---|---|
| Streaming updates | ✅ subscribed paths push instantly | ❌ poll-based, latency = poll interval |
| Per-event device timestamp | ✅ nanosecond timestamp per notification | ❌ snapshot only — collector wall-clock |
| Forwarding-state diffs | ✅ explicit add/delete | ❌ snapshot diff; loses transients |
| Vendor support | ⚠️ Cisco IOS-XR, Arista, Juniper, Nokia | ✅ Universal |
The reconciler’s source-priority order (gnmi > snmp > netconf > ssh)
means SNMP observations are kept but get downgraded when gNMI is also
running for the same MAC. The
disagreement view surfaces cases where
SNMP and gNMI disagree about a port — usually that means gNMI saw a
move SNMP hasn’t polled yet.
See also
Section titled “See also”- The collector source:
src/l2trace/collectors/snmp.py - The mock agent:
tests/fixtures/mock_snmp_agent.py - Event envelope reference — what the four timestamps mean when SNMP can only provide collector wall-clock
- Why bitemporal? — SNMP polls can arrive AFTER faster gNMI updates; the bitemporal model handles this natively as belief revision