Using pySNMP to write SNMP agent (for OpenNMS)

I am trying to write a python SNMP agent that I can embed in my python application so that the application can be remotely controlled using OpenNMS. OpenNMS expects the Agent to implement the HOST-RESOURCES-MIB request for the two fields hrSWRunName and hrSWRunStatus .

I took the pysnmp example as the basis of my code and edited it as I considered necessary. The resulting code is as follows:

 import logging from pysnmp import debug from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.proto.api import v2c from pysnmp.smi import builder, instrum, exval # debug.setLogger(debug.Debug('all')) formatting = '[%(asctime)s-%(levelname)s]-(%(module)s) %(message)s' logging.basicConfig(level=logging.DEBUG, format=formatting, ) logging.info("Starting....") # Create SNMP engine snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4 config.addTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('localhost', 12345)) ) # SNMPv2c setup # SecurityName <-> CommunityName mapping. config.addV1System(snmpEngine, 'my-area', 'public') # Allow read MIB access for this user / securityModels at VACM config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (1, 3, 6, 1, 2, 1), (1, 3, 6, 1, 2, 1)) # Create an SNMP context snmpContext = context.SnmpContext(snmpEngine) logging.debug('Loading HOST-RESOURCES-MIB module...'), mibBuilder = builder.MibBuilder().loadModules('HOST-RESOURCES-MIB') logging.debug('done') logging.debug('Building MIB tree...'), mibInstrum = instrum.MibInstrumController(mibBuilder) logging.debug('done') logging.debug('Building table entry index from human-friendly representation...') # see http://www.oidview.com/mibs/0/HOST-RESOURCES-MIB.html hostRunTable, = mibBuilder.importSymbols('HOST-RESOURCES-MIB', 'hrSWRunEntry') instanceId = hostRunTable.getInstIdFromIndices(1) logging.debug('done') # The following shows the OID name mapping # # hrSWRunTable 1.3.6.1.2.1.25.4.2 <TABLE> # hrSWRunEntry 1.3.6.1.2.1.25.4.2.1 <SEQUENCE> # hrSWRunIndex 1.3.6.1.2.1.25.4.2.1.1 <Integer32> # hrSWRunName 1.3.6.1.2.1.25.4.2.1.2 <InternationalDisplayString> 64 Char # hrSWRunID 1.3.6.1.2.1.25.4.2.1.3 <ProductID> # hrSWRunPath 1.3.6.1.2.1.25.4.2.1.4 <InternationalDisplayString> 128 octets # hrSWRunParameters 1.3.6.1.2.1.25.4.2.1.5 <InternationalDisplayString> 128 octets # hrSWRunType 1.3.6.1.2.1.25.4.2.1.6 <INTEGER> # hrSWRunStatus 1.3.6.1.2.1.25.4.2.1.7 <INTEGER> <<===== This is the key variable used by Opennms # http://docs.opennms.org/opennms/releases/18.0.1/guide-admin/guide-admin.html#_hostresourceswrunmonitor) logging.debug('Create/update HOST-RESOURCES-MIB::hrSWRunTable table row:') varBinds = mibInstrum.writeVars(( (hostRunTable.name + (1,) + instanceId, 1), (hostRunTable.name + (2,) + instanceId, 'AppName'), # <=== Must match OpenNMS service-name variable (hostRunTable.name + (3,) + instanceId, {0,0}), # (hostRunTable.name + (4,) + instanceId, 'All is well'), (hostRunTable.name + (5,) + instanceId, 'If this was not the case it would say so here'), (hostRunTable.name + (6,) + instanceId, 4),# Values are ==> unknown(1), operatingSystem(2), deviceDriver(3), application(4) (hostRunTable.name + (7,) + instanceId, 1) #<<=== This is the status number OpenNMS looks at Values are ==> running(1), runnable(2), notRunnable(3), invalid(4) )) for oid, val in varBinds: print('%s = %s' % ('.'.join([str(x) for x in oid]), val.prettyPrint())) logging.debug('done') logging.debug('Read whole MIB (table walk)') oid, val = (), None while True: oid, val = mibInstrum.readNextVars(((oid, val),))[0] if exval.endOfMib.isSameTypeWith(val): break print('%s = %s' % ('.'.join([str(x) for x in oid]), val.prettyPrint())) logging.debug('done') # logging.debug('Unloading MIB modules...'), # mibBuilder.unloadModules() # logging.debug('done') # --- end of table population --- # Register SNMP Applications at the SNMP engine for particular SNMP context cmdrsp.GetCommandResponder(snmpEngine, snmpContext) cmdrsp.SetCommandResponder(snmpEngine, snmpContext) cmdrsp.NextCommandResponder(snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(snmpEngine, snmpContext) # Register an imaginary never-ending job to keep I/O dispatcher running forever snmpEngine.transportDispatcher.jobStarted(1) # Run I/O dispatcher which would receive queries and send responses try: snmpEngine.transportDispatcher.runDispatcher() except: snmpEngine.transportDispatcher.closeDispatcher() raise 

