trishul-snmp

Getting Started

tsnmp is a package-first SNMP runtime. Start with numeric OIDs and no bundle; add compiled JSON only when you need symbolic resolution or better display metadata.


Install

pip install trishul-snmp

For SNMPv3 auth/priv support (HMAC and AES-128-CFB encryption), install the [v3] extra:

pip install "trishul-snmp[v3]"

The base package always imports correctly without [v3]; only calling auth or priv methods at runtime requires it.

For local development:

pip install -e ".[dev,v3]"

Requires Python >=3.10.


Scope

Current main-branch baseline:

Deliberately out of scope:

Current CLI coverage includes SNMPv2c plus SNMPv3 get, getnext, getbulk, walk, bulkwalk, trap, inform, listen, and decode-notification via explicit --snmp-version {2c,3} selection. SNMPv3 listen requires explicit --local-engine-* values, and SNMPv3 decode-notification requires explicit user/auth/priv inputs.


First numeric GET

This works with no bundle loaded.

import asyncio

from trishul_snmp import V2cManager


async def main() -> None:
    async with V2cManager(host="10.0.0.10", community="public") as manager:
        response = await manager.get("1.3.6.1.2.1.1.3.0")
        for varbind in response.varbinds:
            print(varbind.oid_str, varbind.value_type, varbind.display_value)


asyncio.run(main())

First SNMPv3 GET

Requires pip install "trishul-snmp[v3]" for auth/priv methods.

import asyncio

from trishul_snmp import AuthProtocol, UsmUser, V3Manager

user = UsmUser(
    username="monitor",
    auth_protocol=AuthProtocol.SHA256,
    auth_key=b"authpass123",
)


async def main() -> None:
    async with V3Manager(host="10.0.0.10", user=user) as manager:
        response = await manager.get("1.3.6.1.2.1.1.3.0")
        for varbind in response.varbinds:
            print(varbind.oid_str, varbind.value_type, varbind.display_value)


asyncio.run(main())

V3Manager.open() runs engine discovery automatically before the first request.


First symbolic GET

If you already have compiled JSON artifacts, load them and use symbolic names.

import asyncio

from trishul_snmp import V2cManager, load_bundle

bundle = load_bundle("./IF-MIB.json")


async def main() -> None:
    async with V2cManager(
        host="10.0.0.10",
        community="public",
        bundle=bundle,
    ) as manager:
        response = await manager.get("IF-MIB::ifDescr.1")
        for varbind in response.varbinds:
            print(varbind.display_name, "=", varbind.display_value)


asyncio.run(main())

First walk

import asyncio

from trishul_snmp import V2cManager, load_bundle

bundle = load_bundle("./mibs-json")


async def main() -> None:
    async with V2cManager(
        host="10.0.0.10",
        community="public",
        bundle=bundle,
    ) as manager:
        rows = await manager.walk("IF-MIB::ifTable")
        for row in rows:
            print(row.display_name, "=", row.display_value)


asyncio.run(main())

First SNMPv2c trap send

import asyncio

from trishul_snmp import IntegerValue, V2cNotifier, load_bundle

bundle = load_bundle("./mibs-json")


async def main() -> None:
    async with V2cNotifier(
        host="10.0.0.20",
        community="public",
        bundle=bundle,
    ) as notifier:
        await notifier.send_trap(
            "IF-MIB::linkDown",
            varbinds=[("IF-MIB::ifIndex.7", IntegerValue(7))],
            uptime=123,
        )


asyncio.run(main())

First SNMPv3 inform send

Requires pip install "trishul-snmp[v3]" for auth/priv methods.

import asyncio

from trishul_snmp import AuthProtocol, IntegerValue, UsmUser, V3Notifier, load_bundle

bundle = load_bundle("./mibs-json")
user = UsmUser(
    username="notify",
    auth_protocol=AuthProtocol.SHA256,
    auth_key=b"authpass123",
)


async def main() -> None:
    async with V3Notifier(
        host="10.0.0.20",
        user=user,
        bundle=bundle,
    ) as notifier:
        response = await notifier.send_inform(
            "IF-MIB::linkDown",
            varbinds=[("IF-MIB::ifIndex.7", IntegerValue(7))],
            uptime=123,
        )
        print(response.error_status)


asyncio.run(main())

