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
wire/Pure protocol codec. Responsibilities:
v3message.py)Non-responsibilities:
security/ owns auth/priv)security/Security model abstraction. Responsibilities:
SecurityModel structural 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 trapsUsmModel imports cryptography lazily inside auth/priv methods only; the class is always
importable without the [v3] extra.
transport/Owns request/response transport behavior:
manager/Owns the public runtime behavior:
Response and VarBind modelsnotify/Owns notification-specific runtime behavior:
sysUpTime.0 and snmpTrapOID.0responder/Owns the narrow read-only simulator behavior:
GET, GET_NEXT, and GET_BULK over UDPnoSuchObject and endOfMibView where appropriateCounterRule, RandomNumericRule, UptimeRule, TimestampRule) generate dynamic values on each lookup without application-side callbacksInMemoryObjectSource.from_bundle() populates a source from compiled JSON metadata with sensible defaultsmib/Owns optional symbolic services:
MODULE::symbol inputMibBundle.iter_objects(), iter_notifications(), and search() provide in-memory iteration and substring search over loaded nodescli/Owns command-line UX only:
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 and response_from_pdu() builds the public Response.This flow is shown with V2cManager. V3Manager follows the same request path after
SnmpSession.open() performs engine discovery and UsmModel wraps/unwraps the PDU.
load_bundle(path).normalize_targets() resolves MODULE::symbol input through MibBundle.resolve().enrich_varbinds() uses bundle lookup and render helpers to populate display_name and display_value.walk() resolves the root once at the API boundary.walk_subtree() iterates via GETNEXT or GETBULK.endOfMibView.VarBind objects, optionally enriched by the bundle.load_bundle() builds a MibRegistry from compiled JSON artifacts.bundle.translate(), bundle.resolve(), and bundle.lookup() operate with no network I/O.await V2cNotifier.send_trap(...), await V2cNotifier.send_inform(...), await V3Notifier.send_trap(...), or await V3Notifier.send_inform(...).sysUpTime.0 and snmpTrapOID.0 are inserted first unless explicitly provided.RESPONSE PDU.V3Notifier.send_inform() uses peer-discovered receiver engine state. V3Notifier.send_trap() requires explicit UsmLocalEngine so the outbound message carries the sender’s own authoritative engine state and does not depend on peer discovery during open().V2cNotificationListener(...) or V3NotificationListener(...).UdpServer binds the requested host and port.RESPONSE PDU.NotificationEvent carrying source address, PDU kind, decoded varbinds, notification metadata, and additive v3 security metadata when present.decode_notification(raw_bytes, bundle=...) for v2c or decode_notification(raw_bytes, bundle=..., user=...) for strict v3 USM decode.user was supplied.NotificationEvent.snmpTrapOID.0 is reverse-looked-up into notification_name and declared member_bindings.V2cResponder with an in-memory or callback-backed source.UdpServer binds the requested host and port.GET, GET_NEXT, and GET_BULK are answered from the configured source using lexicographic OID ordering.noSuchObject; next/bulk exhaustion becomes endOfMibView.RESPONSE PDU back to the request source address.The runtime/compiler split is a deliberate architectural boundary:
tsnmp does not import trishul-smi at runtimemanifest.json and oid_index.json are optional accelerators, not correctness requirementsv0.1This 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:
pysnmp replacementRaw MIB ingestion, compiler workflows, writable set, SNMPv1, and full
agent framework support remain outside the current implemented architecture.