The code works without errors. varBinds and the MIB Table walk show what I think I should expect:

 [2016-12-29 16:42:49,323-INFO]-(SNMPAgent) Starting.... [2016-12-29 16:42:49,470-DEBUG]-(SNMPAgent) Loading HOST-RESOURCES-MIB module... [2016-12-29 16:42:49,631-DEBUG]-(SNMPAgent) done [2016-12-29 16:42:49,631-DEBUG]-(SNMPAgent) Building MIB tree... [2016-12-29 16:42:49,631-DEBUG]-(SNMPAgent) done [2016-12-29 16:42:49,631-DEBUG]-(SNMPAgent) Building table entry index from human-friendly representation... [2016-12-29 16:42:49,631-DEBUG]-(SNMPAgent) done [2016-12-29 16:42:49,632-DEBUG]-(SNMPAgent) Create/update HOST-RESOURCES-MIB::hrSWRunTable table row: 1.3.6.1.2.1.25.4.2.1.1.1 = 1 [2016-12-29 16:42:49,651-DEBUG]-(SNMPAgent) done 1.3.6.1.2.1.25.4.2.1.2.1 = TradeLoader 1.3.6.1.2.1.25.4.2.1.3.1 = 0 1.3.6.1.2.1.25.4.2.1.4.1 = All is well 1.3.6.1.2.1.25.4.2.1.5.1 = If this was not the case it would say so here 1.3.6.1.2.1.25.4.2.1.6.1 = 'application' 1.3.6.1.2.1.25.4.2.1.7.1 = 'running' [2016-12-29 16:42:49,651-DEBUG]-(SNMPAgent) Read whole MIB (table walk) 1.3.6.1.2.1.25.4.2.1.1.1 = 1 1.3.6.1.2.1.25.4.2.1.2.1 = TradeLoader 1.3.6.1.2.1.25.4.2.1.3.1 = 0 1.3.6.1.2.1.25.4.2.1.4.1 = All is well 1.3.6.1.2.1.25.4.2.1.5.1 = If this was not the case it would say so here 1.3.6.1.2.1.25.4.2.1.6.1 = 'application' 1.3.6.1.2.1.25.4.2.1.7.1 = 'running' 1.3.6.1.2.1.25.5.1.1.1.1 = <no value> 1.3.6.1.2.1.25.5.1.1.2.1 = <no value> [2016-12-29 16:42:53,490-DEBUG]-(SNMPAgent) done 

Finally, the dispatcher is running.

The problem is that when you try to request an agent, nothing happens. I do not get an answer. I looked through my code, and one obvious thing about this is that I am not explicitly binding snmpEngine to my created MIB. Should I do this?

Any understanding would be greatly appreciated, as I struggle to figure out where to go at the moment.

+5
source share
2 answers

