diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6b21c29..7d33c8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,12 +17,12 @@ jobs: fail-fast: false matrix: python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" + - "3.14" steps: - uses: actions/checkout@v4 with: diff --git a/pyproject.toml b/pyproject.toml index 003d7ae..ef91209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,5 +2,48 @@ requires = ["setuptools >= 42.0.0", "setuptools_scm", "cffi >= 1.0.0"] build-backend = "setuptools.build_meta" +[project] +name = "snimpy" +description = "interactive SNMP tool" +requires-python = ">= 3.9" +dynamic = ["version"] +readme = "README.rst" +license = "ISC" +authors = [ + { name = "Vincent Bernat", email = "bernat@luffy.cx" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: System Administrators", + "Operating System :: POSIX", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: System :: Networking", + "Topic :: Utilities", + "Topic :: System :: Monitoring", +] +dependencies = [ + "cffi >= 1.0.0", + "pysnmp >= 7", + "pysnmpcrypto", + "setuptools", +] + +[project.scripts] +snimpy = "snimpy.main:interact" + +[project.urls] +Homepage = "https://github.com/vincentbernat/snimpy" + +[tool.setuptools] +packages = ["snimpy"] +zip-safe = false + [tool.setuptools_scm] write_to = "snimpy/_version.py" diff --git a/setup.py b/setup.py index 50474bc..969e323 100644 --- a/setup.py +++ b/setup.py @@ -1,48 +1,8 @@ import os -from setuptools import setup, find_packages +from setuptools import setup rtd = os.environ.get("READTHEDOCS", None) == "True" - -if __name__ == "__main__": - readme = open("README.rst").read() - history = open("HISTORY.rst").read().replace(".. :changelog:", "") - - setup( - name="snimpy", - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: ISC License (ISCL)", - "Operating System :: POSIX", - "Programming Language :: Python :: 3", - "Topic :: System :: Networking", - "Topic :: Utilities", - "Topic :: System :: Monitoring", - ], - url="https://github.com/vincentbernat/snimpy", - description="interactive SNMP tool", - long_description=readme + "\n\n" + history, - long_description_content_type="text/x-rst", - author="Vincent Bernat", - author_email="bernat@luffy.cx", - packages=["snimpy"], - entry_points={ - "console_scripts": [ - "snimpy = snimpy.main:interact", - ], - }, - data_files=[("share/man/man1", ["man/snimpy.1"])], - zip_safe=False, - cffi_modules=(not rtd and ["snimpy/smi_build.py:ffi"] or []), - install_requires=[ - "cffi >= 1.0.0", - "pysnmp >= 4, < 6", - "pyasn1 <= 0.6.0", - 'pyasyncore; python_version >= "3.12"', - "setuptools", - ], - setup_requires=["cffi >= 1.0.0", "setuptools_scm"], - use_scm_version=True, - ) +setup( + cffi_modules=(not rtd and ["snimpy/smi_build.py:ffi"] or []), +) diff --git a/snimpy/snmp.py b/snimpy/snmp.py index d04a4bc..99b97cf 100644 --- a/snimpy/snmp.py +++ b/snimpy/snmp.py @@ -30,8 +30,19 @@ import socket import inspect import threading +import asyncio import ipaddress -from pysnmp.entity.rfc3413.oneliner import cmdgen +from pysnmp.hlapi.v3arch.asyncio import ( + SnmpEngine, CommunityData, UsmUserData, + UdpTransportTarget, Udp6TransportTarget, ContextData, + ObjectType, ObjectIdentity, + get_cmd, set_cmd, walk_cmd, bulk_walk_cmd, + usmNoAuthProtocol, usmHMACMD5AuthProtocol, usmHMACSHAAuthProtocol, + usmHMAC128SHA224AuthProtocol, usmHMAC192SHA256AuthProtocol, + usmHMAC256SHA384AuthProtocol, usmHMAC384SHA512AuthProtocol, + usmNoPrivProtocol, usmDESPrivProtocol, usm3DESEDEPrivProtocol, + usmAesCfb128Protocol, usmAesCfb192Protocol, usmAesCfb256Protocol, +) from pysnmp.proto import rfc1902, rfc1905 from pysnmp.smi import error @@ -79,6 +90,12 @@ class Session: _tls = threading.local() + def _run(self, coro): + """Run an async coroutine synchronously using a thread-local loop.""" + if not hasattr(self._tls, "loop"): + self._tls.loop = asyncio.new_event_loop() + return self._tls.loop.run_until_complete(coro) + def __init__(self, host, community="public", version=2, secname=None, @@ -132,55 +149,55 @@ def __init__(self, host, self._version = version self._none = none if version == 3: - self._cmdgen = cmdgen.CommandGenerator() + self._engine = SnmpEngine() self._contextname = contextname else: - if not hasattr(self._tls, "cmdgen"): - self._tls.cmdgen = cmdgen.CommandGenerator() - self._cmdgen = self._tls.cmdgen + if not hasattr(self._tls, "engine"): + self._tls.engine = SnmpEngine() + self._engine = self._tls.engine self._contextname = None if version == 1 and none: raise ValueError("None-GET requests not compatible with SNMPv1") # Put authentication stuff in self._auth if version in [1, 2]: - self._auth = cmdgen.CommunityData( + self._auth = CommunityData( community[0:30], community, version - 1) elif version == 3: if secname is None: secname = community try: authprotocol = { - None: cmdgen.usmNoAuthProtocol, - "MD5": cmdgen.usmHMACMD5AuthProtocol, - "SHA": cmdgen.usmHMACSHAAuthProtocol, - "SHA1": cmdgen.usmHMACSHAAuthProtocol, - "SHA224": cmdgen.usmHMAC128SHA224AuthProtocol, - "SHA256": cmdgen.usmHMAC192SHA256AuthProtocol, - "SHA384": cmdgen.usmHMAC256SHA384AuthProtocol, - "SHA512": cmdgen.usmHMAC384SHA512AuthProtocol, + None: usmNoAuthProtocol, + "MD5": usmHMACMD5AuthProtocol, + "SHA": usmHMACSHAAuthProtocol, + "SHA1": usmHMACSHAAuthProtocol, + "SHA224": usmHMAC128SHA224AuthProtocol, + "SHA256": usmHMAC192SHA256AuthProtocol, + "SHA384": usmHMAC256SHA384AuthProtocol, + "SHA512": usmHMAC384SHA512AuthProtocol, }[authprotocol] except KeyError: raise ValueError("{} is not an acceptable authentication " "protocol".format(authprotocol)) try: privprotocol = { - None: cmdgen.usmNoPrivProtocol, - "DES": cmdgen.usmDESPrivProtocol, - "3DES": cmdgen.usm3DESEDEPrivProtocol, - "AES": cmdgen.usmAesCfb128Protocol, - "AES128": cmdgen.usmAesCfb128Protocol, - "AES192": cmdgen.usmAesCfb192Protocol, - "AES256": cmdgen.usmAesCfb256Protocol, + None: usmNoPrivProtocol, + "DES": usmDESPrivProtocol, + "3DES": usm3DESEDEPrivProtocol, + "AES": usmAesCfb128Protocol, + "AES128": usmAesCfb128Protocol, + "AES192": usmAesCfb192Protocol, + "AES256": usmAesCfb256Protocol, }[privprotocol] except KeyError: raise ValueError("{} is not an acceptable privacy " "protocol".format(privprotocol)) - self._auth = cmdgen.UsmUserData(secname, - authpassword, - privpassword, - authprotocol, - privprotocol) + self._auth = UsmUserData(secname, + authpassword, + privpassword, + authprotocol, + privprotocol) else: raise ValueError("unsupported SNMP version {}".format(version)) @@ -196,11 +213,11 @@ def __init__(self, host, else: port = 161 if mo.group("ipv6"): - self._transport = cmdgen.Udp6TransportTarget((mo.group("ipv6"), - port)) + self._transport = self._run( + Udp6TransportTarget.create((mo.group("ipv6"), port))) elif mo.group("ipv4"): - self._transport = cmdgen.UdpTransportTarget((mo.group("ipv4"), - port)) + self._transport = self._run( + UdpTransportTarget.create((mo.group("ipv4"), port))) else: results = socket.getaddrinfo(mo.group("any"), port, @@ -213,11 +230,18 @@ def __init__(self, host, # an IPv4 address, use that. If not, use IPv6. If we want # to add an option to force IPv6, it is a good place. if [x for x in results if x[0] == socket.AF_INET]: - self._transport = cmdgen.UdpTransportTarget((mo.group("any"), - port)) + self._transport = self._run( + UdpTransportTarget.create((mo.group("any"), port))) else: - self._transport = cmdgen.Udp6TransportTarget((mo.group("any"), - port)) + self._transport = self._run( + Udp6TransportTarget.create((mo.group("any"), port))) + + # Context data + if self._contextname: + self._contextdata = ContextData( + contextName=rfc1902.OctetString(self._contextname)) + else: + self._contextdata = ContextData() # Bulk stuff self.bulk = bulk @@ -231,6 +255,19 @@ def _check_exception(self, value): if isinstance(value, rfc1905.EndOfMibView): raise SNMPEndOfMibView("End of MIB was reached") # noqa: F821 + def _check_error(self, errorIndication, errorStatus): + """Check for SNMP protocol errors in response""" + if errorIndication: + self._check_exception(errorIndication) + raise SNMPException(str(errorIndication)) + if errorStatus: + exc = str(errorStatus.prettyPrint()) + exc = re.sub(r'\W+', '', exc) + exc = "SNMP{}".format(exc[0].upper() + exc[1:]) + if str(exc) in globals(): + raise globals()[exc] + raise SNMPException(errorStatus.prettyPrint()) + def _convert(self, value): """Convert a PySNMP value to some native Python type""" try: @@ -260,44 +297,52 @@ def _convert(self, value): self._check_exception(value) raise NotImplementedError("unable to convert {}".format(repr(value))) - def _op(self, cmd, *oids): - """Apply an SNMP operation""" - kwargs = {} - if self._contextname: - kwargs['contextName'] = rfc1902.OctetString(self._contextname) - errorIndication, errorStatus, errorIndex, varBinds = cmd( - self._auth, self._transport, *oids, **kwargs) - if errorIndication: - self._check_exception(errorIndication) - raise SNMPException(str(errorIndication)) - if errorStatus: - # We try to find a builtin exception with the same message - exc = str(errorStatus.prettyPrint()) - exc = re.sub(r'\W+', '', exc) - exc = "SNMP{}".format(exc[0].upper() + exc[1:]) - if str(exc) in globals(): - raise globals()[exc] - raise SNMPException(errorStatus.prettyPrint()) - if cmd in [self._cmdgen.getCmd, self._cmdgen.setCmd]: - results = [(tuple(name), val) for name, val in varBinds] - else: - results = [(tuple(name), val) - for row in varBinds for name, val in row] - if len(results) > 0 and isinstance(results[-1][1], - rfc1905.EndOfMibView): - results = results[:-1] - if len(results) == 0: - if cmd not in [self._cmdgen.nextCmd, self._cmdgen.bulkCmd]: - raise SNMPException("empty answer") - return tuple([(oid, self._convert(val)) for oid, val in results]) - def get(self, *oids): """Retrieve an OID value using GET. :param oids: a list of OID to retrieve. An OID is a tuple. :return: a list of tuples with the retrieved OID and the raw value. """ - return self._op(self._cmdgen.getCmd, *oids) + objecttypes = [ObjectType(ObjectIdentity(oid)) for oid in oids] + errorIndication, errorStatus, errorIndex, varBinds = self._run( + get_cmd(self._engine, self._auth, self._transport, + self._contextdata, *objecttypes, lookupMib=False)) + self._check_error(errorIndication, errorStatus) + results = [(tuple(name), self._convert(val)) + for name, val in varBinds] + if not results: + raise SNMPException("empty answer") + return tuple(results) + + async def _walk_async(self, *oids): + """Collect results from GETNEXT-based walk.""" + results = [] + for oid in oids: + walker = walk_cmd( + self._engine, self._auth, self._transport, + self._contextdata, + ObjectType(ObjectIdentity(oid)), lookupMib=False) + async for result in walker: + errorIndication, errorStatus, errorIndex, varBinds = result + self._check_error(errorIndication, errorStatus) + for name, val in varBinds: + results.append((tuple(name), val)) + return results + + async def _bulkwalk_async(self, bulk, *oids): + """Collect results from GETBULK-based walk.""" + results = [] + for oid in oids: + walker = bulk_walk_cmd( + self._engine, self._auth, self._transport, + self._contextdata, 0, bulk, + ObjectType(ObjectIdentity(oid)), lookupMib=False) + async for result in walker: + errorIndication, errorStatus, errorIndex, varBinds = result + self._check_error(errorIndication, errorStatus) + for name, val in varBinds: + results.append((tuple(name), val)) + return results def walkmore(self, *oids): """Retrieve OIDs values using GETBULK or GETNEXT. The method is called @@ -310,20 +355,21 @@ def walkmore(self, *oids): """ if self._version == 1 or not self.bulk: - return self._op(self._cmdgen.nextCmd, *oids) - args = [0, self.bulk] + list(oids) - try: - return self._op(self._cmdgen.bulkCmd, *args) - except SNMPTooBig: - # Let's try to ask for less values. We will never increase - # bulk again. We cannot increase it just after the walk - # because we may end up requesting everything twice (or - # more). - nbulk = self.bulk / 2 or False - if nbulk != self.bulk: - self.bulk = nbulk - return self.walk(*oids) - raise + results = self._run(self._walk_async(*oids)) + else: + try: + results = self._run(self._bulkwalk_async(self.bulk, *oids)) + except SNMPTooBig: + # Let's try to ask for less values. We will never increase + # bulk again. We cannot increase it just after the walk + # because we may end up requesting everything twice (or + # more). + nbulk = self.bulk / 2 or False + if nbulk != self.bulk: + self.bulk = nbulk + return self.walk(*oids) + raise + return tuple([(oid, self._convert(val)) for oid, val in results]) def walk(self, *oids): """Walk from given OIDs but don't return any "extra" results. Only @@ -350,8 +396,17 @@ def set(self, *args): """ if len(args) % 2 != 0: raise ValueError("expect an even number of arguments for SET") - varbinds = zip(*[args[0::2], [v.pack() for v in args[1::2]]]) - return self._op(self._cmdgen.setCmd, *varbinds) + objecttypes = [ObjectType(ObjectIdentity(oid), val.pack()) + for oid, val in zip(args[0::2], args[1::2])] + errorIndication, errorStatus, errorIndex, varBinds = self._run( + set_cmd(self._engine, self._auth, self._transport, + self._contextdata, *objecttypes, lookupMib=False)) + self._check_error(errorIndication, errorStatus) + results = [(tuple(name), self._convert(val)) + for name, val in varBinds] + if not results: + raise SNMPException("empty answer") + return tuple(results) def __repr__(self): return "{}(host={},version={})".format( diff --git a/tests/agent.py b/tests/agent.py index 518187a..58aac36 100644 --- a/tests/agent.py +++ b/tests/agent.py @@ -1,10 +1,37 @@ -from multiprocessing import Process, Queue +import asyncio +import multiprocessing as mp import random +import sys from pysnmp.entity import engine, config from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.carrier.asyncio.dgram import udp, udp6 from pysnmp.proto.api import v2c +from pysnmp.smi import error as smi_error + + +# Workaround for pysnmp v7 bug: SetCommandResponder.handle_management_operation +# calls release_state_information before re-raising errors, so process_pdu +# cannot send the error response (KeyError on stateReference). See +# https://github.com/lextudio/pysnmp/issues/230 +def _fixed_set_handle_management_operation(self, snmpEngine, stateReference, + contextName, PDU): + mgmtFun = self.snmpContext.get_mib_instrum(contextName).write_variables + varBinds = v2c.apiPDU.get_varbinds(PDU) + context = dict( + snmpEngine=snmpEngine, acFun=self.verify_access, cbCtx=self.cbCtx) + try: + rspVarBinds = mgmtFun(*varBinds, **context) + except (smi_error.NoSuchObjectError, smi_error.NoSuchInstanceError): + instrumError = smi_error.NotWritableError() + instrumError.update(sys.exc_info()[1]) + raise instrumError + self.send_varbinds(snmpEngine, stateReference, 0, 0, rspVarBinds) + self.release_state_information(stateReference) + + +cmdrsp.SetCommandResponder.handle_management_operation = ( + _fixed_set_handle_management_operation) class TestAgent: @@ -16,15 +43,16 @@ class TestAgent: def __init__(self, ipv6=False, community='public', authpass='authpass', privpass='privpass', emptyTable=True): - q = Queue() + ctx = mp.get_context('forkserver') + q = ctx.Queue() self.ipv6 = ipv6 self.emptyTable = emptyTable self.community = community self.authpass = authpass self.privpass = privpass self.next_port[0] += 1 - self._process = Process(target=self._setup, - args=(q, self.next_port[0])) + self._process = ctx.Process(target=self._setup, + args=(q, self.next_port[0])) self._process.start() self.port = q.get() @@ -37,29 +65,31 @@ def _setup(self, q, port): The port the agent is listening too will be returned using the provided queue. """ + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) snmpEngine = engine.SnmpEngine() if self.ipv6: - config.addSocketTransport( + config.add_transport( snmpEngine, - udp6.domainName, - udp6.Udp6Transport().openServerMode(('::1', port))) + udp6.DOMAIN_NAME, + udp6.Udp6Transport().open_server_mode(('::1', port))) else: - config.addSocketTransport( + config.add_transport( snmpEngine, - udp.domainName, - udp.UdpTransport().openServerMode(('127.0.0.1', port))) + udp.DOMAIN_NAME, + udp.UdpTransport().open_server_mode(('127.0.0.1', port))) # Community is public and MIB is writable - config.addV1System(snmpEngine, 'read-write', self.community) - config.addVacmUser(snmpEngine, 1, 'read-write', 'noAuthNoPriv', - (1, 3, 6), (1, 3, 6)) - config.addVacmUser(snmpEngine, 2, 'read-write', 'noAuthNoPriv', - (1, 3, 6), (1, 3, 6)) - config.addV3User( + config.add_v1_system(snmpEngine, 'read-write', self.community) + config.add_vacm_user(snmpEngine, 1, 'read-write', 'noAuthNoPriv', + (1, 3, 6), (1, 3, 6)) + config.add_vacm_user(snmpEngine, 2, 'read-write', 'noAuthNoPriv', + (1, 3, 6), (1, 3, 6)) + config.add_v3_user( snmpEngine, 'read-write', - config.usmHMACMD5AuthProtocol, self.authpass, - config.usmAesCfb128Protocol, self.privpass) - config.addVacmUser(snmpEngine, 3, 'read-write', 'authPriv', - (1, 3, 6), (1, 3, 6)) + config.USM_AUTH_HMAC96_MD5, self.authpass, + config.USM_PRIV_CFB128_AES, self.privpass) + config.add_vacm_user(snmpEngine, 3, 'read-write', 'authPriv', + (1, 3, 6), (1, 3, 6)) # Build MIB def stringToOid(string): @@ -75,9 +105,9 @@ def flatten(*args): result.append(el) return tuple(result) snmpContext = context.SnmpContext(snmpEngine) - mibBuilder = snmpContext.getMibInstrum().getMibBuilder() + mibBuilder = snmpContext.get_mib_instrum().get_mib_builder() (MibTable, MibTableRow, MibTableColumn, - MibScalar, MibScalarInstance) = mibBuilder.importSymbols( + MibScalar, MibScalarInstance) = mibBuilder.import_symbols( 'SNMPv2-SMI', 'MibTable', 'MibTableRow', 'MibTableColumn', 'MibScalar', 'MibScalarInstance') @@ -85,11 +115,11 @@ def flatten(*args): class RandomMibScalarInstance(MibScalarInstance): previous_value = 0 - def getValue(self, name, idx): + def getValue(self, name, idx, **kwargs): self.previous_value += random.randint(1, 2000) return self.getSyntax().clone(self.previous_value) - mibBuilder.exportSymbols( + mibBuilder.export_symbols( '__MY_SNMPv2_MIB', # SNMPv2-MIB::sysDescr MibScalar((1, 3, 6, 1, 2, 1, 1, 1), v2c.OctetString()), @@ -102,14 +132,14 @@ def getValue(self, name, idx): MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 2), (0,), v2c.ObjectIdentifier((1, 3, 6, 1, 4, 1, 9, 1, 1208)))) - mibBuilder.exportSymbols( + mibBuilder.export_symbols( '__MY_IF_MIB', # IF-MIB::ifNumber MibScalar((1, 3, 6, 1, 2, 1, 2, 1), v2c.Integer()), MibScalarInstance((1, 3, 6, 1, 2, 1, 2, 1), (0,), v2c.Integer(3)), # IF-MIB::ifTable MibTable((1, 3, 6, 1, 2, 1, 2, 2)), - MibTableRow((1, 3, 6, 1, 2, 1, 2, 2, 1)).setIndexNames( + MibTableRow((1, 3, 6, 1, 2, 1, 2, 2, 1)).set_index_names( (0, '__MY_IF_MIB', 'ifIndex')), # IF-MIB::ifIndex MibScalarInstance( @@ -145,7 +175,7 @@ def getValue(self, name, idx): # IF-MIB::ifRcvAddressTable MibTable((1, 3, 6, 1, 2, 1, 31, 1, 4)), - MibTableRow((1, 3, 6, 1, 2, 1, 31, 1, 4, 1)).setIndexNames( + MibTableRow((1, 3, 6, 1, 2, 1, 31, 1, 4, 1)).set_index_names( (0, '__MY_IF_MIB', 'ifIndex'), (1, '__MY_IF_MIB', 'ifRcvAddressAddress')), # IF-MIB::ifRcvAddressStatus @@ -183,78 +213,78 @@ def getValue(self, name, idx): '__MY_SNIMPY-MIB', # SNIMPY-MIB::snimpyIpAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 1), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 1), (0,), v2c.OctetString("AAAA")), # SNIMPY-MIB::snimpyString MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 2), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 2), (0,), v2c.OctetString("bye")), # SNIMPY-MIB::snimpyInteger MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 3), - v2c.Integer()).setMaxAccess("readwrite"), + v2c.Integer()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 3), (0,), v2c.Integer(19)), # SNIMPY-MIB::snimpyEnum MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 4), - v2c.Integer()).setMaxAccess("readwrite"), + v2c.Integer()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 4), (0,), v2c.Integer(2)), # SNIMPY-MIB::snimpyObjectId MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 5), - v2c.ObjectIdentifier()).setMaxAccess("readwrite"), + v2c.ObjectIdentifier()).set_max_access("read-write"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 5), ( 0,), v2c.ObjectIdentifier((1, 3, 6, 4454, 0, 0))), # SNIMPY-MIB::snimpyBoolean MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 6), - v2c.Integer()).setMaxAccess("readwrite"), + v2c.Integer()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 6), (0,), v2c.Integer(1)), # SNIMPY-MIB::snimpyCounter MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 7), - v2c.Counter32()).setMaxAccess("readwrite"), + v2c.Counter32()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 7), (0,), v2c.Counter32(47)), # SNIMPY-MIB::snimpyGauge MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 8), - v2c.Gauge32()).setMaxAccess("readwrite"), + v2c.Gauge32()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 8), (0,), v2c.Gauge32(18)), # SNIMPY-MIB::snimpyTimeticks MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 9), - v2c.TimeTicks()).setMaxAccess("readwrite"), + v2c.TimeTicks()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 9), (0,), v2c.TimeTicks(12111100)), # SNIMPY-MIB::snimpyCounter64 MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 10), - v2c.Counter64()).setMaxAccess("readwrite"), + v2c.Counter64()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 10), (0,), v2c.Counter64(2 ** 48 + 3)), # SNIMPY-MIB::snimpyBits MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 11), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), MibScalarInstance( (1, 3, 6, 1, 2, 1, 45121, 1, 11), (0,), v2c.OctetString(b"\xa0\x80")), # SNIMPY-MIB::snimpyMacAddress MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 15), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 15), ( 0,), v2c.OctetString(b"\x11\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyMacAddressInvalid MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 16), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 16), ( 0,), v2c.OctetString(b"\xf1\x12\x13\x14\x15\x16")), # SNIMPY-MIB::snimpyIndexTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 3)), MibTableRow( - (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1)).setIndexNames( + (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1)).set_index_names( (0, "__MY_SNIMPY-MIB", "snimpyIndexVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexOidVarLen"), (0, "__MY_SNIMPY-MIB", "snimpyIndexFixedLen"), @@ -301,7 +331,7 @@ def getValue(self, name, idx): # SNIMPY-MIB::snimpyReuseIndexTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 7)), MibTableRow( - (1, 3, 6, 1, 2, 1, 45121, 2, 7, 1)).setIndexNames( + (1, 3, 6, 1, 2, 1, 45121, 2, 7, 1)).set_index_names( (0, "__MY_SNIMPY-MIB", "snimpyIndexImplied"), (0, "__MY_SNIMPY-MIB", "snimpySimpleIndex")), # SNIMPY-MIB::snimpyReuseIndexValue @@ -317,7 +347,7 @@ def getValue(self, name, idx): # SNIMPY-MIB::snimpyInvalidTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 5)), MibTableRow( - (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1)).setIndexNames( + (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1)).set_index_names( (0, "__MY_SNIMPY-MIB", "snimpyInvalidIndex")), # SNIMPY-MIB::snimpyInvalidDescr MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), @@ -332,7 +362,7 @@ def getValue(self, name, idx): # SNIMPY-MIB::snimpyEmptyTable MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 6)), MibTableRow( - (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1)).setIndexNames( + (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1)).set_index_names( (0, "__MY_SNIMPY-MIB", "snimpyEmptyIndex"))) kwargs = dict( @@ -344,47 +374,47 @@ def getValue(self, name, idx): snimpyIndexIntIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 2), v2c.Integer( - )).setMaxAccess( + )).set_max_access( "noaccess"), snimpyIndexOidVarLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 3), v2c.ObjectIdentifier( - )).setMaxAccess( + )).set_max_access( "noaccess"), snimpyIndexFixedLen=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 4), v2c.OctetString( - ).setFixedLength( - 6)).setMaxAccess( + ).set_fixed_length( + 6)).set_max_access( "noaccess"), snimpyIndexImplied=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 5), v2c.OctetString( - )).setMaxAccess("noaccess"), + )).set_max_access("not-accessible"), snimpyIndexInt=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6), - v2c.Integer()).setMaxAccess("readwrite"), + v2c.Integer()).set_max_access("read-write"), snimpyInvalidIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 1), - v2c.Integer()).setMaxAccess("noaccess"), + v2c.Integer()).set_max_access("not-accessible"), snimpyInvalidDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 5, 1, 2), - v2c.OctetString()).setMaxAccess("readwrite"), + v2c.OctetString()).set_max_access("read-write"), snimpyReuseIndexValue=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 7, 1, 1), - v2c.Integer()).setMaxAccess("readwrite") + v2c.Integer()).set_max_access("read-write") ) if self.emptyTable: kwargs.update(dict( snimpyEmptyIndex=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 1), - v2c.Integer()).setMaxAccess("noaccess"), + v2c.Integer()).set_max_access("not-accessible"), snimpyEmptyDescr=MibTableColumn( (1, 3, 6, 1, 2, 1, 45121, 2, 6, 1, 2), - v2c.OctetString()).setMaxAccess("readwrite"))) + v2c.OctetString()).set_max_access("read-write"))) - mibBuilder.exportSymbols(*args, **kwargs) + mibBuilder.export_symbols(*args, **kwargs) # Start agent cmdrsp.GetCommandResponder(snmpEngine, snmpContext) @@ -392,5 +422,6 @@ def getValue(self, name, idx): cmdrsp.NextCommandResponder(snmpEngine, snmpContext) cmdrsp.BulkCommandResponder(snmpEngine, snmpContext) q.put(port) - snmpEngine.transportDispatcher.jobStarted(1) - snmpEngine.transportDispatcher.runDispatcher() + snmpEngine.transport_dispatcher.job_started(1) + snmpEngine.transport_dispatcher.run_dispatcher() + loop.close() diff --git a/tox.ini b/tox.ini index 80f35f6..a904e70 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,15 @@ [tox] -envlist = py{36,37,38,39,310,311,312,313}{,-ipython},lint,doc +envlist = py{39,310,311,312,313,314}{,-ipython},lint,doc skip_missing_interpreters = True [gh-actions] python = - 3.6: py36 - 3.7: py37 - 3.8: py38, lint, doc - 3.9: py39 + 3.9: py39, lint, doc 3.10: py310 3.11: py311 3.12: py312 3.13: py313 + 3.14: py314 [testenv] allowlist_externals = make @@ -28,7 +26,7 @@ deps = twine interrogate build -whitelist_externals = make +allowlist_externals = make commands = make lint python -m build @@ -40,7 +38,7 @@ changedir = docs deps = sphinx sphinx-rtd-theme -whitelist_externals = make +allowlist_externals = make commands = make html READTHEDOCS=True