pysnmp output format issue - python

I am trying to get some snmp variables from cisco routers using PySNMP but I am getting a hex output using prettyprint instead of the output I get from my normal snmp browser.
I have tried multiple encode (hex, utf-8 and ascii) and without encode, always not what I was expecting.
Any ideas?
Thanks
Start discovery of 10.148.8.15
1.3.6.1.2.1.1.1.0 = 0x436973636f20494f5320536f6674776172652c20494f532d584520536f66747761726520285838365f36345f4c494e55585f494f53442d554e4956455253414c4b392d4d292c2056657273696f6e2031352e3228342953352c2052454c4541534520534f4654574152452028666331290d0a546563686e6963616c20537570706f72743a20687474703a2f2f7777772e636973636f2e636f6d2f74656368737570706f72740d0a436f707972696768742028632920313938362d3230313420627920436973636f2053797374656d732c20496e632e0d0a436f6d70696c6564205475652032352d4665622d31342031313a3336206279206d63707265
result = {'error': 1, 'value': "default"}
cmdGen = cmdgen.CommandGenerator()
errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
cmdgen.CommunityData(community),
cmdgen.UdpTransportTarget((destination, 161)),
cmdgen.MibVariable(oid)
)
# Check for errors and print out results
if errorIndication:
print(errorIndication)
else:
if errorStatus:
result['error'] = 1
# print('%s at %s' % (
# errorStatus.prettyPrint(),
# errorIndex and varBinds[int(errorIndex)-1] or '?'
# )
# )
else:
result['error'] = 0
for name, val in varBinds:
print('%s = %s' % (name.prettyPrint(), val.prettyPrint()))
result['value'] = val.prettyPrint().encode("ascii")

That is because Cisco SNMP Agent reports control characters (\n) what may potentially screw your script's output. There are several ways to handle this:
Pass lookupMib=True like shown in this example. Then pysnmp would try to look up proper output format for this particular OID at MIB. For this to work pysnmp must be able to find and load pysnmp-formatted MIB file(s).
Add some code to trigger a hex string decoder when 0x prefix is seen:
s = '436973636f...06d63707265'
print(''.join([ chr(int(s[x:x+2], 16)) for x in range(0, len(s), 2) ]))
Cisco IOS Software, IOS-XE Software ...Compiled Tue 25-Feb-14 11:36 by mcpre

Related

Reading OID and display its variable or name in python using pysnmp

I am trying to write a code in python to run a function that reads the OID variable and name of a device.
I managed to find out their OID's but every time I enter the OID number in my code it shows me there is no OID
here is the Python code using pysnmp:
#########################
from pysnmp.entity.rfc3413.oneliner import cmdgen
cmd_gen = cmdgen.CommandGenerator()
error_indication, error_status, error_index, var_binds = cmd_gen.getCmd(
cmdgen.CommunityData('public'),
cmdgen.UdpTransportTarget(('192.168.178.213', 161)),
'.1.3.6.1.4.1.9986.3.22.1.1.3.1.10')
for name, val in var_binds:
print('%s' % val.prettyPrint())
#################
here is the device with one of the OIDs
There is a simple way of traversing OIDs in SNMP called SNMP WALK. It can be implemented as simple as this:
def walk_mib(ipaddress, oid):
for (errorIndication, errorStatus, errorIndex, varBinds) in nextCmd(SnmpEngine(),
CommunityData('public'),
UdpTransportTarget((ipaddress, 161)),
ContextData(),
ObjectType(ObjectIdentity(oid)),
):
if not errorIndication and not errorStatus:
for varBind in varBinds:
result=' = '.join([x.prettyPrint() for x in varBind])
print(result)
walk_mib('<ip address>', start_oid)

pysnmp v3 GETBULK

