etingof / pysnmp

Python SNMP library
http://snmplabs.com/pysnmp/
BSD 2-Clause "Simplified" License
568 stars 188 forks source link

Python3.9 transportDispatcher cannot receive the alarm information sent from snmptrap V3 and does not report an error, but it can receive the alarm information from snmpinform #428

Open nishaoshan opened 1 year ago

nishaoshan commented 1 year ago

Python3.9 pysnmp4.4.12 transportDispatcher cannot receive the alarm information sent from snmptrap and does not report an error, but it can receive the alarm information from snmpinform

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import time
import sys
import logging

from pysnmp.debug import Debug, setLogger
from pysnmp.entity import engine, config
from pysnmp.carrier.asyncore.dgram import udp, udp6
from pysnmp.entity.rfc3413 import ntfrcv

from .trap_asset import get_device_id, add_asset
from .trap_config import load_config
from .trap_save import trap_insert
from common.call_ext import is_device_online,is_audit_product
from common.sg_alarm import ALARM_T_CPU, ALARM_T_MEM, ALARM_T_DISK, ALARM_T_CPU_TEMP, ALARM_T_FAN_SPEED, \
     ALARM_T_ABNORMAL_EV,ALARM_T_START_EV, ALARM_T_NAT_EV, ALARM_T_INF_EV, ALARM_T_LOG_ROLLBACK, \
     ALARM_T_CONNECTION_STATE, ALARM_ASSET_ALARM, ALARM_T_FALL_LOST, ALARM_T_CONCURRENCY

ALARM_CLEAR = 32

class TrapConfig(object):
    status = 1
    listen_port = 162
    support_v2 = 1
    community = 'public'
    support_v3 = 1
    user_name = ''
    auth_proto = 'MD5'
    auth_key = ''
    priv_proto = 'DES'
    priv_key = ''

g_trapConf = TrapConfig()
debug_log = logging.getLogger(__name__)

# Callback function for receiving notifications
def cbFun(snmpEngine, stateReference, contextEngineId, contextName,
          varBinds, cbCtx):
    log = cbCtx
    sqlDict = { "log_time":0,
                "log_module":'trap',
                "log_level":0,
                "log_type":0,
                "log_from":None,
                "log_flag":0,
                "cn_msg":None,
                "en_msg":None
                }

    # Get an execution context...
    execContext = snmpEngine.observer.getExecutionContext(
        'rfc3412.receiveMessage:request'
    )
    sqlDict["log_time"] = int(time.time())
    sqlDict["log_from"] = execContext['transportAddress'][0]
    ALARM_R = '解除'

    asset = None
    ###
    for oid, val in varBinds:
        val = str(val).encode('iso-8859-1').decode('utf-8')
        log.debug('oid:%r, val:%s', oid, val)
        if oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.4":
            if not is_audit_product() and not is_device_online(str(val)):
                log.warn('The device [%s][%s] is not online.', sqlDict["log_from"], str(val))
                return
            sqlDict["log_from"] = val
        # cpuPercentUsage
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.10.1.2":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_CPU
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_CPU + ALARM_CLEAR
        # memoryPercentUsage
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.10.3.2":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_MEM
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_MEM + ALARM_CLEAR
        # CF-diskPercentUsage/harddiskPercentUsage
        elif oid.prettyPrint() in ("1.3.6.1.4.1.32328.6.1.10.2.2", "1.3.6.1.4.1.32328.6.1.10.2.4"):
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_DISK
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_DISK + ALARM_CLEAR
        # cpuTemp
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.10.1.3.1.3":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_CPU_TEMP
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_CPU_TEMP + ALARM_CLEAR
        # FanSpeed
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.10.5.1.1.3":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_FAN_SPEED
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_FAN_SPEED + ALARM_CLEAR
        # attack Event
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.2":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_ABNORMAL_EV
        # startEvent
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.5":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_START_EV
        # natThresholdEvent
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.6":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_NAT_EV
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_NAT_EV + ALARM_CLEAR
        # interfaces
        elif oid.prettyPrint() in ("1.3.6.1.4.1.32328.6.2.3.1.1.7", "1.3.6.1.4.1.32328.6.2.3.1.1.8"):
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_INF_EV
            if ALARM_R in val:
                sqlDict['log_type'] = ALARM_T_INF_EV + ALARM_CLEAR

        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.7":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_CONNECTION_STATE

        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.8":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_LOG_ROLLBACK

        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.9":
            data = json.loads(str(val))
            if data and data["property_terminal"] and len(data["property_terminal"]) > 0:
                sqlDict["log_type"] = ALARM_ASSET_ALARM
                asset = data["property_terminal"][0]
                log.info("asset %s", asset)
                if asset["action"] == 'pending':
                    sqlDict["en_msg"] = "There is a new pending terminal to access network, please audit it at once."
                    sqlDict["cn_msg"] = "您有1个新待审批设备接入网络,请审核是否准入网络。"
                elif asset["action"] == 'abnormal':
                    sqlDict["en_msg"] = "There is a new abnormal terminal to access network, please handle it at once."
                    sqlDict["cn_msg"] = "您有1个新异常仿冒设备接入网络,请审核是否准入网络。"
                else:
                    log.error("unknown type asset: %s", asset)
        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.10":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_CONCURRENCY

        elif oid.prettyPrint() == "1.3.6.1.4.1.32328.6.1.11.11":
            sqlDict["en_msg"] = val
            sqlDict["cn_msg"] = val
            sqlDict["log_type"] = ALARM_T_FALL_LOST

    if sqlDict["log_type"] == ALARM_ASSET_ALARM:
        if sqlDict["log_from"] and asset:
            ne_id = get_device_id(str(sqlDict["log_from"]))
            add_asset(ne_id, asset)
        else:
            log.error("trap does not contain device sn: %s", varBinds)
            return

    if sqlDict["cn_msg"] is None or sqlDict["en_msg"] is None:
        return
    log.debug('ALARM:%s', sqlDict)
    trap_insert(sqlDict, logger=log)

