diff --git a/examples/run_simple_agent_trap.sh b/examples/run_simple_agent_trap.sh new file mode 100644 index 0000000..c0ffc50 --- /dev/null +++ b/examples/run_simple_agent_trap.sh @@ -0,0 +1,166 @@ +# +# python-netsnmpagent simple example agent +# +# Copyright (c) 2013 Pieter Hollants +# Licensed under the GNU Public License (GPL) version 3 +# + +# +# This script makes running simple_agent.py easier for you because it takes +# care of setting everything up so that the example agent can be run +# successfully. +# + +set -u +set -e + +# Find path to snmpd executable +SNMPD_BIN="" +for DIR in /usr/local/sbin /usr/sbin +do + if [ -x $DIR/snmpd ] ; then + SNMPD_BIN=$DIR/snmpd + break + fi +done +if [ -z "$SNMPD_BIN" ] ; then + echo "snmpd executable not found -- net-snmp not installed?" + exit 1 +fi +# Find path to snmptrapd executable +SNMPTRAPD_BIN="" +for DIR in /usr/local/sbin /usr/sbin +do + if [ -x $DIR/snmptrapd ] ; then + SNMPTRAPD_BIN=$DIR/snmptrapd + break + fi +done +if [ -z "$SNMPTRAPD_BIN" ] ; then + echo "snmptrapd executable not found -- net-snmp not installed?" + exit 1 +fi + +# Make sure we leave a clean system upon exit +cleanup() { + if [ -n "$TMPDIR" -a -d "$TMPDIR" ] ; then + # Terminate snmpd, if running + if [ -n "$SNMPD_PIDFILE" -a -e "$SNMPD_PIDFILE" ] ; then + PID="$(cat $SNMPD_PIDFILE)" + if [ -n "$PID" ] ; then + kill -TERM "$PID" + fi + fi + # Terminate snmptrapd, if running + if [ -n "$SNMPTRAPD_PIDFILE" -a -e "$SNMPTRAPD_PIDFILE" ] ; then + PID="$(cat $SNMPTRAPD_PIDFILE)" + if [ -n "$PID" ] ; then + kill -TERM "$PID" + fi + fi + + echo "* Cleaning up..." + + # Clean up temporary directory + rm -rf "$TMPDIR" + fi + + # Make sure echo is back on + stty echo +} +trap cleanup EXIT QUIT TERM KILL INT HUP + + +# Create a temporary directory +TMPDIR="$(mktemp --directory --tmpdir simple_agent.XXXXXXXXXX)" +SNMPD_CONFFILE=$TMPDIR/snmpd.conf +SNMPD_PIDFILE=$TMPDIR/snmpd.pid +AGENTX_SOCK=$TMPDIR/snmpd-agentx.sock + +# Create a minimal snmpd configuration for our purposes +cat <>$SNMPD_CONFFILE +[snmpd] +rocommunity public 127.0.0.1 +rwcommunity simple 127.0.0.1 +agentaddress localhost:5555 +informsink localhost:5556 +smuxsocket localhost:5557 +master agentx +agentXSocket $AGENTX_SOCK + +trapcommunity public +trapsink localhost:5558 +trap2sink localhost:5559 +informsink localhost:5560 + +[snmp] +persistentDir $TMPDIR/state +EOF +touch $TMPDIR/mib_indexes + +# Start a snmpd instance for testing purposes, run as the current user and +# and independent from any other running snmpd instance +$SNMPD_BIN -r -LE warning -M+./ -C -c$SNMPD_CONFFILE -p$SNMPD_PIDFILE + +echo "* Preparing snmptrapd environment..." + +SNMPTRAPD_CONFFILE=$TMPDIR/snmptrapd.conf +SNMPTRAPD_PIDFILE=$TMPDIR/snmptrapd.pid +SNMPTRAPD_HANDLE=$TMPDIR/snmptrapd.handle + +# Create a minimal snmptrapd configuration for our purposes +cat <>$SNMPTRAPD_CONFFILE +snmpTrapdAddr localhost:5558,localhost:5559,localhost:5560 + +doNotRetainNotificationLogs yes + +authCommunity log,execute,net public +#authUser log,execute,net simpleUser noauth + +disableAuthorization yes + +traphandle default $SNMPTRAPD_HANDLE +EOF + +cat <>$SNMPTRAPD_HANDLE +#!/bin/sh + +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +read host +read ip + +logger -t \${0##*/} "host:\$host ip:\$ip" + +while read oid val +do + logger -t \${0##*/} "\$oid \$val" +done +EOF +chmod a+x "$SNMPTRAPD_HANDLE" + +# Start a snmptrapd instance for testing purposes, run as the current user and +# and independent from any other running snmptrapd instance +$SNMPTRAPD_BIN -LE warning -M+./ -C -c$SNMPTRAPD_CONFFILE -p$SNMPTRAPD_PIDFILE -x$AGENTX_SOCK + +# Give the user guidance +echo "* Our snmpd instance is now listening on localhost, port 5555." +echo " From a second console, use the net-snmp command line utilities like this:" +echo "" +echo " cd `pwd`" +echo " snmpwalk -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleMIB" +echo " snmptable -v 2c -c public -M+. -Ci localhost:5555 SIMPLE-MIB::firstTable" +echo " snmpget -v 2c -c public -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0" +echo " snmpset -v 2c -c simple -M+. localhost:5555 SIMPLE-MIB::simpleInteger.0 i 123" +echo "" + +# Workaround to have CTRL-C not generate any visual feedback (we don't do any +# input anyway) +#(ant)stty -echo + +# Now start the simple example agent +echo "* Starting the simple example agent..." +python simple_agent_trap.py -m $AGENTX_SOCK -p $TMPDIR/ + +# Debug mode +#gdb python -ex "run simple_agent_trap.py -m $AGENTX_SOCK -p $TMPDIR/" diff --git a/examples/run_threading_agent_trap.sh b/examples/run_threading_agent_trap.sh new file mode 100644 index 0000000..7757a50 --- /dev/null +++ b/examples/run_threading_agent_trap.sh @@ -0,0 +1,167 @@ +# +# python-netsnmpagent example agent with threading +# +# Copyright (c) 2013 Pieter Hollants +# Licensed under the GNU Public License (GPL) version 3 +# + +# +# This script makes running threading_agent.py easier for you because it takes +# care of setting everything up so that the example agent can be run +# successfully. +# + +set -u +set -e + +# Find path to snmpd executable +SNMPD_BIN="" +for DIR in /usr/local/sbin /usr/sbin +do + if [ -x $DIR/snmpd ] ; then + SNMPD_BIN=$DIR/snmpd + break + fi +done +if [ -z "$SNMPD_BIN" ] ; then + echo "snmpd executable not found -- net-snmp not installed?" + exit 1 +fi + +# Find path to snmptrapd executable +SNMPTRAPD_BIN="" +for DIR in /usr/local/sbin /usr/sbin +do + if [ -x $DIR/snmptrapd ] ; then + SNMPTRAPD_BIN=$DIR/snmptrapd + break + fi +done +if [ -z "$SNMPTRAPD_BIN" ] ; then + echo "snmptrapd executable not found -- net-snmp not installed?" + exit 1 +fi + +# Make sure we leave a clean system upon exit +cleanup() { + if [ -n "$TMPDIR" -a -d "$TMPDIR" ] ; then + # Terminate snmpd, if running + if [ -n "$SNMPD_PIDFILE" -a -e "$SNMPD_PIDFILE" ] ; then + PID="$(cat $SNMPD_PIDFILE)" + if [ -n "$PID" ] ; then + kill -TERM "$PID" + fi + fi + # Terminate snmptrapd, if running + if [ -n "$SNMPTRAPD_PIDFILE" -a -e "$SNMPTRAPD_PIDFILE" ] ; then + PID="$(cat $SNMPTRAPD_PIDFILE)" + if [ -n "$PID" ] ; then + kill -TERM "$PID" + fi + fi + + echo "* Cleaning up..." + + # Clean up temporary directory + rm -rf "$TMPDIR" + fi + + # Make sure echo is back on + stty echo +} +trap cleanup EXIT QUIT TERM KILL INT HUP + +echo "* Preparing snmpd environment..." + +# Create a temporary directory +TMPDIR="$(mktemp --directory --tmpdir threading_agent.XXXXXXXXXX)" +SNMPD_CONFFILE=$TMPDIR/snmpd.conf +SNMPD_PIDFILE=$TMPDIR/snmpd.pid +AGENTX_SOCK=$TMPDIR/snmpd-agentx.sock + +# Create a minimal snmpd configuration for our purposes +cat <>$SNMPD_CONFFILE +[snmpd] +rocommunity public 127.0.0.1 +rwcommunity simple 127.0.0.1 +agentaddress localhost:5555 +informsink localhost:5556 +smuxsocket localhost:5557 +master agentx +agentXSocket $AGENTX_SOCK + +trapcommunity public +trapsink localhost:5558 +trap2sink localhost:5559 +informsink localhost:5560 + +[snmp] +persistentDir $TMPDIR/state +EOF +touch $TMPDIR/mib_indexes + +# Start a snmpd instance for testing purposes, run as the current user and +# and independent from any other running snmpd instance +$SNMPD_BIN -r -LE warning -M+./ -C -c$SNMPD_CONFFILE -p$SNMPD_PIDFILE + +echo "* Preparing snmptrapd environment..." + +SNMPTRAPD_CONFFILE=$TMPDIR/snmptrapd.conf +SNMPTRAPD_PIDFILE=$TMPDIR/snmptrapd.pid +SNMPTRAPD_HANDLE=$TMPDIR/snmptrapd.handle + +# Create a minimal snmptrapd configuration for our purposes +cat <>$SNMPTRAPD_CONFFILE +snmpTrapdAddr localhost:5558,localhost:5559,localhost:5560 + +doNotRetainNotificationLogs yes + +authCommunity log,execute,net public +#authUser log,execute,net simpleUser noauth + +disableAuthorization yes + +traphandle default $SNMPTRAPD_HANDLE +EOF + +cat <>$SNMPTRAPD_HANDLE +#!/bin/sh + +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +read host +read ip + +logger -t \${0##*/} "host:\$host ip:\$ip" + +while read oid val +do + logger -t \${0##*/} "\$oid \$val" +done +EOF +chmod a+x "$SNMPTRAPD_HANDLE" + +# Start a snmptrapd instance for testing purposes, run as the current user +# and independent from any other running snmptrapd instance +$SNMPTRAPD_BIN -LE warning -M+./ -C -c$SNMPTRAPD_CONFFILE -p$SNMPTRAPD_PIDFILE -x$AGENTX_SOCK + +# Give the user guidance +echo "* Our snmpd instance is now listening on localhost, port 5555." +echo " From a second console, use the net-snmp command line utilities like this:" +echo "" +echo " cd `pwd`" +echo " snmpwalk -v 2c -c public -M+. localhost:5555 THREADING-MIB::threadingMIB" +echo " snmpget -v 2c -c public -M+. localhost:5555 THREADING-MIB::threadingString.0" +echo "" + +# Workaround to have CTRL-C not generate any visual feedback (we don't do any +# input anyway) +stty -echo + +# Now start the threading agent +echo "* Starting the threading agent..." +python threading_agent_trap.py -m $AGENTX_SOCK -p $TMPDIR/ + +# Debug mode +# Remember to comment out 'stty -echo' few lines above +#gdb python -ex "run threading_agent_trap.py -m $AGENTX_SOCK -p $TMPDIR/" diff --git a/examples/simple_agent_trap.py b/examples/simple_agent_trap.py new file mode 100644 index 0000000..6cf7a61 --- /dev/null +++ b/examples/simple_agent_trap.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# +# python-netsnmpagent simple example agent +# +# Copyright (c) 2013 Pieter Hollants +# Licensed under the GNU Public License (GPL) version 3 +# + +# +# This is an example of a simple SNMP sub-agent using the AgentX protocol +# to connect to a master agent (snmpd), extending its MIB with the +# information from the included SIMPLE-MIB.txt. +# +# Use the included script run_simple_agent.sh to test this example. +# +# Alternatively, if you want to test with your system-wide snmpd instance, +# it must have as minimal configuration: +# +# rocommunity 127.0.0.1 +# master agentx +# +# snmpd must be started first, then this agent must be started as root +# (because of the AgentX socket under /var/run/agentx/master), eg. via "sudo". +# +# Then, from a separate console and from inside the python-netsnmpagent +# directory, you can run eg.: +# +# snmpwalk -v 2c -c -M+. localhost SIMPLE-MIB::simpleMIB +# +# If you wish to test setting values as well, your snmpd.conf needs a +# line like this: +# +# rwcommunity 127.0.0.1 +# +# Then you can try something like: +# +# snmpset -v 2c -c -M+. localhost \ +# SIMPLE-MIB::simpleInteger i 0 +# + +import sys, os, signal +import optparse +import pprint + +# Make sure we use the local copy, not a system-wide one +sys.path.insert(0, os.path.dirname(os.getcwd())) +import netsnmpagent + +prgname = sys.argv[0] + +# Process command line arguments +parser = optparse.OptionParser() +parser.add_option( + "-m", + "--mastersocket", + dest="mastersocket", + help="Sets the transport specification for the master agent's AgentX socket", + default="/var/run/agentx/master" +) +parser.add_option( + "-p", + "--persistencedir", + dest="persistencedir", + help="Sets the path to the persistence directory", + default="/var/lib/net-snmp" +) +(options, args) = parser.parse_args() + +# Get terminal width for usage with pprint +rows,columns = os.popen("stty size", "r").read().split() + +# First, create an instance of the netsnmpAgent class. We specify the +# fully-qualified path to SIMPLE-MIB.txt ourselves here, so that you +# don't have to copy the MIB to /usr/share/snmp/mibs. +try: + agent = netsnmpagent.netsnmpAgent( + AgentName = "SimpleAgent", + MasterSocket = options.mastersocket, + PersistenceDir = options.persistencedir, + MIBFiles = [ os.path.abspath(os.path.dirname(sys.argv[0])) + + "/SIMPLE-MIB.txt" ] + ) +except netsnmpagent.netsnmpAgentException as e: + print "{0}: {1}".format(prgname, e) + sys.exit(1) + +# Then we create all SNMP scalar variables we're willing to serve. +simpleInteger = agent.Integer32( + oidstr = "SIMPLE-MIB::simpleInteger" +) +simpleIntegerContext1 = agent.Integer32( + oidstr = "SIMPLE-MIB::simpleInteger", + context = "context1", + initval = 200, +) +simpleIntegerRO = agent.Integer32( + oidstr = "SIMPLE-MIB::simpleIntegerRO", + writable = False +) +simpleUnsigned = agent.Unsigned32( + oidstr = "SIMPLE-MIB::simpleUnsigned" +) +simpleUnsignedRO = agent.Unsigned32( + oidstr = "SIMPLE-MIB::simpleUnsignedRO", + writable = False +) +simpleCounter32 = agent.Counter32( + oidstr = "SIMPLE-MIB::simpleCounter32" +) +simpleCounter32Context2 = agent.Counter32( + oidstr = "SIMPLE-MIB::simpleCounter32", + context = "context2", + initval = pow(2,32) - 10, # To rule out endianness bugs +) +simpleCounter64 = agent.Counter64( + oidstr = "SIMPLE-MIB::simpleCounter64" +) +simpleCounter64Context2 = agent.Counter64( + oidstr = "SIMPLE-MIB::simpleCounter64", + context = "context2", + initval = pow(2,64) - 10, # To rule out endianness bugs +) +simpleTimeTicks = agent.TimeTicks( + oidstr = "SIMPLE-MIB::simpleTimeTicks" +) +simpleIpAddress = agent.IpAddress( + oidstr = "SIMPLE-MIB::simpleIpAddress", + initval="127.0.0.1" +) +simpleOctetString = agent.OctetString( + oidstr = "SIMPLE-MIB::simpleOctetString", + initval = "Hello World" +) +simpleDisplayString = agent.DisplayString( + oidstr = "SIMPLE-MIB::simpleDisplayString", + initval = "Nice to meet you" +) + +# Create the first table +firstTable = agent.Table( + oidstr = "SIMPLE-MIB::firstTable", + indexes = [ + agent.DisplayString() + ], + columns = [ + (2, agent.DisplayString("Unknown place")), + (3, agent.Integer32(0)) + ], + counterobj = agent.Unsigned32( + oidstr = "SIMPLE-MIB::firstTableNumber" + ) +) + +# Add the first table row +firstTableRow1 = firstTable.addRow([agent.DisplayString("aa")]) +firstTableRow1.setRowCell(2, agent.DisplayString("Prague")) +firstTableRow1.setRowCell(3, agent.Integer32(20)) + +# Add the second table row +firstTableRow2 = firstTable.addRow([agent.DisplayString("ab")]) +firstTableRow2.setRowCell(2, agent.DisplayString("Barcelona")) +firstTableRow2.setRowCell(3, agent.Integer32(28)) + +# Add the third table row +firstTableRow3 = firstTable.addRow([agent.DisplayString("bb")]) +firstTableRow3.setRowCell(3, agent.Integer32(18)) + +# Create the second table +secondTable = agent.Table( + oidstr = "SIMPLE-MIB::secondTable", + indexes = [ + agent.Integer32() + ], + columns = [ + (2, agent.DisplayString("Unknown interface")), + (3, agent.Unsigned32()) + ], + counterobj = agent.Unsigned32( + oidstr = "SIMPLE-MIB::secondTableNumber" + ) +) + +# Add the first table row +secondTableRow1 = secondTable.addRow([agent.Integer32(1)]) +secondTableRow1.setRowCell(2, agent.DisplayString("foo0")) +secondTableRow1.setRowCell(3, agent.Unsigned32(5030)) + +# Add the second table row +secondTableRow2 = secondTable.addRow([agent.Integer32(2)]) +secondTableRow2.setRowCell(2, agent.DisplayString("foo1")) +secondTableRow2.setRowCell(3, agent.Unsigned32(12842)) + +# Finally, we tell the agent to "start". This actually connects the +# agent to the master agent. +try: + agent.start() +except netsnmpagent.netsnmpAgentException as e: + print "{0}: {1}".format(prgname, e) + sys.exit(1) + +print "{0}: AgentX connection to snmpd established.".format(prgname) + +# Helper function that dumps the state of all registered SNMP variables +def DumpRegistered(): + for context in agent.getContexts(): + print "{0}: Registered SNMP objects in Context \"{1}\": ".format(prgname, context) + vars = agent.getRegistered(context) + pprint.pprint(vars, width=columns) + print +DumpRegistered() + +# Install a signal handler that terminates our simple agent when +# CTRL-C is pressed or a KILL signal is received +def TermHandler(signum, frame): + global loop + loop = False +signal.signal(signal.SIGINT, TermHandler) +signal.signal(signal.SIGTERM, TermHandler) + +# Install a signal handler that dumps the state of all registered values +# when SIGHUP is received +def HupHandler(signum, frame): + DumpRegistered() +signal.signal(signal.SIGHUP, HupHandler) + +# The simple agent's main loop. We loop endlessly until our signal +# handler above changes the "loop" variable. +print "{0}: Serving SNMP requests, send SIGHUP to dump SNMP object state, press ^C to terminate...".format(prgname) + +sendTrap = True +loop = True +while (loop): + # Block and process SNMP requests, if available + agent.check_and_process() + + # Since we didn't give simpleCounter, simpleCounter64 and simpleTimeTicks + # a real meaning in the SIMPLE-MIB, we can basically do with them whatever + # we want. Here, we just increase them, although in different manners. + simpleCounter32.update(simpleCounter32.value() + 2) + simpleCounter64.update(simpleCounter64.value() + 4294967294) + simpleTimeTicks.update(simpleTimeTicks.value() + 1) + + # With counters, you can also call increment() on them + simpleCounter32Context2.increment() # By 1 + simpleCounter64Context2.increment(5) # By 5 + + # agent.check_and_process() is trigering when trap is send so to disable endless loop + # there is flag set to skip sending trap on next check + if sendTrap: + trapOid = 'SNMPv2-MIB::sysDescr' + trapData = [ + { 'oid':'SNMPv2-MIB::sysDescr.0', 'val':'test state' }, + { 'oid':'SIMPLE-MIB::simpleCounter64.0', 'val':1234567890L }, + ] + agent.send_trap( + oid = trapOid, + traps = trapData, + ) + sendTrap = False + else: + sendTrap = True + +print "{0}: Terminating.".format(prgname) +agent.shutdown() diff --git a/examples/threading_agent_trap.py b/examples/threading_agent_trap.py new file mode 100644 index 0000000..a5a9b34 --- /dev/null +++ b/examples/threading_agent_trap.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +# +# python-netsnmpagent example agent with threading +# +# Copyright (c) 2013 Pieter Hollants +# Licensed under the GNU Public License (GPL) version 3 +# + +# +# simple_agent.py demonstrates registering the various SNMP object types quite +# nicely but uses an inferior control flow logic: the main loop blocks in +# net-snmp's check_and_process() call until some event happens (eg. SNMP +# requests need processing). Only then will data be updated, not inbetween. And +# on the other hand, SNMP requests can not be handled while data is being +# updated, which might take longer periods of time. +# +# This example agent uses a more real life-suitable approach by outsourcing the +# data update process into a separate thread that gets woken up through an +# SIGALRM handler at an configurable interval. This does only ensure periodic +# data updates, it also makes sure that SNMP requests will always be replied to +# in time. +# +# Note that this implementation does not address possible locking issues: if +# a SNMP client's requests are processed while the data update thread is in the +# midst of refreshing the SNMP objects, the client might receive partially +# inconsistent data. +# +# Use the included script run_threading_agent.sh to test this example. +# +# Alternatively, see the comment block in the head of simple_agent.py for +# adaptable instructions how to run this example against a system-wide snmpd +# instance. +# + +import sys, os, signal, time +import optparse, threading, subprocess + +# Make sure we use the local copy, not a system-wide one +sys.path.insert(0, os.path.dirname(os.getcwd())) +import netsnmpagent + +prgname = sys.argv[0] + +# Process command line arguments +parser = optparse.OptionParser() +parser.add_option( + "-i", + "--interval", + dest="interval", + help="Set interval in seconds between data updates", + default=30 +) +parser.add_option( + "-m", + "--mastersocket", + dest="mastersocket", + help="Sets the transport specification for the master agent's AgentX socket", + default="/var/run/agentx/master" +) +parser.add_option( + "-p", + "--persistencedir", + dest="persistencedir", + help="Sets the path to the persistence directory", + default="/var/lib/net-snmp" +) +(options, args) = parser.parse_args() + +headerlogged = 0 +def LogMsg(msg): + """ Writes a formatted log message with a timestamp to stdout. """ + + global headerlogged + + if headerlogged == 0: + print "{0:<8} {1:<90} {2}".format( + "Time", + "MainThread", + "UpdateSNMPObjsThread" + ) + print "{0:-^120}".format("-") + headerlogged = 1 + + threadname = threading.currentThread().name + + funcname = sys._getframe(1).f_code.co_name + if funcname == "": + funcname = "Main code path" + elif funcname == "LogNetSnmpMsg": + funcname = "net-snmp code" + else: + funcname = "{0}()".format(funcname) + + if threadname == "MainThread": + logmsg = "{0} {1:<112.112}".format( + time.strftime("%T", time.localtime(time.time())), + "{0}: {1}".format(funcname, msg) + ) + else: + logmsg = "{0} {1:>112.112}".format( + time.strftime("%T", time.localtime(time.time())), + "{0}: {1}".format(funcname, msg) + ) + print logmsg + +def LogNetSnmpMsg(priority, msg): + """ Log handler for log messages generated by net-snmp code. """ + + LogMsg("[{0}] {1}.".format(priority, msg)) + +# Create an instance of the netsnmpAgent class +try: + agent = netsnmpagent.netsnmpAgent( + AgentName = "ThreadingAgent", + MasterSocket = options.mastersocket, + PersistenceDir = options.persistencedir, + MIBFiles = [ os.path.abspath(os.path.dirname(sys.argv[0])) + + "/THREADING-MIB.txt" ], + LogHandler = LogNetSnmpMsg, + ) +except netsnmpagent.netsnmpAgentException as e: + print "{0}: {1}".format(prgname, e) + sys.exit(1) + +# Register the only SNMP object we server, a DisplayString +threadingString = agent.DisplayString( + oidstr = "THREADING-MIB::threadingString", + initval = "" +) + +def UpdateSNMPObjs(): + """ Function that does the actual data update. """ + + global threadingString + + LogMsg("Beginning data update.") + data = "" + + # Obtain the data by calling an external command. We don't use + # subprocess.check_output() here for compatibility with Python versions + # older than 2.7. + LogMsg("Calling external command \"sleep 5; date\".") + proc = subprocess.Popen( + "sleep 5; date", shell=True, env={ "LANG": "C" }, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + output = proc.communicate()[0].splitlines()[0] + rc = proc.poll() + if rc != 0: + LogMsg("An error occured executing the command: {0}".format(output)) + return + + msg = "Updating \"threadingString\" object with data \"{0}\"." + LogMsg(msg.format(output)) + threadingString.update(output) + + # send trap + global agent + trapOid = 'SNMPv2-MIB::sysDescr' + trapData = [ + { 'oid':'SNMPv2-MIB::sysDescr.0', 'val':'test threading agent trap' }, + ] + agent.send_trap( + oid = trapOid, + traps = trapData, + ) + + LogMsg("Data update done, exiting thread.") + +def UpdateSNMPObjsAsync(): + """ Starts UpdateSNMPObjs() in a separate thread. """ + + # UpdateSNMPObjs() will be executed in a separate thread so that the main + # thread can continue looping and processing SNMP requests while the data + # update is still in progress. However we'll make sure only one update + # thread is run at any time, even if the data update interval has been set + # too low. + if threading.active_count() == 1: + LogMsg("Creating thread for UpdateSNMPObjs().") + t = threading.Thread(target=UpdateSNMPObjs, name="UpdateSNMPObjsThread") + t.daemon = True + t.start() + else: + LogMsg("Data update still active, data update interval too low?") + +# Start the agent (eg. connect to the master agent). +try: + agent.start() +except netsnmpagent.netsnmpAgentException as e: + LogMsg("{0}: {1}".format(prgname, e)) + sys.exit(1) + +# Trigger initial data update. +LogMsg("Doing initial call to UpdateSNMPObjsAsync().") +UpdateSNMPObjsAsync() + +# Install a signal handler that terminates our threading agent when CTRL-C is +# pressed or a KILL signal is received +def TermHandler(signum, frame): + global loop + loop = False +signal.signal(signal.SIGINT, TermHandler) +signal.signal(signal.SIGTERM, TermHandler) + +# Define a signal handler that takes care of updating the data periodically +def AlarmHandler(signum, frame): + global loop, timer_triggered + + LogMsg("Got triggered by SIGALRM.") + + if loop: + timer_triggered = True + + UpdateSNMPObjsAsync() + + signal.signal(signal.SIGALRM, AlarmHandler) + signal.setitimer(signal.ITIMER_REAL, float(options.interval)) +msg = "Installing SIGALRM handler triggered every {0} seconds." +msg = msg.format(options.interval) +LogMsg(msg) +signal.signal(signal.SIGALRM, AlarmHandler) +signal.setitimer(signal.ITIMER_REAL, float(options.interval)) + +# The threading agent's main loop. We loop endlessly until our signal +# handler above changes the "loop" variable. +LogMsg("Now serving SNMP requests, press ^C to terminate.") + +loop = True +while loop: + # Block until something happened (signal arrived, SNMP packets processed) + timer_triggered = False + res = agent.check_and_process() + if res == -1 and not timer_triggered and loop: + loop = False + LogMsg("Error {0} in SNMP packet processing!".format(res)) + elif loop and timer_triggered: + LogMsg("net-snmp's check_and_process() returned due to SIGALRM (res={0}), doing another loop.".format(res)) + elif loop: + LogMsg("net-snmp's check_and_process() returned (res={0}), doing another loop.".format(res)) + +LogMsg("Terminating.") +agent.shutdown() diff --git a/netsnmpagent.py b/netsnmpagent.py index f63be6e..c474324 100644 --- a/netsnmpagent.py +++ b/netsnmpagent.py @@ -338,17 +338,11 @@ def _py_index_stop_callback(majorID, minorID, serverarg, clientarg): # Initialize our SNMP object registry self._objs = defaultdict(dict) - def _prepareRegistration(self, oidstr, writable = True): - """ Prepares the registration of an SNMP object. - - "oidstr" is the OID to register the object at. - "writable" indicates whether "snmpset" is allowed. """ - - # Make sure the agent has not been start()ed yet - if self._status != netsnmpAgentStatus.REGISTRATION: - raise netsnmpAgentException("Attempt to register SNMP object " \ - "after agent has been started!") - + def _prepareOID(self, oidstr): + """ Convert OID to c_oid type. + "oidstr" is the oid to read + Return tuple c_oid array and c_size_t length + """ if self.UseMIBFiles: # We can't know the length of the internal OID representation # beforehand, so we use a MAX_OID_LEN sized buffer for the call to @@ -363,6 +357,10 @@ def _prepareRegistration(self, oidstr, writable = True): ctypes.byref(oid_len) ) == 0: raise netsnmpAgentException("read_objid({0}) failed!".format(oidstr)) + + # trim trailing zeroes in oid array + trimOID = c_oid * oid_len.value + oid = trimOID( *oid[0:oid_len.value] ) else: # Interpret the given oidstr as the oid itself. try: @@ -372,6 +370,22 @@ def _prepareRegistration(self, oidstr, writable = True): oid = (c_oid * len(parts))(*parts) oid_len = ctypes.c_size_t(len(parts)) + + return (oid, oid_len) + + + def _prepareRegistration(self, oidstr, writable = True): + """ Prepares the registration of an SNMP object. + + "oidstr" is the OID to register the object at. + "writable" indicates whether "snmpset" is allowed. """ + + # Make sure the agent has not been start()ed yet + if self._status != netsnmpAgentStatus.REGISTRATION: + raise netsnmpAgentException("Attempt to register SNMP object " \ + "after agent has been started!") + + (oid, oid_len) = self._prepareOID( oidstr ) # Do we allow SNMP SETting to this OID? handler_modes = HANDLER_CAN_RWRITE if writable \ @@ -978,5 +992,184 @@ def shutdown(self): # one effectively has to rely on the OS to release resources. #libnsa.shutdown_agent() + def send_trap(self, *args, **kwargs): + '''Send SNMP traps + To send simple trap: + send_trap(,) + send_trap(trap=,specific=) + + Trap data format: + trapData = [ {'oid':,'var':,'type':}, ] + + By default net-snmp lib add sysUpTime.instance with agent uptime. + Optional add 'uptime = ' to send_trap to use other uptime value + + To send V2 trap: + send_trap( + oid = , + traps = trapData + ) + To send V3 trap: + send_trap( + oid = , + traps = trapData , + context = + ) + ''' + + agent = self + + class snmp_pdu(object): + """ clas for handling SNMP PDU objects """ + + pdu = None + + def __init__(self, pduType = SNMP_MSG_TRAP2): + """create PDU object """ + self.pdu = libnsX.snmp_pdu_create(SNMP_MSG_TRAP2) + + def __del__(self): + """ destructor """ + if self.pdu: + self.free() + + def free(self): + """ free PDU struct """ + if self.pdu: + libnsX.snmp_free_pdu(self.pdu) + self.pdu = None + + def variables(self): + """ function variables() + + return netsnmp_variable_list pointer from PDU + """ + return self.pdu.contents.variables + + def _humanToASNtype(self, varType): + """ convert ASN type name to compatible for snmp_add_var() + type char + return single char + """ + if varType == None: + varType = '=' + if len(varType) > 1: + varTypeL = varType.lower() + if varTypeL[0:3] == 'hex': + varType = 'x' + elif varTypeL[0:3] in ('obj','oid'): + varType = 'o' + elif varTypeL[0:4] == 'uinteger': + varType = '3' + elif varTypeL in ('gauge','unsigned32'): + varType = 'u' + elif varTypeL[0:2] == 'ip': + varType = 'a' + elif varTypeL[0:3] == 'dec': + varType = 'd' + elif varTypeL == 'counter64': + varType = 'C' + elif varTypeL in ('integer','integer32'): + varType = 'i' + elif varType[0] == 'B': + varType = 'b' + return varType[0] + + def add(self, varOID, varData, varType = '='): + """add OID value to PDU list + varType can be any of the following chars:" + i for INTEGER, INTEGER32 + u for UNSIGNED, GAUGE + 3 for UINTEGER + c for COUNTER, COUNTER32 + C for COUNTER64 + s for STRING,OCTET_STR + x for HEX STRING + d for DECIMAL STRING + n for NULLOBJ + o for OBJID + t for TIMETICKS + a for IPADDRESS + b for BITS + = undocumented autodetect feature to get MIB 'SYNTAX' definition + """ + + varType = self._humanToASNtype(varType) + + (varOid, varOidLen) = agent._prepareOID(varOID) + ret = 255 + while ret: + ret = libnsX.snmp_add_var( + self.pdu, + ctypes.cast(ctypes.byref(varOid), c_oid_p), + varOidLen.value, + ctypes.c_char(varType), + ctypes.c_char_p('{0}'.format(varData)) + ) + if ret != 0: + if varType != '=': + varType = '=' + print "ZDBG: ret={0}".format(ret) + else: + break + + return ret + + trap = kwargs.get('trap') + specific = kwargs.get('specific') + oid = kwargs.get('oid') + traps = kwargs.get('traps') + context = kwargs.get('context') + uptime = kwargs.get('uptime') + + if oid: + # send itrap SNMPv2 or SNMPv3 + pdu = snmp_pdu() + + if uptime: + pdu.add('SNMPv2-MIB::sysUpTime.0', uptime, 't') + + result = pdu.add('SNMPv2-MIB::snmpTrapOID.0', oid) + if result != 0: + msg = "Failed to add {0} as snmpTrapOID!".format(oid) + raise netsnmpAgentException(msg) + else: + # add traps + if traps == None: + traps = [] + for entry in traps: + if entry.has_key('oid'): + varOid = entry['oid'] + if entry.has_key('val'): + varData = entry['val'] + varType = None + if entry.has_key('type'): + varType = entry['type'] + pdu.add(varOid, varData, varType) + else: + msg = "missing 'val' key in trap list!" + raise netsnmpAgentException(msg) + else: + msg = "missing 'oid' key in trap list!" + raise netsnmpAgentException(msg) + + variables = pdu.variables() + if context: + # SNMPv3 trap have context + context = ctype.c_char_p(context) + libnsa.send_v3trap(variables, context) + else: + # SNMPv2 trap + libnsa.send_v2trap(variables) + else: + # send easy trap + if trap and specific: + trap = ctypes.c_int(trap) + specific = ctypes.c_int(specific) + elif len(args) == 2 and type(args[0]) == int and type(args[1]) == int: + trap = ctypes.c_int(args[0]) + specific = ctypes.c_int(args[1]) + libnsa.send_easy_trap(trap, specific) + class netsnmpAgentException(Exception): pass diff --git a/netsnmpapi.py b/netsnmpapi.py index 99e6b8e..7012cc5 100644 --- a/netsnmpapi.py +++ b/netsnmpapi.py @@ -204,7 +204,9 @@ class netsnmp_handler_registration(ctypes.Structure): pass # include/net-snmp/library/asn1.h ASN_INTEGER = 0x02 ASN_OCTET_STR = 0x04 +ASN_CONSTRUCTOR = 0x20 ASN_APPLICATION = 0x40 +ASN_CONTEXT = 0x80 # counter64 requires some extra work because it can't be reliably represented # by a single C data type @@ -415,3 +417,111 @@ class netsnmp_table_data_set(ctypes.Structure): pass ctypes.c_int # int block ] f.restype = ctypes.c_int + +# include/net-snmp/agent/agent_trap.h +# void send_easy_trap(int trap, int specific); +for f in [ libnsa.send_easy_trap ]: + f.argtypes = [ + ctypes.c_int, # int trap + ctypes.c_int # int specific + ] + f.restype = None # void +# void send_v2trap(netsnmp_variable_list *vars); +for f in [ libnsa.send_v2trap ]: + f.argtypes = [ + netsnmp_variable_list_p # netsnmp_variable_list *vars + ] + f.restype = None # void +# void send_v3trap(netsnmp_variable_list *vars, char *context); +for f in [ libnsa.send_v3trap ]: + f.argtypes = [ + netsnmp_variable_list_p, # netsnmp_variable_list *vars + ctypes.c_char_p # char *context + ] + f.restype = None # void + +# pdu definition +c_ipaddr = (ctypes.c_ubyte * 4) +c_ubyte_p = ctypes.POINTER(ctypes.c_ubyte) + +# include/net-snmp/types.h +class netsnmp_pdu(ctypes.Structure): pass +netsnmp_pdu_p = ctypes.POINTER(netsnmp_pdu) +netsnmp_pdu._fields_ = [ + ("version", ctypes.c_long), # snmp version + ("command", ctypes.c_int), # Type of this PDU + ("reqid", ctypes.c_long), # Request id + ("msgid", ctypes.c_long), # Message id for V3 messages + ("transid", ctypes.c_long), # Unique ID for incoming transactions + ("sessid", ctypes.c_long), # Session id for AgentX messages + ("errstat", ctypes.c_long), # Error status + ("errindex", ctypes.c_long), # Error index + ("time", ctypes.c_ulong), # uptime + ("flags", ctypes.c_ulong), # + ("securityModel", ctypes.c_int), # + # noAuthNoPriv, authNoPriv, authPriv + ("securityLevel", ctypes.c_int), # + ("msgParseModel", ctypes.c_int), # + # Transport-specific opaque data. This replaces the IP-centric address + ("transport_data", ctypes.c_void_p ), # + ("transport_data_length", ctypes.c_int ), # + # The actual transport domain. This SHOULD NOT BE FREE()D. + ("tDomain", c_oid_p), # + ("tDomainLen", ctypes.c_size_t ), # + ("variables", netsnmp_variable_list_p ), # + # SNMPv1 & SNMPv2c fields + ("community", c_ubyte_p ), # community for outgoing requests. + ("community_len", ctypes.c_size_t ), # + # Trap information + ("enterprise", c_oid_p), # System OID + ("enterprise_length", ctypes.c_size_t ), # + ("trap_type", ctypes.c_long ), # trap type + ("specific_type", ctypes.c_long ), # specific type + ("agent address", c_ipaddr ), # This is ONLY used for v1 TRAPs + # SNMPv3 fields + ("contextEngineID", c_ubyte_p ), # context snmpEngineID + ("contextEngineIDLen", ctypes.c_size_t ), # Length of contextEngineID + ("contextName", ctypes.c_char_p), # authoritative contextName + ("contextNameLen", ctypes.c_size_t ), # Length of contextName + ("securityEngineID", c_ubyte_p ), # authoritative snmpEngineID for security + ("securityEngineIDLen", ctypes.c_size_t ), # Length of securityEngineID + ("securityName", ctypes.c_char_p ), # on behalf of this principal + ("securityNameLen", ctypes.c_size_t ), # Length of securityName + # AgentX fields (also uses SNMPv1 community field) + ("priority", ctypes.c_int ), # + ("range_subid", ctypes.c_int ), # + ("securityStateRef", ctypes.c_void_p), # +] + +# include/net-snmp/snmp.h +SNMP_MSG_TRAP = ASN_CONTEXT | ASN_CONSTRUCTOR | 0x4 +SNMP_MSG_TRAP2 = ASN_CONTEXT | ASN_CONSTRUCTOR | 0x7 + +# include/net-snmp/pdu_api.h +#netsnmp_pdu *snmp_pdu_create(int type); +for f in [ libnsX.snmp_pdu_create ]: + f.argtypes = [ + ctypes.c_int + ] + f.restype = netsnmp_pdu_p + +#void snmp_free_pdu( netsnmp_pdu *pdu); +for f in [ libnsX.snmp_free_pdu ]: + f.argumets = [ + netsnmp_pdu_p + ] + f.restype = None + +# int snmp_add_var(netsnmp_pdu *pdu, +# const oid * name, size_t name_length, char type, const char *value) +for f in [ libnsX.snmp_add_var ]: + f.arguments = [ + netsnmp_pdu_p, # netsnmp_pdu *pdu + c_oid_p, # const oid *name + ctypes.c_size_t, # size_t name_length + ctypes.c_char, # char type('=' to get type from OID tree) + ctypes.c_char_p # const char *value + + ] + f.restype = ctypes.c_int +