前言
在运维工作中我们通常借助SNMP协议,以get/walk oid到网络设备返回结果的方式,对网络设备进行被动监控。
但是这种监控方式有如下缺陷:
监控触发时机无法精确把握
可监控项比较有限
Trap是在网络设备上设置各种trap,一旦网络设备(交换机、防火墙、路由器、AC、AP)发生故障时立即触发已设置的trap时,网络设备主动把trap报文汇报到trap-server。(报警全面、及时)
最终目标是可以做1个类似 H3C的IMC(Intelligent management center)智能管理中心。
SNMP协议介绍
什么是SNMP协议?
Simple Network Management Protocol (SNMP是一种基于UDP协议的,简单的网络管理协议):
The core of SNMP is a simple set of operations that gives administrators the ability to get or change the sate of some SNMP-based devive.
管理员通过1组操作(
-
Get:读取网络设备的状态信息。
-
Set:远程配置设备参数。
-
Trap:被监控设备主动 发送SNMP trap报文。
) 去获取和改变基于SNMP设备的状态。
SMI和MIB是什么?
SMI:The Structure of Management Infomaton.管理信息结构
SMI defines how management information is grouped and named;allowed operations、permitted data type and synatax for specifying MIBs.
SMI是一种用于描述单个管理信息节点的数据结构:包含管理节点的oid名称、数据类型、语法。
VarBind: name=1.3.6.1.4.1.2011.5.25.207.1.2.1.1.4.34 =_BindValue: value=ObjectSyntax: simple=SimpleSyntax: string-value=VTY0
MIB(妹播):Management Information Base 管理信息库。
The Management Infromation Base(MIB) can be thought of a database of managed object that the agent tracks
如果我们采用倒置树结构,使用1个全球唯一的数字把每1个被管理节点的名称标识出来,并保证标识每个被管理节点的数字不重复,这样就组成了1个类似DNS的管理信息库,所以MIB就是1个被管理对象名称和oid关系 映射表。
A agent may implement many MIBs(NMS端)
but all agents implement a particular MIB called MIB-II.(从上图可知:MIb2的从1.3.6.1.2.1开头)
不管是华为交换机还是什么思科路由器只要它支持SNMP协议,厂家都都会实现了1个MIB库这就是:MIB2
不同的是每个网络设备厂商都会在1.3.6.1.2.1.N.后面申请1个Enterprise ID例如华为是2011,用来标识自己生产的设备中被管理对象的oid.
ps:以下是华为交换机的oid信息S2700 V100R006C05
from pysnmp.entity.rfc3413.oneliner import cmdgen cg=cmdgen.CommandGenerator() #,安全名称my-agent、社区名public、snmp协议版本,之间用逗号隔开。(snmp协议版本:0代表 snmpv1版本,1代表snmpV2c版本) ret=cg.getCmd(cmdgen.CommunityData('xxxxx','xxxx',1), cmdgen.UdpTransportTarget(('10.44.4.48',161)),'1.3.6.1.2.1.1.1.0') #ip 端口 OID,一个OID对应一种设备(比如网卡、磁盘等,在不同机器上同种设备的OID是一样的) #1.3.6.1.2.1.1.1.0#获取系统基本信息 #1.3.6.1.2.1.1.2.0#获取enterprises.2011.2.23.224 #1.3.6.1.2.1.1.3.0#系统运行时间 #1.3.6.1.2.1.1.4.0#系统联系人 #1.3.6.1.2.1.1.5.0#机器名称 #1.3.6.1.2.1.1.6.0#机器坐在位置 #1.3.6.1.4.1.2011.5.25.31.1.1.1.1.5.67108873 CPU使用率 #1.3.6.1.4.1.2011.5.25.31.1.1.1.1.7.67108873内存使用率 #1.3.6.1.4.1.2011.5.25.31.1.1.1.1.11.67108873机器温度 #(1,3,6,1,2,1,1,5,0) #(1,3,6,1,4,1,2011,6,3,4,1,2,0,0,0) 5秒钟 #(1,3,6,1,4,1,2011,6,3,4,1,3,0,0,0) 1分钟 #1.3.6.1.4.1.2011.6.3.4.1, #(1,3,6,1,4,1,2011,6,3,4,1,4,0,0,0) 5分钟 # print(ret[-1])
SNMP架构
1.NMS向Agent发送Get/Set请求
2.Agent接收请求,获取请求报文中携带的oid
3.从Agent端的MIB中解析oid,执行命令。
4.讲命令的结果返回NMS
SNMP的版本
V1:
The inital version of SNMP protocol
SNMPv1's security is based on communities,which are nothing more than password:plain-text strings that allow any SNMP-based application that knows the strings to gain access to a devive's management infomation.
SNMP协议的安全主要基于社区,社区就是一个明文字符串,只要监控端(NMS)和被监控端(Agent)之间都使用同了1个明文字符串,就说明他们属于同一社区,所以监控端(NMS)就可以获取、设置被监控设备(Agent)上的管理信息。
There are typically these communities in SNMPV1:read-only、read-write、trap
V2/V2C:
It is often referred to as community-string-based SNMPv2
The version of SNMP is technically called SNMPV2C
SNMPV2版本仍然使用基于社区字符串的方式进行传输数据,当扩展了SNMPv1版本的功能,SNMPv2也被称为SNMPV2C。
V3:
It adds support for strong authentication and private comminication between managed entities.
v3版本在SNMP在NMS(监控端)和Agent(被监控端)之间增加了认证和私有社区字符串。
ps:截止目前最流行的版本仍然是V1因为v1版本简单,还有就是网络设备更新迭代慢。
snmpd安装(Agent端)
在Linux上关于snmp的软件包有2个
net-snmp:snmp的Agent也就是(被监端)
net-snmp-utills:NetworkManagement System (NMS监控别人端)
想要支持trap功能:net-snmp+net-snmp-utills全部安装,因为只有安装了net-snmp包才能监听在UDP的162端口上接收trap报文。
安装(agent+NMS)
yum install net-snmp net-snmp-utils
启动snmpd(agent)
[root@zhanggen snmp]# systemctl restart snmpd [root@zhanggen snmp]# netstat -unlp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name udp 0 0 0.0.0.0:161 0.0.0.0:* 3506/snmpd
tcpdump抓包
[root@4 trap_server]# tcpdump udp port 162 and host 10.44.14.64
获取监控信息
[root@zhanggen snmp]# snmpwalk -v 2c -c public localhost SNMPv2-MIB::sysDescr.0 = STRING: Linux zhanggen 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (63840) 0:10:38.40 SNMPv2-MIB::sysContact.0 = STRING: Root <root@localhost> (configure /etc/snmp/snmp.local.conf) SNMPv2-MIB::sysName.0 = STRING: zhanggen SNMPv2-MIB::sysLocation.0 = STRING: Unknown (edit /etc/snmp/snmpd.conf) SNMPv2-MIB::sysORLastChange.0 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORID.1 = OID: SNMP-MPD-MIB::snmpMPDCompliance SNMPv2-MIB::sysORID.2 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance SNMPv2-MIB::sysORID.3 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance SNMPv2-MIB::sysORID.4 = OID: SNMPv2-MIB::snmpMIB SNMPv2-MIB::sysORID.5 = OID: TCP-MIB::tcpMIB SNMPv2-MIB::sysORID.6 = OID: IP-MIB::ip SNMPv2-MIB::sysORID.7 = OID: UDP-MIB::udpMIB SNMPv2-MIB::sysORID.8 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup SNMPv2-MIB::sysORID.9 = OID: SNMP-NOTIFICATION-MIB::snmpNotifyFullCompliance SNMPv2-MIB::sysORID.10 = OID: NOTIFICATION-LOG-MIB::notificationLogMIB SNMPv2-MIB::sysORDescr.1 = STRING: The MIB for Message Processing and Dispatching. SNMPv2-MIB::sysORDescr.2 = STRING: The management information definitions for the SNMP User-based Security Model. SNMPv2-MIB::sysORDescr.3 = STRING: The SNMP Management Architecture MIB. SNMPv2-MIB::sysORDescr.4 = STRING: The MIB module for SNMPv2 entities SNMPv2-MIB::sysORDescr.5 = STRING: The MIB module for managing TCP implementations SNMPv2-MIB::sysORDescr.6 = STRING: The MIB module for managing IP and ICMP implementations SNMPv2-MIB::sysORDescr.7 = STRING: The MIB module for managing UDP implementations SNMPv2-MIB::sysORDescr.8 = STRING: View-based Access Control Model for SNMP. SNMPv2-MIB::sysORDescr.9 = STRING: The MIB modules for managing SNMP Notification, plus filtering. SNMPv2-MIB::sysORDescr.10 = STRING: The MIB module for logging SNMP Notifications. SNMPv2-MIB::sysORUpTime.1 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.2 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.3 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.4 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.5 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.6 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.7 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.8 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.9 = Timeticks: (2) 0:00:00.02 SNMPv2-MIB::sysORUpTime.10 = Timeticks: (2) 0:00:00.02 HOST-RESOURCES-MIB::hrSystemUptime.0 = Timeticks: (121940) 0:20:19.40 HOST-RESOURCES-MIB::hrSystemUptime.0 = No more variables left in this MIB View (It is past the end of the MIB tree) [root@zhanggen snmp]#
获取主机名
[root@zhanggen long]# snmpget -v 2c -c public 127.0.0.1 .1.3.6.1.2.1.1.5.0 SNMPv2-MIB::sysName.0 = STRING: zhanggen
后台启动snmptrapd
[root@zhanggen snmp]# systemctl restart snmptrapd [root@zhanggen snmp]# netstat -unlp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name udp 0 0 0.0.0.0:161 0.0.0.0:* 3506/snmpd udp 0 0 0.0.0.0:162 0.0.0.0:* 4304/snmptrapd [root@zhanggen snmp]#
前台启动snmptrapd
[root@zhanggen bin]# snmptrapd -C -c/etc/snmp/snmptrapd.conf -df -Lo NET-SNMP version 5.7.2 Received 72 byte packet from UDP: [127.0.0.1]:56573->[127.0.0.1]:162 0000: 30 46 02 01 01 04 06 70 75 62 6C 69 63 A7 39 02 0F.....public.9. 0016: 04 29 A7 B1 EC 02 01 00 02 01 00 30 2B 30 18 06 .).........0+0.. 0032: 0A 2B 06 01 06 03 01 01 04 01 00 06 0A 2B 06 01 .+...........+.. 0048: 04 01 8F 65 81 7B 01 30 0F 06 08 2B 06 01 02 01 ...e.{.0...+.... 0064: 01 06 00 04 03 65 65 65 .....eee
2020-06-12 01:18:49 localhost [UDP: [127.0.0.1]:54044->[127.0.0.1]:162]:
SNMPv2-MIB::snmpTrapOID.0 = OID: SNMPv2-SMI::enterprises.2345 SNMPv2-MIB::sysLocation.0 = STRING: just here
-C : 表示不使用net-snmp默认路径下的配置文件snmptrapd.conf; -c : 指定snmptrapd.conf文件; -d : 显示收到和发送的数据报,通过这个选项可以看到数据报文; -f : 默认情况下,snmptrapd是在后台中运行的,加上这个选项,表示在前台运行; -L : 指定日志记录在哪里,后面的o表示直接输出到屏幕上,如果是跟着f表示日志记录到指定的文件中;
snmp utills使用(NMS端)
如果有snmpd工作在161端口,那我么就可以使用snmp utills发送各种SNMP操作了。
发送snmptrap报文
[root@zhanggen nginx]# snmptrap -v 2c -c public 10.44.14.146 "zhanggen" 1.3.6.1.4.1.2345567 SNMPv2-MIB::sysLocation.0 s "this is a trap pud please load oid-->1.3.6.1.4.1.2345 in SNMPv2-MIB::sysLocation.0" [root@zhanggen nginx]#
接收trap server
2020-06-12 18:23:51 localhost [UDP: [127.0.0.1]:55926->[127.0.0.1]:162]: SNMPv2-MIB::snmpTrapOID.0 = OID: SNMPv2-SMI::enterprises.2345 SNMPv2-MIB::sysLocation.0 = STRING: this is a trap pud please load oid-->1.3.6.1.4.1.2345 in SNMPv2-MIB::sysLocation.0
发送v1版本
[root@zhanggen zhanggen]# snmptrap -v1 -c public 172.31.240.253 1.3.6.1.4.1.1 10.10.12.219 2 3 1000 1.3.6.1.9.9.44.1.2.1 i 12 1.3.4.1.2.3.1 s test_snmptrap
参数说明:
-v 1|2c|3 specifies SNMP version to use(指定SNMP协议版本)
127.0.0.1:162 “zhanggen”:Trap server的IP、主机名,主机名称可以为空;
-c COMMUNITY set the community string(设置社区名称:agent和nms的之间通过社区建立信任)
.3.6.1.4.1.2345:企业OID,Enterprise-OID;
-m MIB[:...] load given list of MIBs (ALL loads everything)(Trap消息使用MIB库解析oid和object name, 指定mib库文件)
-M DIR[:...] look in given list of directories for MIBs(指定mib库所在目录)
SNMPv2-MIB::sysLocation.0 s “just here”:SNMPv2-MIB::sysLocation.0 触发SNMPv2-MIB库下sysLocation.0对象(oid对应的objectname)的报警、s:数据类型、数据值。
just her:说明
Trap PDU结构 ( SNMP v1 )
字段 |
类型 |
说明 |
PDU Type |
|
PDU类型,Trap为4 |
Enterprise |
Object Identifier |
产生该Trap的网络管理子系统,基于sysObjectID。如果是企业自定义Trap,此值为企业在enterprise子树下的注册子树。 |
Agent-addr |
NetworkAddress |
产生Trap的SNMP实体地址,一般为代理地址 |
Generic-trap |
Integer |
通用trap类型,取值为coldStart、warmStart、linkDown、linkUp、authenticationFailure、egpNeihborLoss、enterpriseSpecific |
Specific-trap |
Integer |
当Generic-trap为enterpriseSpecific时,specific-trap指明具体的企业自定义Trap类型,specific-trap跟在enterprise后面,组成了Trap的标识 |
Time-stamp |
TimeTicks |
上次初始化网络实体和产生Trap的时间间隔,包含sysUpTime值 |
Variable-bindings |
Sequences of |
和Trap相关的附加信息 |
PDU结构(SNMP v2c)
字段 |
类型 |
说明 |
PDU Type |
|
PDU类型 |
request-id |
Integer |
发送实体通过给每个PDU赋一个id使每一个到同一代理的请求能够被唯一识别,代理应答时原封不动返回request-id值,使发送方可以将应答和请求匹配,也可以使双方有能力处理UDP中可能产生的重复发送的消息 |
Error-status |
Integer |
用于表示在处理请求时出现的异常,对于trap数据包该字段为0。 |
Error-index |
Integer |
错误索引,对于trap数据包该字段为0。 |
variableBindings |
Sequences of |
所要求的实例列表 |
接收trap报文
python的pysnmp实现了对snmp协议的封装,我们既可以使用这个模块发送 trap/get/walk消息,还可以使用它作为trap-server接收trap报文。

