Last updated: 2026-05-15
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 + V2cNotifier + V2cNotificationListener + 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 │
│ transport/ UDP client/server, request matching, retries │
│ wire/ BER / ASN.1 / SNMPv2c 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
├── types.py ← public response and SNMP value models
│
├── wire/
│ ├── ber.py ← BER primitives
│ ├── asn1.py ← ASN.1 value encoding helpers
│ ├── message.py ← SNMP message encode/decode
│ └── 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 ← V2cManager public runtime API
│ ├── operations.py ← target normalization and response shaping
│ └── walk.py ← subtree walk stop rules and iteration
│
├── notify/
│ ├── client.py ← V2cNotifier public send API
│ ├── listener.py ← V2cNotificationListener public receive API
│ ├── 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:
Non-responsibilities:
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.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 notifier.send_trap(...) or await notifier.send_inform(...).sysUpTime.0 and snmpTrapOID.0 are inserted first unless explicitly provided.RESPONSE PDU.V2cNotificationListener(...).UdpServer binds the requested host and port.RESPONSE PDU.NotificationEvent carrying source address, community, PDU kind, decoded varbinds, notification metadata, and optional declared-member bindings.decode_notification(raw_bytes, bundle=...).decode_message() decodes the SNMPv2c envelope and notification PDU.notification_event_from_message() builds the public 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, SNMPv3, and full
agent framework support remain outside the current implemented architecture.