Last updated: 2026-06-04
trishul-snmp is a package-first SNMP runtime. The core runtime handles wire
codec, UDP transport, request dispatch, manager operations, outbound
notification send, inbound notification receive, and a narrow read-only
responder with no MIB compiler dependency. Optional compiled-JSON artifacts add
symbolic translation and richer display.
┌──────────────────────────────────────────────────────────────────────────────┐
│ Python API / CLI │
│ V2cManager / V3Manager · V2cNotifier / V3Notifier │
│ V2cNotificationListener / V3NotificationListener · V2cResponder │
│ decode_notification() │
├──────────────────────────────────────────────────────────────────────────────┤
│ manager/ target normalization, request shaping, walk logic │
│ notify/ send, listen, and offline notification decode │
│ responder/ read-only request handling and simulator sources │
│ security/ SecurityModel protocol · CommunityModel · UsmModel (v3 USM) │
│ session.py shared UdpClient + Dispatcher + Lock + MibBundle │
│ transport/ UDP client/server, request matching, retries │
│ wire/ BER / ASN.1 / SNMPv2c + SNMPv3 message + PDU codec │
│ mib/ optional bundle loading, registry, rendering │
└──────────────────────────────────────────────────────────────────────────────┘
The CLI is intentionally thin. It does not define a second architecture.
trishul_snmp/
├── __init__.py ← public package surface + version
├── __main__.py ← `python -m trishul_snmp`
├── errors.py ← exception hierarchy (incl. AuthenticationError)
├── types.py ← public response and SNMP value models
├── session.py ← shared UdpClient + Dispatcher + Lock + MibBundle
│
├── security/
│ ├── model.py ← SecurityModel protocol (structural)
│ ├── community.py ← CommunityModel for SNMPv2c
│ └── usm.py ← UsmModel, UsmUser, UsmLocalEngine, auth/priv protocols
│
├── wire/
│ ├── ber.py ← BER primitives
│ ├── asn1.py ← ASN.1 value encoding helpers
│ ├── message.py ← SNMPv2c message encode/decode
│ ├── v3message.py ← SNMPv3 outer message + ScopedPDU + USM params codec
│ └── pdu.py ← PDU models and PDU encode/decode
│
├── transport/
│ ├── udp.py ← connected UDP client
│ └── dispatcher.py ← request ids, timeout/retry, response matching
│
├── manager/
│ ├── client.py ← SnmpManager base · V2cManager · V3Manager
│ ├── operations.py ← target normalization and response shaping
│ └── walk.py ← subtree walk stop rules and iteration
│
├── notify/
│ ├── client.py ← SnmpNotifier base · V2cNotifier · V3Notifier
│ ├── listener.py ← V2c/V3 notification listener public receive APIs
│ ├── v3.py ← listener-side v3 decode/report/response helpers
│ ├── events.py ← notification event model + live/offline decode
│ └── __init__.py ← notification package export
│
├── responder/
│ ├── server.py ← V2cResponder public API
│ ├── sources.py ← in-memory and callback-backed data sources
│ ├── rules.py ← simulation rules for dynamic OID values
│ └── __init__.py ← responder package export
│
├── mib/
│ ├── loader.py ← bundle file/directory loading
│ ├── bundle.py ← public MibBundle abstraction
│ ├── registry.py ← symbol and OID lookup registry
│ ├── models.py ← normalized compiled-JSON records
│ └── render.py ← varbind enrichment and display rendering
│
└── cli/
├── main.py ← argument parser and command handlers
├── common.py ← shared options, bundle loading, value parsing
└── output.py ← manager and notification text/JSON rendering
Pure protocol codec. Responsibilities:
- BER primitives
- ASN.1 value encoding/decoding
- SNMPv2c and SNMPv3 message and PDU encode/decode
- SNMPv3 outer message framing, ScopedPDU, and USM security parameters codec (
v3message.py)
Non-responsibilities:
- no UDP socket handling
- no retry/timeout logic
- no bundle translation or enrichment
- no cryptography (
security/owns auth/priv)
Security model abstraction. Responsibilities:
SecurityModelstructural protocol:wrap_pdu(pdu) -> bytes,unwrap_message(data) -> Pdu | NoneCommunityModel: SNMPv2c community string wrapping/matchingUsmModel: SNMPv3 USM — RFC 3414 key derivation, HMAC auth, AES-128-CFB privacy, engine discovery, and sender-authoritative trap handlingUsmUser: immutable credential dataclass (username, auth protocol/key, priv protocol/key)UsmLocalEngine: explicit sender-authoritative engine state for SNMPv3 traps
UsmModel imports cryptography lazily inside auth/priv methods only; the class is always
importable without the [v3] extra.
Owns request/response transport behavior:
- connected UDP client behavior for manager/notifier flows
- bound UDP server behavior for listener/responder flows
- timeout and retry handling for request/response paths
- request-id matching in dispatcher-managed flows
Owns the public runtime behavior:
- normalize numeric or symbolic targets to numeric OIDs
- construct request varbinds
- issue GET / GETNEXT / GETBULK requests
- implement subtree walk logic and stop rules
- shape public
ResponseandVarBindmodels
Owns notification-specific runtime behavior:
- normalize numeric or symbolic notification OIDs
- normalize explicit varbind OIDs for outbound notifications
- auto-populate
sysUpTime.0andsnmpTrapOID.0 - send traps as fire-and-forget
- send informs and wait for matching responses
- receive trap and inform PDUs as structured events
- decode BER-encoded trap/inform messages offline into the same public event model
- auto-acknowledge informs on the listener path
- apply optional community allowlists on the v2c listener path
- authenticate/decrypt inbound SNMPv3 notifications for one configured USM user
- reply to v3 discovery probes and inform requests using explicit local authoritative engine state
- map notification member metadata to received varbinds when a bundle is present
Owns the narrow read-only simulator behavior:
- receive
GET,GET_NEXT, andGET_BULKover UDP - resolve exact and next lexicographic objects from a pluggable source
- synthesize
noSuchObjectandendOfMibViewwhere appropriate - keep source interfaces small enough for fixtures and callback-backed simulation
- simulation rules (
CounterRule,RandomNumericRule,UptimeRule,TimestampRule) generate dynamic values on each lookup without application-side callbacks InMemoryObjectSource.from_bundle()populates a source from compiled JSON metadata with sensible defaults
Owns optional symbolic services:
- load a single compiled module JSON file or bundle directory
- validate module JSON and optional sidecars
- resolve
MODULE::symbolinput - reverse-lookup numeric OIDs for enrichment
- render display names and values
MibBundle.iter_objects(),iter_notifications(), andsearch()provide in-memory iteration and substring search over loaded nodes
Owns command-line UX only:
- parse arguments
- validate v2c/v3 security option combinations before network I/O
- load the optional bundle
- call the same Python API as library users
- render text or JSON output
- current live-command protocol coverage includes SNMPv2c plus SNMPv3 manager, outbound notification send, inbound notification listen, and offline decode
- Caller invokes
await manager.get("1.3.6.1.2.1.1.3.0"). normalize_targets()parses the numeric OID.build_request_varbinds()creates NULL placeholder varbinds.RequestDispatcher.send_pdu()assigns a request id, encodes the SNMP message, and sends it over UDP.UdpClient.receive()waits for a matching response with timeout/retry handling.decode_message()decodes the response andresponse_from_pdu()builds the publicResponse.- With no bundle loaded, enrichment is effectively pass-through.
This flow is shown with V2cManager. V3Manager follows the same request path after
SnmpSession.open() performs engine discovery and UsmModel wraps/unwraps the PDU.
- Caller loads a bundle once via
load_bundle(path). normalize_targets()resolvesMODULE::symbolinput throughMibBundle.resolve().- Network I/O remains numeric only.
- After response decode,
enrich_varbinds()uses bundle lookup and render helpers to populatedisplay_nameanddisplay_value.
walk()resolves the root once at the API boundary.walk_subtree()iterates via GETNEXT or GETBULK.- Walk stops when the response leaves the subtree, repeats or decreases OIDs, or returns
endOfMibView. - The final result is a tuple of
VarBindobjects, optionally enriched by the bundle.
load_bundle()builds aMibRegistryfrom compiled JSON artifacts.bundle.translate(),bundle.resolve(), andbundle.lookup()operate with no network I/O.- A single module JSON file is sufficient for narrow translation use cases.
- Caller invokes
await V2cNotifier.send_trap(...),await V2cNotifier.send_inform(...),await V3Notifier.send_trap(...), orawait V3Notifier.send_inform(...). - Numeric or symbolic notification OIDs are normalized at the API edge.
sysUpTime.0andsnmpTrapOID.0are inserted first unless explicitly provided.- The notification PDU is encoded and sent over UDP.
- Trap send stops after send; inform send waits for a matching
RESPONSEPDU. V3Notifier.send_inform()uses peer-discovered receiver engine state.V3Notifier.send_trap()requires explicitUsmLocalEngineso the outbound message carries the sender's own authoritative engine state and does not depend on peer discovery duringopen().
- Caller opens
V2cNotificationListener(...)orV3NotificationListener(...). UdpServerbinds the requested host and port.- The listener receives inbound datagrams and decodes SNMP messages.
- For v2c, non-notification PDUs and filtered communities are ignored.
- For v3, discovery probes are answered with REPORTs and malformed/wrong-user/auth-failed datagrams are dropped quietly.
- Informs are acknowledged automatically with a matching
RESPONSEPDU. - The listener returns a
NotificationEventcarrying source address, PDU kind, decoded varbinds, notification metadata, and additive v3 security metadata when present.
- Caller invokes
decode_notification(raw_bytes, bundle=...)for v2c ordecode_notification(raw_bytes, bundle=..., user=...)for strict v3 USM decode. - The function routes through the v2c or v3 codec path based on whether
userwas supplied. - The low-level decode builds the public
NotificationEvent. - If a bundle is loaded,
snmpTrapOID.0is reverse-looked-up intonotification_nameand declaredmember_bindings. - No UDP transport or dispatcher code is involved.
- Caller configures
V2cResponderwith an in-memory or callback-backed source. UdpServerbinds the requested host and port.- The responder receives inbound SNMPv2c messages and filters by community.
GET,GET_NEXT, andGET_BULKare answered from the configured source using lexicographic OID ordering.- Missing exact objects become
noSuchObject; next/bulk exhaustion becomesendOfMibView. - The responder sends a matching
RESPONSEPDU back to the request source address.
The runtime/compiler split is a deliberate architectural boundary:
tsnmpdoes not importtrishul-smiat runtime- a single module JSON file must be enough for core enrichment use cases
manifest.jsonandoid_index.jsonare optional accelerators, not correctness requirements- module JSON is the source of truth
- public generic third-party schema normalization is not part of
v0.1
This keeps deployment simple and lets callers supply only the compiled JSON they actually need.
The current main-branch scope is still intentionally narrower than a full SNMP stack:
- SNMPv2c and SNMPv3 USM (noAuthNoPriv, authNoPriv, authPriv AES-128)
- async-first package API first, CLI second
- manager operations plus notification send/receive and narrow read-only response
- live CLI coverage for SNMPv2c plus SNMPv3 manager, notifier, listener, and offline decode paths
- not attempting a full
pysnmpreplacement
Raw MIB ingestion, compiler workflows, writable set, SNMPv1, and full
agent framework support remain outside the current implemented architecture.