I currently have a script which polls multiple oids on multiple devices using PySNMP. It reads the list of hosts in from a file and for some hosts needs to poll 3 or 4 oids, at the moment it is doing this sequentially, so to make it more efficient I want to do a getbulk so I'm only polling each host once.
I've done multiple searches on this and can find plenty of examples using pysnmp and snmp v2 but I can't find an example with snmpv3. I've tried the test script below but it's throwing up an error so can someone take a look and let me know what I'm doing wrong please? My test script looks like this:
from pysnmp.entity.rfc3413.oneliner import cmdgen
host='10.0.0.1'
incount = '.1.3.6.1.2.1.31.1.1.1.6.16'
outcount ='.1.3.6.1.2.1.31.1.1.1.10.16'
errorIndication, errorStatus, errorIndex,
varBindTable = cmdgen.CommandGenerator().bulkCmd(
UsmUserData('snmp_user', 'password', 'password',
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol),
UdpTransportTarget((host, 161)),
0,
25,
(incount),
(outcount),
)
if errorIndication:
print errorIndication
else:
if errorStatus:
print '%s at %s\n' % (
errorStatus.prettyPrint(),
errorIndex and varBindTable[-1][int(errorIndex)-1] or '?'
)
else:
for varBindTableRow in varBindTable:
for name, val in varBindTableRow:
print '%s = %s' % (name.prettyPrint(), val.prettyPrint())
and the error:
Traceback (most recent call last):
File "./multiget.py", line 7, in <module>
errorIndication, errorStatus, errorIndex,
NameError: name 'errorIndication' is not defined
It's pretty much falling at the first hurdle so I've evidently got the syntax wrong, but like I say, I couldn't find an example of this with snmpv3.
Thanks
Ed
It looks like a formatting error in the first place. Try this layout:
from pysnmp.entity.rfc3413.oneliner import cmdgen
host='10.0.0.1'
incount = '.1.3.6.1.2.1.31.1.1.1.6.16'
outcount ='.1.3.6.1.2.1.31.1.1.1.10.16'
cmdGen = cmdgen.CommandGenerator()
(errorIndication, errorStatus, errorIndex,
varBindTable) = cmdGen.bulkCmd(
UsmUserData('snmp_user', 'password', 'password',
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol),
UdpTransportTarget((host, 161)),
0,
25,
incount,
outcount,
)
...
Also, CommandGenerator (i.e. underlying SnmpEngine object) is expensive to create. Therefore it makes sense to keep one around for as long as you plan to use it.

PySNMP 4.4 with Python 2.7 bulkCmd output not including child OID's

