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 Configuration (Recommended)
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
- RFC 3411 — SNMP Architecture
- pysnmp Documentation — Python SNMP library
- NET-SNMP snmpd.conf Manual — Daemon configuration reference
- SNMP MIB Browser — Interactive OID tree exploration
- Zabbix SNMP Monitoring — Production NMS integration
Comments