#!/usr/bin/python3 #encoding=utf8 import datetime,requests,json from pysnmp.carrier.asynsock.dispatch import AsynsockDispatcher from pysnmp.carrier.asynsock.dgram import udp, udp6 from pyasn1.codec.ber import decoder from pysnmp.proto import api class TrapServer(object): def __init__(self,transfer_server): self.trap_server=transfer_server def run_trap_server(self): transportDispatcher = AsynsockDispatcher() #回调函数 transportDispatcher.registerRecvCbFun(self.recive_trapPDU) # UDP/IPv4 transportDispatcher.registerTransport( udp.domainName, udp.UdpSocketTransport().openServerMode(('0.0.0.0', 162)) ) # UDP/IPv6 transportDispatcher.registerTransport( udp6.domainName, udp6.Udp6SocketTransport().openServerMode(('::1', 162)) ) transportDispatcher.jobStarted(1) try: # Dispatcher will never finish as job#1 never reaches zero transportDispatcher.runDispatcher() except: transportDispatcher.closeDispatcher() raise def recive_trapPDU(self, transportDispatcher, transportDomain, transportAddress, wholeMsg): while wholeMsg: # 解析消息版本 msgVersion = int(api.decodeMessageVersion(wholeMsg)) print("消息SNMP版本:0:v1, v1:v2c", msgVersion, datetime.datetime.now()) ##根据snmp版本选择protoModules模块 if msgVersion in api.protoModules: pMod = api.protoModules[msgVersion] else: print('不支持的 SNMP 版本 %s' % msgVersion) return # 根据protoModules解码snmp的消息 reqMsg, wholeMsg = decoder.decode( wholeMsg, asn1Spec=pMod.Message(), ) # SNMPv2TrapPDU: reqPDU = pMod.apiMessage.getPDU(reqMsg) # 判断是否为Mod.TrapPDU if reqPDU.isSameTypeWith(pMod.TrapPDU()): trapPDU_metadata={} if msgVersion == api.protoVersion1: trapPDU_metadata["up_time"] = pMod.apiTrapPDU.getTimeStamp(reqPDU).prettyPrint() trapPDU_metadata["agent_address"] = pMod.apiTrapPDU.getAgentAddr(reqPDU).prettyPrint() trapPDU_metadata["trap_generic"] = pMod.apiTrapPDU.getGenericTrap(reqPDU).prettyPrint() trapPDU_metadata["trap_OID"] =pMod.apiTrapPDU.getEnterprise(reqPDU).prettyPrint()+'.'+pMod.apiTrapPDU.getSpecificTrap(reqPDU).prettyPrint() #self.analy_trapPDU(trapPDU_metadata) varBinds = pMod.apiTrapPDU.getVarBindList(reqPDU) else: varBinds = pMod.apiPDU.getVarBindList(reqPDU) bind_info = '' for bind in varBinds: for line in bind.values(): line=str(line) ''' === 1.3.6.1.4.1.2011.6.122.62.1.4.0 === ObjectSyntax: simple=SimpleSyntax: string=ssh ''' if line.startswith("1.3.6.1"): line="[%s]"%line bind_info+=line trapPDU_metadata["trap_details"]=bind_info self.trap_transfer(trapPDU_metadata) return def trap_transfer(self,data): data=json.dumps(data,ensure_ascii=False) headers = {'Content-Type':'application/json'} requests.post(url=self.trap_server, headers=headers, data=data) if __name__ == '__main__': trapserver=TrapServer("http://127.0.0.1:8001/IToperation/trap/receive/API/") trapserver.run_trap_server()
经典版trap-server无法获取snmp v2c版本报文的up_time、agent_address、trap_generic。
那就使用snmpv1版本把,可以当 AC发送报文过来时获取的agent_address是127.0.1.1。我中途研究了snmp 报文的编码规则 ASN1,试图通过ASN1进行解码。

