await pysnmp.hlapi.nextCmd or pysnmp.hlapi.setCmd stuck and never respond in async function (asyncio)

66 Views Asked by At

For maintanibility purpose and OS portability, I want to change from using netsnmp to pysnmap-lextudio in a python application. Furthermore, since asyncore death is soon, I needed to implement asyncio utilization. I've created a virtual class SnmpManager, and two daughter classes SnmpManagerAsync and SnmpManagerSync to provide synchronous and asynchronous walk and set functionnalities. Depending of a config.ini option, only one daughter class is instancied. Initially, I've created SnmpEngine, UsmUserData, UdpTransportData and ContextData objects in SnmpManager constructor to use them to implement redefined snmp set and walk daughter classes procedures.

But with this solution, when SnmpManagerAsync is instancied (using thus asyncio) the async walk and set procedures are stuck at await setCmd or await nextCmd and the application is blocked.

In order to make it work, I moved the creation of SnmpEngine and UdpTransportData in async procedures as shown below

class SnmpManager:

    privProtocol = {
        "DES": usmDESPrivProtocol,
        "3DES": usm3DESEDEPrivProtocol,
        "AES": usmAesCfb128Protocol,
        "AES128": usmAesCfb128Protocol,
        "AES192": usmAesCfb192Protocol,
        "AES192b": usmAesBlumenthalCfb192Protocol,
        "AES256": usmAesCfb256Protocol,
        "AES256b": usmAesBlumenthalCfb256Protocol,
    }
    authProtocol = {
        "MD5": usmHMACMD5AuthProtocol,
        "SHA": usmHMACSHAAuthProtocol,
        "SHA224": usmHMAC128SHA224AuthProtocol,
        "SHA256": usmHMAC192SHA256AuthProtocol,
        "SHA384": usmHMAC256SHA384AuthProtocol,
        "SHA512": usmHMAC384SHA512AuthProtocol
    }

    def __init__(self, config) -> None:
        self.host = config['SNMP']['host']
        self.port = config['SNMP']['port']
        self.user = config['SNMP']['SecName']
        self.authProto = config['SNMP']['AuthProto']
        self.authKey = config['SNMP']['AuthPass']
        self.privProto = config['SNMP']['PrivProto']
        self.privKey = config['SNMP']['PrivPass']
        self.usm = UsmUserData(userName=config['SNMP']['SecName'],
                          authKey=self.authKey,
                          privKey=self.privKey,
                          authProtocol=self.getAuthProtocol(),
                          privProtocol=self.getPrivProtocol())
        self.contextData = ContextData()

    def getAuthProtocol(self):
        if self.authProto in self.authProtocol.keys():
            auth_protocol = self.authProtocol[self.authProto]
        else:
            auth_protocol = usmNoAuthProtocol
        return auth_protocol

    def getPrivProtocol(self):
        if self.privProto in self.privProtocol.keys():
            priv_protocol = self.privProtocol[self.privProto]
        else:
            priv_protocol = usmNoPrivProtocol
        return priv_protocol
    ...

...

class SnmpManagerAsync(SnmpManager):
    def __init__(self, config) -> None:
        super().__init__(config)

    # snmp walk
    async def async_walk(self, str_oid):
        res = []
        # workaround ? :(: SnmpEngine and UdpTransportTarget must be created locally within asyncio context utilization
        snmp_engine = SnmpEngine()
        udp_transport_target = UdpTransportTarget((self.host, self.port))
        var_binds = [ObjectType(ObjectIdentity(str_oid))]
        while True:
            # print(str_oid)
            error_indication, error_status, error_index, var_binds_table = await nextCmd(
                snmp_engine,
                self.usm,
                udp_transport_target,
                self.contextData,
                *var_binds
            )
            if error_indication:
                print(error_indication)
                return
            elif error_status:
                print('%s at %s' % (error_status.prettyPrint(),
                                    error_index and var_binds_table[int(error_index) - 1][0] or '?'))
                return
            else:
                if isEndOfMib(var_binds_table[-1]):
                    break
                else:
                    for var_bind_row in var_binds_table:
                        for var_bind in var_bind_row:
                            oid = '.' + str(var_bind[0])
                            value = str(var_bind[1])
                            if oid.startswith(str_oid) is True:
                                var_bind_ = self.varBind(oid, '', value, None)
                                res.append(var_bind_)
                    var_binds = var_binds_table[-1]
        return res

    # snmp walk
    def walk(self, str_oid) -> list:
        return asyncio.run(self.async_walk(str_oid))
    ...
...

class SnmpManagerSync(SnmpManager):
    def __init__(self, config) -> None:
        super().__init__(config)
        self.snmpEngine = SnmpEngine()
        self.udpTransportTarget = UdpTransportTarget((self.host, self.port))

    # walk

    def walk(self, str_oid) -> list:
        res = []
        try:
            iterator = nextCmd(
                self.snmpEngine,
                self.usm,
                self.udpTransportTarget,
                self.contextData,
                ObjectType(ObjectIdentity(str_oid)),
                lexicographicMode=False
            )
        ...
  ...

Can someone explain to me why I need to move the creation of these two objects SnmpEngine and UdpTransportData in the async procedure body ?

0

There are 0 best solutions below