pysnmp icon indicating copy to clipboard operation
pysnmp copied to clipboard

walkCmd not returning from loaded custom MIB

Open conradmcha opened this issue 1 year ago • 1 comments
trafficstars

Expected behavior

Code Block to Walk SNMP MIB

Consider the following block of code:

import asyncio
from pysnmp.hlapi.asyncio import *
from pysnmp.smi import builder, view, compiler, rfc1902

# Initialize the MIB builder
mib_builder = builder.MibBuilder()

# Add MIB sources, including the directory containing the converted MIB file
custom_mib_path = '/home/conrad/.pysnmp/mibs'
mib_sources = mib_builder.getMibSources() + (builder.DirMibSource(custom_mib_path),)
mib_builder.setMibSources(*mib_sources)

# Load custom MIB files
compiler.addMibCompiler(mib_builder, sources=['file://' + custom_mib_path])
mib_builder.loadModules('ELTEK-BC2000-DC-POWER-MIB-V2-12')

# Create MIB viewer
mib_view_controller = view.MibViewController(mib_builder)

async def snmp_walk_custom_mib(oid, ip, community='public'):
    result = []
    try:
        identity = ObjectIdentity('ELTEK-BC2000-DC-POWER-MIB-V2-12', oid).loadMibs('ELTEK-BC2000-DC-POWER-MIB-V2-12')
        identity = identity.resolveWithMib(mib_view_controller)
    except Exception as e:
        print(f"Error resolving OID {oid}: {e}")
        return []

    iterator = walkCmd(
        SnmpEngine(),
        CommunityData(community),
        UdpTransportTarget((ip, 161)),
        ContextData(),
        ObjectType(identity),
        lexicographicMode=False
    )
    async for error_indication, error_status, error_index, var_binds in iterator:
        if error_indication:
            print(f"Error indication: {error_indication}")
            break
        elif error_status:
            print(f"Error status: {error_status.prettyPrint()} at {error_index}")
            break
        else:
            for var_bind in var_binds:
                oid, value = var_bind
                try:
                    oid_label = oid.resolveWithMib(mib_view_controller)
                    oid_number = oid.getOid()._value
                    oid_label = mib_view_controller.getNodeName((oid_number))
                except Exception as e:
                    oid_label = 'Unknown'
                    print(f"Error getting label for OID {oid}: {e}")
                result.append(f'{oid.prettyPrint()} = {value.prettyPrint()} ({oid_label})')
    return result

async def main():
    value1 = await snmp_walk_custom_mib('vpwrPanelModuleSerNum', '10.115.0.10')
    print("\n".join(value1))

# Run the main function
asyncio.run(main())

Expected Output

The code should return the correct MIB from ELTEK-BC2000-DC-POWER-MIB-V2-12: ELTEK-BC2000-DC-POWER-MIB-V2-12::vpwrPanelModuleSerNum.0 = 183886010176

Actual Output

However, it returns: SNMPv2-SMI::enterprises.13858.2.4.2.1.3.5.1 = 183886010176

Actual behavior

Issue

The returned object from the walkCmd doesn't use the right MIB but defaults to the SNMPv2-SMI MIB file:

SNMPv2-SMI::enterprises.13858.2.4.2.1.3.5.1 = 183886010176

I believe this is a similar issue than this bug from "etingof" repo: https://github.com/etingof/pysnmp/issues/273

Detailed steps

Issue

From the debug log, we can see that my ELTEK-BC2000-DC-POWER-MIB-V2-12 mib file is loaded properly. Also the mib_view_controller object contains my mib file as well.

Note

This issue only happens for the walkCmd, it works as intended with the getCmd.

The workaround that I've found is to reinstantiate the object based on the returned OID number with the correct MIB:

  for device_result in completed_tasks:
       task_results = []
       for oid_result in device_result:
           if oid_result is not None:
               for varBind in oid_result:
                   if varBind[0]._ObjectIdentity__modName == "ELTEK-BC2000-DC-POWER-MIB-V2-12":
                       full_oid = varBind[0].prettyPrint()
                       key = full_oid.split('::')[-1]
                       value = varBind[1].prettyPrint()                                    
                       task_results.append({key: value})
                       all_keys.add(key)
                   else:
                       full_oid = varBind[0].getOid()._value
                       resolved_varBind = mibViewController.getNodeName((full_oid))
                       last_value = resolved_varBind[1][-1]
                       resolved_varBind_2_str = '.' + '.'.join(map(str, resolved_varBind[2]))
                       key = last_value + resolved_varBind_2_str
                       value = varBind[1]._value
                       if isinstance(value, bytes):
                           decoded_value = value.decode('utf-8')
                       else:
                           decoded_value = value
                       task_results.append({key: decoded_value})
                       all_keys.add(key)

Partial log

...

2024-07-25 16:44:27,548 DEBUG exportSymbols: symbol ELTEK-BC2000-DC-POWER-MIB-V2-12::vpwrDcPowerUnkModule 2024-07-25 16:44:27,548 DEBUG exportSymbols: symbol ELTEK-BC2000-DC-POWER-MIB-V2-12::thirdPartyProduct 2024-07-25 16:44:27,548 DEBUG loadModule: loaded /home/conrad/.pyenv/versions/3.12.0/lib/python3.12/site-packages/pysnmp/smi/mibs/ELTEK-BC2000-DC-POWER-MIB-V2-12/home/conrad/.pyenv/versions/3.12.0/lib/python3.12/site-packages/pysnmp/smi/mibs/ELTEK-BC2000-DC-POWER-MIB-V2-12.py 2024-07-25 16:44:27,548 DEBUG MIB loaded successfully ...

Python package information