from socket import * from pysnmp.proto import api from pyasn1.codec.der.decoder import decode server = socket(AF_INET, SOCK_DGRAM) server.bind(('10.44.13.73', 162)) while True: data1, addr = server.recvfrom(1024) msgVer = int(api.decodeMessageVersion(data1)) pMod = api.protoModules[msgVer] reqMsg, wholeMsg =decode(data1,asn1Spec=pMod.Message()) reqPDU = pMod.apiMessage.getPDU(reqMsg) varBinds = pMod.apiPDU.getVarBindList(reqPDU) print(addr) print('--------') print(varBinds) print('--------')
在研究如何对发送到udp:162的trap报文进行ASN解码的时候,我发现pysnmp模块的官网。
可以同时监听在多个port上,支持snmp v1和v2c版本trap/infro报文。

from pysnmp.entity import engine, config from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity.rfc3413 import ntfrcv # Create SNMP engine with autogenernated engineID and pre-bound # to socket transport dispatcher snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4, first listening interface/port config.addTransport( snmpEngine, udp.domainName + (1,), udp.UdpTransport().openServerMode(('127.0.0.1', 162)) ) # UDP over IPv4, second listening interface/port config.addTransport( snmpEngine, udp.domainName + (2,), udp.UdpTransport().openServerMode(('127.0.0.1', 2162)) ) # SNMPv1/2c setup # SecurityName <-> CommunityName mapping config.addV1System(snmpEngine, 'my-area', 'public') # Callback function for receiving notifications # noinspection PyUnusedLocal,PyUnusedLocal,PyUnusedLocal def cbFun(snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx): print('Notification from ContextEngineId "%s", ContextName "%s"' % (contextEngineId.prettyPrint(), contextName.prettyPrint())) for name, val in varBinds: print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, cbFun) 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: snmpEngine.transportDispatcher.closeDispatcher() raise
可以监听在ipv4和ipv6上,支持snmp v1和v2c版本trap/infro报文。

