Skip to main content

SNMP Protocol: Network Management 2026 — Complete Guide with Python and snmpd

Created: March 11, 2026 Larry Qu 6 min read

Introduction

SNMP (Simple Network Management Protocol) is the standard protocol for collecting and organizing information about managed devices on IP networks. Routers, switches, servers, printers, and UPS units all expose metrics via SNMP — interface utilization, CPU load, temperature, and more. A Network Management System (NMS) polls these devices periodically to build a real-time picture of infrastructure health.

This guide covers the SNMP architecture (manager-agent model, MIB tree, OID structure), Python pysnmp code with parameter explanations for GET, WALK, SET, and trap operations, snmpd daemon configuration on Linux, SNMPv3 user creation, and a complete interface monitoring example.

SNMP Architecture

SNMP uses a manager-agent model. The manager sends queries; the agent responds with data from the device’s MIB (Management Information Base). Agents can also send unsolicited traps to the manager when events occur (link down, temperature threshold exceeded).

flowchart LR
    subgraph NMS["Network Management System"]
        M[SNMP Manager<br/>Zabbix / Prometheus / Custom]
    end

    subgraph Devices["Managed Devices"]
        R[Router<br/>snmpd agent]
        S[Switch<br/>snmpd agent]
        SRV[Server<br/>snmpd agent]
        UPS[UPS<br/>SNMP agent]
    end

    M -->|SNMP GET/BULK| R
    M -->|SNMP GET/BULK| S
    M -->|SNMP GET/BULK| SRV
    M -->|SNMP GET/BULK| UPS

    R -->|SNMP Trap| M
    S -->|SNMP Trap| M
    SRV -->|SNMP Trap| M
    UPS -->|SNMP Trap| M

MIB Structure (OID Tree)

The MIB organizes all manageable objects in a tree hierarchy. Each node has a numeric identifier (OID) and a human-readable name:

flowchart TD
    root["iso (1)"] --> org["org (3)"]
    org --> dod["dod (6)"]
    dod --> internet["internet (1)"]

    internet --> mgmt["mgmt (2)"]
    internet --> private["private (4)"]

    mgmt --> mib2["mib-2 (1)"]

    mib2 --> system["system (1)<br/>.1.3.6.1.2.1.1"]
    mib2 --> interfaces["interfaces (2)<br/>.1.3.6.1.2.1.2"]
    mib2 --> at["at (3)"]
    mib2 --> ip["ip (4)"]

    system --> sysDescr["sysDescr (1.0)<br/>Device description"]
    system --> sysUpTime["sysUpTime (3.0)<br/>Uptime in hundredths of seconds"]
    system --> sysName["sysName (5.0)<br/>Hostname"]

    interfaces --> ifTable["ifTable (2)<br/>Interface table"]
    ifTable --> ifEntry["ifEntry (1)<br/>Per-interface row"]
    ifEntry --> ifDescr["ifDescr (2)<br/>Interface name"]
    ifEntry --> ifInOctets["ifInOctets (10)<br/>Bytes received"]
    ifEntry --> ifOutOctets["ifOutOctets (16)<br/>Bytes transmitted"]
    ifEntry --> ifOperStatus["ifOperStatus (8)<br/>Up/Down/Testing"]

snmpd Server Configuration

On Linux, the snmpd daemon exposes system metrics via SNMP:

# Install
sudo apt install snmpd snmp

# Configure /etc/snmp/snmpd.conf

Basic snmpd.conf (SNMPv2c, Read-Only)

# /etc/snmp/snmpd.conf

# Listen on all interfaces (UDP 161)
agentAddress udp:161

# Read-only community string (change from "public" in production!)
rocommunity public  default -V systemonly

# Allow full MIB access for monitoring
rocommunity monitoring 10.0.0.0/24

# System contact and location
sysLocation  "Data Center A, Row 3"
sysContact  "[email protected]"

# Enable disk monitoring
disk / 10%

# Enable process monitoring
proc sshd 1 10
proc nginx 1 10

# Enable load average monitoring
load 12 10 5

SNMPv3 provides authentication and encryption. Create a user with USM (User-based Security Model):

# Stop snmpd first
sudo systemctl stop snmpd

# Create SNMPv3 user with auth + privacy
sudo net-snmp-config --create-snmpv3-user \
    -a SHA-256 \
    -A auth-passphrase-here \
    -x AES-128 \
    -X priv-passphrase-here \
    -ro \
    monitoring-user

# Restart snmpd
sudo systemctl start snmpd

The -a flag specifies the authentication protocol (SHA-256 recommended), -x specifies the privacy (encryption) protocol (AES-128 minimum). The -ro flag grants read-only access.

Verify snmpd is Working

# SNMPv2c query (system group)
snmpwalk -v 2c -c public localhost .1.3.6.1.2.1.1

# SNMPv3 query
snmpwalk -v 3 -u monitoring-user -l authPriv \
    -a SHA-256 -A auth-passphrase-here \
    -x AES -X priv-passphrase-here \
    localhost .1.3.6.1.2.1.1

Python SNMP Client with pysnmp

The pysnmp library provides an SNMP client in Python. Each operation type (GET, GETNEXT, SET, BULK, trap) maps to a specific function in the hlapi (high-level API) module.

GET: Retrieve a Single OID Value

Sends a GET request to retrieve the value of a specific OID from the target agent:

from pysnmp.hlapi import *

