Transport tweaks

Serve multiple network transports

Listen and respond to SNMP GET/SET/GETNEXT/GETBULK queries with the following options:

  • SNMPv2c
  • with SNMP community “public”
  • allow access to SNMPv2-MIB objects (1.3.6.1.2.1)
  • over IPv4/UDP, listening at 127.0.0.1:161 and over IPv6/UDP, listening at [::1]:161

Either of the following Net-SNMP commands will walk this Agent:

$ snmpwalk -v2c -c public 127.0.0.1 .1.3.6
$ snmpwalk -v2c -c public udp6:[::1] .1.3.6
from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import cmdrsp, context
from pysnmp.carrier.asyncore.dgram import udp, udp6

# Create SNMP engine with autogenernated engineID and pre-bound
# to socket transport dispatcher
snmpEngine = engine.SnmpEngine()

# Transport setup

# UDP over IPv4 at 127.0.0.1:161
config.addTransport(
    snmpEngine,
    udp.domainName,
    udp.UdpTransport().openServerMode(('127.0.0.1', 161))
)
# UDP over IPv6 at [::1]:161
config.addTransport(
    snmpEngine,
    udp6.domainName,
    udp6.Udp6Transport().openServerMode(('::1', 161))
)

# SNMPv2c setup

# SecurityName <-> CommunityName mapping.
config.addV1System(snmpEngine, 'my-area', 'public')

# Allow full MIB access for this user / securityModels at VACM
config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (1, 3, 6, 1, 2, 1), (1, 3, 6, 1, 2, 1))

# Get default SNMP context this SNMP engine serves
snmpContext = context.SnmpContext(snmpEngine)

# Register SNMP Applications at the SNMP engine for particular SNMP context
cmdrsp.GetCommandResponder(snmpEngine, snmpContext)
cmdrsp.SetCommandResponder(snmpEngine, snmpContext)
cmdrsp.NextCommandResponder(snmpEngine, snmpContext)
cmdrsp.BulkCommandResponder(snmpEngine, snmpContext)

# Register an imaginary never-ending job to keep I/O dispatcher running forever
snmpEngine.transportDispatcher.jobStarted(1)

# Run I/O dispatcher which would receive queries and send responses
try:
    snmpEngine.transportDispatcher.runDispatcher()
except:
    snmpEngine.transportDispatcher.closeDispatcher()
    raise

Download script.

Listen on multiple network interfaces

Listen and respond to SNMP GET/SET/GETNEXT/GETBULK queries with the following options:

  • SNMPv2c
  • with SNMP community “public”
  • allow access to SNMPv2-MIB objects (1.3.6.1.2.1)
  • over IPv4/UDP, listening at 127.0.0.1:161 and 127.0.0.2:161 interfaces

Either of the following Net-SNMP commands will walk this Agent:

$ snmpwalk -v2c -c public 127.0.0.1 .1.3.6
$ snmpwalk -v2c -c public 127.0.0.2 .1.3.6
from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import cmdrsp, context
from pysnmp.carrier.asyncore.dgram import udp

# Create SNMP engine with autogenernated engineID and pre-bound
# to socket transport dispatcher
snmpEngine = engine.SnmpEngine()

# Transport setup

# UDP over IPv4 at 127.0.0.1:161
config.addTransport(
    snmpEngine,
    udp.domainName + (1,),
    udp.UdpTransport().openServerMode(('127.0.0.1', 161))
)
# UDP over IPv4 at 127.0.0.2:161
config.addTransport(
    snmpEngine,
    udp.domainName + (2,),
    udp.UdpTransport().openServerMode(('127.0.0.2', 161))
)

# SNMPv2c setup

# SecurityName <-> CommunityName mapping.
config.addV1System(snmpEngine, 'my-area', 'public')

# Allow full MIB access for this user / securityModels at VACM
config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (1, 3, 6, 1, 2, 1), (1, 3, 6, 1, 2, 1))

# Get default SNMP context this SNMP engine serves
snmpContext = context.SnmpContext(snmpEngine)

