From 74fcc27b038da72c6aa9e2c08b4dac63175b8b5f Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 22 Feb 2019 06:43:56 +0100 Subject: Introduce asyncio binding to hlapi.v1arch (#244) The hlapi.v1arch asyncio API is intended to be very similar to hlapi.v3arch.asyncio from its signature viewpoint, however it should be faster at the expense of no SNMPv3 support. --- pysnmp/hlapi/v1arch/asyncio/__init__.py | 13 + pysnmp/hlapi/v1arch/asyncio/cmdgen.py | 567 ++++++++++++++++++++++++++++++ pysnmp/hlapi/v1arch/asyncio/dispatch.py | 29 ++ pysnmp/hlapi/v1arch/asyncio/ntforg.py | 199 +++++++++++ pysnmp/hlapi/v1arch/asyncio/transport.py | 126 +++++++ pysnmp/hlapi/v1arch/asyncore/transport.py | 1 - pysnmp/hlapi/v3arch/asyncio/transport.py | 33 +- 7 files changed, 951 insertions(+), 17 deletions(-) create mode 100644 pysnmp/hlapi/v1arch/asyncio/__init__.py create mode 100644 pysnmp/hlapi/v1arch/asyncio/cmdgen.py create mode 100644 pysnmp/hlapi/v1arch/asyncio/dispatch.py create mode 100644 pysnmp/hlapi/v1arch/asyncio/ntforg.py create mode 100644 pysnmp/hlapi/v1arch/asyncio/transport.py (limited to 'pysnmp') diff --git a/pysnmp/hlapi/v1arch/asyncio/__init__.py b/pysnmp/hlapi/v1arch/asyncio/__init__.py new file mode 100644 index 00000000..efebeeb4 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncio/__init__.py @@ -0,0 +1,13 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2019, Ilya Etingof +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.v1arch.asyncio.transport import * +from pysnmp.hlapi.v1arch.asyncio.cmdgen import * +from pysnmp.hlapi.v1arch.asyncio.ntforg import * +from pysnmp.hlapi.v1arch.asyncio.dispatch import * +from pysnmp.proto.rfc1902 import * +from pysnmp.smi.rfc1902 import * diff --git a/pysnmp/hlapi/v1arch/asyncio/cmdgen.py b/pysnmp/hlapi/v1arch/asyncio/cmdgen.py new file mode 100644 index 00000000..346b7c76 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncio/cmdgen.py @@ -0,0 +1,567 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2019, Ilya Etingof +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.varbinds import * +from pysnmp.hlapi.v1arch.asyncio.transport import * +from pysnmp.smi.rfc1902 import * +from pysnmp.proto import api + +try: + import asyncio + +except ImportError: + import trollius as asyncio + +__all__ = ['getCmd', 'nextCmd', 'setCmd', 'bulkCmd', 'isEndOfMib'] + +VB_PROCESSOR = CommandGeneratorVarBinds() + +isEndOfMib = lambda varBinds: not api.v2c.apiPDU.getNextVarBinds(varBinds)[1] + + +@asyncio.coroutine +def getCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Creates a generator to perform SNMP GET query. + + When iterator gets advanced by :py:mod:`asyncio` main loop, + SNMP GET request is sent (:RFC:`1905#section-4.2.1`). + The iterator yields :py:class:`asyncio.Future` which gets done whenever + response arrives or error occurs. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asynio-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.asyncio.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncio.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. + + 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. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`, + unless :py:class:`~pysnmp.smi.rfc1902.ObjectType` is present + among `varBinds` in which case `lookupMib` gets automatically + enabled. + + 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 OID-value pairs in form of base SNMP types (if + `lookupMib` is `False`) or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + class instances (if `lookupMib` is `True`) representing MIB variables + returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> import asyncio + >>> from pysnmp.hlapi.v1arch.asyncio import * + >>> + >>> @asyncio.coroutine + ... def run(): + ... errorIndication, errorStatus, errorIndex, varBinds = yield from getCmd( + ... SnmpDispatcher(), + ... CommunityData('public'), + ... UdpTransportTarget(('demo.snmplabs.com', 161)), + ... ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)) + ... ) + ... print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> asyncio.get_event_loop().run_until_complete(run()) + (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(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if future.cancelled(): + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiPDU.getVarBinds(rspPdu) + + if lookupMib: + try: + varBinds = VB_PROCESSOR.unmakeVarBinds( + snmpDispatcher.cache, varBinds, lookupMib) + + except Exception as e: + future.set_exception(e) + return + + future.set_result( + (errorIndication, errorStatus, errorIndex, varBinds) + ) + + lookupMib = options.get('lookupMib') + + if not lookupMib and any(isinstance(x, ObjectType) for x in varBinds): + lookupMib = True + + if lookupMib: + varBinds = VB_PROCESSOR.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.PROTOCOL_MODULES[authData.mpModel] + + reqPdu = pMod.GetRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + future = asyncio.Future() + + snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + return future + + +@asyncio.coroutine +def setCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Creates a generator to perform SNMP SET query. + + When iterator gets advanced by :py:mod:`asyncio` main loop, + SNMP SET request is sent (:RFC:`1905#section-4.2.5`). + The iterator yields :py:class:`asyncio.Future` which gets done whenever + response arrives or error occurs. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asynio-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.asyncio.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncio.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. + + 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. + + Other Parameters + ---------------- + \*\*options : + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`, + unless :py:class:`~pysnmp.smi.rfc1902.ObjectType` is present + among `varBinds` in which case `lookupMib` gets automatically + enabled. + + 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 OID-value pairs in form of base SNMP types (if + `lookupMib` is `False`) or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + class instances (if `lookupMib` is `True`) representing MIB variables + returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> import asyncio + >>> from pysnmp.hlapi.v1arch.asyncio import * + >>> + >>> @asyncio.coroutine + ... def run(): + ... errorIndication, errorStatus, errorIndex, varBinds = yield from setCmd( + ... SnmpDispatcher(), + ... CommunityData('public'), + ... UdpTransportTarget(('demo.snmplabs.com', 161)), + ... ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0), 'Linux i386') + ... ) + ... print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> asyncio.get_event_loop().run_until_complete(run()) + (None, 0, 0, [ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.1.0')), DisplayString('Linux i386'))]) + >>> + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if future.cancelled(): + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiPDU.getVarBinds(rspPdu) + + if lookupMib: + try: + varBinds = VB_PROCESSOR.unmakeVarBinds( + snmpDispatcher.cache, varBinds, lookupMib) + + except Exception as e: + future.set_exception(e) + return + + future.set_result( + (errorIndication, errorStatus, errorIndex, varBinds) + ) + + lookupMib = options.get('lookupMib') + + if not lookupMib and any(isinstance(x, ObjectType) for x in varBinds): + lookupMib = True + + if lookupMib: + varBinds = VB_PROCESSOR.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.PROTOCOL_MODULES[authData.mpModel] + + reqPdu = pMod.SetRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + future = asyncio.Future() + + snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + return future + + +@asyncio.coroutine +def nextCmd(snmpDispatcher, authData, transportTarget, + *varBinds, **options): + """Creates a generator to perform SNMP GETNEXT query. + + When iterator gets advanced by :py:mod:`asyncio` main loop, + SNMP GETNEXT request is send (:RFC:`1905#section-4.2.2`). + The iterator yields :py:class:`asyncio.Future` which gets done whenever + response arrives or error occurs. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asynio-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.asyncio.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncio.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. + + 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. + + Other Parameters + ---------------- + \*\*options: + Request options: + + * `lookupMib` - load MIB and resolve response MIB variables at + the cost of slightly reduced performance. Default is `False`, + unless :py:class:`~pysnmp.smi.rfc1902.ObjectType` is present + among `varBinds` in which case `lookupMib` gets automatically + enabled. + + 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 sequences (e.g. 2-D array) of OID-value pairs in form + of base SNMP types (if `lookupMib` is `False`) or + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances (if + `lookupMib` is `True`) 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. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> import asyncio + >>> from pysnmp.hlapi.asyncio import * + >>> + >>> @asyncio.coroutine + ... def run(): + ... errorIndication, errorStatus, errorIndex, varBinds = yield from nextCmd( + ... SnmpDispatcher(), + ... CommunityData('public'), + ... UdpTransportTarget(('demo.snmplabs.com', 161)), + ... ContextData(), + ... ObjectType(ObjectIdentity('SNMPv2-MIB', 'system')) + ... ) + ... print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> asyncio.get_event_loop().run_until_complete(run()) + (None, 0, 0, [[ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'), DisplayString('Linux i386'))]]) + >>> + """ + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if future.cancelled(): + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBindTable = pMod.apiPDU.getVarBindTable(reqPdu, rspPdu) + + if lookupMib: + try: + varBindTable = [ + VB_PROCESSOR.unmakeVarBinds(snmpDispatcher.cache, + varBindTableRow, lookupMib) + for varBindTableRow in varBindTable + ] + + except Exception as e: + future.set_exception(e) + return + + future.set_result( + (errorIndication, errorStatus, errorIndex, varBindTable) + ) + + lookupMib = options.get('lookupMib') + + if not lookupMib and any(isinstance(x, ObjectType) for x in varBinds): + lookupMib = True + + if lookupMib: + varBinds = VB_PROCESSOR.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.PROTOCOL_MODULES[authData.mpModel] + + reqPdu = pMod.GetNextRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + future = asyncio.Future() + + snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + return future + + +@asyncio.coroutine +def bulkCmd(snmpDispatcher, authData, transportTarget, + nonRepeaters, maxRepetitions, *varBinds, **options): + """Creates a generator to perform SNMP GETBULK query. + + When iterator gets advanced by :py:mod:`asyncio` main loop, + SNMP GETBULK request is send (:RFC:`1905#section-4.2.3`). + The iterator yields :py:class:`asyncio.Future` which gets done whenever + response arrives or error occurs. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asynio-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.asyncio.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncio.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: :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`, + unless :py:class:`~pysnmp.smi.rfc1902.ObjectType` is present + among `varBinds` in which case `lookupMib` gets automatically + enabled. + + 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 sequence of sequences (e.g. 2-D array) of OID-value pairs in form + of base SNMP types (if `lookupMib` is `False`) or + :py:class:`~pysnmp.smi.rfc1902.ObjectType` class instances (if + `lookupMib` is `True`) 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. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> import asyncio + >>> from pysnmp.hlapi.v1arch.asyncio import * + >>> + >>> @asyncio.coroutine + ... def run(): + ... errorIndication, errorStatus, errorIndex, varBinds = yield from bulkCmd( + ... SnmpDispatcher(), + ... CommunityData('public'), + ... UdpTransportTarget(('demo.snmplabs.com', 161)), + ... 0, 2, + ... ObjectType(ObjectIdentity('SNMPv2-MIB', 'system')) + ... ) + ... print(errorIndication, errorStatus, errorIndex, varBinds) + >>> + >>> asyncio.get_event_loop().run_until_complete(run()) + (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'))], [ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.1.2.0')), ObjectIdentifier('1.3.6.1.4.1.424242.1.1'))]]) + >>> + """ + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if future.cancelled(): + return + + errorStatus = pMod.apiPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiPDU.getErrorIndex(rspPdu) + + varBindTable = pMod.apiBulkPDU.getVarBindTable(reqPdu, rspPdu) + + if lookupMib: + try: + varBindTable = [ + VB_PROCESSOR.unmakeVarBinds(snmpDispatcher.cache, + varBindTableRow, lookupMib) + for varBindTableRow in varBindTable + ] + + except Exception as e: + future.set_exception(e) + return + + future.set_result( + (errorIndication, errorStatus, errorIndex, varBindTable) + ) + + lookupMib = options.get('lookupMib') + + if not lookupMib and any(isinstance(x, ObjectType) for x in varBinds): + lookupMib = True + + if lookupMib: + varBinds = VB_PROCESSOR.makeVarBinds(snmpDispatcher.cache, varBinds) + + pMod = api.PROTOCOL_MODULES[authData.mpModel] + + reqPdu = pMod.GetBulkRequestPDU() + pMod.apiPDU.setDefaults(reqPdu) + pMod.apiBulkPDU.setNonRepeaters(reqPdu, nonRepeaters) + pMod.apiBulkPDU.setMaxRepetitions(reqPdu, maxRepetitions) + pMod.apiPDU.setVarBinds(reqPdu, varBinds) + + future = asyncio.Future() + + snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + return future diff --git a/pysnmp/hlapi/v1arch/asyncio/dispatch.py b/pysnmp/hlapi/v1arch/asyncio/dispatch.py new file mode 100644 index 00000000..d908beb9 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncio/dispatch.py @@ -0,0 +1,29 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2019, Ilya Etingof +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.carrier.asyncio.dispatch import AsyncioDispatcher +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. + """ + PROTO_DISPATCHER = AsyncioDispatcher diff --git a/pysnmp/hlapi/v1arch/asyncio/ntforg.py b/pysnmp/hlapi/v1arch/asyncio/ntforg.py new file mode 100644 index 00000000..25d831e9 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncio/ntforg.py @@ -0,0 +1,199 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2019, Ilya Etingof +# License: http://snmplabs.com/pysnmp/license.html +# +from pysnmp.hlapi.v1arch.auth import * +from pysnmp.hlapi.varbinds import * +from pysnmp.hlapi.v1arch.asyncio.transport import * +from pysnmp.smi.rfc1902 import * +from pysnmp.proto import api + +try: + import asyncio + +except ImportError: + import trollius as asyncio + +__all__ = ['sendNotification'] + +VB_PROCESSOR = NotificationOriginatorVarBinds() + + +@asyncio.coroutine +def sendNotification(snmpDispatcher, authData, transportTarget, + notifyType, *varBinds, **options): + """Creates a generator to send SNMP notification. + + When iterator gets advanced by :py:mod:`asyncio` main loop, + SNMP TRAP or INFORM notification is send (:RFC:`1905#section-4.2.6`). + The iterator yields :py:class:`asyncio.Future` which gets done whenever + response arrives or error occurs. + + Parameters + ---------- + snmpDispatcher: :py:class:`~pysnmp.hlapi.v1arch.asyncore.SnmpDispatcher` + Class instance representing asynio-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.asyncio.UdpTransportTarget` or + :py:class:`~pysnmp.hlapi.v1arch.asyncio.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 = + 1. SNMPv2-SMI::snmpTrapOID.0 = {SNMPv2-MIB::coldStart, ...} + + Optional variable-bindings (applicable to SNMP v1 TRAP): + + 2. SNMP-COMMUNITY-MIB::snmpTrapAddress.0 = + 3. SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 = + 4. SNMP-COMMUNITY-MIB::snmpTrapEnterprise.0 = + + 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`, + unless :py:class:`~pysnmp.smi.rfc1902.ObjectType` or + :py:class:`~pysnmp.smi.rfc1902.NotificationType` is present + among `varBinds` in which case `lookupMib` gets automatically + enabled. + + 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 OID-value pairs in form of base SNMP types (if + `lookupMib` is `False`) or :py:class:`~pysnmp.smi.rfc1902.ObjectType` + class instances (if `lookupMib` is `True`) representing MIB variables + returned in SNMP response. + + Raises + ------ + PySnmpError + Or its derivative indicating that an error occurred while + performing SNMP operation. + + Examples + -------- + >>> import asyncio + >>> from pysnmp.hlapi.asyncio import * + >>> + >>> @asyncio.coroutine + ... def run(): + ... errorIndication, errorStatus, errorIndex, varBinds = yield from sendNotification( + ... SnmpDispatcher(), + ... CommunityData('public'), + ... UdpTransportTarget(('demo.snmplabs.com', 162)), + ... 'trap', + ... NotificationType(ObjectIdentity('IF-MIB', 'linkDown'))) + ... print(errorIndication, errorStatus, errorIndex, varBinds) + ... + >>> asyncio.get_event_loop().run_until_complete(run()) + (None, 0, 0, []) + >>> + """ + + def _cbFun(snmpDispatcher, stateHandle, errorIndication, rspPdu, _cbCtx): + if future.cancelled(): + return + + errorStatus = pMod.apiTrapPDU.getErrorStatus(rspPdu) + errorIndex = pMod.apiTrapPDU.getErrorIndex(rspPdu) + + varBinds = pMod.apiTrapPDU.getVarBinds(rspPdu) + + try: + varBindsUnmade = VB_PROCESSOR.unmakeVarBinds(snmpDispatcher.cache, varBinds, + lookupMib) + except Exception as e: + future.set_exception(e) + + else: + future.set_result( + (errorIndication, errorStatus, errorIndex, varBindsUnmade) + ) + + lookupMib = options.get('lookupMib') + + if not lookupMib and any(isinstance(x, (NotificationType, ObjectType)) + for x in varBinds): + lookupMib = True + + if lookupMib: + varBinds = VB_PROCESSOR.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.PROTOCOL_MODULES[api.SNMP_VERSION_2C] + + 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) + + future = asyncio.Future() + + snmpDispatcher.sendPdu(authData, transportTarget, reqPdu, cbFun=_cbFun) + + if notifyType == 'trap': + def __trapFun(future): + if future.cancelled(): + return + future.set_result((None, 0, 0, [])) + + loop = asyncio.get_event_loop() + loop.call_soon(__trapFun, future) + + return future diff --git a/pysnmp/hlapi/v1arch/asyncio/transport.py b/pysnmp/hlapi/v1arch/asyncio/transport.py new file mode 100644 index 00000000..5d4895e5 --- /dev/null +++ b/pysnmp/hlapi/v1arch/asyncio/transport.py @@ -0,0 +1,126 @@ +# +# This file is part of pysnmp software. +# +# Copyright (c) 2005-2019, Ilya Etingof +# License: http://snmplabs.com/pysnmp/license.html +# +import socket +import sys + +from pysnmp.carrier.asyncio.dgram import udp +from pysnmp.carrier.asyncio.dgram import udp6 +from pysnmp.error import PySnmpError +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 + :py:class:`~pysnmp.hlapi.v1arch.asyncio.AsyncCommandGenerator` and + :py:class:`~pysnmp.hlapi.v1arch.asyncio.AsyncNotificationOriginator` + objects scheduled on I/O by :py:class:`~pysnmp.hlapi.SnmpDispatcher` + class instance. + + See :RFC:`1906#section-3` for more information on the UDP 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 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. + tagList: str + Arbitrary string that contains a list of tag values which are used + to select target addresses for a particular operation + (:RFC:`3413#section-4.1.4`). + + Examples + -------- + >>> from pysnmp.hlapi.v1arch.asyncore import UdpTransportTarget + >>> UdpTransportTarget(('demo.snmplabs.com', 161)) + UdpTransportTarget(('195.218.195.228', 161), timeout=1, retries=5) + >>> + """ + TRANSPORT_DOMAIN = udp.domainName + PROTO_TRANSPORT = udp.UdpAsyncioTransport + + 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 as exc: + raise PySnmpError('Bad IPv4/UDP transport address %s: %s' % ( + '@'.join([str(x) for x in transportAddr]), exc)) + + +class Udp6TransportTarget(AbstractTransportTarget): + """Represent UDP/IPv6 transport endpoint. + + This object can be used for passing UDP/IPv6 configuration + information to the + :py:class:`~pysnmp.hlapi.v1arch.asyncio.AsyncCommandGenerator` and + :py:class:`~pysnmp.hlapi.v1arch.asyncio.AsyncNotificationOriginator` + objects scheduled on I/O by :py:class:`~pysnmp.hlapi.SnmpDispatcher` + class instance. + + 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. + tagList: str + Arbitrary string that contains a list of tag values which are used + to select target addresses for a particular operation + (:RFC:`3413#section-4.1.4`). + + Examples + -------- + >>> from pysnmp.hlapi.asyncio 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)) + Udp6TransportTarget(('fedc:ba98:7654:3210:fedc:ba98:7654:3210', 161), timeout=1, retries=5, tagList='') + >>> Udp6TransportTarget(('1080:0:0:0:8:800:200C:417A', 161)) + Udp6TransportTarget(('1080::8:800:200c:417a', 161), timeout=1, retries=5, tagList='') + >>> Udp6TransportTarget(('::0', 161)) + Udp6TransportTarget(('::', 161), timeout=1, retries=5, tagList='') + >>> Udp6TransportTarget(('::', 161)) + Udp6TransportTarget(('::', 161), timeout=1, retries=5, tagList='') + >>> + """ + TRANSPORT_DOMAIN = udp6.domainName + PROTO_TRANSPORT = udp6.Udp6AsyncioTransport + + 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 as exc: + raise PySnmpError('Bad IPv6/UDP transport address %s: %s' % ( + '@'.join([str(x) for x in transportAddr]), exc)) diff --git a/pysnmp/hlapi/v1arch/asyncore/transport.py b/pysnmp/hlapi/v1arch/asyncore/transport.py index 507424fe..ad180337 100644 --- a/pysnmp/hlapi/v1arch/asyncore/transport.py +++ b/pysnmp/hlapi/v1arch/asyncore/transport.py @@ -42,7 +42,6 @@ class UdpTransportTarget(AbstractTransportTarget): >>> UdpTransportTarget(('demo.snmplabs.com', 161)) UdpTransportTarget(('195.218.195.228', 161), timeout=1, retries=5) >>> - """ TRANSPORT_DOMAIN = udp.DOMAIN_NAME PROTO_TRANSPORT = udp.UdpSocketTransport diff --git a/pysnmp/hlapi/v3arch/asyncio/transport.py b/pysnmp/hlapi/v3arch/asyncio/transport.py index 9079c1c8..0b795163 100644 --- a/pysnmp/hlapi/v3arch/asyncio/transport.py +++ b/pysnmp/hlapi/v3arch/asyncio/transport.py @@ -16,27 +16,30 @@ __all__ = ['Udp6TransportTarget', 'UdpTransportTarget'] class UdpTransportTarget(AbstractTransportTarget): - """Creates UDP/IPv4 configuration entry and initialize socket API if needed. + """Represent UDP/IPv6 transport endpoint. - This object can be used for adding new entries to Local Configuration - Datastore (LCD) managed by :py:class:`~pysnmp.hlapi.SnmpEngine` + This object can be used for passing UDP/IPv6 configuration + information to the + :py:class:`~pysnmp.hlapi.v3arch.asyncio.AsyncCommandGenerator` and + :py:class:`~pysnmp.hlapi.v3arch.asyncio.AsyncNotificationOriginator` + Datastore (LCD) managed by :py:class:`~pysnmp.hlapi.v3arch.SnmpEngine` class instance. See :RFC:`1906#section-3` for more information on the UDP transport mapping. Parameters ---------- - transportAddr : tuple + 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 IPv4 address in quad-dotted form, port is an integer. - timeout : int + timeout: int Response timeout in seconds. - retries : int + retries: int Maximum number of request retries, 0 retries means just a single request. - tagList : str + tagList: str Arbitrary string that contains a list of tag values which are used to select target addresses for a particular operation (:RFC:`3413#section-4.1.4`). @@ -47,7 +50,6 @@ class UdpTransportTarget(AbstractTransportTarget): >>> UdpTransportTarget(('demo.snmplabs.com', 161)) UdpTransportTarget(('195.218.195.228', 161), timeout=1, retries=5, tagList='') >>> - """ TRANSPORT_DOMAIN = udp.domainName PROTO_TRANSPORT = udp.UdpAsyncioTransport @@ -68,10 +70,10 @@ class Udp6TransportTarget(AbstractTransportTarget): """Creates UDP/IPv6 configuration entry and initialize socket API if needed. This object can be used by - :py:class:`~pysnmp.hlapi.asyncio.AsyncCommandGenerator` or - :py:class:`~pysnmp.hlapi.asyncio.AsyncNotificationOriginator` - and their derevatives for adding new entries to Local Configuration - Datastore (LCD) managed by :py:class:`~pysnmp.hlapi.SnmpEngine` + :py:class:`~pysnmp.hlapi.v3arch.asyncio.AsyncCommandGenerator` or + :py:class:`~pysnmp.hlapi.v3arch.asyncio.AsyncNotificationOriginator` + and their derivatives for adding new entries to Local Configuration + Datastore (LCD) managed by :py:class:`~pysnmp.hlapi.v3arch.SnmpEngine` class instance. See :RFC:`1906#section-3`, :RFC:`2851#section-4` for more information @@ -84,12 +86,12 @@ class Udp6TransportTarget(AbstractTransportTarget): 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 + timeout: int Response timeout in seconds. - retries : int + retries: int Maximum number of request retries, 0 retries means just a single request. - tagList : str + tagList: str Arbitrary string that contains a list of tag values which are used to select target addresses for a particular operation (:RFC:`3413#section-4.1.4`). @@ -108,7 +110,6 @@ class Udp6TransportTarget(AbstractTransportTarget): >>> Udp6TransportTarget(('::', 161)) Udp6TransportTarget(('::', 161), timeout=1, retries=5, tagList='') >>> - """ TRANSPORT_DOMAIN = udp6.domainName PROTO_TRANSPORT = udp6.Udp6AsyncioTransport -- cgit v1.2.1