使用 pySNMP 编写 SNMP 代理(用于 OpenNMS)

Using pySNMP to write an SNMP agent (for OpenNMS)

我正在尝试编写一个 python SNMP 代理,我可以将其嵌入到我的 python 应用程序中,以便 OpenNMS 可以远程监控该应用程序。 OpenNMS 期望 Agent 实现 HOST-RESOURCES-MIB 查询两个字段 hrSWRunNamehrSWRunStatus.

我以 pysnmp 示例作为我的代码的基础,并根据需要对其进行了编辑。生成的代码如下所示:

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

代码运行没有产生错误。 varBinds 和 MIB Table walk 展示了我认为我应该期待的:

[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

最后,调度程序启动。

问题是当我尝试查询代理时没有任何反应。我没有得到回应。我查看了我的代码,一件明显的事情是我没有明确 link 我创建的 MIB 的 snmpEngine。我应该这样做吗?

任何见解都会受到极大的欢迎,因为我正在努力了解目前该去哪里。

你说

The problem is that when I try and query the agent nothing happens. I get no response

看起来有 2 个问题:

a) 本地主机在我的系统上解析为环回地址 127.0.0.1,因此您只能从与您的代理相同的系统访问它。

b) 您在 addVacmUser 中注册为 SNMP 版本 2,因此您无法使用 V1

访问

之后:

% 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: 
...

尽管没有 HOST-RESOURCE-MIB 对象。那又是一个问题。

我想我只是 post 回答我的问题,因为我花了很长时间才弄清楚如何做我需要的事情。希望其他人会发现这很有用。下面的代码允许我填充 pysnmp 已知的任何 MIB,然后使 MIB 作为 V2 SNMP 代理可用于网络。

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's 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

记录器消息显示

[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

并且可以查询代理显示得到结果如下:

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)