For SNMPv3 traps, supply explicit sender-authoritative engine state:

import asyncio

from trishul_snmp import (
    IntegerValue,
    UsmLocalEngine,
    UsmUser,
    V3Notifier,
    load_bundle,
)

bundle = load_bundle("./mibs-json")
user = UsmUser(username="notify")
local_engine = UsmLocalEngine(
    engine_id=bytes.fromhex("8000010203"),
    engine_boots=7,
    engine_time=99,
)


async def main() -> None:
    async with V3Notifier(
        host="10.0.0.20",
        user=user,
        bundle=bundle,
        local_engine=local_engine,
    ) as notifier:
        await notifier.send_trap(
            "IF-MIB::linkDown",
            varbinds=[("IF-MIB::ifIndex.7", IntegerValue(7))],
            uptime=123,
        )


asyncio.run(main())

V3Notifier.send_inform() discovers peer engine state automatically on first use. V3Notifier.send_trap() does not use peer discovery; it requires explicit UsmLocalEngine so the sender’s own authoritative engine state is carried on the wire.


First read-only responder

import asyncio

from trishul_snmp import IntegerValue, OctetStringValue, V2cResponder, load_bundle

bundle = load_bundle("./mibs-json")


async def main() -> None:
    async with V2cResponder(
        host="127.0.0.1",
        port=1161,
        communities=["public"],
        bundle=bundle,
    ) as responder:
        responder.set_objects(
            [
                ("IF-MIB::ifIndex.1", IntegerValue(1)),
                ("IF-MIB::ifDescr.1", OctetStringValue(b"eth0")),
            ]
        )
        await responder.serve_forever()


asyncio.run(main())

Offline translation

No SNMP traffic is involved here.

from trishul_snmp import load_bundle

bundle = load_bundle("./mibs-json")

print(bundle.translate("IF-MIB::ifDescr.7"))
print(bundle.translate("1.3.6.1.2.1.2.2.1.2.7"))

End-to-end flows

Offline translation:

  1. Compile MIBs separately with tsmi or provide an equivalent supported JSON artifact.
  2. Load a compiled module JSON file or bundle directory with load_bundle().
  3. tsnmp validates and normalizes the bundle.
  4. Translation resolves symbol to numeric OID and numeric OID back to symbolic name.

Live numeric GET with no bundle:

  1. Call manager.get() with numeric OIDs.
  2. The manager builds numeric request varbinds.
  3. The transport sends an SNMPv2c message over UDP.
  4. The response is decoded into typed SNMP values.
  5. display_value is still available without symbolic metadata.

Live SNMPv3 GET:

  1. Construct a UsmUser and open V3Manager(...).
  2. V3Manager.open() performs RFC 3414 engine discovery once.
  3. UsmModel wraps the request PDU into an SNMPv3 message, with auth/priv enabled as configured.
  4. The response is authenticated and decrypted if required, then decoded into typed SNMP values.

Live symbolic walk with a bundle:

  1. Load compiled JSON artifacts with load_bundle().
  2. Call manager.walk("IF-MIB::ifTable").
  3. The symbolic root is translated to a numeric OID before network I/O.
  4. The walk uses GETBULK by default or GETNEXT when requested.
  5. Returned numeric OIDs are enriched back into symbolic names and formatted display values.

Live notification send:

  1. Load a bundle only if you want symbolic notification or varbind targets.
  2. Use V2cNotifier.send_trap() or V2cNotifier.send_inform() for SNMPv2c notifications.
  3. Use V3Notifier.send_inform() for SNMPv3 USM informs.
  4. Use V3Notifier.send_trap() for SNMPv3 traps only when you have explicit UsmLocalEngine state.
  5. tsnmp auto-populates sysUpTime.0 and snmpTrapOID.0 unless you override them.
  6. Inform requests wait for a matching response; traps are fire-and-forget.
  7. SNMPv3 informs use peer-discovered receiver state; SNMPv3 traps use the sender’s local authoritative state.

Responder simulation:

  1. Create V2cResponder with either the default in-memory source or a callback-backed source.
  2. Seed exact objects numerically or symbolically when a bundle is loaded.
  3. Call serve() or serve_forever() to answer GET, GET_NEXT, and GET_BULK.
  4. Missing exact objects return noSuchObject; end-of-tree lookups return endOfMibView.

Where Next