pysnmp-lextudio 6.2.1

Operating system information

Ubuntu 22.04.4 LTS

Python information

3.12.0

(Optional) Contents of your test script

No response

Relevant log output

No response

conradmcha avatar Jul 26 '24 21:07 conradmcha

small_debug.log

Attached full log file here

conradmcha avatar Jul 26 '24 21:07 conradmcha

The right way to ensure your custom MIB documents are loaded and used at the right time is to pass mib_view_controller to your SnmpEngine instance,

snmpEngine = SnmpEngine()
snmpEngine.cache["mibViewController"] = mib_view_controller

Don't use resolveWithMib which has a much smaller effective scope.

lextm avatar Aug 26 '24 09:08 lextm

@lextm This didn't fix the issue. I've tried with a simple code using a different custom mib file and the same error happens again.

import logging
from pysnmp.hlapi.asyncio import (
    SnmpEngine,
    CommunityData,
    UdpTransportTarget,
    ContextData,
    ObjectType,
    ObjectIdentity,
    walkCmd
)
from pysnmp.smi import builder, view
logging.basicConfig(level=logging.DEBUG)  
device_ip = '10.111.111.111'  
community = 'random'          
port = 161                    
mib_module = 'FELE-BATTERY-MONITORING-MIB'
dependency_mib = 'FELE-MIB'  
entry_oid_name = 'batteryEntry'  
def list_loaded_mibs(mib_builder):
    """
    Prints the loaded MIB modules and their symbols for verification.
    """
    print("\n--- Loaded MIB Modules ---")
    for module_name in mib_builder.mibSymbols.keys():
        print(module_name)
    print("\n--- Symbols in Each MIB Module ---")
    for module_name, symbols in mib_builder.mibSymbols.items():
        print(f"\nModule: {module_name}")
        for symbol_name in symbols.keys():
            print(f"  {symbol_name}")
async def snmp_walk(device_ip, community, port, mib_module, dependency_mib, entry_oid_name):
    """
    Performs an SNMP walk to retrieve the subtree under the specified table entry OID using symbolic OID.
    """
    
    mib_builder = builder.MibBuilder()
    
    try:
        
        mib_builder.loadModules(dependency_mib)
        logging.debug(f"Successfully loaded MIB module: {dependency_mib}")
        
        mib_builder.loadModules(mib_module)
        logging.debug(f"Successfully loaded MIB module: {mib_module}")
        
        list_loaded_mibs(mib_builder)
    except Exception as e:
        print(f"Failed to load MIB modules: {e}")
        return
    
    mib_view = view.MibViewController(mib_builder)
    
    snmp_engine = SnmpEngine()
    
    snmp_engine.cache["mibViewController"] = mib_view
    
    try:
        battery_entry_oid = mib_builder.importSymbols(mib_module, entry_oid_name)[0].getName()
        logging.debug(f"'batteryEntry' OID: {'.'.join(str(x) for x in battery_entry_oid)}")
    except Exception as e:
        print(f"Failed to import 'batteryEntry' from MIB '{mib_module}': {e}")
        return
    
    try:
        oid = ObjectIdentity(mib_module, entry_oid_name).resolveWithMib(mib_view)
        logging.debug(f"Resolved OID: {oid.prettyPrint()}")
    except Exception as e:
        print(f"Failed to resolve OID '{entry_oid_name}' in MIB '{mib_module}': {e}")
        return
    
    walk_iterator = walkCmd(
        snmp_engine,
        CommunityData(community, mpModel=1),  
        UdpTransportTarget((device_ip, port)),
        ContextData(),
        ObjectType(oid),
        lexicographicMode=False  
    )
    try:
        async for (errorIndication, errorStatus, errorIndex, varBinds) in walk_iterator:
            if errorIndication:
                print(f"Error: {errorIndication}")
                break
            elif errorStatus:
                print(f"Error Status: {errorStatus.prettyPrint()} at {errorIndex}")
                break
            else:
                for varBind in varBinds:
                    
                    try:
                        
                        symbolic_oid = varBind[0].prettyPrint()
                        value = varBind[1].prettyPrint()
                    except Exception as e:
                        logging.debug(f"Failed to resolve OID '{varBind[0]}' with MIB view: {e}")
                        symbolic_oid = varBind[0].prettyPrint()
                        value = varBind[1].prettyPrint()
                    
                    print(f'{symbolic_oid} = {value}')
    except Exception as e:
        print(f"An exception occurred during SNMP walk: {e}")
    finally:
        
        snmp_engine.transportDispatcher.closeDispatcher()
def main():
    asyncio.run(snmp_walk(device_ip, community, port, mib_module, dependency_mib, entry_oid_name))
if __name__ == '__main__':
    main()

SNMPv2-SMI is still being used. This is the partial output:

SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.1.1 = SNDGCAJWBS1
SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.1.2 = SNDGCAJWBS1
SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.1.3 = SNDGCAJWBS1
SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.1.4 = SNDGCAJWBS1
SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.2.1 = SNDGCAJWBS2
SNMPv2-SMI::enterprises.56952.1.1.2.3.3.2.1.2.2.2 = SNDGCAJWBS2

I've tried using different releases as well, this code is running on the latest release:

pysmi                   1.5.0
pysnmp                  7.1.3

The compiled '.py' mib files reside in: .venv/lib/python3.8/site-packages/pysnmp/smi/mibs/

conradmcha avatar Sep 30 '24 15:09 conradmcha

Given the resource constraints, our focus is to cover the essentials, which are working as expected like test_v1_walk_mib in this commit.

We aren’t able to assist troubleshooting every possible case unless it is backed by commercial support contracts.

lextm avatar Sep 30 '24 17:09 lextm