from pysnmp.entity import engine, config from pysnmp.carrier.asyncore.dgram import udp, udp6 from pysnmp.entity.rfc3413 import ntfrcv # Create SNMP engine with autogenernated engineID and pre-bound # to socket transport dispatcher snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4 config.addTransport( snmpEngine, udp.domainName, udp.UdpTransport().openServerMode(('127.0.0.1', 162)) ) # UDP over IPv6 config.addTransport( snmpEngine, udp6.domainName, udp6.Udp6Transport().openServerMode(('::1', 162)) ) # SNMPv1/2c setup # SecurityName <-> CommunityName mapping config.addV1System(snmpEngine, 'my-area', 'public') # Callback function for receiving notifications # noinspection PyUnusedLocal,PyUnusedLocal,PyUnusedLocal def cbFun(snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx): print('Notification from ContextEngineId "%s", ContextName "%s"' % (contextEngineId.prettyPrint(), contextName.prettyPrint())) for name, val in varBinds: print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, cbFun) 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: snmpEngine.transportDispatcher.closeDispatcher() raise
监听在ipv4 ip地址的162端口

from pysnmp.entity import engine, config from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity.rfc3413 import ntfrcv # Create SNMP engine with autogenernated engineID and pre-bound # to socket transport dispatcher snmpEngine = engine.SnmpEngine() # Transport setup # UDP over IPv4, first listening interface/port config.addTransport( snmpEngine, udp.domainName + (1,), udp.UdpTransport().openServerMode(('127.0.0.1', 162)) ) # SNMPv1/2c setup # SecurityName <-> CommunityName mapping config.addV1System(snmpEngine, 'my-area', 'public') # Callback function for receiving notifications # noinspection PyUnusedLocal,PyUnusedLocal def cbFun(snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx): # Get an execution context... execContext = snmpEngine.observer.getExecutionContext( 'rfc3412.receiveMessage:request' ) # ... and use inner SNMP engine data to figure out peer address print('Notification from %s, ContextEngineId "%s", ContextName "%s"' % ('@'.join([str(x) for x in execContext['transportAddress']]), contextEngineId.prettyPrint(), contextName.prettyPrint())) for name, val in varBinds: print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, cbFun) 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: snmpEngine.transportDispatcher.closeDispatcher() raise
1个完整trap报文
以下是pysnmp 接收到的一个完整的trap报文,由
- transportDomain
- transportAddress
- wholeMsg
- messageProcessingModel
- securityModel
- securityName
- securityLevel
- contextEngineId
- contextName
- pdu
构成。