# Register SNMP Applications at the SNMP engine for particular SNMP context
cmdrsp.GetCommandResponder(snmpEngine, snmpContext)
cmdrsp.SetCommandResponder(snmpEngine, snmpContext)
cmdrsp.NextCommandResponder(snmpEngine, snmpContext)
cmdrsp.BulkCommandResponder(snmpEngine, snmpContext)

# Register an imaginary never-ending job to keep I/O dispatcher running forever
snmpEngine.transportDispatcher.jobStarted(1)

# Run I/O dispatcher which would receive queries and send responses
try:
    snmpEngine.transportDispatcher.runDispatcher()
except:
    snmpEngine.transportDispatcher.closeDispatcher()
    raise

Download script.

Running at secondary network interface

Listen on all local IPv4 interfaces respond to SNMP GET/SET/GETNEXT/GETBULK queries with the following options:

  • SNMPv3
  • with USM user ‘usr-md5-des’, auth: MD5, priv DES
  • allow access to SNMPv2-MIB objects (1.3.6.1.2.1)
  • over IPv4/UDP, listening at 0.0.0.0:161
  • preserve local IP address when responding (Python 3.3+ required)

The following Net-SNMP command will walk this Agent:

$ snmpwalk -v3 -u usr-md5-des -l authPriv -A authkey1 -X privkey1 localhost .1.3.6

In the situation when UDP responder receives a datagram targeted to a secondary (AKA virtial) IP interface or a non-local IP interface (e.g. routed through policy routing or iptables TPROXY facility), OS stack will by default put primary local IP interface address into the IP source field of the response IP packet. Such datagram may not reach the sender as either the sender itself or a stateful firewall somewhere in between would not be able to match response to original request.

The following script solves this problem by preserving original request destination IP address and put it back into response IP packet’s source address field.

To respond from a non-local (e.g. spoofed) IP address, uncomment the .enableTransparent() method call and run this script as root.

from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import cmdrsp, context
from pysnmp.carrier.asyncore.dgram import udp

# Create SNMP engine
snmpEngine = engine.SnmpEngine()

# Transport setup

# Initialize asyncore-based UDP/IPv4 transport
udpSocketTransport = udp.UdpSocketTransport().openServerMode(('0.0.0.0', 161))

# Use sendmsg()/recvmsg() for socket communication (used for preserving
# original destination IP address when responding)
udpSocketTransport.enablePktInfo()

# Enable IP source spoofing (requires root privileges)
# udpSocketTransport.enableTransparent()

# Register this transport at SNMP Engine
config.addTransport(
    snmpEngine,
    udp.domainName,
    udpSocketTransport
)

# SNMPv3/USM setup

# user: usr-md5-des, auth: MD5, priv DES
config.addV3User(
    snmpEngine, 'usr-md5-des',
    config.usmHMACMD5AuthProtocol, 'authkey1',
    config.usmDESPrivProtocol, 'privkey1'
)

# Allow full MIB access for each user at VACM
config.addVacmUser(snmpEngine, 3, 'usr-md5-des', 'authPriv', (1, 3, 6, 1, 2, 1), (1, 3, 6, 1, 2, 1))

# Get default SNMP context this SNMP engine serves
snmpContext = context.SnmpContext(snmpEngine)

# Register SNMP Applications at the SNMP engine for particular SNMP context
cmdrsp.GetCommandResponder(snmpEngine, snmpContext)
cmdrsp.SetCommandResponder(snmpEngine, snmpContext)
cmdrsp.NextCommandResponder(snmpEngine, snmpContext)
cmdrsp.BulkCommandResponder(snmpEngine, snmpContext)

# Register an imaginary never-ending job to keep I/O dispatcher running forever
snmpEngine.transportDispatcher.jobStarted(1)

# Run I/O dispatcher which would receive queries and send responses
try:
    snmpEngine.transportDispatcher.runDispatcher()
except:
    snmpEngine.observer.unregisterObserver()
    snmpEngine.transportDispatcher.closeDispatcher()
    raise

Download script.

See also: library reference.