0x00 SNMP TRAP簡介
SNMP(Simple Network Management Protocol) trap是一種很有用,但是也容易讓人難以理解的協議。
雖然名字叫做簡單網絡管理協議,但實際上並不是字面上的意思,尤其是看到.1.3.6.1.2.1.1.1.0這樣一串串詭異的數字時候,就會有點讓人崩潰。
不管怎么說,現在所有的網絡設備的都需要支持SNMP。而且現在還有很多的開源的網絡管理系統,所以就有利於我們來學習和理解SNMP。
SNMP trap是由被管理的設備主動的向管理服務器發送設備的異常情況,可以看成是管理服務器被動的去接收的過程。
所以會有很多的工具會把snmptrap集成到自己的工具中,對網絡設備進行監控。
把SNMP trap集成的到Nagios當中的,大體的工作流程是:
1. 由snmptrapd來接收網絡設備發出的trap
2. snmptrapd調用snmptt(snmp trap translator 翻譯器)
3. snmptt中定義了每一種trap的級別,以及什么樣的trap才有必要寫入到syslog中
4. SEC(simple event correlator是一個事件收集器)從syslog中讀取每一個事件,並把調用用戶的腳本snmptraphandling.py 來處理。
5. snmptraphandling.py 會調用根據主機名和trap的級別來調用相當的Nagios命令行。
這個過程包含了很多層,所以感覺很復雜,還是深入進去看看snmptrapd是怎么工作的。從而盡可能簡化snmptrap的使用方法。
snmptrapd來自開源軟件Net-SNMP. Net-SNMP會有很多的用途,當然使用trap也是一個很有用的用途。當snmptrapd接收到trap以后,可以調用用戶自定義的腳本或者命令行來處理trap。如果想使用這個功能就需要在snmptrapd的配置文件中設置traphandle。 snmptrapd接收到的trap信息格式是:
1. 數據包來源的主機名字。
2. 數據包來源的ip地址。
3. 數據包中的內容。
traphandle的工作就是讀取這些內容並進行處理就可以了。
Trap信息例子:
cisco2611.lon.altinity 192.168.10.20 RFC1213-MIB::sysUpTime.0 0:18:14:45.66 SNMPv2-MIB::snmpTrapOID.0 IF-MIB::linkDown RFC1213-MIB::ifIndex.2 2 RFC1213-MIB::ifDescr.2 "Serial0/0" RFC1213-MIB::ifType.2 ppp OLD-CISCO-INTERFACES-MIB::locIfReason.2 "administratively down" SNMP-COMMUNITY-MIB::snmpTrapAddress.0 192.168.10.20 SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 "public" SNMPv2-MIB::snmpTrapEnterprise.0 CISCO-SMI::ciscoProducts.186
因為snmptt對trap信息中的OID進行了翻譯,如果不進行翻譯的話,那么trap信息的樣子應該是
cisco2611.lon.altinity 192.168.10.20 .1.3.6.1.2.1.1.3.0 0:18:13:59.95 .1.3.6.1.6.3.1.1.4.1.0 .1.3.6.1.6.3.1.1.5.3 .1.3.6.1.2.1.2.2.1.1.2 2 .1.3.6.1.2.1.2.2.1.2.2 "Serial0/0" .1.3.6.1.2.1.2.2.1.3.2 ppp .1.3.6.1.4.1.9.2.2.1.1.20.2 "administratively down" .1.3.6.1.6.3.18.1.3.0 192.168.10.20 .1.3.6.1.6.3.18.1.4.0 "public" .1.3.6.1.6.3.1.1.4.3.0 .1.3.6.1.4.1.9.1.186
這就說明了,snmptt擁有自己的配置文件,文件中包含了OID和對應的屬性名,並且是使用OID來進行索引的。如果snmptt在配置文件中無法找到對應的OID的話,那么snmptt也就無法對信息進行翻譯,我們看到的信息內容也就是原始的格式了。
snmptt使用的配置文件叫做MIBs(Management Information Base管理信息基礎),在MIBs以OID為索引的key,可以快速查找到對應的文字形式和警告的級別。每一個MIB都有自己固定的定義格式,其中會包含一個宏(用來說明要顯示信息的內容)。
可是為什么要有MIBs的存在呢?直接由snmptrapd來完成OID到信息的翻譯不可以么?
1. MIBs是可以由用戶自己來定義,使用的。因為各廠商對trap信息定義的內容肯定是不同的,所以想要snmp有一定的擴展性,那么就必須要支持用戶自定義MIBs文件。這也就是MIBs存在的原因。
2. 如果由snmptrapd自己來完成翻譯也是可以的,因為如果每次都去通過檢索OID對應的消息的話,那對性能的影響是非常的大的,因此snmptrapd就需要讀入MIBs。問題也就出現了,如果更新MIBs話,就要把snmptrapd重新啟動,必定會影響到trap消息的接收。所以把snmptt作為一個獨立的deamon存在是很正確的選擇。
最后要做的事情就是添加traphandle了,handle會影響到snmptrap的接收性能,所以處理的速度要快,因為handle有可能1秒中會有數百次的調用。
這樣以來上面的處理流程就可以進行簡化的:
1. snmptrapd接收到trap的信息
2. snmptrapd調用handle(如果不是調用現有的命令的話,在handle中就可以完成所有的處理了,就沒有下面的處理了。又節省了一步,哈哈)
3. handle中再調用Nagios的命令行就可以了
這樣一看流程就簡單許多了,以后再使用snmptrap的時候也可以參考這個處理的流程
0x01 SNMP TRAP流程
SNMP Trap流程可參考這篇文章
0x02 SNMP Trap接受用Python實現
下載 安裝pysnmp-4.2.5.tar.gz (md5)
https://pypi.python.org/pypi/pysnmp/
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 from test_case_common import * def cbFun(transportDispatcher, transportDomain, transportAddress, wholeMsg): while wholeMsg: msgVer = int(api.decodeMessageVersion(wholeMsg)) if msgVer in api.protoModules: pMod = api.protoModules[msgVer] else: print('Unsupported SNMP version %s' % msgVer) return reqMsg, wholeMsg = decoder.decode( wholeMsg, asn1Spec=pMod.Message(), ) print('Notification message from %s:%s: ' % ( transportDomain, transportAddress ) ) reqPDU = pMod.apiMessage.getPDU(reqMsg) if reqPDU.isSameTypeWith(pMod.TrapPDU()): if msgVer == api.protoVersion1: print('Enterprise: %s' % ( pMod.apiTrapPDU.getEnterprise(reqPDU).prettyPrint() ) ) print('Agent Address: %s' % ( pMod.apiTrapPDU.getAgentAddr(reqPDU).prettyPrint() ) ) print('Generic Trap: %s' % ( pMod.apiTrapPDU.getGenericTrap(reqPDU).prettyPrint() ) ) print('Specific Trap: %s' % ( pMod.apiTrapPDU.getSpecificTrap(reqPDU).prettyPrint() ) ) print('Uptime: %s' % ( pMod.apiTrapPDU.getTimeStamp(reqPDU).prettyPrint() ) ) varBinds = pMod.apiTrapPDU.getVarBindList(reqPDU) else: varBinds = pMod.apiPDU.getVarBindList(reqPDU) print('Var-binds:') for oid, val in varBinds: a = oid.prettyPrint().strip() b = val.prettyPrint().strip().split('\n') print a for line in b: item = line.strip() if item.startswith('string-value'): print 'string-value='+item.replace('string-value=0x','').decode('hex') else: print item return wholeMsg if __name__ == '__main__': transportDispatcher = AsynsockDispatcher() transportDispatcher.registerRecvCbFun(cbFun) # 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