0

I have two scenarios, both reference SNMP.py in this answer:

pysnmp (v4.2.3) and pysnmp-mibs (v0.1.4):

>>> # pysnmp-mibs 0.1.4 and pysnmp 4.2.3
>>> from SNMP import v2c
>>> snmp = v2c('172.16.1.1', 'public')
>>> snmp.walk('ifName')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "SNMP.py", line 115, in walk
    (('', oid),),
  File "/usr/local/lib/python2.6/dist-packages/pysnmp-4.2.3-py2.6.egg/pysnmp/entity/rfc3413/oneliner/cmdgen.py", line 449, in nextCmd
  File "/usr/local/lib/python2.6/dist-packages/pysnmp-4.2.3-py2.6.egg/pysnmp/entity/rfc3413/oneliner/cmdgen.py", line 150, in makeReadVarBinds
  File "/usr/local/lib/python2.6/dist-packages/pysnmp-4.2.3-py2.6.egg/pysnmp/entity/rfc3413/oneliner/mibvar.py", line 161, in resolveWithMib
  File "/usr/local/lib/python2.6/dist-packages/pysnmp-4.2.3-py2.6.egg/pysnmp/smi/builder.py", line 295, in importSymbols
pysnmp.smi.error.SmiError: importSymbols: empty MIB module name
>>>

pysnmp (v4.2.2) and pysnmp-mibs (v0.1.3):

>>> # After removing pysnmp / pysnmp-mibs / pyasn.1 and installing with:
>>> #   easy_install pysnmp==4.2.2 pysnmp-mibs==0.1.3
>>> snmp = v2c('172.16.1.1', 'public')
>>> snmp.walk('ifName')
[SNMPObject(modName='IF-MIB', symName='ifName', index=1, value='Null0'), SNMPObject(modName='IF-MIB', symName='ifName', index=2, value='Internal-Data0/0'), SNMPObject(modName='IF-MIB', symName='ifName', index=3, value='Ethernet0/0'), SNMPObject(modName='IF-MIB', symName='ifName', index=4, value='Ethernet0/1'), SNMPObject(modName='IF-MIB', symName='ifName', index=5, value='Ethernet0/2'), SNMPObject(modName='IF-MIB', symName='ifName', index=11, value='Internal-Data0/1'), SNMPObject(modName='IF-MIB', symName='ifName', index=12, value='_internal_loopback'), SNMPObject(modName='IF-MIB', symName='ifName', index=13, value='Virtual254'), SNMPObject(modName='IF-MIB', symName='ifName', index=14, value='Vlan1'), SNMPObject(modName='IF-MIB', symName='ifName', index=15, value='OUTSIDE'), SNMPObject(modName='IF-MIB', symName='ifName', index=16, value='INSIDE')]
>>>

Question

What is wrong when I use pysnmp v4.2.3? Is the issue in SNMP.py or somewhere in the pysnmp libraries?

Update 1

When I use cmdgen.MibVariable(oid).loadMibs(), as suggested by Ilya, I still get errors...

pysnmp.smi.error.NoSuchObjectError: NoSuchObjectError({'str': "Can't resolve node name ::('ifName',) at <pysnmp.smi.view.MibViewController instance at 0x2af9e60>"})

How can I resolve this error? I am using the following SNMP.py code:


from collections import namedtuple as NT
from datetime import datetime
import string
import re

from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.smi import builder, view, error
from numpy import int64, float64

# NOTE!!!
# It is best to install the pysnmp-mibs package from pypi... this makes
# a lot of symbolic MIB names "just work"


# See this link below for many oneliner examples...
# http://pysnmp.sourceforge.net/examples/4.x/v3arch/oneliner/index.html

