diff options
Diffstat (limited to 'pysnmp')
40 files changed, 2319 insertions, 244 deletions
diff --git a/pysnmp/entity/rfc3413/cmdgen.py b/pysnmp/entity/rfc3413/cmdgen.py index 90180c67..9f5d891d 100644 --- a/pysnmp/entity/rfc3413/cmdgen.py +++ b/pysnmp/entity/rfc3413/cmdgen.py @@ -18,28 +18,6 @@ getNextHandle = nextid.Integer(0x7fffffff) __null = univ.Null('') -def getNextVarBinds(varBinds, origVarBinds=None): - errorIndication = None - idx = nonNulls = len(varBinds) - rspVarBinds = [] - while idx: - idx -= 1 - if varBinds[idx][1].tagSet in (rfc1905.NoSuchObject.tagSet, - rfc1905.NoSuchInstance.tagSet, - rfc1905.EndOfMibView.tagSet): - nonNulls -= 1 - elif origVarBinds is not None: - if v2c.ObjectIdentifier(origVarBinds[idx][0]).asTuple() >= varBinds[idx][0].asTuple(): - errorIndication = errind.oidNotIncreasing - - rspVarBinds.insert(0, (varBinds[idx][0], __null)) - - if not nonNulls: - rspVarBinds = [] - - return errorIndication, rspVarBinds - - class CommandGenerator(object): _null = univ.Null('') @@ -289,9 +267,7 @@ class NextCommandGenerator(NextCommandGeneratorSingleRun): elif not varBindTable: errorIndication, varBinds = errind.emptyResponse, () else: - errorIndication, varBinds = getNextVarBinds( - varBindTable[-1], v2c.apiPDU.getVarBinds(reqPDU) - ) + errorIndication, varBinds = v2c.apiPDU.getNextVarBinds(varBindTable[-1]) if not cbFun(snmpEngine, sendRequestHandle, errorIndication, v2c.apiPDU.getErrorStatus(PDU), @@ -371,9 +347,7 @@ class BulkCommandGenerator(BulkCommandGeneratorSingleRun): elif not varBindTable: errorIndication, varBinds = errind.emptyResponse, () else: - errorIndication, varBinds = getNextVarBinds( - varBindTable[-1], v2c.apiPDU.getVarBinds(reqPDU) - ) + errorIndication, varBinds = v2c.apiBulkPDU.getNextVarBinds(varBindTable[-1]) nonRepeaters = v2c.apiBulkPDU.getNonRepeaters(reqPDU) if nonRepeaters: varBinds = v2c.apiBulkPDU.getVarBinds(reqPDU)[:int(nonRepeaters)] + varBinds[int(nonRepeaters):] diff --git a/pysnmp/hlapi/__init__.py b/pysnmp/hlapi/__init__.py index f60b0ed4..da9788e3 100644 --- a/pysnmp/hlapi/__init__.py +++ b/pysnmp/hlapi/__init__.py @@ -4,12 +4,6 @@ # Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> # License: http://snmplabs.com/pysnmp/license.html # -from pysnmp.proto.rfc1902 import * -from pysnmp.proto.rfc1905 import NoSuchInstance, NoSuchObject, EndOfMibView -from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.entity.engine import * # default is synchronous asyncore-based API -from pysnmp.hlapi.asyncore.sync import * +from pysnmp.hlapi.v3arch import * diff --git a/pysnmp/hlapi/asyncore/sync/__init__.py b/pysnmp/hlapi/asyncore/sync/__init__.py deleted file mode 100644 index f412713a..00000000 --- a/pysnmp/hlapi/asyncore/sync/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# This file is part of pysnmp software. -# -# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> -# License: http://snmplabs.com/pysnmp/license.html -# -from pysnmp.proto.rfc1902 import * -from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.asyncore.transport import * -from pysnmp.entity.engine import * - -try: - from pysnmp.hlapi.asyncore.sync.cmdgen import * - from pysnmp.hlapi.asyncore.sync.ntforg import * -except SyntaxError: - from pysnmp.hlapi.asyncore.sync.compat.cmdgen import * - from pysnmp.hlapi.asyncore.sync.compat.ntforg import * diff --git a/pysnmp/hlapi/twisted/__init__.py b/pysnmp/hlapi/twisted/__init__.py deleted file mode 100644 index 4e3e129f..00000000 --- a/pysnmp/hlapi/twisted/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from pysnmp.proto.rfc1902 import * -from pysnmp.smi.rfc1902 import * -from pysnmp.entity.engine import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.twisted.transport import * -from pysnmp.hlapi.twisted.cmdgen import * -from pysnmp.hlapi.twisted.ntforg import * diff --git a/pysnmp/hlapi/v1arch/__init__.py b/pysnmp/hlapi/v1arch/__init__.py new file mode 100644 index 00000000..22290a8f --- /dev/null +++ b/pysnmp/hlapi/v1arch/__init__.py @@ -0,0 +1,14 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.proto.rfc1902 import * +from pysnmp.proto.rfc1905 import NoSuchInstance, NoSuchObject, EndOfMibView +from pysnmp.smi.rfc1902 import * +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncore.dispatch import * + +# default is synchronous asyncore-based API +from pysnmp.hlapi.v1arch.asyncore.sync import * diff --git a/pysnmp/hlapi/v1arch/asyncore/__init__.py b/pysnmp/hlapi/v1arch/asyncore/__init__.py new file mode 100644 index 00000000..d58bee7b --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/__init__.py @@ -0,0 +1,13 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.proto.rfc1902 import * +from pysnmp.smi.rfc1902 import * +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncore.transport import * +from pysnmp.hlapi.v1arch.asyncore.cmdgen import * +from pysnmp.hlapi.v1arch.asyncore.ntforg import * +from pysnmp.hlapi.v1arch.asyncore.dispatch import * diff --git a/pysnmp/hlapi/v1arch/asyncore/cmdgen.py b/pysnmp/hlapi/v1arch/asyncore/cmdgen.py new file mode 100644 index 00000000..ddce3b7e --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/cmdgen.py @@ -0,0 +1,627 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# + +from pysnmp.hlapi.varbinds import * +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncore import * +from pysnmp.smi.rfc1902 import * +from pysnmp.proto import api +from pysnmp import error + +__all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd'] + +vbProcessor = CommandGeneratorVarBinds() + + +def getCmd(snmpDispatcher, authData, transportTarget, *varBinds, **options): + """Initiate SNMP GET query over SNMPv1/v2c. + + Based on passed parameters, prepares SNMP GET packet + (:RFC:`1905#section-4.2.1`) and schedules its transmission by + I/O framework at a later point of time. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + + authData: :py:class:`~pysnmp.hlapi.v1arch.CommunityData` + Class instance representing SNMPv1/v2c credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.v1arch.asyncore.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncore.Udp6TransportTarget` Class instance representing + transport type along with SNMP peer address. + + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None`. + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. + + Note + ---- + The `SnmpDispatcher` object may be expensive to create, therefore it is + advised to maintain it for the lifecycle of the application/thread for + as long as possible. + + Note + ---- + User-supplied `cbFun` callable must have the following call + signature: + + * snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + * stateHandle (int): Unique request identifier. Can be used + for matching multiple ongoing requests with received responses. + * errorIndication (str): evaluates to `True` to indicate SNMP dispatcher + error. + * errorStatus (int): evaluates to `True` to indicate SNMP PDU error. + * errorIndex (int): Non-zero value refers to `varBinds[errorIndex-1]` + * varBinds (tuple): A sequence of + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + representing MIB variables returned in SNMP response in exactly + the same order as `varBinds` in request. + * `cbCtx` (object): Original user-supplied object. + + Returns + ------- + stateHandle: int + Unique request identifier. Can be used for matching received + responses with ongoing requests. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import * + >>> + >>> def cbFun(snmpDispatcher, stateHandle, errorIndication, + >>> errorStatus, errorIndex, varBinds, cbCtx): + >>> print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> snmpDispatcher = SnmpDispatcher() + >>> + >>> stateHandle = getCmd( + >>> snmpDispatcher, + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)), + >>> cbFun=cbFun + >>> ) + >>> + >>> snmpDispatcher.transportDispatcher.runDispatcher() + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if not cbFun: + return + + if errorIndication: + cbFun(errorIndication, pMod.Integer(0), pMod.Integer(0), None, + cbCtx=cbCtx, snmpDispatcher=snmpDispatcher, stateHandle=stateHandle) + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiPDU.getVarBinds(rspPdu) + + if lookupMib: + varBinds = vbProcessor.unmakeVarBinds(snmpDispatcher.cache, varBinds) + + nextStateHandle = pMod.getNextRequestID() + + nextVarBinds = cbFun(errorIndication, errorStatus, errorIndex, varBinds, + cbCtx=cbCtx, + snmpDispatcher=snmpDispatcher, + stateHandle=stateHandle, + nextStateHandle=nextStateHandle) + + if not nextVarBinds: + return + + pMod.apiPDU.setRequestID(reqPdu, nextStateHandle) + pMod.apiPDU.setVarBinds(reqPdu, nextVarBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + lookupMib, cbFun, cbCtx = [options.get(x) for x in ('lookupMib', 'cbFun', 'cbCtx')] + + if lookupMib: + varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.protoModules[authData.mpModel] + + reqPdu = pMod.GetRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + +def setCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Initiate SNMP SET query over SNMPv1/v2c. + + Based on passed parameters, prepares SNMP SET packet + (:RFC:`1905#section-4.2.5`) and schedules its transmission by + I/O framework at a later point of time. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + + authData: :py:class:`~pysnmp.hlapi.v1arch.CommunityData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.v1arch.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.v1arch.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer + address. + + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None`. + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. + + Note + ---- + The `SnmpDispatcher` object may be expensive to create, therefore it is + advised to maintain it for the lifecycle of the application/thread for + as long as possible. + + Note + ---- + User-supplied `cbFun` callable must have the following call + signature: + + * snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + * stateHandle (int): Unique request identifier. Can be used + for matching multiple ongoing requests with received responses. + * errorIndication (str): evaluates to `True` to indicate SNMP dispatcher + error. + * errorStatus (int): evaluates to `True` to indicate SNMP PDU error. + * errorIndex (int): Non-zero value refers to `varBinds[errorIndex-1]` + * varBinds (tuple): A sequence of + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + representing MIB variables returned in SNMP response in exactly + the same order as `varBinds` in request. + * `cbCtx` (object): Original user-supplied object. + + Returns + ------- + stateHandle: int + Unique request identifier. Can be used for matching received + responses with ongoing requests. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import * + >>> + >>> def cbFun(snmpDispatcher, stateHandle, errorIndication, + >>> errorStatus, errorIndex, varBinds, cbCtx): + >>> print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> snmpDispatcher = SnmpDispatcher() + >>> + >>> stateHandle = setCmd( + >>> snmpDispatcher, + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ('1.3.6.1.2.1.1.4.0', OctetString('info@snmplabs.com')), + >>> cbFun=cbFun + >>> ) + >>> + >>> snmpDispatcher.transportDispatcher.runDispatcher() + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if not cbFun: + return + + if errorIndication: + cbFun(errorIndication, pMod.Integer(0), pMod.Integer(0), None, + cbCtx=cbCtx, snmpDispatcher=snmpDispatcher, stateHandle=stateHandle) + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiPDU.getVarBinds(rspPdu) + + if lookupMib: + varBinds = vbProcessor.unmakeVarBinds(snmpDispatcher.cache, varBinds) + + nextStateHandle = pMod.getNextRequestID() + + nextVarBinds = cbFun(errorIndication, errorStatus, errorIndex, varBinds, + cbCtx=cbCtx, + snmpDispatcher=snmpDispatcher, + stateHandle=stateHandle, + nextStateHandle=nextStateHandle) + + if not nextVarBinds: + return + + pMod.apiPDU.setRequestID(reqPdu, nextStateHandle) + pMod.apiPDU.setVarBinds(reqPdu, nextVarBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + lookupMib, cbFun, cbCtx = [options.get(x) for x in ('lookupMib', 'cbFun', 'cbCtx')] + + if lookupMib: + varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.protoModules[authData.mpModel] + + reqPdu = pMod.SetRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + +def nextCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Initiate SNMP GETNEXT query over SNMPv1/v2c. + + Based on passed parameters, prepares SNMP GETNEXT packet + (:RFC:`1905#section-4.2.2`) and schedules its transmission by + I/O framework at a later point of time. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing SNMP dispatcher. + + authData: :py:class:`~pysnmp.hlapi.v1arch.CommunityData` or :py:class:`~pysnmp.hlapi.v1arch.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.v1arch.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.v1arch.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer + address. + + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None`. + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. + + Notes + ----- + User-supplied `cbFun` callable must have the following call + signature: + + * snmpDispatcher (:py:class:`~pysnmp.hlapi.v1arch.snmpDispatcher`): + Class instance representing SNMP dispatcher. + * stateHandle (int): Unique request identifier. Can be used + for matching multiple ongoing requests with received responses. + * errorIndication (str): True value indicates SNMP dispatcher error. + * errorStatus (str): True value indicates SNMP PDU error. + * errorIndex (int): Non-zero value refers to `varBinds[errorIndex-1]` + * varBindTable (tuple): A sequence of sequences (e.g. 2-D array) of + variable-bindings represented as :class:`tuple` or + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + representing a table of MIB variables returned in SNMP response. + Inner sequences represent table rows and ordered exactly the same + as `varBinds` in request. Response to GETNEXT always contain a + single row. + * `cbCtx` (object): Original user-supplied object. + + Returns + ------- + stateHandle: int + Unique request identifier. Can be used for matching received + responses with ongoing requests. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import * + >>> + >>> def cbFun(snmpDispatcher, stateHandle, errorIndication, + >>> errorStatus, errorIndex, varBinds, cbCtx): + >>> print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> snmpDispatcher = snmpDispatcher() + >>> + >>> stateHandle = nextCmd( + >>> snmpDispatcher, + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ('1.3.6.1.2.1.1', None), + >>> cbFun=cbFun + >>> ) + >>> + >>> snmpDispatcher.transportDispatcher.runDispatcher() + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if not cbFun: + return + + if errorIndication: + cbFun(errorIndication, pMod.Integer(0), pMod.Integer(0), None, + cbCtx=cbCtx, snmpDispatcher=snmpDispatcher, stateHandle=stateHandle) + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBindTable = pMod.apiPDU.getVarBindTable(reqPdu, rspPdu) + + errorIndication, nextVarBinds = pMod.apiPDU.getNextVarBinds( + varBindTable[-1], errorIndex=errorIndex + ) + + if options.get('lookupMib'): + varBindTable = [ + vbProcessor.unmakeVarBinds(snmpDispatcher.cache, vbs) for vbs in varBindTable + ] + + nextStateHandle = pMod.getNextRequestID() + + nextVarBinds = cbFun(errorIndication, errorStatus, errorIndex, varBindTable, + cbCtx=cbCtx, + snmpDispatcher=snmpDispatcher, + stateHandle=stateHandle, + nextStateHandle=nextStateHandle, + nextVarBinds=nextVarBinds) + + if not nextVarBinds: + return + + pMod.apiPDU.setRequestID(reqPdu, nextStateHandle) + pMod.apiPDU.setVarBinds(reqPdu, nextVarBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + lookupMib, cbFun, cbCtx = [options.get(x) for x in ('lookupMib', 'cbFun', 'cbCtx')] + + if lookupMib: + varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.protoModules[authData.mpModel] + + reqPdu = pMod.GetNextRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + +def bulkCmd(snmpDispatcher, authData, transportTarget, + nonRepeaters, maxRepetitions, *varBinds, **options): + """Initiate SNMP GETBULK query over SNMPv2c. + + Based on passed parameters, prepares SNMP GETBULK packet + (:RFC:`1905#section-4.2.3`) and schedules its transmission by + I/O framework at a later point of time. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing SNMP dispatcher. + + authData: :py:class:`~pysnmp.hlapi.v1arch.CommunityData` or :py:class:`~pysnmp.hlapi.v1arch.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.v1arch.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.v1arch.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer + address. + + nonRepeaters: int + One MIB variable is requested in response for the first + `nonRepeaters` MIB variables in request. + + maxRepetitions: int + `maxRepetitions` MIB variables are requested in response for each + of the remaining MIB variables in the request (e.g. excluding + `nonRepeaters`). Remote SNMP dispatcher may choose lesser value than + requested. + + \*varBinds: :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None`. + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. + + Notes + ----- + User-supplied `cbFun` callable must have the following call + signature: + + * snmpDispatcher (:py:class:`~pysnmp.hlapi.v1arch.snmpDispatcher`): + Class instance representing SNMP dispatcher. + * stateHandle (int): Unique request identifier. Can be used + for matching multiple ongoing requests with received responses. + * errorIndication (str): True value indicates SNMP dispatcher error. + * errorStatus (str): True value indicates SNMP PDU error. + * errorIndex (int): Non-zero value refers to `varBinds[errorIndex-1]` + * varBindTable (tuple): A sequence of sequences (e.g. 2-D array) of + variable-bindings represented as :class:`tuple` or + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + representing a table of MIB variables returned in SNMP response, with + up to ``maxRepetitions`` rows, i.e. ``len(varBindTable) <= maxRepetitions``. + + For ``0 <= i < len(varBindTable)`` and ``0 <= j < len(varBinds)``, + ``varBindTable[i][j]`` represents: + + - For non-repeaters (``j < nonRepeaters``), the first lexicographic + successor of ``varBinds[j]``, regardless the value of ``i``, or an + :py:class:`~pysnmp.smi.rfc1902.ObjectType` instance with the + :py:obj:`~pysnmp.proto.rfc1905.endOfMibView` value if no such successor + exists; + - For repeaters (``j >= nonRepeaters``), the ``i``-th lexicographic + successor of ``varBinds[j]``, or an + :py:class:`~pysnmp.smi.rfc1902.ObjectType` instance with the + :py:obj:`~pysnmp.proto.rfc1905.endOfMibView` value if no such successor + exists. + + See :rfc:`3416#section-4.2.3` for details on the underlying + ``GetBulkRequest-PDU`` and the associated ``GetResponse-PDU``, such as + specific conditions under which the server may truncate the response, + causing ``varBindTable`` to have less than ``maxRepetitions`` rows. + * `cbCtx` (object): Original user-supplied object. + + Returns + ------- + stateHandle : int + Unique request identifier. Can be used for matching received + responses with ongoing requests. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import * + >>> + >>> def cbFun(snmpDispatcher, stateHandle, errorIndication, + >>> errorStatus, errorIndex, varBinds, cbCtx): + >>> print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> snmpDispatcher = snmpDispatcher() + >>> + >>> stateHandle = bulkCmd( + >>> snmpDispatcher, + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> 0, 2, + >>> ('1.3.6.1.2.1.1', None), + >>> cbFun=cbFun + >>> ) + >>> + >>> snmpDispatcher.transportDispatcher.runDispatcher() + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if not cbFun: + return + + if errorIndication: + cbFun(errorIndication, pMod.Integer(0), pMod.Integer(0), None, + cbCtx=cbCtx, snmpDispatcher=snmpDispatcher, stateHandle=stateHandle) + return + + errorStatus = pMod.apiBulkPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiBulkPDU.getErrorIndex(rspPdu) + + varBindTable = pMod.apiBulkPDU.getVarBindTable(reqPdu, rspPdu) + + errorIndication, nextVarBinds = pMod.apiBulkPDU.getNextVarBinds( + varBindTable[-1], errorIndex=errorIndex + ) + + if options.get('lookupMib'): + varBindTable = [ + vbProcessor.unmakeVarBinds(snmpDispatcher.cache, vbs) for vbs in varBindTable + ] + + nextStateHandle = pMod.getNextRequestID() + + nextVarBinds = cbFun(errorIndication, errorStatus, errorIndex, varBindTable, + cbCtx=cbCtx, + snmpDispatcher=snmpDispatcher, + stateHandle=stateHandle, + nextStateHandle=nextStateHandle, + nextVarBinds=nextVarBinds) + + if not nextVarBinds: + return + + pMod.apiBulkPDU.setRequestID(reqPdu, nextStateHandle) + pMod.apiBulkPDU.setVarBinds(reqPdu, nextVarBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + if authData.mpModel < 1: + raise error.PySnmpError('GETBULK PDU is only supported in SNMPv2c and SNMPv3') + + lookupMib, cbFun, cbCtx = [options.get(x) for x in ('lookupMib', 'cbFun', 'cbCtx')] + + if lookupMib: + varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.protoModules[authData.mpModel] + + reqPdu = pMod.GetBulkRequestPDU() + pMod.apiBulkPDU.setDefaults(reqPdu) + pMod.apiBulkPDU.setNonRepeaters(reqPdu, nonRepeaters) + pMod.apiBulkPDU.setMaxRepetitions(reqPdu, maxRepetitions) + pMod.apiBulkPDU.setVarBinds(reqPdu, varBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) diff --git a/pysnmp/hlapi/v1arch/asyncore/dispatch.py b/pysnmp/hlapi/v1arch/asyncore/dispatch.py new file mode 100644 index 00000000..d77b8551 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/dispatch.py @@ -0,0 +1,29 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.carrier.asyncore.dispatch import AsyncoreDispatcher +from pysnmp.hlapi.v1arch.dispatch import AbstractSnmpDispatcher + +__all__ = ['SnmpDispatcher'] + + +class SnmpDispatcher(AbstractSnmpDispatcher): + """Creates SNMP message dispatcher object. + + `SnmpDispatcher` object manages send and receives SNMP PDU + messages through underlying transport dispatcher and dispatches + them to the callers. + + `SnmpDispatcher` is the only stateful object, all `hlapi.v1arch` SNMP + operations require an instance of `SnmpDispatcher`. Users do not normally + request services directly from `SnmpDispather`, but pass it around to + other `hlapi.v1arch` interfaces. + + It is possible to run multiple instances of `SnmpDispatcher` in the + application. In a multithreaded environment, each thread that + works with SNMP must have its own `SnmpDispatcher` instance. + """ + protoDispatcher = AsyncoreDispatcher diff --git a/pysnmp/hlapi/v1arch/asyncore/ntforg.py b/pysnmp/hlapi/v1arch/asyncore/ntforg.py new file mode 100644 index 00000000..a7767051 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/ntforg.py @@ -0,0 +1,188 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.hlapi.varbinds import * +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncore import * +from pysnmp.smi.rfc1902 import * +from pysnmp.proto import api +from pysnmp.proto.proxy import rfc2576 +from pysnmp import error + +__all__ = ['sendNotification'] + +vbProcessor = NotificationOriginatorVarBinds() + + +def sendNotification(snmpDispatcher, authData, transportTarget, + notifyType, *varBinds, **options): + """Send SNMP notification. + + Based on passed parameters, prepares SNMP TRAP or INFORM + notification (:RFC:`1905#section-4.2.6`) and schedules its + transmission by I/O framework at a later point of time. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + + authData: :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + notifyType: str + Indicates type of notification to be sent. Recognized literal + values are *trap* or *inform*. + + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU places rigid requirement on the ordering of + the variable-bindings. + + Mandatory variable-bindings: + + 0. SNMPv2-MIB::sysUpTime.0 = <agent uptime> + 1. SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Optional variable-bindings (applicable to SNMP v1 TRAP): + + 2. SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + 3. SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + 4. SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + Informational variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None`. + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. + + Note + ---- + The `SnmpDispatcher` object may be expensive to create, therefore it is + advised to maintain it for the lifecycle of the application/thread for + as long as possible. + + + Returns + ------- + sendRequestHandle: int + Unique request identifier. Can be used for matching received + responses with ongoing *INFORM* requests. Returns `None` for + *TRAP* notifications. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import * + >>> + >>> snmpDispatcher = SnmpDispatcher() + >>> + >>> sendNotification( + >>> snmpDispatcher, + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 162)), + >>> 'trap', + >>> NotificationType(ObjectIdentity('SNMPv2-MIB', 'coldStart')), + >>> lookupMib=True + >>> ) + >>> snmpDispatcher.transportDispatcher.runDispatcher() + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if not cbFun: + return + + if errorIndication: + cbFun(errorIndication, pMod.Integer(0), pMod.Integer(0), None, + cbCtx=cbCtx, snmpDispatcher=snmpDispatcher, stateHandle=stateHandle) + return + + errorStatus = pMod.apiTrapPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiTrapPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiTrapPDU.getVarBinds(rspPdu) + + if lookupMib: + varBinds = vbProcessor.unmakeVarBinds(snmpDispatcher.cache, varBinds) + + nextStateHandle = pMod.getNextRequestID() + + nextVarBinds = cbFun(errorIndication, errorStatus, errorIndex, varBinds, + cbCtx=cbCtx, + snmpDispatcher=snmpDispatcher, + stateHandle=stateHandle, + nextStateHandle=nextStateHandle) + + if not nextVarBinds: + return + + pMod.apiTrapPDU.setRequestID(reqPdu, nextStateHandle) + pMod.apiTrapPDU.setVarBinds(reqPdu, nextVarBinds) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + lookupMib, cbFun, cbCtx = [options.get(x) for x in ('lookupMib', 'cbFun', 'cbCtx')] + + if lookupMib: + varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + # # make sure required PDU payload is in place + # completeVarBinds = [] + # + # # ensure sysUpTime + # if len(varBinds) < 1 or varBinds[0][0] != pMod.apiTrapPDU.sysUpTime: + # varBinds.insert(0, (ObjectIdentifier(pMod.apiTrapPDU.sysUpTime), pMod.Integer(0))) + # + # # ensure sysUpTime + # if len(varBinds) < 1 or varBinds[0][0] != pMod.apiTrapPDU.sysUpTime: + # varBinds.insert(0, (ObjectIdentifier(pMod.apiTrapPDU.sysUpTime), pMod.Integer(0))) + # + # # ensure snmpTrapOID + # if len(varBinds) < 2 or varBinds[1][0] != pMod.apiTrapPDU.snmpTrapOID: + # varBinds.insert(0, (ObjectIdentifier(pMod.apiTrapPDU.sysUpTime), pMod.Integer(0))) + + # input PDU is always v2c + pMod = api.protoModules[api.protoVersion2c] + + if notifyType == 'trap': + reqPdu = pMod.TrapPDU() + else: + reqPdu = pMod.InformRequestPDU() + + pMod.apiTrapPDU.setDefaults(reqPdu) + pMod.apiTrapPDU.setVarBinds(reqPdu, varBinds) + + if authData.mpModel == 0: + reqPdu = rfc2576.v2ToV1(reqPdu) + + return snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + diff --git a/pysnmp/hlapi/v1arch/asyncore/sync/__init__.py b/pysnmp/hlapi/v1arch/asyncore/sync/__init__.py new file mode 100644 index 00000000..5481cb96 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/sync/__init__.py @@ -0,0 +1,21 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.proto.rfc1902 import * +from pysnmp.smi.rfc1902 import * +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncore.transport import * +from pysnmp.hlapi.v1arch.asyncore.cmdgen import * +from pysnmp.hlapi.v1arch.asyncore.ntforg import * +from pysnmp.hlapi.v1arch.asyncore.dispatch import * + +try: + from pysnmp.hlapi.v1arch.asyncore.sync.cmdgen import * + from pysnmp.hlapi.v1arch.asyncore.sync.ntforg import * + +except SyntaxError: + from pysnmp.hlapi.v1arch.asyncore.sync.compat.cmdgen import * + from pysnmp.hlapi.v1arch.asyncore.sync.compat.ntforg import * diff --git a/pysnmp/hlapi/v1arch/asyncore/sync/cmdgen.py b/pysnmp/hlapi/v1arch/asyncore/sync/cmdgen.py new file mode 100644 index 00000000..9b6b2b24 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/sync/cmdgen.py @@ -0,0 +1,570 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from sys import version_info +from pysnmp.hlapi.v1arch.asyncore import cmdgen +from pysnmp.hlapi.varbinds import * +from pysnmp.proto.rfc1905 import endOfMibView +from pysnmp.proto import errind +from pyasn1.type.univ import Null + +__all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd'] + +if version_info[:2] < (2, 6): + __all__.append('next') + + # noinspection PyShadowingBuiltins + def next(iter): + return iter.next() + +vbProcessor = CommandGeneratorVarBinds() + + +def getCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Creates a generator to perform one or more SNMP GET queries. + + On each iteration, new SNMP GET request is send (:RFC:`1905#section-4.2.1`). + The iterator blocks waiting for response to arrive or error to occur. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + + authData: :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + \*varBinds: :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options: + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`. + + Yields + ------ + errorIndication: str + True value indicates local SNMP error. + errorStatus: str + True value indicates SNMP PDU error reported by remote. + errorIndex: int + Non-zero value refers to `varBinds[errorIndex-1]` + varBinds: tuple + A sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` class + instances representing MIB variables returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Notes + ----- + The `getCmd` generator will be exhausted immediately unless + a new sequence of `varBinds` are send back into running generator + (supported since Python 2.6). + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import * + >>> + >>> g = getCmd(snmpDispatcher(), + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0))) + >>> + >>> next(g) + (None, 0, 0, [ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.1.0')), + DisplayString('SunOS zeus.snmplabs.com 4.1.3_U1 1 sun4m'))]) + """ + + def cbFun(*args, **kwargs): + response[:] = args + + options['cbFun'] = cbFun + + errorIndication, errorStatus, errorIndex = None, 0, 0 + response = [] + + while True: + if varBinds: + cmdgen.getCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options) + + snmpDispatcher.transportDispatcher.runDispatcher() + + errorIndication, errorStatus, errorIndex, varBinds = response + + varBinds = (yield errorIndication, errorStatus, errorIndex, varBinds) + + if not varBinds: + break + + +def setCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Creates a generator to perform one or more SNMP SET queries. + + On each iteration, new SNMP SET request is send (:RFC:`1905#section-4.2.5`). + The iterator blocks waiting for response to arrive or error to occur. + + Parameters + ---------- + snmpDispatcher : :py:class:`~pysnmp.hlapi.snmpDispatcher` + Class instance representing SNMP engine. + + authData : :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget : :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + \*varBinds : :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + Default is `True`. + + Yields + ------ + errorIndication : str + True value indicates SNMP engine error. + errorStatus : str + True value indicates SNMP PDU error. + errorIndex : int + Non-zero value refers to `varBinds[errorIndex-1]` + varBinds : tuple + A sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` class + instances representing MIB variables returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Notes + ----- + The `setCmd` generator will be exhausted immidiately unless + a new sequence of `varBinds` are send back into running generator + (supported since Python 2.6). + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import * + >>> + >>> g = setCmd(snmpDispatcher(), + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0), 'Linux i386')) + >>> + >>> next(g) + (None, 0, 0, [ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.1.0')), + DisplayString('Linux i386'))]) + """ + + def cbFun(*args, **kwargs): + response[:] = args + + options['cbFun'] = cbFun + + errorIndication, errorStatus, errorIndex = None, 0, 0 + response = [] + + while True: + if varBinds: + cmdgen.setCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options) + + snmpDispatcher.transportDispatcher.runDispatcher() + + errorIndication, errorStatus, errorIndex, varBinds = response + + varBinds = (yield errorIndication, errorStatus, errorIndex, varBinds) + + if not varBinds: + break + + +def nextCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Create a generator to perform one or more SNMP GETNEXT queries. + + On each iteration, new SNMP GETNEXT request is send + (:RFC:`1905#section-4.2.2`). The iterator blocks waiting for response + to arrive or error to occur. + + Parameters + ---------- + snmpDispatcher : :py:class:`~pysnmp.hlapi.snmpDispatcher` + Class instance representing SNMP engine. + + authData : :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget : :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + \*varBinds : :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + Default is `True`. + * `lexicographicMode` - walk SNMP agent's MIB till the end (if `True`), + otherwise (if `False`) stop iteration when all response MIB + variables leave the scope of initial MIB variables in + `varBinds`. Default is `True`. + * `ignoreNonIncreasingOid` - continue iteration even if response + MIB variables (OIDs) are not greater then request MIB variables. + Be aware that setting it to `True` may cause infinite loop between + SNMP management and agent applications. Default is `False`. + * `maxRows` - stop iteration once this generator instance processed + `maxRows` of SNMP conceptual table. Default is `0` (no limit). + * `maxCalls` - stop iteration once this generator instance processed + `maxCalls` responses. Default is 0 (no limit). + + Yields + ------ + errorIndication: str + True value indicates SNMP engine error. + errorStatus: str + True value indicates SNMP PDU error. + errorIndex: int + Non-zero value refers to `varBinds[errorIndex-1]` + varBindTable: tuple + A 2-dimensional array of :py:class:`~pysnmp.smi.rfc1902.ObjectType` class + instances representing a table of MIB variables returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Notes + ----- + The `nextCmd` generator will be exhausted on any of the following + conditions: + + * SNMP engine error occurs thus `errorIndication` is `True` + * SNMP PDU `errorStatus` is reported as `True` + * SNMP :py:class:`~pysnmp.proto.rfc1905.EndOfMibView` values + (also known as *SNMP exception values*) are reported for all + MIB variables in `varBinds` + * *lexicographicMode* option is `True` and SNMP agent reports + end-of-mib or *lexicographicMode* is `False` and all + response MIB variables leave the scope of `varBinds` + + At any moment a new sequence of `varBinds` could be send back into + running generator (supported since Python 2.6). + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import * + >>> + >>> g = nextCmd(snmpDispatcher(), + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr'))) + >>> next(g) + (None, 0, 0, [[ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.1.0')), DisplayString('SunOS zeus.snmplabs.com 4.1.3_U1 1 sun4m'))]]) + >>> g.send([ObjectType(ObjectIdentity('IF-MIB', 'ifInOctets'))]) + (None, 0, 0, [(ObjectName('1.3.6.1.2.1.2.2.1.10.1'), Counter32(284817787))]) + """ + + def cbFun(*args, **kwargs): + response[:] = args + (kwargs.get('nextVarBinds', ()),) + + options['cbFun'] = cbFun + + lexicographicMode = options.pop('lexicographicMode', True) + maxRows = options.pop('maxRows', 0) + maxCalls = options.pop('maxCalls', 0) + + initialVarBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + totalRows = totalCalls = 0 + + errorIndication, errorStatus, errorIndex, varBindTable = None, 0, 0, () + response = [] + + while True: + if not varBinds: + yield (errorIndication, errorStatus, errorIndex, varBindTable) + return + + cmdgen.nextCmd(snmpDispatcher, authData, transportTarget, + *[(x[0], Null('')) for x in varBinds], **options) + + snmpDispatcher.transportDispatcher.runDispatcher() + + errorIndication, errorStatus, errorIndex, varBindTable, varBinds = response + + if errorIndication: + yield (errorIndication, errorStatus, errorIndex, varBindTable) + return + + elif errorStatus: + if errorStatus == 2: + # Hide SNMPv1 noSuchName error which leaks in here + # from SNMPv1 Agent through internal pysnmp proxy. + errorStatus = errorStatus.clone(0) + errorIndex = errorIndex.clone(0) + yield (errorIndication, errorStatus, errorIndex, varBindTable) + return + + else: + varBindRow = varBindTable and varBindTable[-1] + + if not lexicographicMode: + for idx, varBind in enumerate(varBindRow): + name, val = varBind + if not isinstance(val, Null): + if initialVarBinds[idx][0].isPrefixOf(name): + break + else: + return + + for varBindRow in varBindTable: + nextVarBinds = (yield errorIndication, errorStatus, errorIndex, varBindRow) + + if nextVarBinds: + initialVarBinds = varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, nextVarBinds) + + totalRows += 1 + totalCalls += 1 + + if maxRows and totalRows >= maxRows: + return + + if maxCalls and totalCalls >= maxCalls: + return + + +def bulkCmd(snmpDispatcher, authData, transportTarget, + nonRepeaters, maxRepetitions, *varBinds, **options): + """Creates a generator to perform one or more SNMP GETBULK queries. + + On each iteration, new SNMP GETBULK request is send + (:RFC:`1905#section-4.2.3`). The iterator blocks waiting for response + to arrive or error to occur. + + Parameters + ---------- + snmpDispatcher : :py:class:`~pysnmp.hlapi.snmpDispatcher` + Class instance representing SNMP engine. + + authData : :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget : :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + nonRepeaters : int + One MIB variable is requested in response for the first + `nonRepeaters` MIB variables in request. + + maxRepetitions : int + `maxRepetitions` MIB variables are requested in response for each + of the remaining MIB variables in the request (e.g. excluding + `nonRepeaters`). Remote SNMP engine may choose lesser value than + requested. + + \*varBinds : :py:class:`~pysnmp.smi.rfc1902.ObjectType` + One or more class instances representing MIB variables to place + into SNMP request. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + Default is `True`. + * `lexicographicMode` - walk SNMP agent's MIB till the end (if `True`), + otherwise (if `False`) stop iteration when all response MIB + variables leave the scope of initial MIB variables in + `varBinds`. Default is `True`. + * `ignoreNonIncreasingOid` - continue iteration even if response + MIB variables (OIDs) are not greater then request MIB variables. + Be aware that setting it to `True` may cause infinite loop between + SNMP management and agent applications. Default is `False`. + * `maxRows` - stop iteration once this generator instance processed + `maxRows` of SNMP conceptual table. Default is `0` (no limit). + * `maxCalls` - stop iteration once this generator instance processed + `maxCalls` responses. Default is 0 (no limit). + + Yields + ------ + errorIndication : str + True value indicates SNMP engine error. + errorStatus : str + True value indicates SNMP PDU error. + errorIndex : int + Non-zero value refers to \*varBinds[errorIndex-1] + varBinds: tuple + A sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` class + instances representing MIB variables returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Notes + ----- + The `bulkCmd` generator will be exhausted on any of the following + conditions: + + * SNMP engine error occurs thus `errorIndication` is `True` + * SNMP PDU `errorStatus` is reported as `True` + * SNMP :py:class:`~pysnmp.proto.rfc1905.EndOfMibView` values + (also known as *SNMP exception values*) are reported for all + MIB variables in `varBinds` + * *lexicographicMode* option is `True` and SNMP agent reports + end-of-mib or *lexicographicMode* is `False` and all + response MIB variables leave the scope of `varBinds` + + At any moment a new sequence of `varBinds` could be send back into + running generator (supported since Python 2.6). + + Setting `maxRepetitions` value to 15..50 might significantly improve + system performance, as many MIB variables get packed into a single + response message at once. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import * + >>> + >>> g = bulkCmd(snmpDispatcher(), + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 161)), + >>> 0, 25, + >>> ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr'))) + >>> next(g) + (None, 0, 0, [[ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.1.0')), DisplayString('SunOS zeus.snmplabs.com 4.1.3_U1 1 sun4m'))]]) + >>> g.send([ObjectType(ObjectIdentity('IF-MIB', 'ifInOctets'))]) + (None, 0, 0, [[(ObjectName('1.3.6.1.2.1.2.2.1.10.1'), Counter32(284817787))]]) + """ + + def cbFun(*args, **kwargs): + response[:] = args + (kwargs.get('nextVarBinds', ()),) + + options['cbFun'] = cbFun + + lexicographicMode = options.pop('lexicographicMode', True) + maxRows = options.pop('maxRows', 0) + maxCalls = options.pop('maxCalls', 0) + + initialVarBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, varBinds) + + nullVarBinds = [False] * len(initialVarBinds) + + totalRows = totalCalls = 0 + + errorIndication, errorStatus, errorIndex, varBindTable = None, 0, 0, () + response = [] + + stopFlag = False + + while not stopFlag: + if not varBinds: + yield (errorIndication, errorStatus, errorIndex, varBinds) + return + + if maxRows and totalRows < maxRows: + maxRepetitions = min(maxRepetitions, maxRows - totalRows) + + cmdgen.bulkCmd(snmpDispatcher, authData, transportTarget, + nonRepeaters, maxRepetitions, + *[(x[0], Null('')) for x in varBinds], **options) + + snmpDispatcher.transportDispatcher.runDispatcher() + + errorIndication, errorStatus, errorIndex, varBindTable, varBinds = response + + if errorIndication: + yield (errorIndication, errorStatus, errorIndex, ()) + return + + elif errorStatus: + if errorStatus == 2: + # Hide SNMPv1 noSuchName error which leaks in here + # from SNMPv1 Agent through internal pysnmp proxy. + errorStatus = errorStatus.clone(0) + errorIndex = errorIndex.clone(0) + yield (errorIndication, errorStatus, errorIndex, varBindTable and varBindTable[0] or []) + return + + else: + for rowIdx, varBindRow in enumerate(varBindTable): + stopFlag = True + if len(varBindRow) != len(initialVarBinds): + varBindTable = rowIdx and varBindTable[:rowIdx - 1] or [] + break + + for colIdx, varBind in enumerate(varBindRow): + name, val = varBind + if nullVarBinds[colIdx]: + varBindRow[colIdx] = name, endOfMibView + continue + + stopFlag = False + + if isinstance(val, Null): + nullVarBinds[colIdx] = True + + elif not lexicographicMode and not initialVarBinds[colIdx][0].isPrefixOf(name): + varBindRow[colIdx] = name, endOfMibView + nullVarBinds[colIdx] = True + + if stopFlag: + varBindTable = rowIdx and varBindTable[:rowIdx - 1] or [] + break + + totalRows += len(varBindTable) + totalCalls += 1 + + if maxRows and totalRows >= maxRows: + if totalRows > maxRows: + varBindTable = varBindTable[:-(totalRows - maxRows)] + stopFlag = True + + if maxCalls and totalCalls >= maxCalls: + stopFlag = True + + for varBindRow in varBindTable: + nextVarBinds = (yield errorIndication, errorStatus, errorIndex, varBindRow) + + if nextVarBinds: + initialVarBinds = varBinds = vbProcessor.makeVarBinds(snmpDispatcher.cache, nextVarBinds) diff --git a/pysnmp/hlapi/v1arch/asyncore/sync/ntforg.py b/pysnmp/hlapi/v1arch/asyncore/sync/ntforg.py new file mode 100644 index 00000000..25912ff4 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/sync/ntforg.py @@ -0,0 +1,140 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from sys import version_info +from pysnmp.hlapi.v1arch.asyncore import ntforg +from pysnmp.hlapi.varbinds import * +from pysnmp.proto import errind +from pyasn1.type.univ import Null + +__all__ = ['sendNotification'] + +if version_info[:2] < (2, 6): + __all__.append('next') + + # noinspection PyShadowingBuiltins + def next(iter): + return iter.next() + + +def sendNotification(snmpDispatcher, authData, transportTarget, + notifyType, *varBinds, **options): + """Creates a generator to send one or more SNMP notifications. + + On each iteration, new SNMP TRAP or INFORM notification is send + (:RFC:`1905#section-4,2,6`). The iterator blocks waiting for + INFORM acknowledgement to arrive or error to occur. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asyncore-based asynchronous event loop and + associated state information. + + authData: :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + Class instance representing SNMP credentials. + + transportTarget: :py:class:`~pysnmp.hlapi.asyncore.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.asyncore.Udp6TransportTarget` + Class instance representing transport type along with SNMP peer address. + + notifyType: str + Indicates type of notification to be sent. Recognized literal + values are *trap* or *inform*. + + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU places rigid requirement on the ordering of + the variable-bindings. + + Mandatory variable-bindings: + + 0. SNMPv2-MIB::sysUpTime.0 = <agent uptime> + 1. SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Optional variable-bindings (applicable to SNMP v1 TRAP): + + 2. SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + 3. SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + 4. SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + Informational variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`. + + Yields + ------ + errorIndication: str + True value indicates local SNMP error. + errorStatus: str + True value indicates SNMP PDU error reported by remote. + errorIndex: int + Non-zero value refers to `varBinds[errorIndex-1]` + varBinds: tuple + A sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` class + instances representing MIB variables returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Notes + ----- + The `sendNotification` generator will be exhausted immediately unless + an instance of :py:class:`~pysnmp.smi.rfc1902.NotificationType` class + or a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` `varBinds` + are send back into running generator (supported since Python 2.6). + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import * + >>> + >>> g = sendNotification(SnmpDispatcher(), + >>> CommunityData('public'), + >>> UdpTransportTarget(('demo.snmplabs.com', 162)), + >>> 'trap', + >>> NotificationType(ObjectIdentity('IF-MIB', 'linkDown'))) + >>> next(g) + (None, 0, 0, []) + """ + + def cbFun(*args, **kwargs): + response[:] = args + + options['cbFun'] = cbFun + + errorIndication, errorStatus, errorIndex = None, 0, 0 + + response = [None, 0, 0, []] + + while True: + if varBinds: + ntforg.sendNotification(snmpDispatcher, authData, transportTarget, + notifyType, *varBinds, **options) + + snmpDispatcher.transportDispatcher.runDispatcher() + + errorIndication, errorStatus, errorIndex, varBinds = response + + varBinds = (yield errorIndication, errorStatus, errorIndex, varBinds) + + if not varBinds: + break diff --git a/pysnmp/hlapi/v1arch/asyncore/transport.py b/pysnmp/hlapi/v1arch/asyncore/transport.py new file mode 100644 index 00000000..be5e336d --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncore/transport.py @@ -0,0 +1,111 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +import socket +import sys + +from pysnmp import error +from pysnmp.carrier.asyncore.dgram import udp, udp6 +from pysnmp.hlapi.transport import AbstractTransportTarget + +__all__ = ['Udp6TransportTarget', 'UdpTransportTarget'] + + +class UdpTransportTarget(AbstractTransportTarget): + """Represent UDP/IPv4 transport endpoint. + + This object can be used for passing UDP/IPv4 configuration information + to the low-level SNMP applications. + + See :RFC:`1906#section-3` for more information on the UDP transport mapping. + + Parameters + ---------- + transportAddr: :py:class:`tuple` + Indicates remote address in Python :py:mod:`socket` module format + which is a tuple of FQDN, port where FQDN is a string representing + either hostname or IPv4 address in quad-dotted form, port is an + integer. + timeout: :py:class:`int` + Response timeout in seconds. + retries: :py:class:`int` + Maximum number of request retries, 0 retries means just a single + request. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import UdpTransportTarget + >>> UdpTransportTarget(('demo.snmplabs.com', 161)) + UdpTransportTarget(('195.218.195.228', 161), timeout=1, retries=5) + >>> + + """ + transportDomain = udp.domainName + protoTransport = udp.UdpSocketTransport + + def _resolveAddr(self, transportAddr): + try: + return socket.getaddrinfo(transportAddr[0], + transportAddr[1], + socket.AF_INET, + socket.SOCK_DGRAM, + socket.IPPROTO_UDP)[0][4][:2] + except socket.gaierror: + raise error.PySnmpError('Bad IPv4/UDP transport address %s: %s' % ( + '@'.join([str(x) for x in transportAddr]), sys.exc_info()[1])) + + +class Udp6TransportTarget(AbstractTransportTarget): + """Represent UDP/IPv6 transport endpoint. + + This object can be used for passing UDP/IPv4 configuration information + to the low-level SNMP applications. + + See :RFC:`1906#section-3`, :RFC:`2851#section-4` for more information + on the UDP and IPv6 transport mapping. + + Parameters + ---------- + transportAddr : tuple + Indicates remote address in Python :py:mod:`socket` module format + which is a tuple of FQDN, port where FQDN is a string representing + either hostname or IPv6 address in one of three conventional forms + (:RFC:`1924#section-3`), port is an integer. + timeout : int + Response timeout in seconds. + retries : int + Maximum number of request retries, 0 retries means just a single + request. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import Udp6TransportTarget + >>> Udp6TransportTarget(('google.com', 161)) + Udp6TransportTarget(('2a00:1450:4014:80a::100e', 161), timeout=1, retries=5) + >>> Udp6TransportTarget(('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', 161)) + Udp6TransportTarget(('fedc:ba98:7654:3210:fedc:ba98:7654:3210', 161), timeout=1, retries=5) + >>> Udp6TransportTarget(('1080:0:0:0:8:800:200C:417A', 161)) + Udp6TransportTarget(('1080::8:800:200c:417a', 161), timeout=1, retries=5) + >>> Udp6TransportTarget(('::0', 161)) + Udp6TransportTarget(('::', 161), timeout=1, retries=5) + >>> Udp6TransportTarget(('::', 161)) + Udp6TransportTarget(('::', 161), timeout=1, retries=5) + >>> + + """ + transportDomain = udp6.domainName + protoTransport = udp6.Udp6SocketTransport + + def _resolveAddr(self, transportAddr): + try: + return socket.getaddrinfo(transportAddr[0], + transportAddr[1], + socket.AF_INET6, + socket.SOCK_DGRAM, + socket.IPPROTO_UDP)[0][4][:2] + except socket.gaierror: + raise error.PySnmpError('Bad IPv6/UDP transport address %s: %s' % ( + '@'.join([str(x) for x in transportAddr]), sys.exc_info()[1])) diff --git a/pysnmp/hlapi/v1arch/auth.py b/pysnmp/hlapi/v1arch/auth.py new file mode 100644 index 00000000..761f9738 --- /dev/null +++ b/pysnmp/hlapi/v1arch/auth.py @@ -0,0 +1,45 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# + +__all__ = ['CommunityData'] + + +class CommunityData(object): + """Creates SNMP v1/v2c configuration entry. + + This object can be used by + :py:class:`~pysnmp.hlapi.v1arch.asyncore.AsyncCommandGenerator` or + :py:class:`~pysnmp.hlapi.v1arch.asyncore.AsyncNotificationOriginator` + and their derivatives for conveying SNMP v1/v2c configuration. + + Parameters + ---------- + communityName: py:class:`str` + SNMP v1/v2c community string. + mpModel: py:class:`int` + SNMP version - 0 for SNMPv1 and 1 for SNMPv2c. + + Examples + -------- + >>> from pysnmp.hlapi.v1arch import CommunityData + >>> CommunityData('public') + CommunityData(communityName=<COMMUNITY>, mpModel=1) + """ + + def __init__(self, communityName, mpModel=1): + self.mpModel = mpModel + self.communityName = communityName + + def __hash__(self): + return hash((self.communityName, self.mpModel)) + + def __repr__(self): + return '%s(communityName=<COMMUNITY>, mpModel=%r)' % ( + self.__class__.__name__, + self.communityName, + self.mpModel, + ) diff --git a/pysnmp/hlapi/v1arch/dispatch.py b/pysnmp/hlapi/v1arch/dispatch.py new file mode 100644 index 00000000..0c222281 --- /dev/null +++ b/pysnmp/hlapi/v1arch/dispatch.py @@ -0,0 +1,168 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from time import time + +from pysnmp.proto.api import verdec +from pysnmp.proto import api +from pysnmp.proto import error +from pysnmp import debug + +from pyasn1.codec.ber import encoder, decoder + +__all__ = [] + + +class AbstractSnmpDispatcher(object): + """Creates SNMP message dispatcher object. + + `SnmpDispatcher` object manages send and receives SNMP PDU + messages through underlying transport dispatcher and dispatches + them to the callers. + + `SnmpDispatcher` is the only stateful object, all `hlapi.v1arch` SNMP + operations require an instance of `SnmpDispatcher`. Users do not normally + request services directly from `SnmpDispather`, but pass it around to + other `hlapi.v1arch` interfaces. + + It is possible to run multiple instances of `SnmpDispatcher` in the + application. In a multithreaded environment, each thread that + works with SNMP must have its own `SnmpDispatcher` instance. + """ + + protoDispatcher = None + + def __init__(self, transportDispatcher=None): + if transportDispatcher: + self.transportDispatcher = transportDispatcher + else: + self.transportDispatcher = self.protoDispatcher() + + self._automaticDispatcher = transportDispatcher is not self.transportDispatcher + self._configuredTransports = set() + + self._pendingReqs = {} + + self.transportDispatcher.registerRecvCbFun(self._recvCb) + self.transportDispatcher.registerTimerCbFun(self._timerCb) + + self.cache = {} + + def __repr__(self): + return '%s(transportDispatcher=%s)' % (self.__class__.__name__, self.transportDispatcher) + + def close(self): + self.transportDispatcher.unregisterRecvCbFun() + self.transportDispatcher.unregisterTimerCbFun() + if self._automaticDispatcher: + self.transportDispatcher.close() + + for requestId, stateInfo in self._pendingReqs.items(): + cbFun = stateInfo['cbFun'] + cbCtx = stateInfo['cbCtx'] + + if cbFun: + cbFun(self, 'Request #%d terminated' % requestId, None, cbCtx) + + self._pendingReqs.clear() + + def sendPdu(self, authData, transportTarget, reqPdu, cbFun=None, cbCtx=None): + if (self._automaticDispatcher and + transportTarget.transportDomain not in self._configuredTransports): + self.transportDispatcher.registerTransport( + transportTarget.transportDomain, transportTarget.protoTransport().openClientMode() + ) + self._configuredTransports.add(transportTarget.transportDomain) + + pMod = api.protoModules[authData.mpModel] + + reqMsg = pMod.Message() + pMod.apiMessage.setDefaults(reqMsg) + pMod.apiMessage.setCommunity(reqMsg, authData.communityName) + pMod.apiMessage.setPDU(reqMsg, reqPdu) + + outgoingMsg = encoder.encode(reqMsg) + + requestId = pMod.apiPDU.getRequestID(reqPdu) + + self._pendingReqs[requestId] = dict( + outgoingMsg=outgoingMsg, + transportTarget=transportTarget, + cbFun=cbFun, cbCtx=cbCtx, + timestamp=time() + transportTarget.timeout, retries=0 + ) + + self.transportDispatcher.sendMessage( + outgoingMsg, transportTarget.transportDomain, transportTarget.transportAddr + ) + + if (reqPdu.__class__ is getattr(pMod, 'SNMPv2TrapPDU', None) or + reqPdu.__class__ is getattr(pMod, 'TrapPDU', None)): + return requestId + + self.transportDispatcher.jobStarted(id(self)) + + return requestId + + def _recvCb(self, snmpEngine, transportDomain, transportAddress, wholeMsg): + try: + mpModel = verdec.decodeMessageVersion(wholeMsg) + + except error.ProtocolError: + return null # n.b the whole buffer gets dropped + + debug.logger & debug.flagDsp and debug.logger('receiveMessage: msgVersion %s, msg decoded' % mpModel) + + pMod = api.protoModules[mpModel] + + while wholeMsg: + rspMsg, wholeMsg = decoder.decode(wholeMsg, asn1Spec=pMod.Message()) + rspPdu = pMod.apiMessage.getPDU(rspMsg) + + requestId = pMod.apiPDU.getRequestID(rspPdu) + + try: + stateInfo = self._pendingReqs.pop(requestId) + + except KeyError: + continue + + self.transportDispatcher.jobFinished(id(self)) + + cbFun = stateInfo['cbFun'] + cbCtx = stateInfo['cbCtx'] + + if cbFun: + cbFun(self, requestId, None, rspPdu, cbCtx) + + return wholeMsg + + def _timerCb(self, timeNow): + for requestId, stateInfo in self._pendingReqs.items(): + if stateInfo['timestamp'] < timeNow: + continue + + retries = stateInfo['retries'] + transportTarget = stateInfo['transportTarget'] + + if retries == transportTarget.retries: + cbFun = stateInfo['cbFun'] + cbCtx = stateInfo['cbCtx'] + + if cbFun: + del self._pendingReqs[requestId] + cbFun(self, requestId, 'Request #%d timed out' % requestId, None, cbCtx) + self.transportDispatcher.jobFinished(id(self)) + continue + + stateInfo['retries'] += 1 + stateInfo['timestamp'] = timeNow + transportTarget.timeout + + outgoingMsg = stateInfo['outgoingMsg'] + + self.transportDispatcher.sendMessage( + outgoingMsg, transportTarget.transportDomain, transportTarget.transportAddr + ) diff --git a/pysnmp/hlapi/v3arch/__init__.py b/pysnmp/hlapi/v3arch/__init__.py new file mode 100644 index 00000000..8f36181c --- /dev/null +++ b/pysnmp/hlapi/v3arch/__init__.py @@ -0,0 +1,15 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.proto.rfc1902 import * +from pysnmp.proto.rfc1905 import NoSuchInstance, NoSuchObject, EndOfMibView +from pysnmp.smi.rfc1902 import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.entity.engine import * + +# default is synchronous asyncore-based API +from pysnmp.hlapi.v3arch.asyncore.sync import * diff --git a/pysnmp/hlapi/asyncio/__init__.py b/pysnmp/hlapi/v3arch/asyncio/__init__.py index 24e71f7c..98f67187 100644 --- a/pysnmp/hlapi/asyncio/__init__.py +++ b/pysnmp/hlapi/v3arch/asyncio/__init__.py @@ -7,8 +7,8 @@ from pysnmp.proto.rfc1902 import * from pysnmp.smi.rfc1902 import * from pysnmp.entity.engine import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.asyncio.transport import * -from pysnmp.hlapi.asyncio.cmdgen import * -from pysnmp.hlapi.asyncio.ntforg import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.asyncio.transport import * +from pysnmp.hlapi.v3arch.asyncio.cmdgen import * +from pysnmp.hlapi.v3arch.asyncio.ntforg import * diff --git a/pysnmp/hlapi/asyncio/cmdgen.py b/pysnmp/hlapi/v3arch/asyncio/cmdgen.py index e449d477..bf97bcd4 100644 --- a/pysnmp/hlapi/asyncio/cmdgen.py +++ b/pysnmp/hlapi/v3arch/asyncio/cmdgen.py @@ -32,12 +32,13 @@ # THE POSSIBILITY OF SUCH DAMAGE. # from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.asyncio.transport import * +from pysnmp.hlapi.v3arch.asyncio.transport import * from pysnmp.entity.rfc3413 import cmdgen +from pysnmp.proto.api import v2c try: import asyncio @@ -49,7 +50,7 @@ __all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd', 'isEndOfMib'] vbProcessor = CommandGeneratorVarBinds() lcd = CommandGeneratorLcdConfigurator() -isEndOfMib = lambda x: not cmdgen.getNextVarBinds(x)[1] +isEndOfMib = lambda varBinds: not v2c.apiPDU.getNextVarBinds(varBinds) @asyncio.coroutine @@ -135,7 +136,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, if future.cancelled(): return try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, + varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine.cache, varBinds, lookupMib) except Exception as e: future.set_exception(e) @@ -151,7 +152,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.GetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), future) ) return future @@ -240,7 +241,7 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, if future.cancelled(): return try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, + varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine.cache, varBinds, lookupMib) except Exception as e: future.set_exception(e) @@ -256,7 +257,7 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.SetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), future) ) return future @@ -349,7 +350,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, if future.cancelled(): return try: - varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine, + varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] @@ -367,7 +368,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.NextCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), future) ) return future @@ -489,7 +490,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, if future.cancelled(): return try: - varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine, + varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] @@ -507,7 +508,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.BulkCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, nonRepeaters, maxRepetitions, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), future) ) return future diff --git a/pysnmp/hlapi/asyncio/ntforg.py b/pysnmp/hlapi/v3arch/asyncio/ntforg.py index 0bd59b7e..d43bf979 100644 --- a/pysnmp/hlapi/asyncio/ntforg.py +++ b/pysnmp/hlapi/v3arch/asyncio/ntforg.py @@ -9,11 +9,11 @@ # Zachary Lorusso <zlorusso@gmail.com> # from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.asyncio.transport import * +from pysnmp.hlapi.v3arch.asyncio.transport import * from pysnmp.entity.rfc3413 import ntforg try: @@ -29,7 +29,7 @@ lcd = NotificationOriginatorLcdConfigurator() @asyncio.coroutine def sendNotification(snmpEngine, authData, transportTarget, contextData, - notifyType, varBinds, **options): + notifyType, *varBinds, **options): """Creates a generator to send SNMP notification. When iterator gets advanced by :py:mod:`asyncio` main loop, @@ -55,13 +55,30 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, Indicates type of notification to be sent. Recognized literal values are *trap* or *inform*. - varBinds: tuple - Single :py:class:`~pysnmp.smi.rfc1902.NotificationType` class instance - representing a minimum sequence of MIB variables required for - particular notification type. - Alternatively, a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` - objects could be passed instead. In the latter case it is up to - the user to ensure proper Notification PDU contents. + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU places rigid requirement on the ordering of + the variable-bindings. + + Mandatory variable-bindings: + + 0. SNMPv2-MIB::sysUpTime.0 = <agent uptime> + 1. SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Optional variable-bindings (applicable to SNMP v1 TRAP): + + 2. SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + 3. SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + 4. SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + Informational variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE Other Parameters ---------------- @@ -125,7 +142,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, if future.cancelled(): return try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, + varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine.cache, varBinds, lookupMib) except Exception as e: future.set_exception(e) @@ -145,7 +162,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, notifyName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), future) ) diff --git a/pysnmp/hlapi/asyncio/transport.py b/pysnmp/hlapi/v3arch/asyncio/transport.py index 4e7b9110..86423dfc 100644 --- a/pysnmp/hlapi/asyncio/transport.py +++ b/pysnmp/hlapi/v3arch/asyncio/transport.py @@ -6,9 +6,10 @@ # import socket import sys + from pysnmp.carrier.asyncio.dgram import udp, udp6 -from pysnmp.hlapi.transport import AbstractTransportTarget from pysnmp.error import PySnmpError +from pysnmp.hlapi.transport import AbstractTransportTarget __all__ = ['Udp6TransportTarget', 'UdpTransportTarget'] diff --git a/pysnmp/hlapi/asyncore/__init__.py b/pysnmp/hlapi/v3arch/asyncore/__init__.py index 303b4b84..9cef379f 100644 --- a/pysnmp/hlapi/asyncore/__init__.py +++ b/pysnmp/hlapi/v3arch/asyncore/__init__.py @@ -6,9 +6,9 @@ # from pysnmp.proto.rfc1902 import * from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.asyncore.transport import * -from pysnmp.hlapi.asyncore.cmdgen import * -from pysnmp.hlapi.asyncore.ntforg import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.asyncore.transport import * +from pysnmp.hlapi.v3arch.asyncore.cmdgen import * +from pysnmp.hlapi.v3arch.asyncore.ntforg import * from pysnmp.entity.engine import * diff --git a/pysnmp/hlapi/asyncore/cmdgen.py b/pysnmp/hlapi/v3arch/asyncore/cmdgen.py index 4cedfa82..fac237e2 100644 --- a/pysnmp/hlapi/asyncore/cmdgen.py +++ b/pysnmp/hlapi/v3arch/asyncore/cmdgen.py @@ -6,18 +6,19 @@ # from pysnmp.entity.rfc3413 import cmdgen from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.asyncore.transport import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * +from pysnmp.hlapi.v3arch.asyncore.transport import * +from pysnmp.proto.api import v2c __all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd', 'isEndOfMib'] vbProcessor = CommandGeneratorVarBinds() lcd = CommandGeneratorLcdConfigurator() -isEndOfMib = lambda x: not cmdgen.getNextVarBinds(x)[1] +isEndOfMib = lambda varBinds: not v2c.apiPDU.getNextVarBinds(varBinds) def getCmd(snmpEngine, authData, transportTarget, contextData, @@ -51,15 +52,14 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, Other Parameters ---------------- \*\*options : - Request options: - * `lookupMib` - load MIB and resolve response MIB variables at - the cost of slightly reduced performance. Default is `True`. - * `cbFun` (callable) - user-supplied callable that is invoked - to pass SNMP response data or error to user at a later point - of time. Default is `None`. - * `cbCtx` (object) - user-supplied object passing additional - parameters to/from `cbFun`. Default is `None`. + * `lookupMib` (bool) - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + * `cbFun` (callable) - user-supplied callable that is invoked + to pass SNMP response data or error to user at a later point + of time. Default is `None` + * `cbCtx` (object) - user-supplied object passing additional + parameters to/from `cbFun`. Default is `None`. Notes ----- @@ -118,7 +118,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, return cbFun(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, vbProcessor.unmakeVarBinds( - snmpEngine, varBinds, lookupMib + snmpEngine.cache, varBinds, lookupMib ), cbCtx) addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) @@ -126,7 +126,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, return cmdgen.GetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), options.get('cbFun'), options.get('cbCtx')) ) @@ -229,14 +229,14 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, return cbFun(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, vbProcessor.unmakeVarBinds( - snmpEngine, varBinds, lookupMib + snmpEngine.cache, varBinds, lookupMib ), cbCtx) addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) return cmdgen.SetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, - contextData.contextName, vbProcessor.makeVarBinds(snmpEngine, varBinds), + contextData.contextName, vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), options.get('cbFun'), options.get('cbCtx')) ) @@ -339,7 +339,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, lookupMib, cbFun, cbCtx = cbCtx return cbFun(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, - [vbProcessor.unmakeVarBinds(snmpEngine, varBindTableRow, lookupMib) for varBindTableRow in + [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable], cbCtx) @@ -347,7 +347,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, return cmdgen.NextCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), options.get('cbFun'), options.get('cbCtx')) ) @@ -478,16 +478,17 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): lookupMib, cbFun, cbCtx = cbCtx - return cbFun(snmpEngine, sendRequestHandle, errorIndication, + return cbFun(snmpEngine.cache, sendRequestHandle, errorIndication, errorStatus, errorIndex, - [vbProcessor.unmakeVarBinds(snmpEngine, varBindTableRow, lookupMib) for varBindTableRow in + [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable], cbCtx) addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + return cmdgen.BulkCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, nonRepeaters, maxRepetitions, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), options.get('cbFun'), options.get('cbCtx')) ) diff --git a/pysnmp/hlapi/asyncore/ntforg.py b/pysnmp/hlapi/v3arch/asyncore/ntforg.py index ecd418c1..09cb6dfc 100644 --- a/pysnmp/hlapi/asyncore/ntforg.py +++ b/pysnmp/hlapi/v3arch/asyncore/ntforg.py @@ -6,11 +6,11 @@ # from pysnmp.smi.rfc1902 import * from pysnmp.entity.rfc3413 import ntforg -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.asyncore.transport import * +from pysnmp.hlapi.v3arch.asyncore.transport import * __all__ = ['sendNotification'] @@ -19,8 +19,7 @@ lcd = NotificationOriginatorLcdConfigurator() def sendNotification(snmpEngine, authData, transportTarget, contextData, - notifyType, varBinds, cbFun=None, cbCtx=None, - lookupMib=False): + notifyType, *varBinds, **options): """Send SNMP notification. Based on passed parameters, prepares SNMP TRAP or INFORM @@ -47,26 +46,58 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, Indicates type of notification to be sent. Recognized literal values are *trap* or *inform*. - varBinds : tuple - Single :py:class:`~pysnmp.smi.rfc1902.NotificationType` class - instance representing a minimum sequence of MIB variables - required for particular notification type. Alternatively, - a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` - objects could be passed instead. In the latter case it is up to - the user to ensure proper Notification PDU contents. + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU includes some housekeeping items that + are required for SNMP to function. + + Agent information: + + * SNMPv2-MIB::sysUpTime.0 = <agent uptime> + * SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Applicable to SNMP v1 TRAP: + + * SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + * SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + * SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + .. note:: + + Unless user passes some of these variable-bindings, `.sendNotification()` + call will fill in the missing items. + + User variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE + + .. note:: + + The :py:class:`~pysnmp.smi.rfc1902.NotificationType` object ensures + properly formed SNMP notification (to comply MIB definition). If you + build notification PDU out of :py:class:`~pysnmp.smi.rfc1902.ObjectType` + objects or simple tuples of OID-value objects, it is your responsibility + to provide well-formed notificaton payload. Other Parameters ---------------- - cbFun : callable - user-supplied callable that is invoked to pass SNMP response - to *INFORM* notification or error to user at a later point of - time. The `cbFun` callable is never invoked for *TRAP* notifications. - cbCtx : object - user-supplied object passing additional parameters to/from - `cbFun`. - lookupMib : bool - `lookupMib` - load MIB and resolve response MIB variables at - the cost of slightly reduced performance. Default is `True`. + \*\*options: + + * lookupMib: bool + `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. + * cbFun: callable + user-supplied callable that is invoked to pass SNMP response + to *INFORM* notification or error to user at a later point of + time. The `cbFun` callable is never invoked for *TRAP* notifications. + * cbCtx: object + user-supplied object passing additional parameters to/from + `cbFun` Notes ----- @@ -126,7 +157,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, vbProcessor.unmakeVarBinds( - snmpEngine, varBinds, lookupMib + snmpEngine.cache, varBinds, lookupMib ), cbCtx ) @@ -136,6 +167,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, return ntforg.NotificationOriginator().sendVarBinds( snmpEngine, notifyName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), - __cbFun, (lookupMib, cbFun, cbCtx) + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, + (options.get('lookupMib', True), + options.get('cbFun'), options.get('cbCtx')) ) diff --git a/pysnmp/hlapi/v3arch/asyncore/sync/__init__.py b/pysnmp/hlapi/v3arch/asyncore/sync/__init__.py new file mode 100644 index 00000000..be6f9ec1 --- /dev/null +++ b/pysnmp/hlapi/v3arch/asyncore/sync/__init__.py @@ -0,0 +1,19 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.proto.rfc1902 import * +from pysnmp.smi.rfc1902 import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.asyncore.transport import * +from pysnmp.entity.engine import * + +try: + from pysnmp.hlapi.v3arch.asyncore.sync.cmdgen import * + from pysnmp.hlapi.v3arch.asyncore.sync.ntforg import * +except SyntaxError: + from pysnmp.hlapi.v3arch.asyncore.sync.compat.cmdgen import * + from pysnmp.hlapi.v3arch.asyncore.sync.compat.ntforg import * diff --git a/pysnmp/hlapi/asyncore/sync/cmdgen.py b/pysnmp/hlapi/v3arch/asyncore/sync/cmdgen.py index 3868ac66..00848e52 100644 --- a/pysnmp/hlapi/asyncore/sync/cmdgen.py +++ b/pysnmp/hlapi/v3arch/asyncore/sync/cmdgen.py @@ -5,7 +5,7 @@ # License: http://snmplabs.com/pysnmp/license.html # from sys import version_info -from pysnmp.hlapi.asyncore import cmdgen +from pysnmp.hlapi.v3arch.asyncore import cmdgen from pysnmp.hlapi.varbinds import * from pysnmp.proto.rfc1905 import endOfMibView from pysnmp.proto import errind @@ -346,7 +346,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, vbProcessor = CommandGeneratorVarBinds() - initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine.cache, varBinds)] totalRows = totalCalls = 0 @@ -408,7 +408,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, if initialVarBinds: varBinds = initialVarBinds - initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine.cache, varBinds)] if maxRows and totalRows >= maxRows: return @@ -546,7 +546,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, vbProcessor = CommandGeneratorVarBinds() - initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine.cache, varBinds)] nullVarBinds = [False] * len(initialVars) totalRows = totalCalls = 0 @@ -632,4 +632,3 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, varBinds = initialVarBinds initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] nullVarBinds = [False] * len(initialVars) - diff --git a/pysnmp/hlapi/asyncore/sync/compat/__init__.py b/pysnmp/hlapi/v3arch/asyncore/sync/compat/__init__.py index 8c3066b2..8c3066b2 100644 --- a/pysnmp/hlapi/asyncore/sync/compat/__init__.py +++ b/pysnmp/hlapi/v3arch/asyncore/sync/compat/__init__.py diff --git a/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py b/pysnmp/hlapi/v3arch/asyncore/sync/compat/cmdgen.py index 066d1d9f..ff760ee5 100644 --- a/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py +++ b/pysnmp/hlapi/v3arch/asyncore/sync/compat/cmdgen.py @@ -6,7 +6,7 @@ # # This is a Python 2.6- version of the same file at level up # -from pysnmp.hlapi.asyncore import cmdgen +from pysnmp.hlapi.v3arch.asyncore import cmdgen from pysnmp.hlapi.varbinds import * from pysnmp.proto.rfc1905 import endOfMibView from pysnmp.proto import errind @@ -100,7 +100,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, vbProcessor = CommandGeneratorVarBinds() - initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine.cache, varBinds)] totalRows = totalCalls = 0 @@ -172,7 +172,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, vbProcessor = CommandGeneratorVarBinds() - initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine.cache, varBinds)] nullVarBinds = [False] * len(initialVars) totalRows = totalCalls = 0 diff --git a/pysnmp/hlapi/asyncore/sync/compat/ntforg.py b/pysnmp/hlapi/v3arch/asyncore/sync/compat/ntforg.py index 80221713..173dbc95 100644 --- a/pysnmp/hlapi/asyncore/sync/compat/ntforg.py +++ b/pysnmp/hlapi/v3arch/asyncore/sync/compat/ntforg.py @@ -6,7 +6,7 @@ # # This is a Python 2.6- version of the same file at level up # -from pysnmp.hlapi.asyncore import ntforg +from pysnmp.hlapi.v3arch.asyncore import ntforg __all__ = ['sendNotification', 'next'] diff --git a/pysnmp/hlapi/asyncore/sync/ntforg.py b/pysnmp/hlapi/v3arch/asyncore/sync/ntforg.py index 6f732d75..8e80a740 100644 --- a/pysnmp/hlapi/asyncore/sync/ntforg.py +++ b/pysnmp/hlapi/v3arch/asyncore/sync/ntforg.py @@ -5,7 +5,7 @@ # License: http://snmplabs.com/pysnmp/license.html # from sys import version_info -from pysnmp.hlapi.asyncore import ntforg +from pysnmp.hlapi.v3arch.asyncore import ntforg __all__ = ['sendNotification'] @@ -18,12 +18,12 @@ if version_info[:2] < (2, 6): def sendNotification(snmpEngine, authData, transportTarget, contextData, - notifyType, varBinds, **options): + notifyType, *varBinds, **options): """Creates a generator to send one or more SNMP notifications. On each iteration, new SNMP TRAP or INFORM notification is send (:RFC:`1905#section-4,2,6`). The iterator blocks waiting for - INFORM acknowlegement to arrive or error to occur. + INFORM acknowledgement to arrive or error to occur. Parameters ---------- @@ -43,21 +43,49 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, Indicates type of notification to be sent. Recognized literal values are *trap* or *inform*. - varBinds: tuple - Single :py:class:`~pysnmp.smi.rfc1902.NotificationType` class instance - representing a minimum sequence of MIB variables required for - particular notification type. - Alternatively, a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` - objects could be passed instead. In the latter case it is up to - the user to ensure proper Notification PDU contents. + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU includes some housekeeping items that + are required for SNMP to function. + + Agent information: + + * SNMPv2-MIB::sysUpTime.0 = <agent uptime> + * SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Applicable to SNMP v1 TRAP: + + * SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + * SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + * SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + .. note:: + + Unless user passes some of these variable-bindings, `.sendNotification()` + call will fill in the missing items. + + User variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE + + .. note:: + + The :py:class:`~pysnmp.smi.rfc1902.NotificationType` object ensures + properly formed SNMP notification (to comply MIB definition). If you + build notification PDU out of :py:class:`~pysnmp.smi.rfc1902.ObjectType` + objects or simple tuples of OID-value objects, it is your responsibility + to provide well-formed notificaton payload. Other Parameters ---------------- - \*\*options : - Request options: - - * `lookupMib` - load MIB and resolve response MIB variables at - the cost of slightly reduced performance. Default is `True`. + \*\*options: + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `True`. Yields ------ @@ -79,7 +107,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, Notes ----- - The `sendNotification` generator will be exhausted immidiately unless + The `sendNotification` generator will be exhausted immediately unless an instance of :py:class:`~pysnmp.smi.rfc1902.NotificationType` class or a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` `varBinds` are send back into running generator (supported since Python 2.6). @@ -113,8 +141,8 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, while True: if varBinds: ntforg.sendNotification(snmpEngine, authData, transportTarget, - contextData, notifyType, varBinds, - cbFun, cbCtx, + contextData, notifyType, *varBinds, + cbFun=cbFun, cbCtx=cbCtx, lookupMib=options.get('lookupMib', True)) snmpEngine.transportDispatcher.runDispatcher() diff --git a/pysnmp/hlapi/asyncore/transport.py b/pysnmp/hlapi/v3arch/asyncore/transport.py index 2c103632..6019bd02 100644 --- a/pysnmp/hlapi/asyncore/transport.py +++ b/pysnmp/hlapi/v3arch/asyncore/transport.py @@ -6,9 +6,10 @@ # import socket import sys + +from pysnmp import error from pysnmp.carrier.asyncore.dgram import udp, udp6 from pysnmp.hlapi.transport import AbstractTransportTarget -from pysnmp import error __all__ = ['Udp6TransportTarget', 'UdpTransportTarget'] @@ -42,7 +43,7 @@ class UdpTransportTarget(AbstractTransportTarget): Examples -------- - >>> from pysnmp.hlapi.asyncore import UdpTransportTarget + >>> from pysnmp.hlapi.v3arch.asyncore import UdpTransportTarget >>> UdpTransportTarget(('demo.snmplabs.com', 161)) UdpTransportTarget(('195.218.195.228', 161), timeout=1, retries=5, tagList='') >>> @@ -92,7 +93,7 @@ class Udp6TransportTarget(AbstractTransportTarget): Examples -------- - >>> from pysnmp.hlapi.asyncore import Udp6TransportTarget + >>> from pysnmp.hlapi.v3arch.asyncore import Udp6TransportTarget >>> Udp6TransportTarget(('google.com', 161)) Udp6TransportTarget(('2a00:1450:4014:80a::100e', 161), timeout=1, retries=5, tagList='') >>> Udp6TransportTarget(('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210', 161)) diff --git a/pysnmp/hlapi/auth.py b/pysnmp/hlapi/v3arch/auth.py index e4650f4b..e4650f4b 100644 --- a/pysnmp/hlapi/auth.py +++ b/pysnmp/hlapi/v3arch/auth.py diff --git a/pysnmp/hlapi/context.py b/pysnmp/hlapi/v3arch/context.py index 754c43cc..754c43cc 100644 --- a/pysnmp/hlapi/context.py +++ b/pysnmp/hlapi/v3arch/context.py diff --git a/pysnmp/hlapi/lcd.py b/pysnmp/hlapi/v3arch/lcd.py index 94c10abf..3c11635e 100644 --- a/pysnmp/hlapi/lcd.py +++ b/pysnmp/hlapi/v3arch/lcd.py @@ -6,7 +6,7 @@ # from pysnmp.entity import config from pysnmp import nextid, error -from pysnmp.hlapi.auth import * +from pysnmp.hlapi.v3arch.auth import * __all__ = ['CommandGeneratorLcdConfigurator', 'NotificationOriginatorLcdConfigurator'] diff --git a/pysnmp/hlapi/v3arch/twisted/__init__.py b/pysnmp/hlapi/v3arch/twisted/__init__.py new file mode 100644 index 00000000..f141bf55 --- /dev/null +++ b/pysnmp/hlapi/v3arch/twisted/__init__.py @@ -0,0 +1,8 @@ +from pysnmp.proto.rfc1902 import * +from pysnmp.smi.rfc1902 import * +from pysnmp.entity.engine import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.twisted.transport import * +from pysnmp.hlapi.v3arch.twisted.cmdgen import * +from pysnmp.hlapi.v3arch.twisted.ntforg import * diff --git a/pysnmp/hlapi/twisted/cmdgen.py b/pysnmp/hlapi/v3arch/twisted/cmdgen.py index 94490527..745ce9d2 100644 --- a/pysnmp/hlapi/twisted/cmdgen.py +++ b/pysnmp/hlapi/v3arch/twisted/cmdgen.py @@ -5,13 +5,14 @@ # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.twisted.transport import * +from pysnmp.hlapi.v3arch.twisted.transport import * from pysnmp.entity.rfc3413 import cmdgen from pysnmp.proto import errind +from pysnmp.proto.api import v2c from twisted.internet.defer import Deferred from twisted.python.failure import Failure @@ -20,7 +21,8 @@ __all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd', 'isEndOfMib'] vbProcessor = CommandGeneratorVarBinds() lcd = CommandGeneratorLcdConfigurator() -isEndOfMib = lambda x: not cmdgen.getNextVarBinds(x)[1] +isEndOfMib = lambda varBinds: not v2c.apiPDU.getNextVarBinds(varBinds) + def getCmd(snmpEngine, authData, transportTarget, contextData, *varBinds, **options): @@ -118,7 +120,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, deferred.errback(Failure(errorIndication)) else: try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) + varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine.cache, varBinds, lookupMib) except Exception as e: deferred.errback(Failure(e)) @@ -133,7 +135,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.GetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), deferred) ) return deferred @@ -234,7 +236,7 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, deferred.errback(Failure(errorIndication)) else: try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) + varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine.cache, varBinds, lookupMib) except Exception as e: deferred.errback(Failure(e)) @@ -249,7 +251,7 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.SetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), deferred) ) return deferred @@ -361,7 +363,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, deferred.errback(Failure(errorIndication)) else: try: - varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine, + varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] @@ -379,7 +381,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.NextCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), __cbFun, + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), deferred) ) return deferred @@ -519,7 +521,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, deferred.errback(Failure(errorIndication)) else: try: - varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine, + varBindsUnmade = [vbProcessor.unmakeVarBinds(snmpEngine.cache, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] @@ -537,7 +539,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, cmdgen.BulkCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, nonRepeaters, maxRepetitions, - vbProcessor.makeVarBinds(snmpEngine, varBinds), + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), deferred) ) diff --git a/pysnmp/hlapi/twisted/ntforg.py b/pysnmp/hlapi/v3arch/twisted/ntforg.py index fbbdc0f1..0f57068d 100644 --- a/pysnmp/hlapi/twisted/ntforg.py +++ b/pysnmp/hlapi/v3arch/twisted/ntforg.py @@ -5,11 +5,11 @@ # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * -from pysnmp.hlapi.context import * -from pysnmp.hlapi.lcd import * +from pysnmp.hlapi.v3arch.auth import * +from pysnmp.hlapi.v3arch.context import * +from pysnmp.hlapi.v3arch.lcd import * from pysnmp.hlapi.varbinds import * -from pysnmp.hlapi.twisted.transport import * +from pysnmp.hlapi.v3arch.twisted.transport import * from pysnmp.entity.rfc3413 import ntforg from twisted.internet import reactor from twisted.internet.defer import Deferred @@ -21,7 +21,7 @@ vbProcessor = NotificationOriginatorVarBinds() lcd = NotificationOriginatorLcdConfigurator() def sendNotification(snmpEngine, authData, transportTarget, contextData, - notifyType, varBinds, **options): + notifyType, *varBinds, **options): """Sends SNMP notification. Based on passed parameters, prepares SNMP TRAP or INFORM message @@ -30,29 +30,59 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, Parameters ---------- - snmpEngine : :py:class:`~pysnmp.hlapi.SnmpEngine` + snmpEngine: :py:class:`~pysnmp.hlapi.SnmpEngine` Class instance representing SNMP engine. - authData : :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` + authData: :py:class:`~pysnmp.hlapi.CommunityData` or :py:class:`~pysnmp.hlapi.UsmUserData` Class instance representing SNMP credentials. transportTarget : :py:class:`~pysnmp.hlapi.twisted.UdpTransportTarget` or :py:class:`~pysnmp.hlapi.twisted.Udp6TransportTarget` Class instance representing transport type along with SNMP peer address. - contextData : :py:class:`~pysnmp.hlapi.ContextData` + contextData: :py:class:`~pysnmp.hlapi.ContextData` Class instance representing SNMP ContextEngineId and ContextName values. - notifyType : str + notifyType: str Indicates type of notification to be sent. Recognized literal values are *trap* or *inform*. - varBinds: tuple - Single :py:class:`~pysnmp.smi.rfc1902.NotificationType` class instance - representing a minimum sequence of MIB variables required for - particular notification type. - Alternatively, a sequence of :py:class:`~pysnmp.smi.rfc1902.ObjectType` - objects could be passed instead. In the latter case it is up to - the user to ensure proper Notification PDU contents. + \*varBinds: :class:`tuple` of OID-value pairs or :py:class:`~pysnmp.smi.rfc1902.ObjectType` or :py:class:`~pysnmp.smi.rfc1902.NotificationType` + One or more objects representing MIB variables to place + into SNMP notification. It could be tuples of OID-values + or :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances + of :py:class:`~pysnmp.smi.rfc1902.NotificationType` objects. + + SNMP Notification PDU includes some housekeeping items that + are required for SNMP to function. + + Agent information: + + * SNMPv2-MIB::sysUpTime.0 = <agent uptime> + * SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Applicable to SNMP v1 TRAP: + + * SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = <agent-IP> + * SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = <snmp-community-name> + * SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = <enterprise-OID> + + .. note:: + + Unless user passes some of these variable-bindings, `.sendNotification()` + call will fill in the missing items. + + User variable-bindings: + + * SNMPv2-SMI::NOTIFICATION-TYPE + * SNMPv2-SMI::OBJECT-TYPE + + .. note:: + + The :py:class:`~pysnmp.smi.rfc1902.NotificationType` object ensures + properly formed SNMP notification (to comply MIB definition). If you + build notification PDU out of :py:class:`~pysnmp.smi.rfc1902.ObjectType` + objects or simple tuples of OID-value objects, it is your responsibility + to provide well-formed notificaton payload. Other Parameters ---------------- @@ -126,7 +156,9 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, deferred.errback(Failure(errorIndication)) else: try: - varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) + varBindsUnmade = vbProcessor.unmakeVarBinds( + snmpEngine.cache, varBinds, lookupMib + ) except Exception as e: deferred.errback(Failure(e)) @@ -148,7 +180,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, notifyName, contextData.contextEngineId, contextData.contextName, - vbProcessor.makeVarBinds(snmpEngine, varBinds), + vbProcessor.makeVarBinds(snmpEngine.cache, varBinds), __cbFun, (options.get('lookupMib', True), deferred) ) diff --git a/pysnmp/hlapi/twisted/transport.py b/pysnmp/hlapi/v3arch/twisted/transport.py index aeea22da..0db45a70 100644 --- a/pysnmp/hlapi/twisted/transport.py +++ b/pysnmp/hlapi/v3arch/twisted/transport.py @@ -4,10 +4,12 @@ # Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> # License: http://snmplabs.com/pysnmp/license.html # -import socket, sys +import socket +import sys + from pysnmp.carrier.twisted.dgram import udp -from pysnmp.hlapi.transport import AbstractTransportTarget from pysnmp.error import PySnmpError +from pysnmp.hlapi.transport import AbstractTransportTarget __all__ = ['UdpTransportTarget'] diff --git a/pysnmp/hlapi/varbinds.py b/pysnmp/hlapi/varbinds.py index ac9151a8..6c0c2905 100644 --- a/pysnmp/hlapi/varbinds.py +++ b/pysnmp/hlapi/varbinds.py @@ -4,68 +4,86 @@ # Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com> # License: http://snmplabs.com/pysnmp/license.html # -from pysnmp.smi import view from pysnmp.smi.rfc1902 import * +from pysnmp.smi import builder +from pysnmp.smi import view __all__ = ['CommandGeneratorVarBinds', 'NotificationOriginatorVarBinds'] -class AbstractVarBinds(object): +class MibViewControllerManager(object): @staticmethod - def getMibViewController(snmpEngine): - mibViewController = snmpEngine.getUserContext('mibViewController') - if not mibViewController: - mibViewController = view.MibViewController( - snmpEngine.getMibBuilder() - ) - snmpEngine.setUserContext(mibViewController=mibViewController) + def getMibViewController(userCache): + try: + mibViewController = userCache['mibViewController'] + + except KeyError: + mibViewController = view.MibViewController(builder.MibBuilder()) + userCache['mibViewController'] = mibViewController + return mibViewController -class CommandGeneratorVarBinds(AbstractVarBinds): - def makeVarBinds(self, snmpEngine, varBinds): - mibViewController = self.getMibViewController(snmpEngine) - __varBinds = [] +class CommandGeneratorVarBinds(MibViewControllerManager): + def makeVarBinds(self, userCache, varBinds): + mibViewController = self.getMibViewController(userCache) + + resolvedVarBinds = [] + for varBind in varBinds: if isinstance(varBind, ObjectType): pass + elif isinstance(varBind[0], ObjectIdentity): varBind = ObjectType(*varBind) + elif isinstance(varBind[0][0], tuple): # legacy varBind = ObjectType(ObjectIdentity(varBind[0][0][0], varBind[0][0][1], *varBind[0][1:]), varBind[1]) + else: varBind = ObjectType(ObjectIdentity(varBind[0]), varBind[1]) - __varBinds.append(varBind.resolveWithMib(mibViewController)) + resolvedVarBinds.append(varBind.resolveWithMib(mibViewController)) - return __varBinds + return resolvedVarBinds - def unmakeVarBinds(self, snmpEngine, varBinds, lookupMib=True): + def unmakeVarBinds(self, userCache, varBinds, lookupMib=True): if lookupMib: - mibViewController = self.getMibViewController(snmpEngine) + mibViewController = self.getMibViewController(userCache) varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib(mibViewController) for x in varBinds] return varBinds -class NotificationOriginatorVarBinds(AbstractVarBinds): - def makeVarBinds(self, snmpEngine, varBinds): - mibViewController = self.getMibViewController(snmpEngine) +class NotificationOriginatorVarBinds(MibViewControllerManager): + def makeVarBinds(self, userCache, varBinds): + mibViewController = self.getMibViewController(userCache) + if isinstance(varBinds, NotificationType): - varBinds.resolveWithMib(mibViewController) - __varBinds = [] + return varBinds.resolveWithMib(mibViewController) + + resolvedVarBinds = [] + for varBind in varBinds: + if isinstance(varBind, NotificationType): + resolvedVarBinds.extend(varBind.resolveWithMib(mibViewController)) + continue + if isinstance(varBind, ObjectType): pass + elif isinstance(varBind[0], ObjectIdentity): varBind = ObjectType(*varBind) + else: varBind = ObjectType(ObjectIdentity(varBind[0]), varBind[1]) - __varBinds.append(varBind.resolveWithMib(mibViewController)) - return __varBinds - def unmakeVarBinds(self, snmpEngine, varBinds, lookupMib=False): + resolvedVarBinds.append(varBind.resolveWithMib(mibViewController)) + + return resolvedVarBinds + + def unmakeVarBinds(self, userCache, varBinds, lookupMib=False): if lookupMib: - mibViewController = self.getMibViewController(snmpEngine) + mibViewController = self.getMibViewController(userCache) varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib(mibViewController) for x in varBinds] return varBinds diff --git a/pysnmp/proto/api/v1.py b/pysnmp/proto/api/v1.py index 709a0b68..e6a138df 100644 --- a/pysnmp/proto/api/v1.py +++ b/pysnmp/proto/api/v1.py @@ -139,12 +139,20 @@ class PDUAPI(object): def getVarBindTable(self, reqPDU, rspPDU): if apiPDU.getErrorStatus(rspPDU) == 2: - varBindRow = [] - for varBind in apiPDU.getVarBinds(reqPDU): - varBindRow.append((varBind[0], null)) - return [varBindRow] + varBindRow = [(vb[0], null) for vb in apiPDU.getVarBinds(reqPDU)] else: - return [apiPDU.getVarBinds(rspPDU)] + varBindRow = apiPDU.getVarBinds(rspPDU) + return [varBindRow] + + def getNextVarBinds(self, varBinds, errorIndex=None): + errorIndication = None + + if errorIndex: + return errorIndication, [] + + rspVarBinds = [(vb[0], null) for vb in varBinds] + + return errorIndication, rspVarBinds apiPDU = PDUAPI() diff --git a/pysnmp/proto/api/v2c.py b/pysnmp/proto/api/v2c.py index eec78e79..cf381ced 100644 --- a/pysnmp/proto/api/v2c.py +++ b/pysnmp/proto/api/v2c.py @@ -60,6 +60,25 @@ class PDUAPI(v1.PDUAPI): def getVarBindTable(self, reqPDU, rspPDU): return [apiPDU.getVarBinds(rspPDU)] + def getNextVarBinds(self, varBinds, errorIndex=None): + errorIndication = None + + rspVarBinds = [] + + if errorIndex: + return errorIndication, rspVarBinds + + for idx, varBind in enumerate(varBinds): + if varBind[1].tagSet in ( + rfc1905.NoSuchObject.tagSet, + rfc1905.NoSuchInstance.tagSet, + rfc1905.EndOfMibView.tagSet): + continue + + rspVarBinds.append((varBind[0], null)) + + return errorIndication, rspVarBinds + def setEndOfMibError(self, pdu, errorIndex): varBindList = self.getVarBindList(pdu) varBindList[errorIndex - 1].setComponentByPosition( @@ -115,10 +134,15 @@ class BulkPDUAPI(PDUAPI): reqVarBinds = self.getVarBinds(reqPDU) N = min(int(nonRepeaters), len(reqVarBinds)) - R = max(len(reqVarBinds) - N, 0) rspVarBinds = self.getVarBinds(rspPDU) + # shortcut for the most trivial case + if N == 0 and len(reqVarBinds) == 1: + return [[vb] for vb in rspVarBinds] + + R = max(len(reqVarBinds) - N, 0) + varBindTable = [] if R: |