transportDomain ----------- (1, 3, 6, 1, 6, 1, 1, 1) transportAddress ----------- ('10.8.193.10', 32774) wholeMsg ----------- b"0\x82\x01H\x02\x01\x00\x04\x08hbit@618\xa4\x82\x017\x06\n+\x06\x01\x04\x01\x83\x042\x00\x0b@\x04\x7f\x00\x01\x01\x02\x01\x06\x02\x01\x14C\x04rQ\xa6\xa40\x82\x01\x150#\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x01\x04\x13EAP_OPP_CACHED_KEYS0\x15\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x02\x04\x05DOT110\x11\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x03\x02\x01\x060\x81\x88\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x04\x04xOpportunistic Key Cache used for client 'F8-DA-0C-54-29-79' on wlan 'JD' radio 'XH-6F01-AP03-5708:R1'. Skipping 802.1x. 0\x16\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x05\x04\x06\x84$\x8d\x87W\x080!\x06\x0c+\x06\x01\x04\x01\x83\x042\x01\x02\x01\x06\x04\x11XH-6F01-AP03-5708" messageProcessingModel ----------- 0 securityModel ----------- 1 securityName ----------- my-area securityLevel ----------- 1 contextEngineId ----------- O¸4$¥H contextName ----------- pdu ----------- TrapPDU: enterprise=1.3.6.1.4.1.388.50.0.11 agent-addr=NetworkAddress: internet=127.0.1.1 generic-trap=enterpriseSpecific specific-trap=20 time-stamp=1917953700 variable-bindings=VarBindList: VarBind: name=1.3.6.1.4.1.388.50.1.2.1.1 value=ObjectSyntax: simple=SimpleSyntax: string=EAP_OPP_CACHED_KEYS VarBind: name=1.3.6.1.4.1.388.50.1.2.1.2 value=ObjectSyntax: simple=SimpleSyntax: string=DOT11 VarBind: name=1.3.6.1.4.1.388.50.1.2.1.3 value=ObjectSyntax: simple=SimpleSyntax: number=6 VarBind: name=1.3.6.1.4.1.388.50.1.2.1.4 value=ObjectSyntax: simple=SimpleSyntax: string=Opportunistic Key Cache used for client 'F8-DA-0C-54-29-79' on wlan 'JD' radio 'XH-6F01-AP03-5708:R1'. Skipping 802.1x. VarBind: name=1.3.6.1.4.1.388.50.1.2.1.5 value=ObjectSyntax: simple=SimpleSyntax: string=0x84248d875708 VarBind: name=1.3.6.1.4.1.388.50.1.2.1.6 value=ObjectSyntax: simple=SimpleSyntax: string=XH-6F01-AP03-5708
发送snmpget
查看1个具体的oid节点
[root@zhanggen long]# snmpget -v 2c -c 社区名称 10.44.4.48 1.3.6.1.4.1.2011.5.2.1.4.1.1.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108 SNMPv2-SMI::enterprises.2011.5.2.1.4.1.1.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108 = STRING: "360.local" [root@zhanggen long]# snmpget -v 2c -c 社区名称 10.44.4.48 1.3.6.1.4.1.2011.5.2.1.4.1.1.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108
发送snmpwalk
如果对某个叶子节点的OID值做walk遍历
[root@zhanggen]# snmpwalk -v 2c -c 社区名称 10.44.4.48 1.3.6.1.4.1.2011.5 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.1.4.97.117.116.104 = STRING: "auth" SNMPv2-SMI::enterprises.2011.5.2.1.1.1.1.6.114.97.100.105.117.115 = STRING: "radius" SNMPv2-SMI::enterprises.2011.5.2.1.1.1.1.7.100.101.102.97.117.108.116 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.1.1.2.4.97.117.116.104 = INTEGER: 5 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.2.6.114.97.100.105.117.115 = INTEGER: 3 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.2.7.100.101.102.97.117.108.116 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.3.4.97.117.116.104 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.3.6.114.97.100.105.117.115 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.1.1.3.7.100.101.102.97.117.108.116 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.2.1.1.7.100.101.102.97.117.108.116 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.2.1.2.7.100.101.102.97.117.108.116 = INTEGER: 2 SNMPv2-SMI::enterprises.2011.5.2.1.2.1.3.7.100.101.102.97.117.108.116 = INTEGER: 2 SNMPv2-SMI::enterprises.2011.5.2.1.2.1.4.7.100.101.102.97.117.108.116 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.2.1.5.7.100.101.102.97.117.108.116 = INTEGER: 0 SNMPv2-SMI::enterprises.2011.5.2.1.2.1.6.7.100.101.102.97.117.108.116 = INTEGER: 1 SNMPv2-SMI::enterprises.2011.5.2.1.4.1.1.7.100.101.102.97.117.108.116 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.1.13.100.101.102.97.117.108.116.95.97.100.109.105.110 = STRING: "sss" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.1.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108 = STRING: "ssss" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.2.7.100.101.102.97.117.108.116 = STRING: "ssss" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.2.13.100.101.102.97.117.108.116.95.97.100.109.105.110 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.2.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108 = STRING: "auth" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.3.7.100.101.102.97.117.108.116 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.3.13.100.101.102.97.117.108.116.95.97.100.109.105.110 = STRING: "default" SNMPv2-SMI::enterprises.2011.5.2.1.4.1.3.14.51.54.48.98.117.121.97.100.46.108.111.99.97.108 = STRING: "default"
项目部署