I thought that I would just post the answer to my question, since it took me so long to figure out how to do what I needed. Hope someone finds this helpful. The following code allows me to populate any MIB known by pysnmp, and then make the MIB available to the network as an SNMP V2 agent.

 import logging from pysnmp import debug from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.proto.api import v2c from pysnmp.smi import builder, instrum, exval # Uncomment this to turn pysnmp debugging on #debug.setLogger(debug.Debug('all')) formatting = '[%(asctime)s-%(levelname)s]-(%(module)s) %(message)s' logging.basicConfig(level=logging.DEBUG, format=formatting, ) logging.info("Starting....") # Create SNMP engine snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4 config.addTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('0.0.0.0', 12345)) ) # SNMPv2c setup # SecurityName <-> CommunityName mapping. config.addV1System(snmpEngine, 'my-area', 'public') # Allow read MIB access for this user / securityModels at VACM # Limit access to just the custom MIB. Widen if need be config.addVacmUser(snmpEngine, 2, 'my-area', 'noAuthNoPriv', (1, 3, 6, 1, 2, 1, 25, 4), (1, 3, 6, 1, 2, 1, 25, 4)) # Create an SNMP context and ensure the custom MIB is loaded # Your system must have this MIB installed otherwise pysnmp # can't load it! snmpContext = context.SnmpContext(snmpEngine) logging.debug('Loading HOST-RESOURCES-MIB module...'), mibBuilder = snmpContext.getMibInstrum().getMibBuilder() mibBuilder.loadModules('HOST-RESOURCES-MIB') mibInstrum = snmpContext.getMibInstrum() logging.debug('done') logging.debug('Building table entry index from human-friendly representation...') # see http://www.oidview.com/mibs/0/HOST-RESOURCES-MIB.html hostRunTable, = mibBuilder.importSymbols('HOST-RESOURCES-MIB', 'hrSWRunEntry') instanceId = hostRunTable.getInstIdFromIndices(1) logging.debug('done') # The following shows the OID name mapping # # hrSWRunTable 1.3.6.1.2.1.25.4.2 <TABLE> # hrSWRunEntry 1.3.6.1.2.1.25.4.2.1 <SEQUENCE> # hrSWRunIndex 1.3.6.1.2.1.25.4.2.1.1 <Integer32> # hrSWRunName 1.3.6.1.2.1.25.4.2.1.2 <InternationalDisplayString> 64 Char # hrSWRunID 1.3.6.1.2.1.25.4.2.1.3 <ProductID> # hrSWRunPath 1.3.6.1.2.1.25.4.2.1.4 <InternationalDisplayString> 128 octets # hrSWRunParameters 1.3.6.1.2.1.25.4.2.1.5 <InternationalDisplayString> 128 octets # hrSWRunType 1.3.6.1.2.1.25.4.2.1.6 <INTEGER> # hrSWRunStatus 1.3.6.1.2.1.25.4.2.1.7 <INTEGER> <<===== This is the key variable used by Opennms # We are going to use OpenNMS as the SNMP manager. OpenNMS will poll this agent to check on its status. The manual # states: # # "This monitor tests the running state of one or more processes. It does this using SNMP and by inspecting the # hrSwRunTable of the HOST-RESOURCES-MIB. The test is done by matching a given process as hrSWRunName against # the numeric value of the hrSWRunStatus". hrSWRunName is matched against the process name defined in the OpenNMS # config file under the heading "service-name". hrSWRunStatus is set to whatever your desired status is. OpenNMS # will compare this value against the config file variable run-level. If hrSWRunStatus > run-level the process # will be marked as having problems. for the complete page see: # http://docs.opennms.org/opennms/releases/18.0.1/guide-admin/guide-admin.html#_hostresourceswrunmonitor) # I have made up an enterprise MIB for us. The number is moot as it not going to go anywhere but the code needs # something valid. # The enterprise MIB I have chosen is enterpriseMib = (1, 3, 6, 1, 4, 1, 50000, 0) logging.debug('Create/update HOST-RESOURCES-MIB::hrSWRunTable table row:') varBinds = mibInstrum.writeVars(( (hostRunTable.name + (1,) + instanceId, 1), (hostRunTable.name + (2,) + instanceId, 'TradeLoader'), # <=== Must match OpenNMS service-name variable (hostRunTable.name + (3,) + instanceId, enterpriseMib), # (hostRunTable.name + (4,) + instanceId, 'All is well'), (hostRunTable.name + (5,) + instanceId, 'If this was not the case it would say so here'), (hostRunTable.name + (6,) + instanceId, 4),# Values are ==> unknown(1), operatingSystem(2), deviceDriver(3), application(4) (hostRunTable.name + (7,) + instanceId, 1) #<<=== This is the status number OpenNMS looks at Values are ==> running(1), runnable(2), notRunnable(3), invalid(4) )) # --- end of table population --- logging.debug('Confirm that the data has been set by reading whole MIB (table walk)') oid, val = (), None while True: oid, val = mibInstrum.readNextVars(((oid, val),))[0] if exval.endOfMib.isSameTypeWith(val): break print('%s = %s' % ('.'.join([str(x) for x in oid]), val.prettyPrint())) logging.debug('done') # Register SNMP Applications at the SNMP engine for particular SNMP context cmdrsp.GetCommandResponder(snmpEngine, snmpContext) cmdrsp.SetCommandResponder(snmpEngine, snmpContext) cmdrsp.NextCommandResponder(snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(snmpEngine, snmpContext) # Register an imaginary never-ending job to keep I/O dispatcher running forever snmpEngine.transportDispatcher.jobStarted(1) # Run I/O dispatcher which would receive queries and send responses try: snmpEngine.transportDispatcher.runDispatcher() except: snmpEngine.transportDispatcher.closeDispatcher() raise 