# GET: retrieve sysDescr.0 from localhost
iterator = getCmd(
    SnmpEngine(),                          # SNMP engine state
    CommunityData('public', mpModel=0),    # v2c community string
    UdpTransportTarget(('localhost', 161)),# target agent
    ContextData(),                         # SNMPv3 context (empty for v2c)
    ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'))  # sysDescr.0
)

# iterator returns (errorIndication, errorStatus, errorIndex, varBinds)
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)

if errorIndication:
    print(f"Error: {errorIndication}")
elif errorStatus:
    print(f"SNMP error: {errorStatus} at {errorIndex}")
else:
    # varBinds is a list of (OID, value) tuples
    for varBind in varBinds:
        print(f'{varBind[0]} = {varBind[1]}')
# Output: SNMPv2-MIB::sysDescr.0 = Linux server-01 6.8.0-x86_64

WALK: Retrieve an Entire Subtree

WALK follows the OID tree lexicographically, retrieving all children of a given OID:

iterator = nextCmd(
    SnmpEngine(),
    CommunityData('public'),
    UdpTransportTarget(('localhost', 161)),
    ContextData(),
    ObjectType(ObjectIdentity('1.3.6.1.2.1.1')),  # system subtree
    lexicographicMode=False
)

for errorIndication, errorStatus, errorIndex, varBinds in iterator:
    if errorIndication:
        print(errorIndication)
        break
    for name, value in varBinds:
        print(f'{name} = {value}')

SET: Modify a Device Configuration

SET writes a value to a writable OID. This requires SNMPv2c with a private (write-enabled) community or SNMPv3 with write access:

iterator = setCmd(
    SnmpEngine(),
    CommunityData('private', mpModel=0),   # write-enabled community
    UdpTransportTarget(('localhost', 161)),
    ContextData(),
    ObjectType(ObjectIdentity('1.3.6.1.2.1.1.5.0'), 'NewDeviceName')  # sysName.0
)

This changes the device’s hostname to NewDeviceName. Use SET sparingly — it modifies device state.

BULK: Efficient Large Data Retrieval

GETBULK retrieves large amounts of data in fewer requests by returning multiple rows per call:

iterator = bulkCmd(
    SnmpEngine(),
    CommunityData('public'),
    UdpTransportTarget(('localhost', 161)),
    ContextData(),
    0, 25,  # non-repeaters=0, max-repetitions=25
    ObjectType(ObjectIdentity('1.3.6.1.2.1.1'))  # system subtree
)

non-repeaters: number of non-repeating OIDs (usually 0). max-repetitions: how many rows to fetch per OID per request. Higher values reduce round trips.

Interface Monitoring Example

A complete function that collects interface statistics from a device:

def get_interface_stats(host):
    """Collect interface name, bytes in/out, speed, and operational status.

    Uses GETNEXT to walk the ifTable subtree and returns a list
    of interface dictionaries.
    """
    iterator = nextCmd(
        SnmpEngine(),
        CommunityData('public'),
        UdpTransportTarget((host, 161)),
        ContextData(),
        ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.2')),     # ifDescr
        ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.10')),    # ifInOctets
        ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.16')),    # ifOutOctets
        ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.5')),     # ifSpeed
        ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.8')),     # ifOperStatus
        lexicographicMode=False
    )

    interfaces = {}
    for errorIndication, errorStatus, errorIndex, varBinds in iterator:
        if errorIndication:
            print(f"Error: {errorIndication}")
            break

        # varBinds contains one row of the interface table
        # Index 0 = ifDescr, 1 = ifInOctets, 2 = ifOutOctets, etc.
        if_name = str(varBinds[0][1])
        interfaces[if_name] = {
            'in_octets': int(varBinds[1][1]),
            'out_octets': int(varBinds[2][1]),
            'speed': int(varBinds[3][1]),
            'status': int(varBinds[4][1])
        }

    return interfaces

# Usage
stats = get_interface_stats('192.168.1.1')
for name, data in stats.items():
    print(f"{name}: {data['in_octets']} in, {data['out_octets']} out, "
          f"{data['speed']} bps, status={'up' if data['status'] == 1 else 'down'}")

Receiving SNMP Traps

Traps are unsolicited notifications from agents to the manager. Listen for them with:

from pysnmp.hlapi import *

def on_trap(transportDispatcher, contextAddr, vars):
    """Callback invoked when a trap is received."""
    print("Trap received from:", contextAddr)
    for oid, value in vars:
        print(f"  {oid} = {value}")

# Set up trap receiver on UDP port 162
transportDispatcher.registerRecvFunct(on_trap)
transportDispatcher.jobStarted(1)

print("Listening for SNMP traps on port 162...")
try:
    transportDispatcher.runDispatcher()
except KeyboardInterrupt:
    transportDispatcher.closeDispatcher()

snmpwalk and snmpget CLI Examples

# Walk the entire system subtree
snmpwalk -v 2c -c public localhost .1.3.6.1.2.1.1

# Get a single value
snmpget -v 2c -c public localhost .1.3.6.1.2.1.1.3.0  # sysUpTime

# Walk interface table
snmpwalk -v 2c -c public localhost .1.3.6.1.2.1.2.2

# Common output:
# IF-MIB::ifDescr.1 = "eth0"
# IF-MIB::ifDescr.2 = "eth1"
# IF-MIB::ifInOctets.1 = 1234567890
# IF-MIB::ifOutOctets.1 = 987654321

Resources

Comments

👍 Was this article helpful?