#!/usr/bin/python3 # -*- coding:utf-8-*- import pytz import logging import datetime import sys,os,django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trap_server.settings") django.setup() # 在Django视图之外,调用Django功能设置环境变量! from app01 import models import pymysql import datetime import requests import hashlib from pysnmp.entity import engine, config from pysnmp.carrier.asyncore.dgram import udp from pysnmp.entity.rfc3413 import ntfrcv import threading import json tz = pytz.timezone('Asia/Shanghai') #记录错误日志 def recordErrors(wrongLog): logger = logging.getLogger() log_file = './wrongIP.log' logger.setLevel(logging.INFO) file_handler = logging.FileHandler(log_file) file_handler.setLevel(logging.ERROR) log_formatter = logging.Formatter('%(asctime)s[%(levelname)s]: %(message)s') logger.addHandler(file_handler) logger.error(wrongLog) file_handler.setFormatter(log_formatter) #报警推送模块 class PushTarp(): def __init__(self): self.ddURL="http://wlyw.jd.com/api/notice/dd" self.mailURL='http://basics.jd.com/api/write/email_add' self.getMailsByERPsURL="http://hn.jdwl.com/v/api/Setting/getMailsByERPs" self.mailToken='a2ec38d38fbaae3b3fdc3f98a98c0d8b' self.headers = {'content-type': 'application/json'} self.key= "vXO9jJRI6E0J" self.appid=2 #获取陈洋发送咚咚接口的token def generate_token(self): today = datetime.datetime.now().strftime('%Y-%m-%d') m = hashlib.md5() m.update(self.key.encode("utf-8")) md5key = m.hexdigest() m = hashlib.md5() m.update((today + md5key).encode("utf-8")) token = m.hexdigest() return token #判断是否应该推送报警 def decide_alarm(self,**alarm_data): warehouse = alarm_data['warehouse'] current_policy = models.AlarmsPolicy.objects.filter(warehouse=warehouse).first() if current_policy and current_policy.alarm_trigger: trap_level = alarm_data['trap_level'] if trap_level == '严重': now = datetime.datetime.now() AnHourAgo = now - datetime.timedelta(hours=1) within_anhour = models.TrapInfo.objects.filter(trap_time__range=[AnHourAgo, now],trap_oid=alarm_data['trap_oid']) if within_anhour : return elif trap_level=='灾难': pass return current_policy #从森伟的接口把erp账号,转换成邮箱账号 def getMailsByERPs(self,*erpList): data = { 'erpList': json.dumps(list(erpList)) } response = requests.post(url=self.getMailsByERPsURL, params=data) return response.json() #把报警推送到报警负责人的咚咚和邮箱 def transfer_media(self,**alarm_data): if alarm_data.get('trap_level',) in ['严重', "灾难"]: sentence=self.decide_alarm(**alarm_data) if(sentence): current_policy=sentence receiver=current_policy.notifier.strip(',').split(',') try: self.send_email(*receiver,**alarm_data) self.send_dongdong(*receiver, **alarm_data) except Exception: print("%s库房:报警发送给 %s 失败,请检查接收者erp账号是否正确?"%(alarm_data['warehouse'],current_policy.notifier.strip(','))) return else: print("推送报警到咚咚和邮件成功") current_policy.notice_time = datetime.datetime.now() current_policy.save() def send_dongdong(self,*receiver,**alarm_data): msg = '''时间:{trap_time}\r\n预警来源:{brief_position}\r\n设备:{device_type}\r\nip:{agent_IPaddress}\r\n问题:{trap_name}\r\n预警类型:{trap_type}\r\n等级:{trap_level}'''.format(**alarm_data) data = {"token": self.generate_token(), "appid": self.appid, "erp":','.join(receiver) , "msg": msg} response = requests.post(url=self.ddURL, headers=self.headers, data=data) return response.json() def send_email(self,*receiver,**alarm_data): response_dict=self.getMailsByERPs(*receiver) receivers=','.join(list(response_dict['data'].values())) content='<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>{trap_name}</title></head><body><h1>{trap_details}</h1></body></html>'.format(**alarm_data) emial_title='{brief_position}/{agent_IPaddress}/{trap_name}'.format(**alarm_data) data={ "token":self.mailToken, 'user':'trap@jd.com', 'pwd':"HBit@2020.com", 'name':"预警系统", "title":emial_title, "content":content, "add": receivers, "bcc":"zhanggen24@jd.com",#每次发生邮件报警都会抄送 "add_erp":"zhanggen24" } response=requests.post(url=self.mailURL,data=data) return response.json() class SqlHelper(object): def __init__(self): # 读取配置文件 self.connect() def connect(self): self.conn = pymysql.connect(host='172.18.172.13', port=3358, user='lldp_rw', passwd='llDP!20190OO0~', db='net_detection', charset='utf8') # else: # self.conn = pymysql.connect(host='192.168.158.132', port=3358, user='salt', passwd='saltstack', # db='net_detection', charset='utf8') self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) def get_list(self,sql,args): self.cursor.execute(sql,args) result = self.cursor.fetchall() return result def get_one(self,sql,args): self.cursor.execute(sql,args) result = self.cursor.fetchone() return result def modify(self,sql,args): self.cursor.execute(sql,args) self.conn.commit() def multiple_modify(self,sql,args): # self.cursor.executemany('insert into bd(id,name)values(%s,%s)',[(1,'alex'),(2,'eric')]) self.cursor.executemany(sql,args) self.conn.commit() def create(self,sql,args): self.cursor.execute(sql,args) self.conn.commit() return self.cursor.lastrowid def close(self): self.cursor.close() self.conn.close() def ip2long(self,ip): ip_list = ip.split('.') result = 0 for i in range(4): # 0,1,2,3 result = result + int(ip_list[i]) * 256 ** (3 - i) return result def ip_info(self, ip): self.ipnumber = self.ip2long(ip) sql = "SELECT * FROM t_storage_ip_region WHERE %s>= start_ip AND %s<= end_ip" % (self.ipnumber, self.ipnumber) ret = self.get_one(sql, []) # print(ret) sql = "SELECT * FROM t_storage_netinfo WHERE id=%d;" % (ret["storage_id"]) ret = self.get_one(sql, []) sql = "SELECT * FROM t_region WHERE id=%d and is_delete=%d;" % (ret["region_id"], 0) ret = self.get_one(sql, []) warehouse = ret['region_name'] sql = "SELECT * FROM t_region WHERE id=%d and is_delete=%d;" % (ret["parent_id"], 0) ret = self.get_one(sql, []) city = ret['region_name'] sql = "SELECT * FROM t_region WHERE id=%d and is_delete=%d;" % (ret["parent_id"], 0) ret = self.get_one(sql, []) province = ret['region_name'] sql = "SELECT * FROM t_region WHERE id=%d and is_delete=%d;" % (ret["parent_id"], 0) ret = self.get_one(sql, []) region = ret['region_name'] return {"agent_IPaddress": ip, "warehouse": warehouse, "city": city, "province": province, "region": region, "agent_IPaddress_number": self.ipnumber,"brief_position":'%s-%s-%s-%s'%(region,province,city,warehouse)} pushDDobj=PushTarp() #入库报警信息缓冲区 class MessageBuffer(): def __init__(self): self.buffer=[] SQLdata_buffer=MessageBuffer() #报警写库、报警推送模块(线程异步) class AlarmNotificationThread(threading.Thread): def __init__(self,**trapdata): threading.Thread.__init__(self) self.trapdata=trapdata #设置缓冲区2分钟,push一次缓冲到数据库 def transferToDb(self): trap_info_obj=models.TrapInfo(**self.trapdata) SQLdata_buffer.buffer.append(trap_info_obj) # print(len(SQLdata_buffer.buffer)) if len(SQLdata_buffer.buffer)>=1: first_triger_time=SQLdata_buffer.buffer[0].trap_time second_delta=(datetime.datetime.now()-first_triger_time).seconds if second_delta>120: print("push缓冲区%s条数据到MySQL数据库。"%(len(SQLdata_buffer.buffer))) models.TrapInfo.objects.bulk_create(SQLdata_buffer.buffer) SQLdata_buffer.buffer=[] def transferToReciver(self): pushDDobj.transfer_media(**self.trapdata) def run(self): self.transferToDb() #记录数据库 self.transferToReciver()#发咚咚和邮件 #报警处理模块 class AlarmHandleThread(threading.Thread): def __init__(self,mysqlobj,snmpEngine,trap_message): threading.Thread.__init__(self) self.mysqlobj=mysqlobj self.snmpEngine=snmpEngine self.trap_message=trap_message self.execContext = self.snmpEngine.observer.getExecutionContext( 'rfc3412.receiveMessage:request' ) self.agent_address = [str(x) for x in self.execContext['transportAddress']][0] print("开局:--------------", self.agent_address) self.var_binds =trap_message def transformTrap(self): # 开始转换trap报文-----trap报警信息 trap_data = {} # 获取客户端的ip地址 trap_time = datetime.datetime.now() trap_data["trap_time"] = trap_time try: #获取发送trap的网络设备网络的IP信息 ip_info = self.mysqlobj.ip_info(self.agent_address) except Exception: #从李勇数据库中获取不到IP地址信息 print('错误的IP地址-------%s', self.agent_address) # 开启1个线程异步写日志到文件 # wrongipError = 'This is a error ipadress %s' % (self.agent_address) # recordErrorsTask = threading.Thread(target=recordErrors, args=(wrongipError,)) # recordErrorsTask.start() return trap_data.update(**ip_info) trap_pdu_info = '' for name, val in self.var_binds: # 通过trap报文中固定参数1.3.6.1.6.3.1.1.4.1.0,锁定trap-oid if name.prettyPrint() == "1.3.6.1.6.3.1.1.4.1.0": # 1.3:检查是否存在解析记录。 trap_data['trap_oid'] = val.prettyPrint() mib_entry = models.MibInfo.objects.filter(trap_oid=val.prettyPrint()).first() # 如果报警存在解析记录 if mib_entry: # 如果trap-oid存在黑名单 if mib_entry.ip_excluded: if mib_entry.ip_excluded == 'all': print("%s被黑名单遮蔽all" % (val.prettyPrint())) return print(val.prettyPrint()) excluded_list = mib_entry.ip_excluded.split(',') print(excluded_list) if self.agent_address in excluded_list: print("%s的------%s被黑名单策略" % (self.agent_address, val.prettyPrint())) return # 如果trap-oid存在白名单开始根据解析记录转换报警 trap_data["trap_name"] = mib_entry.trap_name trap_data["device_type"] = mib_entry.device_type trap_data["trap_type"] = mib_entry.trap_type trap_data["trap_level"] = mib_entry.trap_level trap_data['solution'] = mib_entry.solution # 1.6添加trap报文 each_line = "<p>参数名称:【%s】 参数值:%s</p>" % (name.prettyPrint(), val.prettyPrint()) trap_pdu_info += each_line trap_data["trap_details"] = '<div>%s</div>' % trap_pdu_info # 开启1个线程,异步推送报警到咚咚和邮件。 AlarmNotificationTask = AlarmNotificationThread(**trap_data) AlarmNotificationTask.start() def run(self): self.transformTrap() #报警接收模块udp class TrapServer(threading.Thread): def __init__(self, ip, port, security_name, community_name): threading.Thread.__init__(self) self.ip = ip self.port = port self.security_name = security_name self.community_name = community_name self.snmp_engine = engine.SnmpEngine() self.snmp_config = config self.mysqlobj = SqlHelper() self.mysqlobj.connect() # 初始化snmp-trap-server的配置 self.snmp_config.addTransport( self.snmp_engine, udp.domainName + (1,), udp.UdpTransport().openServerMode((self.ip, self.port)) ) self.snmp_config.addV1System(self.snmp_engine, self.security_name, self.community_name) # self.snmp_config.addV1System(self.snmp_engine, self.security_name, "111") #trap-server的回调函数 def callback_function(self, snmpEngine, stateReference, contextEngineId, contextName, varBinds, cbCtx): # Get an execution context...获取执行上下文,包含所有的请求信息。 #开启多线程处理trap报文 handle_Thread=AlarmHandleThread(mysqlobj=self.mysqlobj,snmpEngine=snmpEngine,trap_message=varBinds) handle_Thread.start() def run_server(self): ntfrcv.NotificationReceiver(self.snmp_engine,self.callback_function) # this job would never finish self.snmp_engine.transportDispatcher.jobStarted(10,20) # Run I/O dispatcher which would receive queries and send confirmations try: self.snmp_engine.transportDispatcher.runDispatcher() except: self.snmp_engine.transportDispatcher.closeDispatcher() print("trap-server错误") raise if __name__ == '__main__': trap_server = TrapServer("0.0.0.0", 162, "zhanggen", "public") #public trap_server.run_server()
python的GIL锁限制了python的多线程在同一时刻,只能有1个线程运行在1个CPU之上。
所以我们遇到计算密集型问题、和大数据量导致IO队列阻塞时。可以使用多进程。
Nginx也支持对UDP报文进行转发和负载均衡,这样我正好启动多进程监听在不同的UDP协议端口上,把CPU资源最大限度利用起来。

# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user root; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } stream { log_format main '$remote_addr - [$time_local] 处理主机: $upstream_addr'; access_log /var/log/nginx/access.log main; upstream trap-server { server 192.168.1.101:162; server 192.168.1.101:163; server 192.168.1.101:164; } server { listen 192.168.56.138:162 udp; proxy_responses 1; proxy_timeout 20s; proxy_pass trap-server; } }
鸟枪换炮之后
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2017-06-12 MySQL查询性能调优化