Log messages display

 [2017-01-09 16:30:15,401-INFO]-(SNMPAgent) Starting.... [2017-01-09 16:30:15,490-DEBUG]-(SNMPAgent) Loading HOST-RESOURCES-MIB module... [2017-01-09 16:30:15,513-DEBUG]-(SNMPAgent) done [2017-01-09 16:30:15,513-DEBUG]-(SNMPAgent) Building table entry index from human-friendly representation... [2017-01-09 16:30:15,515-DEBUG]-(SNMPAgent) done [2017-01-09 16:30:15,515-DEBUG]-(SNMPAgent) Create/update HOST-RESOURCES-MIB::hrSWRunTable table row: [2017-01-09 16:30:15,536-DEBUG]-(SNMPAgent) Confirm that the data has been set by reading whole MIB (table walk) 1.3.6.1.2.1.25.4.2.1.1.1 = 1 1.3.6.1.2.1.25.4.2.1.2.1 = TradeLoader 1.3.6.1.2.1.25.4.2.1.3.1 = 1.3.6.1.4.1.50000.0 1.3.6.1.2.1.25.4.2.1.4.1 = All is well 1.3.6.1.2.1.25.4.2.1.5.1 = If this was not the case it would say so here 1.3.6.1.2.1.25.4.2.1.6.1 = 'application' 1.3.6.1.2.1.25.4.2.1.7.1 = 'running' 1.3.6.1.2.1.25.5.1.1.1.1 = <no value> 1.3.6.1.2.1.25.5.1.1.2.1 = <no value> 1.3.6.1.6.3.10.2.1.1.0 = 0x80004fb805049c06c8 1.3.6.1.6.3.10.2.1.2.0 = 2 1.3.6.1.6.3.10.2.1.3.0 = 0 1.3.6.1.6.3.10.2.1.4.0 = 65507 1.3.6.1.6.3.16.1.1.1.1.0 = 1.3.6.1.6.3.16.1.2.1.1.2.7.109.121.45.97.114.101.97 = 2 1.3.6.1.6.3.16.1.2.1.2.2.7.109.121.45.97.114.101.97 = my-area 1.3.6.1.6.3.16.1.2.1.3.2.7.109.121.45.97.114.101.97 = v-1203634843-2 1.3.6.1.6.3.16.1.2.1.4.2.7.109.121.45.97.114.101.97 = 'nonVolatile' 1.3.6.1.6.3.16.1.2.1.5.2.7.109.121.45.97.114.101.97 = 'active' 1.3.6.1.6.3.16.1.4.1.1.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 1.3.6.1.6.3.16.1.4.1.2.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 2 1.3.6.1.6.3.16.1.4.1.3.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 'noAuthNoPriv' 1.3.6.1.6.3.16.1.4.1.4.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 'exact' 1.3.6.1.6.3.16.1.4.1.5.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = rv-1203634843-2 1.3.6.1.6.3.16.1.4.1.6.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = wv-1203634843-2 1.3.6.1.6.3.16.1.4.1.7.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = nv-1203634843-2 1.3.6.1.6.3.16.1.4.1.8.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 'nonVolatile' 1.3.6.1.6.3.16.1.4.1.9.14.118.45.49.50.48.51.54.51.52.56.52.51.45.50.0.2.1 = 'active' 1.3.6.1.6.3.16.1.5.2.1.1.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = rv-1203634843-2 1.3.6.1.6.3.16.1.5.2.1.1.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = wv-1203634843-2 1.3.6.1.6.3.16.1.5.2.1.2.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 1.3.6.1.2.1.25.4 1.3.6.1.6.3.16.1.5.2.1.2.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 1.3.6.1.2.1.25.4 1.3.6.1.6.3.16.1.5.2.1.3.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 1.3.6.1.6.3.16.1.5.2.1.3.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 1.3.6.1.6.3.16.1.5.2.1.4.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'included' 1.3.6.1.6.3.16.1.5.2.1.4.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'included' 1.3.6.1.6.3.16.1.5.2.1.5.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'nonVolatile' 1.3.6.1.6.3.16.1.5.2.1.5.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'nonVolatile' 1.3.6.1.6.3.16.1.5.2.1.6.15.114.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'active' 1.3.6.1.6.3.16.1.5.2.1.6.15.119.118.45.49.50.48.51.54.51.52.56.52.51.45.50.11.1.3.6.1.2.1.25.4 = 'active' 1.3.6.1.6.3.18.1.1.1.1.109.121.45.97.114.101.97 = my-area 1.3.6.1.6.3.18.1.1.1.2.109.121.45.97.114.101.97 = public 1.3.6.1.6.3.18.1.1.1.3.109.121.45.97.114.101.97 = my-area 1.3.6.1.6.3.18.1.1.1.4.109.121.45.97.114.101.97 = 0x80004fb805049c06c8 1.3.6.1.6.3.18.1.1.1.5.109.121.45.97.114.101.97 = 1.3.6.1.6.3.18.1.1.1.6.109.121.45.97.114.101.97 = 1.3.6.1.6.3.18.1.1.1.7.109.121.45.97.114.101.97 = 'nonVolatile' 1.3.6.1.6.3.18.1.1.1.8.109.121.45.97.114.101.97 = 'active' [2017-01-09 16:30:15,683-DEBUG]-(SNMPAgent) done 

