在多线程环境中使用 pysnmp 的 SNMP 代理超时

SNMP Agent using pysnmp in a multithreaded environment Timeout

我正在尝试编写一个可用于监视我的 python 进程的 SNMP 代理。为此,我编写了一个 class,它使用 pysnmp 实现了 SNMP 代理。

这个代理的核心部分工作(即我可以使用 snmpwalk 来询问代理并且返回的数据是正确的)。为了允许我更新代理 MIB 值,我在它自己的线程中有 运行 dispatcher()。我的问题是,当我使用 snmpwalk 与代理交谈时会超时(snmpwalk 正确地遍历 MIB,但随后超时)。

有没有人知道我做错了什么?

代理代码如下:

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.smi import exval

import threading

formatting = '[%(asctime)s-%(levelname)s]-(%(module)s) %(message)s'
logging.basicConfig(level=logging.DEBUG, format=formatting, )

class SNMPAgent(object):

    def _main(self):

        logging.debug("Creating SNMP Agent....")

        self._snmpEngine = engine.SnmpEngine()

        config.addTransport(
            self._snmpEngine,
            udp.domainName,
            udp.UdpTransport().openServerMode((self._agentHost, self._agentPort))
        )

        config.addV1System(self._snmpEngine, 'my-area', self._communityName)

        config.addVacmUser(self._snmpEngine,
                           2,
                           'my-area',
                           'noAuthNoPriv',
                           (1, 3, 6),
                           (1, 3, 6))

        snmpContext = context.SnmpContext(self._snmpEngine)
        mibBuilder = snmpContext.getMibInstrum().getMibBuilder()
        mibBuilder.loadModules('HOST-RESOURCES-MIB')
        self._mibInstrum = snmpContext.getMibInstrum()

        self._hostRunTable, = mibBuilder.importSymbols('HOST-RESOURCES-MIB', 'hrSWRunEntry')
        self._instanceId = self._hostRunTable.getInstIdFromIndices(1)


        # 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

        self._setVars()

        cmdrsp.GetCommandResponder(self._snmpEngine, snmpContext)
        cmdrsp.SetCommandResponder(self._snmpEngine, snmpContext)
        cmdrsp.NextCommandResponder(self._snmpEngine, snmpContext)
        cmdrsp.BulkCommandResponder(self._snmpEngine, snmpContext)

    def runAgent(self):
        '''
        Run the Agent 
        '''
        t = threading.Thread(target=self._runAgentFunc, args = ())
        t.daemon = True
        t.start()



    def _runAgentFunc(self):

        try:
            self._snmpEngine.transportDispatcher.jobStarted(1)
            self._snmpEngine.transportDispatcher.runDispatcher()
        except:
            self._snmpEngine.transportDispatcher.closeDispatcher()
            raise

    def updateAgentStatus(self, runStatus, text1, text2):

        self._mibDict = {"hrSWRunIndex" : 1, 
                         "hrSWRunName" : self._name,
                         "hrSWRunID" : self._enterpriseMIB,   
                         "hrSWRunPath" : text1[:128] if text1 is not None else '',     
                         "hrSWRunParameters" : text2[:128] if text1 is not None else '',
                         "hrSWRunType" : 4,
                         "hrSWRunStatus" : 1
                         }
        self._setVars()
    def _setVars(self):

        varBinds = self._mibInstrum.writeVars((
            (self._hostRunTable.name + (1,) + self._instanceId, self._mibDict["hrSWRunIndex"]),
            (self._hostRunTable.name + (2,) + self._instanceId, self._mibDict["hrSWRunName"]), # <=== Must match OpenNMS service-name variable
            (self._hostRunTable.name + (3,) + self._instanceId, self._mibDict["hrSWRunID" ]),  #  
            (self._hostRunTable.name + (4,) + self._instanceId, self._mibDict["hrSWRunPath"]),
            (self._hostRunTable.name + (5,) + self._instanceId, self._mibDict["hrSWRunParameters"]),
            (self._hostRunTable.name + (6,) + self._instanceId, self._mibDict["hrSWRunType"]), # Values are ==> unknown(1), operatingSystem(2), deviceDriver(3), application(4)     
            (self._hostRunTable.name + (7,) + self._instanceId, self._mibDict["hrSWRunStatus"])  #<<=== This is the status number OpenNMS looks at Values are ==> running(1), runnable(2), notRunnable(3), invalid(4)
            ))

    def __init__(self, name, host, port, community, text1='Service up', text2=''):
        '''
        #=======================================================================
        # Constructor
        # name      -- the (process) name the agent should publish (must match 
        #              the openNMS name
        # host      -- the host name or ip the agent will run on
        # port      -- the port the snmp agent will listen on
        # community -- the community name the agent will use (usually 'public')
        # text1     -- the first status text string published (128 char max)
        # text2     -- the second status text string published (128 char max) 
        #=======================================================================
        '''
        self._name = name
        self._agentHost = host
        self._agentPort = port
        self._communityName = community

        self._enterpriseMIB = (1, 3, 6, 1, 4, 1, 50000, 0) # Made up for now
        self._mibInstrum = None
        self._snmpEngine = None
        self._dataChanged = False

        self._mibDict = {"hrSWRunIndex" : 1, 
                         "hrSWRunName" : self._name,
                         "hrSWRunID" : self._enterpriseMIB,   
                         "hrSWRunPath" : text1[:128] if text1 is not None else '',     
                         "hrSWRunParameters" : text2[:128] if text1 is not None else '',
                         "hrSWRunType" : 4,
                         "hrSWRunStatus" : 1
                         }

        self._main()

