安裝
安裝pysnmp
pip install pysnmp -i https://pypi.douban.com
安裝pysnmp-mibs
pip install pysnmp-mibs
常用操作
在本教程中,我們將逐步創建和運行幾個不同的SNMP命令請求和通知。我們將使用PySNMP同步高級API hlapi,這是最簡單的使用。
創建SNMP Engine
SNMP引擎是PySNMP中的一箇中心傘形對象。所有的PySNMP操作都涉及 SnmpEngine
類實例。PySNMP應用程序可以運行多個獨立的SNMP引擎,每個引擎由它自己的SnmpEngine
對象引導。
>>> from pysnmp.hlapi import *
>>>
>>> SnmpEngine()
SnmpEngine(snmpEngineId=OctetString(hexValue='80004fb80567'))
SNMP引擎具有可自動或者手動分配的唯一標識符。此標識符用於SNMP協議操作。
查詢SNMP
我們發送SNMP GET命令去SNMP agent讀取一個MIB對象。爲此,我們將調用同步的高級的 getCmd()
方法。通過調用相應的函數,可以以不同的類似方式使用其他SNMP命令。
>>> from pysnmp.hlapi import *
>>> [ x for x in dir() if 'Cmd' in x ]
['bulkCmd', 'getCmd', 'nextCmd', 'setCmd']
>>> getCmd
<function getCmd at 0x222b330>
選擇SNMP協議和憑證
我們可以選擇三種SNMP協議版本。爲了使用SNMP的版本1或者2c,我們需要傳遞正確初始化的CommunityData
類的實例。對於第三個版本,我們需要傳遞 UsmUserData
類的實例
SNMP共同體名以及SNMP v1和v2c之間的選擇通過 CommunityData
對象傳遞給SNMP LCD。
>>> from pysnmp.hlapi import *
>>>
>>> CommunityData('public', mpModel=0) # SNMPv1
CommunityData('public')
>>> CommunityData('public', mpModdel=1) # SNMPv2
CommunityData('public')
使用 UsmUserData
對象進行LCD配置意味着使用SNMPv3。除了設置USM用戶名之外,UsmUserData
對象還可以將密碼密鑰和密碼協議傳輸到SNMP配置LCD。
>>> from pysnmp.hlapi import *
>>>
>>> UsmUserData('testuser', authKey='myauthkey')
UsmUserData(userName='testuser', authKey=<AUTHKEY>)
>>> UsmUserDdata('testuser', authKey='myauthkey', privKey='myencky')
UsmUserData(userName='testuser', authKey=<AUTHKEY>, privKey=<PRIVKEY>)
PySNMP支持MD5和SHA消息授權算法,DES,AES128/192/256 和 3DES加密算法。
爲了簡單起見,我們使用SNMPv2。儘管不太安全,但它仍然是最流行的版本。
>>> from pysnmp.hlapi import *
>>>
>>> g = getCmd(SnmpEngine(), CommunityData('public'),
...
設置傳輸和目標target
PySNMP支持UDP-over-IPv4和UDP-over-IPv6網絡傳輸。在本例中,我們將通過Internet上調度IPv4在 demo.snmplabs.com
上查詢可用的公告SNMP模擬器。傳輸配置分別以正確初始化的 UdpTrasportTarget
或者 Udp6TransportTarget
對象的形式傳遞給SNMP LCD。
>>> from pysnmp.hlapi imort *
>>>
>>> g = getCmd(SnmpEngine(),
· · · CommunityData('public'),
· · · UdpTransportTarget(('demo.snmplabs.com', 161)),
· · ·
addressing SNMP context:調用SNMP上下文
SNMP上下文是SNMP(v3)消息頭中的一個參數,它處理SNMP引擎在託管實體上提供的MIB的特定集合。SNMP引擎可以爲許多相同的MIB對象提供服務,這些對象表示被管理的硬件或軟件的完全不同的實例。這裏可以用SNMP上下文。
爲了在高級API hlapi中指示SNMP上下文,應該使用正確初始化的ContextData對象。對於本例,我們將使用“empty”上下文(默認)
>>> from pysnmp.hlapi import *
>>>
>>> g = getCmd(SnmpEngine(),
· · · CommunityData('public'),
· · · UdpTransportTarget(('demo.snmplabs.com', 161)),
· · · ContextData(),
· · ·
Specifying MIB object:指定MIB對象
最後,我們必須指定要讀取的MIB對象。在協議層上,MIB對象由OIDs標識,但人們傾向於通過名稱來調用它們,哈哈哈!
$ snmpget -v2c -c public demo.snmplabs.com SNMPv2-MIB::sysDescr.0
SNMPv2-MIB::sysDescr.0 = STRING: SunOS zeus.snmplabs.com
$
$ snmpget -v2c -c public demo.snmplabs.com 1.3.6.1.2.1.1.1.0
SNMPv2-MIB::sysDescr.0 = STRING: SunOS zeus.snmplabs.com
對象名稱和OID都來自MIB。名稱和OID鏈接由稱爲對象類型的高級SMI構造完成。下面是一個示例MIB對象頂一頂sysUpTime與OID…mgmt.mib-2.system.3 值的類型爲 TimeTicks
。
sysUpTime OBJECT-TYPE
SYNTAX TimeTicks
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The time (in hundredths of a second) since the network management portion of the system was last re-initialized."
:: = { system 3 }
在PySNMP中,我們使用負責MIB對象標識的 ObjectIdentity
類。ObjectIdentity標識從人的角度處理MIB對象的方法。它需要諮詢MIB才能進入完全“解決”的狀態。ObjectIdentity可以使用MIB對象名進行初始化,在進行MIB查找之後,它將開始執行類似於OID的操作。
>>> from pysnmp.hlapi import *
>>>
>>> x = ObjectIdentity('SNMPv2-MIB', 'system')
>>> # . . . calling MIB lookup . . .
>>> tuple(x)
(1, 3, 6, 1, 2, 1, 1, 1)
>>> x = ObjectIdentity('iso.org.dod.internet.mgmt.mib-2.system.sysDdescr')
>>> # . . . calling MIB lookup . . .
>>> str(x)
'1.3.6.1.2.1.1.1'
calling MIB lookup類似如下
from pysnmp.smi import builder, view
mib_builder = builder.MibBuilder()
mib_view = view.MibViewController(mib_builder)
mib_var = ObjectIdentity('SNMPv2-MIB', 'system', '0')
mib_var.resolveWithMib(mib_view)
print tuple(mib_var)
print str(mib_var)
MIB解析是指將MIB對象名稱到OID的轉換,反之亦然。
ObjectType
類的實例標識PySNMP中的對象類型SMI的構造。ObjectType是引用ObjectIdentity和SNMP類型實例的容器對象。作爲一個Python對象,它看起來像一個(OID, value)元組。
>>> from pysnmp.hlapi import *
>>> x = ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0), 'Linux i386 box'))
>>> # . . . calling MIB lookup . . .
>>> x[0].prettyPrint()
'SNMPv2-MIB::sysDescr.0'
>>> x[1].prettyPrint()
'Linux i386 box'
末尾的零表示MIB對象的實例。MIB中描述的對象指示聲明,它們從來不包含任何數據。數據存儲在MIB對象的實例中,通過向MIB對象標識符附加額外信息(稱爲索引)來尋址。
對於標量MIB對象,按照慣例索引是’0’。ObjectIdentity類將索引作爲其初始化器。
>>> x = ObjectIdentity('SNMPv2-MIB', 'system', 0)
>>> # ... calling MIB lookup ...
>>> tuple(x)
(1, 3, 6, 1, 2, 1, 1, 1, 0)
我們將讀取在SNMPv2-MIB模塊中定義的 sysDesc 標量MIB對象的實例。
>>> from pysnmp.hlapi import *
>>> g = getCmd(SnmpEngine(),
... CommunityData('public'),
... UdpTransportTarget(('demo.snmplabs.com', 161)),
... ContextData(),
... ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)))
默認情況下,PySNMP將在你的本地的文件系統中搜索你所引用的ASN.1 MIB文件。還可以將其配置爲從遠程主機自動下載,如示例所示。我們維護了一個ASN.1 MIB模塊的集合,你可以在你的SNMP項目中使用它。
note:
一個“ASN.1 MIB”是一個iddentifier和types的純文本描述。它是製造商用來描述其SNMP服務的通用格式,與Perl的Net::SNMP和幾乎所有SNMP工具使用的格式相同。
Reading scalar value
我們終於可以發送SNMP查詢並希望收到一些有意義的響應。
同步API的獨特之處在於它是圍繞Python生成器的思想構建的。任何函數調用都以生成器對象結束。死給!對生成器對象的迭代執行實際的SNMP通信。在每次構建和發送SNMP消息時,都要等待、接受和解析響應。
>>> from pysnmp.hlapi import *
>>>
>>> g = getCmd(SnmpEngine(),
... CommunityData('public'),
... UdpTransportTarget(('demo.snmplabs.com', 161)),
... ContextData(),
... ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysUpTime', 0)))
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.3.0'), TimeTicks(44430646))])
Working with SNMP tables
SNMP定義了表的概念,當一個給定的MIB對象將應用於一個屬性的多個實例時,將使用表。例如,將網絡接口的屬性放到SNMP表中。屬性的每個實例都由附加到MIB對象的後綴來尋址。
表再MIBs中指定,它們的索引通過INDEX語句聲明。表索引是非零整數、字符串或任何基本SNMP類型。
在協議層,所有的索引都是OID的一部分。爲了方便人們使用索引,SNMP管理應用程序用DISPLAY-HINT子句來實現OID和SNMP類型之間的自動索引轉換。
ifEntry OBJECT-TYPE
SYNTAX IfEntry
INDEX { ifIndex }
::= { ifTable 1 }
ifIndex OBJECT-TYPE
SYNTAX InterfaceIndex
::= { ifEntry 1 }
ifDescr OBJECT-TYPE
SYNTAX DisplayString (SIZE (0..255))
::= { ifEntry 2 }
InterfaceIndex ::= TEXTUAL-CONVENTION
DISPLAY-HINT "d"
SYNTAX Integer32 (1..2147483647)
PySNMP的用法是:
>>> x = ObjectIdentity('IF-MIB', 'ifDescr', 123)
>>> # . . . calling MIB lookup . . .
>>> str(x)
'1.3.6.1.2.1.2.2.1.2.123'
有些SNMP表有許多索引。這些索引中的每一個都成爲OID的一部分,彼此相連,並最終連接到MIB對象的OID。
從語義的角度看,每個索引都反映了一個MIB對象重要而獨特的屬性。
tcpConnectionEntry OBJECT-TYPE
SYNTAX TcpConnectionEntry
INDEX { tcpConnectionLocalAddressType,
tcpConnectionLocalAddress,
tcpConnectionLocalPort,
tcpConnectionRemAddressType,
tcpConnectionRemAddress,
tcpConnectionRemPort }
::= { tcpConnectionTable 1 }
tcpConnectionLocalPort OBJECT-TYPE
SYNTAX InetPortNumber
::= { tcpConnectionEntry 3 }
PySNMP的 ObjectIdentity這個類以易讀的方式接收任意數量的索引,並將他們轉換爲OID。
>>> x = ObjectIdentity('TCP-MIB', 'tcpConnectionState',
. . . 'ipv4', '195.218.254.105', 41511,
. . . 'ipv4', '194.67.1.250', 993)
>>> # . . . calling MIB lookup . . .
>>> str(x)
'1.3.6.1.2.1.6.19.1.7.1.4.195.218.254.105.41511.1.4.194.67.1.250.993'
來讀取TCP-MIB::tcpConnectionState 這個對象:
>>> from pysnmp.hlapi import *
>>>
>>> g = getCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . ObjectType(ObjectIdentity('TCP-MIB', 'tcpConnectionState',
. . . 'ipv4', '195.218.254.105', 41511,
. . . 'ipv4', '194.67.1.250', 993)
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity(ObjectName('1.3.6.1.2.1.6.19.1.7.1.4.195.218.254.105.41511.1.4.194.67.1.250.993')), Integer(5))])
SNMP command operations:SNMP命令操作
SNMP允許你請求一個MIB對象,通過next獲取下一個值。通過這種方式,你可以提前讀取你不知道的MIB對象。MIB對象根據它們的OID進行概念排序。這個功能由 nextCmd()
函數實現。
>>> from pysnmp.hlapi import *
>>> g = nextCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr')))
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'), DisplayString('SunOS zeus.snmplabs.com'))])
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.2.0'), ObjectIdentity(ObjectIdentifier('1.3.6.1.4.1.8072.3.2.10')))])
對生成器對象的迭代遍歷了SNMP agent的MIB對象s。
SNMPv2爲GETNEXT命令引入了重要的優化——修改後叫GETBULK,能立即收集和響應一組“下一個”MIB對象。附加的非重複器和大量重複參數可用於影響MIB對象的批處理。
PySNMP在協議級別隱藏了GETBULK的優化,爲了方便,bulkCmd()函數公開了和getNext()相同的生成器API。
>>> from pysnmp.hlapi imort *
>>>
>>> N, R = 0, 25
>>> g = bulkCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . N, R,
. . . ObjectType(ObjectIdentity('1.3.6')))
>>>
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'), DisplayString('SunOS zeus.snmplabs.com'))])
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.2.0'), ObjectIdentifier('1.3.6.1.4.1.20408'))])
Python生成器不僅可以生成數據,還可以將數據發送到正在運行的生成器對象中。高級API hlapi使用該特性對一組新的MIB對象重複相同的SNMP操作。
>>> from pysnmp.hlapi import *
>>>
>>> g = nextCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . ObjectType(ObjectIdentity('IF-MIB', 'ifTable')))
>>>
>>> g.send([ObjectType(ObjectIdentity('IF-MIB', 'ifInOctets'))])
(None, 0, 0, [(ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.10.1'), Counter32(284817787))])
你可以通過在一個PDU中列出許多不相關的MIB對象來操作它們。響應PDU將攜帶一個MIB對象列表及其值,其順序與請求消息中的順序完全相同。
>>> from pysnmp.hlapi import *
>>>
>>> g = getCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)),
. . . ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysUpTime', 0))
. . . )
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'), DisplayString('SunOS zeus.snmplabs.com')), ObjectType(ObjectIdentity('1.3.6.1.2.1.1.3.0'), TimeTicks(44430646))])
SNMP的配置管理部分依賴於SNMP SET命令。儘管它在被管理實體上的實現被證明是有些苛刻的(由於鎖和事務行爲需求)。因此,供應商傾向於將其排除在外,從而將託管實體呈現爲只讀。
PySNMP通過 setCmd()
函數統一支持設置。
>>> from pysnmp.hlapi import *
>>>
>>> g = setCmd(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 161)),
. . . ContextData(),
. . . ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0), 'Linux i386')
. . . )
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.1.0'), DisplayString('Linux i386'))])
sending SNMP notifications:發送SNMP通知
託管實體可以向管理實體發送未經請求的消息。在SNMP中稱爲通知notification。通知有助於減少輪詢,輪詢在大型網絡中是一個麻煩的問題。
SNMP通知是枚舉的,每個通知都有明確的語義。這是通過一種稱爲NOTIFICATION-TYPE的特殊高級SMI構造實現的。與定義MIB對象的 OBJECT-TYPE一樣,NOTIFICATION-TYPE也有唯一的OID,但是它引用的是其他的MIB對象序列,而不是SNMP值。這些MIB對象由OBJECTS子句指定,當發送通知時,它們的當前值包含在通知消息中。
linkUp NOTIFICATION-TYPE
OBJECTS { ifIndex, ifAdminStatus, ifOperStatus }
STATUS current
DESCRIPTION
"..."
::= { snmpTraps 4 }
爲了在PySNMP中建模NOTIFICATION-TYPE結構,我們有一個Notification Type類的容器對象。它由ObjectIdentity
類標識,並引用 ObjectType
類的實例的序列。
從行爲的角度來看,NotificationType類似於ObjectType類實例的序列。
>>> from pysnmp.hlapi import *
>>>
>>> x = NotificationType(ObjectIdentity('IF-MIB', 'linkUp'))
>>> # . . . calling MIB lookup . . .
>>>
>>> [ str(y) for x in n ]['SNMPv2-MIB::snmpTrapOID.0 = 1.3.6.1.6.3.1.1.5.3', 'IF-MIB::ifIndex = ', 'IF-MIB::ifAdminStatus = ', 'IF-MIB::ifOperStatus = ']
使用PySNMP發送通知與發送SNMP命令沒有太大的區別。區別在於PDU綁定的構建方式。SNMP中有兩種不同的通知,trap和inform。
在trap中,agent-to-manager 是單向的,不發生響應或確認消息。
>>> from pysnmp.hlapi import *
>>>
>>> g = sendNotification(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 162)),
. . . ContextData(),
. . . 'trap',
. . . NotificationType(ObjectIdentity('IF-MIB', 'linkUp'), instanceIndex=(123,))
. . . )
>>> next(g)
(None, 0, 0, [])
inform通知很像命令。區別在於PDU格式。Inform用於manager-to-manager通信以及agent-to-manager通信。
>>> from pysnmp.hlapi import *
>>>
>>> g = sendNotification(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 162)),
. . . ContextData(),
. . . 'inform',
. . . NotificationType(ObjectIdentity('IF-MIB', 'linkUp'), instanceIndex=(123,))
. . . )
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.3.0'), TimeTicks(0)), ObjectType(ObjectIdentity('1.3.6.1.6.3.1.1.4.1.0'), ObjectIdentity('1.3.6.1.6.3.1.1.5.4')), ObjectType(ObjectName('1.3.6.1.2.1.2.2.1.1.123'), Null('')), ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.7.123'), Null('')), ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.8.123'), Null(''))])
在後一個示例中,可以看到MIB對象(ifIndex、ifAdminStatus、ifOperStatus)從IF-MIB::linkUp通知自動展開。
要按索引查找SNMP表對象的特定行,可以通過instanceIndex參數將MIB對象的索引部分傳遞給NotificationType。
如你所見,擴展的MIB對象的實際值是NULLs。這是因爲在這些示例中,我們的簡單腳本不能訪問那些MIB對象。我們可以通過傳遞NotificationType 對象來提供丟失的信息,該對象將MIB對象OID映射到當前值。
>>> from pysnmp.hlapi import *
>>>
>>> mib = {ObjectIdentifier('1.3.6.1.2.1.2.2.1.1.123'): 123,
. . . ObjectIdentifier('1.3.6.1.2.1.2.2.1.7.123'): 'testing',
. . . ObjectIdentifier('1.3.6.1.2.1.2.2.1.8.123'): 'up'}
>>>
>>> g = sendNotification(SnmpEngine(),
. . . CommunityData('public'),
. . . UdpTransportTarget(('demo.snmplabs.com', 162)),
. . . ContextData(),
. . . 'inform',
. . . NotificationType(ObjectIdentity('IF-MIB', 'linkUp'), instanceIndex=(123,), objects=mib)
. . . )
>>> next(g)
(None, 0, 0, [ObjectType(ObjectIdentity('1.3.6.1.2.1.1.3.0'), TimeTicks(0)), ObjectType(ObjectIdentity('1.3.6.1.6.3.1.1.4.1.0'), ObjectIdentity('1.3.6.1.6.3.1.1.5.4')), ObjectType(ObjectName('1.3.6.1.2.1.2.2.1.1.123'), InterfaceIndex(123)), ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.7.123'), Integer(3)), ObjectType(ObjectIdentity('1.3.6.1.2.1.2.2.1.8.123'), Integer(1))])
High-volume messaging
在管理大型網絡時,按順序讀取MIB對象會導致延遲。在某個時間點,延遲變得無法忍受。並行化查詢的解決方案是衆所周知的——您可以通過將單個操作轉移到多個進程或多個執行線程中,或者圍繞異步I/O模型構建應用程序來實現這一點。
與其他解決方案相比,異步模型是最輕量級和可伸縮的。這個想法很簡單:永遠不要等待I/O——只要可能,就做其他事情。這背後的問題是,執行流變成了非線性的,這影響了人類讀者對程序的分析。
PySNMP高級API採用了三種流行的異步I/O框架——asyncore、twisted和asyncio。有關異步API的更多信息,請參閱PySNMPlibrary reference和example。