To start off with, I'm new to Python and PySNMP. I'm trying to pass a list of network devices to bulkCmd to obtain information on all the physical interfaces.
Currently it is only collecting the first interface and then moves on to the next network device in the list. I have made changes with lexicographic and maxCalls, repetitions but none make any difference.
I have successfully polled all interfaces when sending a single bulkCmd to single network device.
Code:
from pysnmp.hlapi import *
routers = ["router1", "router2"]
#adds routers to getCmd and bulkCmd
def snmpquery (hostip):
errorIndication, errorStatus, errorIndex, varBinds = next (
bulkCmd(SnmpEngine(),
CommunityData('communitystring'),
UdpTransportTarget((hostip, 161)),
ContextData(),
0, 50,
ObjectType(ObjectIdentity('IF-MIB', 'ifDescr')),
ObjectType(ObjectIdentity('IF-MIB', 'ifAlias')),
ObjectType(ObjectIdentity('IF-MIB', 'ifOperStatus')),
lexicographicMode=True
)
)
# Check for errors and print out results
if errorIndication:
print(errorIndication)
elif errorStatus:
print('%s at %s' % (errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
else:
for varBind in varBinds:
print(' = '.join([x.prettyPrint() for x in varBind]))
# calls snmpquery for all routers in list
for router in routers:
snmpquery(router)
Output:
IF-MIB::ifDescr.1 = GigabitEthernet0/0
IF-MIB::ifAlias.1 = InterfaceDesc
IF-MIB::ifOperStatus.1 = 'up'
IF-MIB::ifDescr.1 = GigabitEthernet0/0
IF-MIB::ifAlias.1 = InterfaceDesc
IF-MIB::ifOperStatus.1 = 'up'
bulkSNMP returns an iterator and you were using next() on it which retrieves only the first of the iterations. You probably got the idea from the PySNMP documentation, which doesn't do a great job of showing how to retrieve all the results.
You should use a for loop to loop over all of the iterations, as follows:
from pysnmp.hlapi import *
routers = ["router1", "router2"]
def snmpquery (hostip):
snmp_iter = bulkCmd(SnmpEngine(),
CommunityData('communitystring'),
UdpTransportTarget((hostip, 161)),
ContextData(),
0, 50,
ObjectType(ObjectIdentity('IF-MIB', 'ifDescr')),
ObjectType(ObjectIdentity('IF-MIB', 'ifAlias')),
ObjectType(ObjectIdentity('IF-MIB', 'ifOperStatus')),
lexicographicMode=True)
for errorIndication, errorStatus, errorIndex, varBinds in snmp_iter:
# Check for errors and print out results
if errorIndication:
print(errorIndication)
elif errorStatus:
print('%s at %s' % (errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
else:
for varBind in varBinds:
print(' = '.join([x.prettyPrint() for x in varBind]))
# calls snmpquery for all routers in list
for router in routers:
snmpquery(router)
Also, be careful about indentation when posting Python-related questions, as it is significant.
You need to iterate over the generator produced by the bulkCmd function to repeat SNMP queries to pull SNMP managed objects that did not fit into previous response packet(s). Just ditch the next() call and run for loop over bulkCmd().
Side note 1: you may not need lexicographicMode=True if you want to fetch managed objects that reside just under the MIB table columns (e.g. IF-MIB::ifDescr etc).
Side note 2: if you have many SNMP agents on your network, you may consider speeding up the process of data retrieval by talking to the in parallel. You'd use the same getBulk() call, it's just the underlaying network I/O that does the parallelism.

Making PySNMP resolve Object IDs

I'm trying to do a SNMP-walk with PySNMP. I wrote the following script, which works, but for every device I "walk" the OIDs of only about ten rows can be resolved to a "real name".
from pysnmp.entity.rfc3413.oneliner import cmdgen
from os.path import exists
import sys
import os
# Turn on debugging
#debug.setLogger(debug.Debug('msgproc', 'secmod'))
# Enter parameters
target_IP = raw_input('Target IP (192.168.13.100): ') or '192.168.13.100'
target_port = raw_input('Target port (161): ') or 161
max_timeout = int(raw_input('Maximum timeout in seconds (1):')) or 1
max_retries = int(raw_input('Maximum number of retries (0):')) or 0
target_file_name = raw_input('Target filename (.txt is added): ') + '.txt'
# Check for already existing file
path = 'walks/'
if not os.path.exists(path):
os.makedirs(path)
if exists(path+target_file_name):
sys.exit("The file '%s' already exists. Try again." % target_file_name)
else:
target_file = open(path+target_file_name, 'w+')
# Initialize counter to zero
counter = 0
# Create command generator
cmdGen = cmdgen.CommandGenerator()
# Get data
errorIndication, errorStatus, errorIndex, varBindTable = cmdGen.nextCmd(
cmdgen.CommunityData('public'),
cmdgen.UdpTransportTarget((target_IP, target_port), timeout=max_timeout, retries=max_retries),
'1.3',
lexicographicMode=True,
#maxRows=1000,
ignoreNonIncreasingOid=True,
lookupNames=True
)
# Print errors and values to file
if errorIndication:
print(errorIndication)
else:
# Print error messages
if errorStatus:
print('%s at %s' % (
errorStatus.prettyPrint(),
errorIndex and varBindTable[-1][int(errorIndex)-1] or '?'
)
)
else:
# Print values
for varBindTableRow in varBindTable:
for name, val in varBindTableRow:
counter += 1
target_file.write("(%s)\t %s value = \t%s\n" % (counter, name.prettyPrint(), val.prettyPrint()))
# Finish the operation
target_file.close()
print('Writing to %s successful. %d lines have been written' % (target_file_name, counter))
sys.exit(0)
The result is a file with very many rows.
the first entrys look like this:
(1) SNMPv2-MIB::sysDescr."0" value = Hirschmann MAR
(2) SNMPv2-MIB::sysObjectID."0" value = 1.5.6.9.1.1.248.4.10.90
(3) SNMPv2-MIB::sysUpTime."0" value = 2626357
(4) SNMPv2-MIB::sysContact."0" value = Hirschmann Automation and Control GmbH
(5) SNMPv2-MIB::sysName."0" value = mar1030.plc-s7-15000
(6) SNMPv2-MIB::sysLocation."0" value = Hirschmann MAR
(7) SNMPv2-MIB::sysServices."0" value = 2
(8) SNMPv2-MIB::sysORLastChange."0" value = 300
But then the OIDs are not resolved anymore:
(9) SNMPv2-SMI::mib-2."2.1.0" value = 27
(10) SNMPv2-SMI::mib-2."2.2.1.1.1" value = 1
(11) SNMPv2-SMI::mib-2."2.2.1.1.2" value = 2
(12) SNMPv2-SMI::mib-2."2.2.1.1.3" value = 3
.....
What causes this and what can I do about it?
To resolve MIBs with pysnmp you have to load them into pysnmp engine prior to make SNMP queries. Your script performs MIB resolution for SNMPv2-MIB because it is automatically loaded by pysnmp engine.
To make use of plain-text MIBs with pysnmp you have to convert them from plain text form (e.g. ASN.1) into pysnmp format (Python code). You could use the build-pysnmp-mib shell script (based on libsmi) or download and install a pre-compiled pack of common MIBs (pysnmp-mibs Python package).
To load some or all available MIBs you could use the MibVariable.loadMibs(*mibs) method. See this script for example.

Manipulate strings in python

I need to control my xbee using data stored in mysql. Included in the database is the long address of the xbee that is used to identify which xbee I am communicating with on the network.
The following code works perfectly, but in this example I am not retrieving the address from the database. It is just an example of what does work.
addr3 = '\x00\x13\xa2\x00#\n!\x1c'
xbee.send('remote_at',
frame_id='\x01',
dest_addr_long=addr3, #<- this works!
command='D0',
parameter='\x04')
Now as soon as I retrieve \x00\x13\xa2\x00#\n!\x1c from the database (it is stored as a varchar), I get an error saying:
"% (field['name'], field['len']))
ValueError: The data provided for 'dest_addr_long' was not 8 bytes long"
Here is the code (I included the output of the six print lines below to help with debugging)
with con:
cur = con.cursor()
cur.execute("SELECT addrlong, pinStatus FROM deviceStatus WHERE addrlong <>''")
for i in range(cur.rowcount):
row = cur.fetchone()
addr1 = row[0]
Status = row[1]
addr2 = repr(addr1)
addr3 = '\x00\x13\xa2\x00#\n!\x1c'
print "Address1: %s" % addr1
print "Address2: %s" % addr2
print "Address3: %s" % addr3
print "Size of Addr1: %s" % sys.getsizeof(addr1)
print "Size of Addr2: %s" % sys.getsizeof(addr2)
print "Size of Addr3: %s" % sys.getsizeof(addr3)
if Status == 0: #turn off
xbee.send('remote_at',
frame_id='\x01',
dest_addr_long=addr2, #<-problem is here
command='D0',
parameter='\x04')
if Status == 1: #turn on
xbee.send('remote_at',
frame_id='\x01',
dest_addr_long=addr2, #<-problem is here
command='D0',
parameter='\x05')
and the output is
Address1: \x00\x13\xa2\x00#\n!\x1c
Address2: '\\x00\\x13\\xa2\\x00#\\n!\\x1c'
Address3: ?#
!
Size of Addr1: 45
Size of Addr2: 53
Size of Addr3: 29
I've obviously tried simply dest_addr_long=addr1, to no avail.
I have tried many combinations of string manipulation such as adding and removing the parenthesis and dozens of combinations of str and repr but I think I am on the wrong path completely.
I guess what I need to ask is why does
addr3 = '\x00\x13\xa2\x00#\n!\x1c'
print "Address3: %s" % addr3
output
Address3: ?#
!
and once I understand that, then how do I manipulate addr1 from the database to match addr3 because the line dest_addr_long=addr3, works perfectly.
That is an ASCII representation of a byte string. \x00, for example, means 00 ie NUL, and \x13 is ESC; # and ! are literal characters, but \n means a newline character. That's how it is 8 bytes long.
You can get the actual bytes back by decoding with 'string-escape':
>>> s='\x00\x13\xa2\x00#\n!\x1c'
>>> print s.decode('string-escape')
�#
!
(although the result of print will look different on a terminal).

Categories