# AuthProtocolConvert
def AuthProtocolConvert(proto):
    auth_proto = config.usmNoAuthProtocol

    if proto == "MD5":
        auth_proto = config.usmHMACMD5AuthProtocol
    elif proto == "SHA1":
        auth_proto = config.usmHMACSHAAuthProtocol

    return auth_proto

# PrivProtocolConvert
def PrivProtocolConvert(proto):
    priv_proto = config.usmNoPrivProtocol

    if proto == "DES":
        priv_proto = config.usmDESPrivProtocol
    elif proto == "AES-128":
        priv_proto = config.usmAesCfb128Protocol

    return priv_proto

# ################ main function ################
def main_loop(logger=None):
    if logger:
        global debug_log
        debug_log = logger
    # load trap config
    load_config(g_trapConf, debug_log)

    # local variables
    auth_proto = AuthProtocolConvert(g_trapConf.auth_proto)
    priv_proto = PrivProtocolConvert(g_trapConf.priv_proto)

    # global enable
    if not g_trapConf.status:
        debug_log.info('trap status disable, exit')
        return

    # debug
    # setLogger(Debug('msgproc', 'secmod', 'app', 'dsp', 'io'))

    # Create SNMP engine with auto-generated engineID and pre-bound
    # to socket transport dispatcher
    snmpEngine = engine.SnmpEngine()

    # Transport setup
    # UDP over IPv4
    domain_name = udp6.domainName
    transport = udp6.Udp6Transport().openServerMode(('::', g_trapConf.listen_port))
    config.addTransport(snmpEngine, domain_name, transport)

    # SNMPv2 setup
    if g_trapConf.support_v2:
        config.addV1System(snmpEngine, 'my-area', g_trapConf.community)

    # SNMPv3/USM setup
    if g_trapConf.support_v3:
        config.addV3User(
            snmpEngine, g_trapConf.user_name,
            auth_proto, g_trapConf.auth_key,
            priv_proto, g_trapConf.priv_key
        )

    # Register SNMP Application at the SNMP engine
    ntfrcv.NotificationReceiver(snmpEngine, cbFun, cbCtx=debug_log)

    snmpEngine.transportDispatcher.jobStarted(1)  # this job would never finish

    # Run I/O dispatcher which would receive queries and send confirmations
    try:
        snmpEngine.transportDispatcher.runDispatcher()
    except Exception as e:
        snmpEngine.transportDispatcher.closeDispatcher()
        debug_log.error(e)
nishaoshan commented 1 year ago

How can I receive the alarm information sent by snmptrap v3, not only snmpinform, because the sender uses snmptrap instead of snmpinform。But there is no such problem in python 2。In addition, there is no problem with v1 and v2 in Python 3.9。 If python3 wants to use snmptrap to receive messages, the trap receiver may need to add a securityEngineId. If you do not add a securityEngineId, you cannot obtain snmptrap. Only snmpinform can be obtained

lextm commented 1 year ago

SNMP v3 TRAPs work differently from INFORMs.

About that, Ilya explained enough in #324

nishaoshan commented 1 year ago

您好!您的邮件我已查收!谢谢!