class v2c(object):
    """Build an SNMPv2c manager object"""
    def __init__(self, ipaddr=None, device=None, community='Public',
        retries=3, timeout=9):
        self.device = device
        self.ipaddr = ipaddr
        self.community = community
        self.SNMPObject = NT('SNMPObject', ['modName', 'datetime', 'symName',
            'index', 'value'])
        self.SNMPIndexed = NT('SNMPIndexed', ['modName', 'datetime', 'symName',
            'index', 'value'])
        self.query_timeout = float(timeout)/int(retries)
        self.query_retries = int(retries)
        self._index = None

        self.cmdGen = cmdgen.CommandGenerator()

    def index(self, oid=None):
        """Build an SNMP Manager index to reference in get or walk operations.  First v2c.index('ifName').  Then, v2c.get_index('ifHCInOctets', 'eth0') or v2c.walk_index('ifHCInOctets').  Instead of referencing a numerical index, the index will refer to the value that was indexed."""
        self._index = dict()
        self._intfobj = dict()
        snmpidx = self.walk(oid=oid)
        for ii in snmpidx:
            ## the dicts below are keyed by the SNMP index number
            # value below is the text string of the intf name
            self._index[ii.index] = ii.value
            # value below is the intf object
            if not (self.device is None):
                self._intfobj[ii.index] = self.device.find_match_intf(ii.value,
                    enforce_format=False)

    def walk_index(self, oid=None):
        """Example usage, first index with v2c.index('ifName'), then v2c.get_index('ifHCInOctets', 'eth0')"""
        if not (self._index is None):
            tmp = list()
            snmpvals = self.walk(oid=oid)
            for idx, ii in enumerate(snmpvals):
                tmp.append([ii.modName, datetime.now(), ii.symName,
                    self._index[ii.index], ii.value])

            return map(self.SNMPIndexed._make, tmp)
        else:
            raise ValueError, "Must populate with SNMP.v2c.index() first"

    def walk(self, oid=None):
        if isinstance(self._format(oid), tuple):
            errorIndication, errorStatus, errorIndex, \
            varBindTable = self.cmdGen.nextCmd(
                        cmdgen.CommunityData('test-agent', self.community),
                        cmdgen.UdpTransportTarget((self.ipaddr, 161),
                        retries=self.query_retries,
                        timeout=self.query_timeout),
                        self._format(oid),
                    )
            # Parsing only for now... no return value...
            self._parse(errorIndication, errorStatus, errorIndex, varBindTable)
        elif isinstance(oid, str):
            errorIndication, errorStatus, errorIndex, \
                             varBindTable = self.cmdGen.nextCmd(
                # SNMP v2
                cmdgen.CommunityData('test-agent', self.community),
                # Transport
                cmdgen.UdpTransportTarget((self.ipaddr, 161)),
                cmdgen.MibVariable(oid).loadMibs(),
                )
            return self._parse_resolve(errorIndication, errorStatus,
                errorIndex, varBindTable)
        else:
            raise ValueError, "Unknown oid format: %s" % oid

    def get_index(self, oid=None, index=None):
        """In this case, index should be similar to the values you indexed from... i.e. if you index with ifName, get_index('ifHCInOctets', 'eth0')"""
        if not (self._index is None) and isinstance(index, str):
            # Map the interface name provided in index to an ifName index...
            snmpvals = None
            for idx, value in self._index.items():
                if index == value:
                    # if there is an exact match between the text index and the
                    # snmp index value...
                    snmpvals = self.get(oid=oid, index=idx)
                    break
            else:
                # TRY mapping the provided text index into an interface obj
                _intfobj = self.device.find_match_intf(index)
                if not (_intfobj is None):
                    for key, val in self._intfobj.items():
                        if (val==_intfobj):
                            snmpvals = self.get(oid=oid, index=key)
                            break

            # Ensure we only parse a valid response...
            if not (snmpvals is None):
                tmp = [snmpvals.modName, datetime.now(), snmpvals.symName,
                    self._index[snmpvals.index], snmpvals.value]
                return self.SNMPIndexed._make(tmp)

        elif not isinstance(index, str):
            raise ValueError, "index must be a string value"
        else:
            raise ValueError, "Must populate with SNMP.v2c.index() first"

    def get(self, oid=None, index=None):
        if isinstance(self._format(oid), tuple):
            errorIndication, errorStatus, errorIndex, \
            varBindTable = self.cmdGen.getCmd(
                        cmdgen.CommunityData('test-agent', self.community),
                        cmdgen.UdpTransportTarget((self.ipaddr, 161),
                        retries=self.query_retries,
                        timeout=self.query_timeout),
                        self._format(oid),
                    )
            # Parsing only for now... no return value...
            self._parse(errorIndication, errorStatus, errorIndex, varBindTable)
        elif isinstance(oid, str) and isinstance(index, int):
            errorIndication, errorStatus, errorIndex, \
                             varBindTable = self.cmdGen.getCmd(
                # SNMP v2
                cmdgen.CommunityData('test-agent', self.community),
                # Transport
                cmdgen.UdpTransportTarget((self.ipaddr, 161)),
                cmdgen.MibVariable(oid).loadMibs(),
                )
            return self._parse_resolve(errorIndication, errorStatus,
                errorIndex, [varBindTable])[0]
        else:
            raise ValueError, "Unknown oid format: %s" % oid

    def bulkwalk(self, oid=None):
        """SNMP bulkwalk a device.  NOTE: This often is faster, but does not work as well as a simple SNMP walk"""
        if isinstance(self._format(oid), tuple):
            errorIndication, errorStatus, errorIndex, varBindTable = self.cmdGen.bulkCmd(
                        cmdgen.CommunityData('test-agent', self.community),
                        cmdgen.UdpTransportTarget((self.ipaddr, 161),
                        retries=self.query_retries,
                        timeout=self.query_timeout),
                0,
                25,
                self._format(oid),
                )
            return self._parse(errorIndication, errorStatus,
                errorIndex, varBindTable)
        elif isinstance(oid, str):
            errorIndication, errorStatus, errorIndex, varBindTable = self.cmdGen.bulkCmd(
                        cmdgen.CommunityData('test-agent', self.community),
                        cmdgen.UdpTransportTarget((self.ipaddr, 161),
                        retries=self.query_retries,
                        timeout=self.query_timeout),
                0,
                25,
                cmdgen.MibVariable(oid).loadMibs(),
                )
            return self._parse_resolve(errorIndication, errorStatus,
                errorIndex, varBindTable)
        else:
            raise ValueError, "Unknown oid format: %s" % oid

    def _parse_resolve(self, errorIndication=None, errorStatus=None,
        errorIndex=None, varBindTable=None):
        """Parse MIB walks and resolve into MIB names"""
        retval = list()
        if errorIndication:
            print errorIndication
        else:
            if errorStatus:
                print '%s at %s\n' % (
                    errorStatus.prettyPrint(),
                    varBindTable[-1][int(errorIndex)-1]
                    )
            else:
                for varBindTableRow in varBindTable:
                    for oid, val in varBindTableRow:
                        (symName, modName), indices = cmdgen.mibvar.oidToMibName(
                            self.cmdGen.mibViewController, oid
                            )
                        val = cmdgen.mibvar.cloneFromMibValue(
                            self.cmdGen.mibViewController, modName, symName,
                            val)
                        # Try to parse the index as an int first,
                        # then as a string
                        try:
                            index = int(string.join(map(lambda v: v.prettyPrint(), indices), '.'))
                        except ValueError:
                            index = str(string.join(map(lambda v: v.prettyPrint(), indices), '.'))

                        # Re-format values as float or integer, if possible...
                        tmp = val.prettyPrint()
                        if re.search(r"""^\s*\d+\s*$""", tmp):
                            value = int64(tmp)
                        elif re.search(r"""^\s*\d+\.\d+\s*$""", tmp):
                            value = float64(tmp)
                        else:
                            value = tmp

                        retval.append(self.SNMPObject._make([modName,
                            datetime.now(), symName, index, value]))
            return retval

    def _parse(self, errorIndication, errorStatus, errorIndex,
        varBindTable):
        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())

    def _format(self, oid):
        """Format a numerical OID in the form of 1.3.4.1.2.1 into a tuple"""
        if isinstance(oid, str):
            if re.search('(\d+\.)+\d+', oid):
                tmp = list()
                for ii in oid.split('.'):
                    tmp.append(int(ii))
                return tuple(tmp)
        else:
            return oid
