pysnmp-sky/pysnmp/proto/rfc3412.py

527 lines
21 KiB
Python

#
# This file is part of pysnmp software.
#
# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com>
# License: http://pysnmp.sf.net/license.html
#
import sys
from pyasn1.compat.octets import null
from pysnmp.smi import builder, instrum
from pysnmp.proto import errind, error, cache
from pysnmp.proto.api import verdec # XXX
from pysnmp.error import PySnmpError
from pysnmp import nextid, debug
class MsgAndPduDispatcher(object):
"""SNMP engine PDU & message dispatcher. Exchanges SNMP PDU's with
applications and serialized messages with transport level.
"""
def __init__(self, mibInstrumController=None):
if mibInstrumController is None:
self.mibInstrumController = instrum.MibInstrumController(
builder.MibBuilder()
)
else:
self.mibInstrumController = mibInstrumController
self.mibInstrumController.mibBuilder.loadModules(
'SNMPv2-MIB', 'SNMP-MPD-MIB', 'SNMP-COMMUNITY-MIB',
'SNMP-TARGET-MIB', 'SNMP-USER-BASED-SM-MIB'
)
# Requests cache
self.__cache = cache.Cache()
# Registered context engine IDs
self.__appsRegistration = {}
# Source of sendPduHandle and cache of requesting apps
self.__sendPduHandle = nextid.Integer(0xffffff)
# To pass transport info to app (legacy)
self.__transportInfo = {}
# legacy
def getTransportInfo(self, stateReference):
if stateReference in self.__transportInfo:
return self.__transportInfo[stateReference]
else:
raise error.ProtocolError(
'No data for stateReference %s' % stateReference
)
# Application registration with dispatcher
# 4.3.1
def registerContextEngineId(self, contextEngineId, pduTypes, processPdu):
"""Register application with dispatcher"""
# 4.3.2 -> noop
# 4.3.3
for pduType in pduTypes:
k = (contextEngineId, pduType)
if k in self.__appsRegistration:
raise error.ProtocolError(
'Duplicate registration %r/%s' % (contextEngineId, pduType)
)
# 4.3.4
self.__appsRegistration[k] = processPdu
debug.logger & debug.flagDsp and debug.logger(
'registerContextEngineId: contextEngineId %r pduTypes %s' % (contextEngineId, pduTypes))
# 4.4.1
def unregisterContextEngineId(self, contextEngineId, pduTypes):
"""Unregister application with dispatcher"""
# 4.3.4
if contextEngineId is None:
# Default to local snmpEngineId
contextEngineId, = self.mibInstrumController.mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB',
'snmpEngineID')
for pduType in pduTypes:
k = (contextEngineId, pduType)
if k in self.__appsRegistration:
del self.__appsRegistration[k]
debug.logger & debug.flagDsp and debug.logger(
'unregisterContextEngineId: contextEngineId %r pduTypes %s' % (contextEngineId, pduTypes))
def getRegisteredApp(self, contextEngineId, pduType):
k = (contextEngineId, pduType)
if k in self.__appsRegistration:
return self.__appsRegistration[k]
k = (null, pduType)
if k in self.__appsRegistration:
return self.__appsRegistration[k] # wildcard
# Dispatcher <-> application API
# 4.1.1
def sendPdu(self, snmpEngine, transportDomain, transportAddress,
messageProcessingModel, securityModel, securityName,
securityLevel, contextEngineId, contextName,
pduVersion, PDU, expectResponse, timeout=0,
cbFun=None, cbCtx=None):
"""PDU dispatcher -- prepare and serialize a request or notification"""
# 4.1.1.2
k = int(messageProcessingModel)
if k in snmpEngine.messageProcessingSubsystems:
mpHandler = snmpEngine.messageProcessingSubsystems[k]
else:
raise error.StatusInformation(
errorIndication=errind.unsupportedMsgProcessingModel
)
debug.logger & debug.flagDsp and debug.logger(
'sendPdu: securityName %s, PDU\n%s' % (securityName, PDU.prettyPrint()))
# 4.1.1.3
sendPduHandle = self.__sendPduHandle()
if expectResponse:
self.__cache.add(
sendPduHandle,
messageProcessingModel=messageProcessingModel,
sendPduHandle=sendPduHandle,
timeout=timeout + snmpEngine.transportDispatcher.getTimerTicks(),
cbFun=cbFun,
cbCtx=cbCtx
)
debug.logger & debug.flagDsp and debug.logger('sendPdu: current time %d ticks, one tick is %s seconds' % (
snmpEngine.transportDispatcher.getTimerTicks(), snmpEngine.transportDispatcher.getTimerResolution()))
debug.logger & debug.flagDsp and debug.logger(
'sendPdu: new sendPduHandle %s, timeout %s ticks, cbFun %s' % (sendPduHandle, timeout, cbFun))
origTransportDomain = transportDomain
origTransportAddress = transportAddress
# 4.1.1.4 & 4.1.1.5
try:
(transportDomain,
transportAddress,
outgoingMessage) = mpHandler.prepareOutgoingMessage(
snmpEngine, origTransportDomain, origTransportAddress,
messageProcessingModel, securityModel, securityName,
securityLevel, contextEngineId, contextName,
pduVersion, PDU, expectResponse, sendPduHandle
)
debug.logger & debug.flagDsp and debug.logger('sendPdu: MP succeeded')
except PySnmpError:
if expectResponse:
self.__cache.pop(sendPduHandle)
self.releaseStateInformation(snmpEngine, sendPduHandle, messageProcessingModel)
raise
# 4.1.1.6
if snmpEngine.transportDispatcher is None:
if expectResponse:
self.__cache.pop(sendPduHandle)
raise error.PySnmpError('Transport dispatcher not set')
snmpEngine.observer.storeExecutionContext(
snmpEngine, 'rfc3412.sendPdu',
dict(transportDomain=transportDomain,
transportAddress=transportAddress,
outgoingMessage=outgoingMessage,
messageProcessingModel=messageProcessingModel,
securityModel=securityModel,
securityName=securityName,
securityLevel=securityLevel,
contextEngineId=contextEngineId,
contextName=contextName,
pdu=PDU)
)
try:
snmpEngine.transportDispatcher.sendMessage(
outgoingMessage, transportDomain, transportAddress
)
except PySnmpError:
if expectResponse:
self.__cache.pop(sendPduHandle)
raise
snmpEngine.observer.clearExecutionContext(snmpEngine, 'rfc3412.sendPdu')
# Update cache with orignal req params (used for retrying)
if expectResponse:
self.__cache.update(sendPduHandle,
transportDomain=origTransportDomain,
transportAddress=origTransportAddress,
securityModel=securityModel,
securityName=securityName,
securityLevel=securityLevel,
contextEngineId=contextEngineId,
contextName=contextName,
pduVersion=pduVersion,
PDU=PDU)
return sendPduHandle
# 4.1.2.1
def returnResponsePdu(self, snmpEngine, messageProcessingModel,
securityModel, securityName, securityLevel,
contextEngineId, contextName, pduVersion,
PDU, maxSizeResponseScopedPDU, stateReference,
statusInformation):
# Extract input values and initialize defaults
k = int(messageProcessingModel)
if k in snmpEngine.messageProcessingSubsystems:
mpHandler = snmpEngine.messageProcessingSubsystems[k]
else:
raise error.StatusInformation(
errorIndication=errind.unsupportedMsgProcessingModel
)
debug.logger & debug.flagDsp and debug.logger(
'returnResponsePdu: PDU %s' % (PDU and PDU.prettyPrint() or "<empty>",))
# 4.1.2.2
try:
(transportDomain,
transportAddress,
outgoingMessage) = mpHandler.prepareResponseMessage(
snmpEngine, messageProcessingModel, securityModel,
securityName, securityLevel, contextEngineId, contextName,
pduVersion, PDU, maxSizeResponseScopedPDU, stateReference,
statusInformation
)
debug.logger & debug.flagDsp and debug.logger('returnResponsePdu: MP suceeded')
except error.StatusInformation:
# 4.1.2.3
raise
# Handle oversized messages XXX transport constrains?
snmpEngineMaxMessageSize, = self.mibInstrumController.mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB',
'snmpEngineMaxMessageSize')
if snmpEngineMaxMessageSize.syntax and \
len(outgoingMessage) > snmpEngineMaxMessageSize.syntax:
snmpSilentDrops, = self.mibInstrumController.mibBuilder.importSymbols('__SNMPv2-MIB', 'snmpSilentDrops')
snmpSilentDrops.syntax += 1
raise error.StatusInformation(errorIndication=errind.tooBig)
snmpEngine.observer.storeExecutionContext(
snmpEngine,
'rfc3412.returnResponsePdu',
dict(transportDomain=transportDomain,
transportAddress=transportAddress,
outgoingMessage=outgoingMessage,
messageProcessingModel=messageProcessingModel,
securityModel=securityModel,
securityName=securityName,
securityLevel=securityLevel,
contextEngineId=contextEngineId,
contextName=contextName,
pdu=PDU)
)
# 4.1.2.4
snmpEngine.transportDispatcher.sendMessage(outgoingMessage,
transportDomain,
transportAddress)
snmpEngine.observer.clearExecutionContext(
snmpEngine, 'rfc3412.returnResponsePdu'
)
# 4.2.1
def receiveMessage(self, snmpEngine, transportDomain,
transportAddress, wholeMsg):
"""Message dispatcher -- de-serialize message into PDU"""
# 4.2.1.1
snmpInPkts, = self.mibInstrumController.mibBuilder.importSymbols(
'__SNMPv2-MIB', 'snmpInPkts'
)
snmpInPkts.syntax += 1
# 4.2.1.2
try:
restOfWholeMsg = null # XXX fix decoder non-recursive return
msgVersion = verdec.decodeMessageVersion(wholeMsg)
except error.ProtocolError:
snmpInASNParseErrs, = self.mibInstrumController.mibBuilder.importSymbols('__SNMPv2-MIB',
'snmpInASNParseErrs')
snmpInASNParseErrs.syntax += 1
return null # n.b the whole buffer gets dropped
debug.logger & debug.flagDsp and debug.logger('receiveMessage: msgVersion %s, msg decoded' % msgVersion)
messageProcessingModel = msgVersion
k = int(messageProcessingModel)
if k in snmpEngine.messageProcessingSubsystems:
mpHandler = snmpEngine.messageProcessingSubsystems[k]
else:
snmpInBadVersions, = self.mibInstrumController.mibBuilder.importSymbols('__SNMPv2-MIB', 'snmpInBadVersions')
snmpInBadVersions.syntax += 1
return restOfWholeMsg
# 4.2.1.3 -- no-op
# 4.2.1.4
try:
(messageProcessingModel, securityModel, securityName,
securityLevel, contextEngineId, contextName,
pduVersion, PDU, pduType, sendPduHandle,
maxSizeResponseScopedPDU, statusInformation,
stateReference) = mpHandler.prepareDataElements(
snmpEngine, transportDomain, transportAddress, wholeMsg
)
debug.logger & debug.flagDsp and debug.logger('receiveMessage: MP succeded')
except error.StatusInformation:
statusInformation = sys.exc_info()[1]
if 'sendPduHandle' in statusInformation:
# Dropped REPORT -- re-run pending reqs queue as some
# of them may be waiting for this REPORT
debug.logger & debug.flagDsp and debug.logger(
'receiveMessage: MP failed, statusInformation %s, forcing a retry' % statusInformation)
self.__expireRequest(
statusInformation['sendPduHandle'],
self.__cache.pop(statusInformation['sendPduHandle']),
snmpEngine,
statusInformation
)
return restOfWholeMsg
debug.logger & debug.flagDsp and debug.logger('receiveMessage: PDU %s' % PDU.prettyPrint())
# 4.2.2
if sendPduHandle is None:
# 4.2.2.1 (request or notification)
debug.logger & debug.flagDsp and debug.logger('receiveMessage: pduType %s' % pduType)
# 4.2.2.1.1
processPdu = self.getRegisteredApp(contextEngineId, pduType)
# 4.2.2.1.2
if processPdu is None:
# 4.2.2.1.2.a
snmpUnknownPDUHandlers, = self.mibInstrumController.mibBuilder.importSymbols('__SNMP-MPD-MIB',
'snmpUnknownPDUHandlers')
snmpUnknownPDUHandlers.syntax += 1
# 4.2.2.1.2.b
statusInformation = {
'errorIndication': errind.unknownPDUHandler,
'oid': snmpUnknownPDUHandlers.name,
'val': snmpUnknownPDUHandlers.syntax
}
debug.logger & debug.flagDsp and debug.logger('receiveMessage: unhandled PDU type')
# XXX fails on unknown PDU
try:
(destTransportDomain,
destTransportAddress,
outgoingMessage) = mpHandler.prepareResponseMessage(
snmpEngine, messageProcessingModel,
securityModel, securityName, securityLevel,
contextEngineId, contextName, pduVersion,
PDU, maxSizeResponseScopedPDU, stateReference,
statusInformation
)
except error.StatusInformation:
debug.logger & debug.flagDsp and debug.logger(
'receiveMessage: report failed, statusInformation %s' % sys.exc_info()[1])
return restOfWholeMsg
# 4.2.2.1.2.c
try:
snmpEngine.transportDispatcher.sendMessage(
outgoingMessage, destTransportDomain,
destTransportAddress
)
except PySnmpError: # XXX
pass
debug.logger & debug.flagDsp and debug.logger('receiveMessage: reporting succeeded')
# 4.2.2.1.2.d
return restOfWholeMsg
else:
snmpEngine.observer.storeExecutionContext(
snmpEngine, 'rfc3412.receiveMessage:request',
dict(transportDomain=transportDomain,
transportAddress=transportAddress,
wholeMsg=wholeMsg,
messageProcessingModel=messageProcessingModel,
securityModel=securityModel,
securityName=securityName,
securityLevel=securityLevel,
contextEngineId=contextEngineId,
contextName=contextName,
pdu=PDU)
)
# pass transport info to app (legacy)
if stateReference is not None:
self.__transportInfo[stateReference] = (
transportDomain, transportAddress
)
# 4.2.2.1.3
processPdu(snmpEngine, messageProcessingModel,
securityModel, securityName, securityLevel,
contextEngineId, contextName, pduVersion,
PDU, maxSizeResponseScopedPDU, stateReference)
snmpEngine.observer.clearExecutionContext(
snmpEngine, 'rfc3412.receiveMessage:request'
)
# legacy
if stateReference is not None:
del self.__transportInfo[stateReference]
debug.logger & debug.flagDsp and debug.logger('receiveMessage: processPdu succeeded')
return restOfWholeMsg
else:
# 4.2.2.2 (response)
# 4.2.2.2.1
cachedParams = self.__cache.pop(sendPduHandle)
# 4.2.2.2.2
if cachedParams is None:
snmpUnknownPDUHandlers, = self.mibInstrumController.mibBuilder.importSymbols('__SNMP-MPD-MIB',
'snmpUnknownPDUHandlers')
snmpUnknownPDUHandlers.syntax += 1
return restOfWholeMsg
debug.logger & debug.flagDsp and debug.logger(
'receiveMessage: cache read by sendPduHandle %s' % sendPduHandle)
# 4.2.2.2.3
# no-op ? XXX
snmpEngine.observer.storeExecutionContext(
snmpEngine, 'rfc3412.receiveMessage:response',
dict(transportDomain=transportDomain,
transportAddress=transportAddress,
wholeMsg=wholeMsg,
messageProcessingModel=messageProcessingModel,
securityModel=securityModel,
securityName=securityName,
securityLevel=securityLevel,
contextEngineId=contextEngineId,
contextName=contextName,
pdu=PDU)
)
# 4.2.2.2.4
processResponsePdu = cachedParams['cbFun']
processResponsePdu(snmpEngine, messageProcessingModel,
securityModel, securityName, securityLevel,
contextEngineId, contextName, pduVersion,
PDU, statusInformation,
cachedParams['sendPduHandle'],
cachedParams['cbCtx'])
snmpEngine.observer.clearExecutionContext(
snmpEngine, 'rfc3412.receiveMessage:response'
)
debug.logger & debug.flagDsp and debug.logger('receiveMessage: processResponsePdu succeeded')
return restOfWholeMsg
def releaseStateInformation(self, snmpEngine, sendPduHandle,
messageProcessingModel):
k = int(messageProcessingModel)
if k in snmpEngine.messageProcessingSubsystems:
mpHandler = snmpEngine.messageProcessingSubsystems[k]
mpHandler.releaseStateInformation(sendPduHandle)
self.__cache.pop(sendPduHandle)
# Cache expiration stuff
# noinspection PyUnusedLocal
def __expireRequest(self, cacheKey, cachedParams, snmpEngine,
statusInformation=None):
timeNow = snmpEngine.transportDispatcher.getTimerTicks()
timeoutAt = cachedParams['timeout']
if statusInformation is None and timeNow < timeoutAt:
return
processResponsePdu = cachedParams['cbFun']
debug.logger & debug.flagDsp and debug.logger('__expireRequest: req cachedParams %s' % cachedParams)
# Fail timed-out requests
if not statusInformation:
statusInformation = error.StatusInformation(
errorIndication=errind.requestTimedOut
)
self.releaseStateInformation(snmpEngine,
cachedParams['sendPduHandle'],
cachedParams['messageProcessingModel'])
processResponsePdu(snmpEngine, None, None, None, None, None,
None, None, None, statusInformation,
cachedParams['sendPduHandle'],
cachedParams['cbCtx'])
return True
# noinspection PyUnusedLocal
def receiveTimerTick(self, snmpEngine, timeNow):
self.__cache.expire(self.__expireRequest, snmpEngine)