Python中的SNMP模块
SNMP ,简称 简单网络管理协议 ,是SDN的必备工具,也是控制软件中设备的最佳选择。即使不考虑这一点,应用程序访问也是SNMP的主要目的。毫无疑问,所有监控系统都利用SNMP来监视和控制服务器和网络设备。在脚本中具备SNMP的强大功能将是令人惊叹的。因此,在本教程中,我们将讨论Python编程语言中SNMP的应用。
但在开始之前,让我们先了解一下SNMP。
理解SNMP
SNMP ,也称为 简单网络管理协议 ,是管理服务器和远程设备(代理)之间进行通信的标准方式。SNMP的目标是使管理者能够理解(甚至更改)代理上的数据。例如,管理者可以检查哪些接口正常运行,哪些接口已经下线,或者更改远程设备的主机名。
我们的脚本使得一个在管理站点上运行的Python程序能够控制执行SNMP代理的远程设备。
SNMP代理准备了大部分管理者可以读取或更改的详细信息,并将其存储在一个特定的表中,即MIB。MIB是一种类似树状结构的组织形式,其中每个节点都由一串数字表示。例如, 1.3.6.1.2.1.1 表示系统的描述。如果有人好奇这串数字从哪里来,那就是整个树结构的表示方式!其中每个数字都对应一个名称。因此,我们可以将其翻译成更具说明性的形式: iso.org.dod.internet.mgmt.mib-2.system.sysDescr 。
现在,让我们借助Python的 PySNMP 模块来讨论在Python编程语言中使用SNMP的用途。
理解PySNMP模块
PySNMP 是一个开源模块,用于Python。与telnet或HTTP不同,Python并没有原生实现SNMP。毕竟,只有网络和系统工程师才会需要一名Python开发者在工厂工作。 PySNMP 在弥补Python缺陷方面表现出色。总的来说, PySNMP 模块允许我们利用任何版本的SNMP,无论是作为代理还是管理者。创建代理意味着我们正在构建一个应用程序或设备。但是,我们将只讨论在管理远程设备时使用 PySNMP 的用法。
此外,我们将了解Python中 PySNMP 的不同功能。本教程的主要目标是创建一个简单的Python程序,以简化我们的工作。在程序中,我们将拥有所需的所有SNMP操作。
那么,让我们开始吧。
准备环境
首先,我们需要安装 PySNMP 模块。我们可以使用pip安装程序来安装所需的模块,命令如下:
语法:
$ pip install pysnmp
模块将以Python和pip的版本安装在系统中。
验证安装
为了检查模块是否已正确安装在系统中,我们可以尝试导入模块并执行程序。
安装完成后,创建一个新的Python文件,并在其中输入以下语法。
示例:
# importing the required module
import pysnmp
现在,保存文件并使用以下命令在命令提示符中运行文件。
语法:
$ python .py
如果程序在运行过程中没有引发任何导入错误,则模块已正确安装。否则建议重新安装模块并参考其官方文档。
理解Python SNMP Get操作
SNMP的Get操作使我们能够获取MIB中单个对象的值。我们还可以利用它获取单个对象的列表。我们可以开始编写如下所示的get()函数:
示例:
# importing the required module
from pysnmp import hlapi
# defining the get() function
def get(
target,
oids,
credentials,
port = 161,
engine = hlapi.SnmpEngine(),
context = hlapi.ContextData()
):
handler = hlapi.getCmd(
engine,
credentials,
hlapi.UdpTransportTarget((target, port)),
context,
*construct_object_types(oids)
)
return fetch(handler, 1)[0]
解释:
从上面的代码片段中,我们可以观察到对PySNMP的高级API的利用。我们定义了一个简单的函数 get() ,它首先需要一个目标(IP或远程设备名称)。然后,它需要我们需要获取的对象ID(OID)的列表,之后是会话认证的一组凭据。我们还可以指定一个不同的UDP端口(如果需要),并利用现有的SNMP引擎或自定义上下文。我们可能需要在同一设备上的所有操作中使用相同的引擎,这样可以节省资源。但是,对于一个简单的代码片段,这并不是必需的,因此我们可以忽略引擎和上下文。
该函数生成一个SNMP会话的处理程序,并从中获取详细信息。为了执行这个操作,它依赖于我们需要创建的两个方法: construct_object_types 和 fetch 。
构建对象类型
正如我们之前讨论的,拥有更高的强度意味着更复杂。因此, hlapi.getCmd() 函数需要一些特殊的 hlapi.ObjectType 对象,而不是简单的字符串OID列表。因此, construct_object_type 函数根据 PySNMP 的需求进行创建。如果我们没有时间,我们可以简单地复制粘贴它到代码中。然而,这应该是一个非常简单的函数; 让我们看一下:
示例:
def construct_object_types(listOfOids):
objectTypes = []
for oid in listOfOids:
objectTypes.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))
return objectTypes
解释:
上面的代码段返回一个可以通过在前面添加 * 来扩展的列表,就像我们在 get() 方法中所做的那样。
获取数据
fetch() 函数是Python SMP教程的杰作。一般来说,我们编写它是为了可以在基于 PySNMP 的其他函数中重复使用,比如 get-bulk。它简单地根据 count 变量循环处理多次的处理程序。如果有任何错误引发,流程将停止,并返回 RuntimeError 消息。 在任何其他情况下,它将数据存储在一个字典列表中。
示例:
def fetch(handler, count):
res = []
for i in range(count):
try:
error_indication, error_status, error_index, var_binds = next(handler)
if not error_indication and not error_status:
items = {}
for var_bind in var_binds:
items[str(var_bind[0])] = cast(var_bind[1])
res.append(items)
else:
raise RuntimeError('Got SNMP error: {0}'.format(error_indication))
except StopIteration:
break
return res
解释:
在上面的代码片段中,我们构建了一个try-except方法,以特定的原因停止迭代。在用户指定的计数高于我们实际拥有的对象数量的情况下,我们只需停止并返回目前为止所得到的结果。这就是这个结构的目的。
为什么需要返回一个字典列表?在每个获取操作中,我们可以获取各种对象ID。因此,每个字典将由对象ID作为键和MIB中OID的值作为该键的值组成。在需要在单个获取中使用多个OID的情况下,我们将返回一个具有多个键的字典。但为什么还需要一个列表?使用获取操作,我们只能检索数据一次。然而,正如我们将在获取批量中看到的那样,我们可能需要在各个实例上多次获取类似的详细信息。让我们用一个示例来更好地理解。假设我们需要检查所有接口上的错误:信息始终是错误的;然而,我们有不同的实例(每个接口一个)。我们可以使用列表来可视化它,其中每个数据元素都是表示实例的字典。
注意:fetch()函数依赖于我们必须创建的另一个函数:cast()。此函数将从PySNMP接收到的数据转换为int、float或string。
让我们考虑以下相同的代码片段:
示例:
def cast(val):
try:
return int(val)
except (ValueError, TypeError):
try:
return float(val)
except (ValueError, TypeError):
try:
return str(val)
except (ValueError, TypeError):
pass
return val
说明:
在上面的代码片段中,我们定义了一个函数,在其中使用了 try-except 方法来检查是否发生了错误。
提供凭据
PySNMP 库的身份验证系统非常强大且简单。没有必要在其上面再写一个额外的层,因此我们可以直接利用它。其中的 get() 函数以及其他函数都有一个特殊的credentials变量,其中包含了身份验证对象。如果我们使用SNMPv2c或SNMPv3,这个对象会有所不同。
在SNMPv2c(或更低版本)的情况下,我们需要指定community。我们可以使用以下方式进行设置:
语法:
hlapi.CommunityData('JAVATPOINT')
相反,SNMPv3非常复杂。这是因为它利用了一个用户,这个用户有两个密码和两个协议:一个用于身份验证,另一个用于加密。因此,我们需要指定用户的名称、身份验证密码、身份验证协议、加密密码和加密协议。我们可以使用 UsmUserData 类来实现。
让我们来看下面的示例,更好地理解一下。
示例:
hlapi.UsmUserData(
'testuser',
authKey = 'authenticationkey',
privKey = 'encryptionkey',
authProtocol = hlapi.usmHMACSHAAuthProtocol,
privProtocol = hlapi.usmAesCfb128Protocol
)
解释:
在上面的代码片段中,我们使用了 UsmUserData 类并输入了所需的详细信息。它比看起来更简单;我们必须知道远程设备的协议。然而,我们中的一些人可能会参考关于 UsmUserData 的完整官方文档。
获取主机名
现在,让我们测试 get() 函数。我们将使用它来检索设备的主机名,即对象1.3.6.1.2.1.1.5.0。我们可以简单地编写以下代码片段:
范例:
print(get('10.0.0.1', ['1.3.6.1.2.1.1.5.0'], hlapi.CommunityData('JAVATPOINT')))
输出:
{'1.3.6.1.2.1.1.5.0': 'R1.sdn.local'}
解释:
在上面的代码片段中,我们打印了获取设备主机名的 get() 函数的结果。
注意:这里我们不是获取一个字典列表,而是仅一个字典。这是故意的, get() 函数始终如此。通常情况下,我们知道函数只执行一次,它不能创建多个实例。因此,我们返回从fetch()获取的第一个元素。
理解Python SNMP Get Bulk
get_bulk() 方法检索同一对象ID的多个实例,比如每个接口的实例。当我们在处理表格时,比如接口一的路由表时,这个函数变得可支持。它非常简单,并且类似于 get() 方法。但它需要一些额外的细节:从哪个对象开始和我们需要获取的实例数量。我们将它们传递给 start_from 和 count 。
让我们考虑以下代码片段来理解 get_bulk() 函数的工作原理。
示例:
def get_bulk(
target,
oids,
credentials,
count,
start_from = 0,
port = 161,
engine = hlapi.SnmpEngine(),
context = hlapi.ContextData()):
handler = hlapi.bulkCmd(
engine,
credentials,
hlapi.UdpTransportTarget(( target, port )),
context,
start_from, count,
*construct_object_types( oids )
)
return fetch(handler, count)
解释:
在上面的代码片段中,我们定义了 get_bulk() 函数,并在其中指定了处理程序。最后,我们返回了 fetch() 函数。因此,我们期望得到一个字典列表,所以不需要像使用 get() 函数那样提取只有第一个字典。
理解Python SNMP Get Bulk Auto
get_bulk_auto() 函数是 get_bulk() 函数的改进版本。假设我们想使用 get_bulk() 循环遍历设备接口。我们如何知道接口的数量?这变得必要,因为SNMP需要知道要迭代多少次。我们事先无法知道,但是我们可以使用SNMP作为选项来查找这些信息。
我们可以使用 get_bulk_auto() 函数告诉代码从另一个OID中检索计数变量。因此,我们可以指定一个对象ID,而不是指定一个数字。函数将为该对象进行get操作,并将其用作计数。
让我们考虑以下示例以便更好地理解。
示例:
def get_bulk_auto(
target,
oids,
credentials,
count_oid,
start_from = 0,
port = 161,
engine = hlapi.SnmpEngine(),
context = hlapi.ContextData()):
count = get(
target,
[count_oid],
credentials,
port,
engine,
context
)[count_oid]
return get_bulk(target, oids, credentials, count, start_from, port, engine, context)
解释:
在上面的代码片段中,我们定义了 get_bulk_auto() 函数,并使用 get() 和返回 get_bulk() 函数指定了 count 变量。
使用Python的SNMP Get Bulk和Get Bulk Auto功能
让我们考虑执行以下代码片段。
示例:
ele = get_bulk_auto(
'10.0.0.1',
['1.3.6.1.2.1.2.2.1.2 ', '1.3.6.1.2.1.31.1.1.1.18'],
hlapi.CommunityData('JAVATPOINT'),
'1.3.6.1.2.1.2.1.0')
for i in ele:
for x, y in i.items():
print("{0} = {1}".format(x, y))
print('')
输出:
1.3.6.1.2.1.2.2.1.2.1=FastEthernet1/0
1.3.6.1.2.1.31.1.1.1.18.1=
1.3.6.1.2.1.2.2.1.2.2=FastEthernet0/0
1.3.6.1.2.1.31.1.1.1.18.2=
1.3.6.1.2.1.2.2.1.2.3=FastEthernet0/1
1.3.6.1.2.1.31.1.1.1.18.3=Test Desc
1.3.6.1.2.1.2.2.1.2.4=Serial2/0
1.3.6.1.2.1.31.1.1.1.18.4=
1.3.6.1.2.1.2.2.1.2.5=Serial2/1
1.3.6.1.2.1.31.1.1.1.18.5=
1.3.6.1.2.1.2.2.1.2.6=Serial2/2
1.3.6.1.2.1.31.1.1.1.18.6=
1.3.6.1.2.1.2.2.1.2.7=Serial2/3
1.3.6.1.2.1.31.1.1.1.18.7=
1.3.6.1.2.1.2.2.1.2.9=Null0
1.3.6.1.2.1.31.1.1.1.18.9=