Community
  • 1
  • 1
Mike Pennington
  • 41,899
  • 19
  • 136
  • 174

1 Answers1

0

Besides other changes to MIB access interface, pysnmp developers consider an-empty-string-as-a-wildcard-indicator to be too error prone so they replaced it with an explicit call MibVariable.loadModules().

So in case is pysnmp.version exists and indicates it's 4.2.3 or greater, in SNMP.py you should replace:

...
(('', oid),),
...

with something like:

...
cmdgen.MibVariable(oid).loadMibs(),
...

It's the loadMibs() method which does the magic. See more on MibVariable usage here and other details here.

BTW, MibVariable seems to handle OID detection and conversion all by itself so you might not need to duplicate that logic in SNMP.py

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
Ilya Etingof
  • 5,440
  • 1
  • 17
  • 21
  • Thank you, but when I use your suggestion, I get `pysnmp.smi.error.NoSuchObjectError: NoSuchObjectError({'str': "Can't resolve node name ::('ifName',) at "})` – Mike Pennington Sep 21 '12 at 11:04
  • Yes, the valid syntax is MibVariable('IF-MIB', 'ifName') or MibVariable('ifName').loadMibs() or MibVariable('1.3.6.1.2.1.31.1.1.1.1') – Ilya Etingof Sep 22 '12 at 06:43
  • ...or MibVariable('ifName').loadMibs('IF-MIB'). Please note that calling loadMibs() without argument(s) makes pysnmp loading ALL available MIBs what takes time and memory. On the other hand, referring to a MIB symbol while its MIB is not yet loaded will fail. – Ilya Etingof Sep 22 '12 at 06:51
  • Ilya, thank you for your response, but I still do not understand what change you are suggesting. I updated my question above with the explicit code I am using. If you could offer guidance on what is wrong, I would appreciate it. – Mike Pennington Sep 26 '12 at 09:18