I'm working on a small script that'll act as an snmp pass_persist handler. I want it to read a file in (called 'numbers', for now in the same dir) which just contains some integers and return these as an oid tree.
I've been stuck on this for a few days now, and I realise now it's due to a fundamental misunderstanding of how snmpd works. I'm using the snmpd.conf man page which makes no mention of any difference in how 'get' and 'getnext' requests are to be handled, but I assume there is one. I can't for the life of me get snmpwalk to work with this script.
Could someone who knows a little more about snmp look this code over ? I've seen several other versions of pass scripts, including a few in python but I've not been able to see from looking at the code how they handle the protocol differently to my code. I saw one implementation that handled a blank command ( '' ), but others that apparently didn't.
Basically, I'm pretty confused at this point ! - It's also proving pretty hard to debug snmpd as it's the one calling my script, not me. I'm logging what i can, and running snmpd in the foreground, but other than that it's all a bit "black-box".
Can anyone shed some light ?
i.e: numbers file:
101 102 103 I want returned as: .1.3.6.1.4.1..[snip]..1 = 101 .1.3.6.1.4.1..[snip]..2 = 102 .1.3.6.1.4.1..[snip]..3 = 103
My script (I'm not worried about returning anything other than integers, and i know the file close will never be reached, but it makes me feel better):
#!/bin/python -u
import os,sys, syslog
def getLine():
return sys.stdin.readline().strip()
def getFileLine(sub_oid, lines):
sub_oid = int(sub_oid)
if sub_oid >= len(lines):
return 'NONE'
else:
return lines[sub_oid]
def printOutput(oid, var_type, varbind_value):
if varbind_value == 'NONE':
print 'NONE'
else:
print oid
print var_type
print varbind_value
######################################################
sub_oid = 0
FH = open('numbers','r')
lines = FH.readlines()
while True:
command = getLine()
syslog.syslog("command: %s" % command)
if command == 'PING':
syslog.syslog('got a ping')
print 'PONG'
elif command == 'get':
given_oid = getLine()
sub_oid = int(given_oid.split('.')[-1])
varbind_value = getFileLine(sub_oid, lines)
printOutput(given_oid, 'integer', varbind_value.strip())
elif command == 'getnext':
given_oid = getLine()
syslog.syslog("got a requested oid of: %s" % given_oid)
sub_oid = int(given_oid.split('.')[-1])
varbind_value = getFileLine(sub_oid, lines)
printOutput(given_oid, 'integer', varbind_value.strip())
else:
syslog.syslog("Unknown command: %s" % command)
FH.close()
First of all, there's an already written snmp-passpersist Python module specifically for this task. Its page has a link to a real-world usage example. An example code for your case is below.
Regarding your specific questions:
The description of getnext
is indeed notoriously unclear in both specification and wikipedia. It is explained well in TUT:snmpgetnext - Net-SNMP Wiki.
In brief, it retrieves the first valid OID (and its value) that goes after the specified one in the agent's hierarchy. The "hierarchy" here can be represented as an ordered list of all OIDs the agent knows of that are valid at the moment.
pass_persist
handler - "NONE"
) when the hierarchy is exhausted.getnext
is indeed different from get
too (its request type ID is 1
, get
's is 0
).get
: this only means that "walking" and "guessing" will not work (walking might loop indefinitely). This is how a handler I maintained at my last occupation worked and this is just what is happening in your present code too :^).getFileLine(int(sub_oid)+1, lines)
- since your code is already smart enough to return "NONE"
on exhaustion. "Guessing" will still not work, but... do you need it?net-snmpd
has a plethora of logging options - search "log" on its manpage (and, hell, it's free software! you can always consult or even debug the source when all else fails). But in this particular case, logging stdin and stdout while giving queries with snmpget
/snmpgetnext
and/or using sniffer is more than enough.
With the aforementioned snmp-passpersist
, your code boils down to:
base_oid=".1.3.6.1.4.1..[snip]"
data_file="<path>"
import snmp_passpersist as snmp
pp=snmp.PassPersist(base_oid)
for l in (l.rstrip() for l in open(data_file)):
pp.add_int(l,int(l))
pp.start(user_func=lambda:True,refresh=1800) # If data updates are needed,
# replace lambda with a real fn
# and adjust refresh (sec)
If you need to monitor changes to the file, you can either poll it (like the comment above suggests to) or (in Linux) use something like pyinotify - in this case, you'll probably need to replace pp.main_update()
before calling pp.start()
or otherwise patch up the module's machinery somehow.