我调用这段代码如下(这只是测试我可以更改状态):

from SNMPAgent import SNMPAgent
from time import sleep

agent = SNMPAgent("test", "127.0.0.1", 12345, "public", "This is my test message 1", "This is my test message 2")

    agent.runAgent()
    sleep(10) # Wait for it to start

    while True:
        agent.updateAgentStatus(3, "Oh no", "Something is wrong!")
        sleep(30)
        agent.updateAgentStatus(2, "Whew", "Everything is fixed")
        sleep(30)

要遍历我使用的代理 MIB:

snmpwalk -v 2c -c public -n my-context 127.0.0.1:12345 1.3.6.1.2.1.25.4.2

这显示数据正在更新,但在遍历 MIB 结束时,代理超时:

HOST-RESOURCES-MIB::hrSWRunIndex.1 = INTEGER: 1
HOST-RESOURCES-MIB::hrSWRunName.1 = STRING: "test"
HOST-RESOURCES-MIB::hrSWRunID.1 = OID: SNMPv2-SMI::enterprises.50000.0
HOST-RESOURCES-MIB::hrSWRunPath.1 = STRING: "Whew"
HOST-RESOURCES-MIB::hrSWRunParameters.1 = STRING: "Everything is fixed"
HOST-RESOURCES-MIB::hrSWRunType.1 = INTEGER: application(4)
HOST-RESOURCES-MIB::hrSWRunStatus.1 = INTEGER: running(1)
Timeout: No Response from 127.0.0.1:12345

启用 pysnmp 调试会显示由特定 OID 的未设置整数值引起的序列化错误:

[2017-01-13 02:05:18,387-DEBUG]-(debug) generateResponseMsg: Message:
 version='version-2c'
 community=public
 data=PDUs:
  response=ResponsePDU:
   request-id=1950819527
   error-status='noError'
   error-index=0
   variable-bindings=VarBindList:
    VarBind:
     name=1.3.6.1.2.1.25.5.1.1.1.1
     =_BindValue:
      value=ObjectSyntax:
       simple=SimpleSyntax:
        integer-value=<no value>

2017-01-13 02:05:18,387 pysnmp: generateResponseMsg: serialization failure: Uninitialized ASN.1 value ("__eq__" attribute looked up)
[2017-01-13 02:05:18,387-DEBUG]-(debug) generateResponseMsg: serialization failure: Uninitialized ASN.1 value ("__eq__" attribute looked up)
2017-01-13 02:05:18,388 pysnmp: StatusInformation: {'errorIndication': <pysnmp.proto.errind.SerializationError object at 0x10162f828>}
[2017-01-13 02:05:18,388-DEBUG]-(debug) StatusInformation: {'errorIndication': <pysnmp.proto.errind.SerializationError object at 0x10162f828>}

您应该确保为所有 non-defaulted SNMP table 列设置值。

附带说明一下,您似乎在没有显式同步的情况下从主线程和 SNMP 代理线程管理您的 MIB。这可能会导致竞争条件...