And an agent can be requested to show the results as follows:

 snmpwalk -v 2c -c public -n my-context 0.0.0.0:12345 1.3.6 HOST-RESOURCES-MIB::hrSWRunIndex.1 = INTEGER: 1 HOST-RESOURCES-MIB::hrSWRunName.1 = STRING: "TradeLoader" HOST-RESOURCES-MIB::hrSWRunID.1 = OID: SNMPv2-SMI::enterprises.50000.0 HOST-RESOURCES-MIB::hrSWRunPath.1 = STRING: "All is well" HOST-RESOURCES-MIB::hrSWRunParameters.1 = STRING: "If this was not the case it would say so here" HOST-RESOURCES-MIB::hrSWRunType.1 = INTEGER: application(4) HOST-RESOURCES-MIB::hrSWRunStatus.1 = INTEGER: running(1) HOST-RESOURCES-MIB::hrSWRunStatus.1 = No more variables left in this MIB View (It is past the end of the MIB tree) 
+2
source

You speak

The problem is that when you try to request an agent, nothing happens. I do not get a response

Looks like 2 problems:

a) localhost resolves loopback address 127.0.0.1 on my system, so that you can only access it from the same system as your agent.

b) you register as SNMP version 2 in addVacmUser, so you cannot access using V1

After that:

 % snmpwalk -v 2c -c public 127.0.0.1:12345 SNMPv2-MIB::sysDescr.0 = STRING: PySNMP engine version 4.3.2, Python 2.7.5 (default, Nov 3 2014, 14:33:39) [GCC 4.8.3 20140911 (Red Hat 4.8.3-7)] SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.20408 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (1) 0:00:00.01 SNMPv2-MIB::sysContact.0 = STRING: ... 

although there are no HOST-RESOURCE-MIB objects. This is another problem.

+1
source

Source: https://habr.com/ru/post/1262045/


All Articles