python使用SNMP-网络管理协议
前言
什么是SNMP?
SNMP是广泛应用于TCP/IP网络的网络管理标准协议,该协议能够支持网络管理系统,用以监测连接到网络上的设备是否有任何引起管理上关注的情况。SNMP采用轮询机制,提供最基本的功能集,适合小型、快速、低价格的环境使用,而且SNMP以用户数据报协议(UDP)报文为承载,因而受到绝大多数设备的支持,同时保证管理信息在任意两点传送,便于管理员在网络上的任何节点检索信息,进行故障排查。
为什么需要SNMP?
随着网络技术的飞速发展,在网络不断普及的同时也给网络管理带来了一些问题:
- 网络设备数量成几何级数增加,使得网络管理员对设备的管理变得越来越困难;同时,网络作为一个复杂的分布式系统,其覆盖地域不断扩大,也使得对这些设备进行实时监控和故障排查变得极为困难。
- 网络设备种类多种多样,不同设备厂商提供的管理接口(如命令行接口)各不相同,这使得网络管理变得愈发复杂。
在这种背景下,SNMP应运而生,SNMP是广泛应用于TCP/IP网络的网络管理标准协议,该协议能够支持网络管理系统,用以监测连接到网络上的设备是否有任何引起管理上关注的情况。通过“利用网络管理网络”的方式:
- 网络管理员可以利用SNMP平台在网络上的任意节点完成信息查询、信息修改和故障排查等工作,工作效率得以提高。
- 屏蔽了设备间的物理差异,SNMP仅提供最基本的功能集,使得管理任务与被管理设备的物理特性、网络类型相互独立,因而可以实现对不同设备的统一管理,管理成本低。
- 设计简单、运行代价低,SNMP采用“尽可能简单”的设计思想,其在设备上添加的软件/硬件、报文的种类和报文的格式都力求简单,因而运行SNMP给设备造成的影响和代价都被最小化。
SNMP的基本组件
SNMP基本组件包括网络管理系统NMS(Network Management System)、代理进程(Agent)、被管对象(Managed Object)和管理信息库MIB(Management Information Base)。如图所示他们共同构成SNMP的管理模型,在SNMP的体系结构中都起着至关重要的作用。
SNMP版本
SNMP有三种版本:SNMPv1,SNMPv2c和SNMPv3。
- SNMPv1:SNMP的第一个版本,它提供了一种监控和管理计算机网络的系统方法,它基于团体名认证,安全性较差,且返回报文的错误码也较少。它在RFC 1155和RFC 1157中定义。
- SNMPv2c:第二个版本SNMPv2c引入了GetBulk和Inform操作,支持更多的标准错误码信息,支持更多的数据类型。它在RFC 1901,RFC 1905和RFC 1906中定义。
- SNMPv3:鉴于SNMPv2c在安全性方面没有得到改善,IETF颁布了SNMPv3版本,提供了基于USM(User Security Module)的认证加密和基于VACM(View-based Access Control Model)的访问控制,是迄今为止最安全的版本。SNMPv3在RFC 1905,RFC 1906,RFC 2571,RFC 2572,RFC 2574和RFC 2575中定义。
SNMP端口
SNMP端口是SNMP通信端点,SNMP消息传输通过UDP进行,通常使用UDP端口号161/162。有时也使用传输层安全性(TLS)或数据报传输层安全性(DTLS)协议,端口使用情况如下表所示。
表1-1 SNMP端口使用介绍
过程
协议
端口号
代理进程接收请求信息
UDP协议
161
NMS与代理进程之间的通信
UDP协议
161
NMS接收通知信息
UDP协议
162
代理进程生成通知信息
任何可用的端口
接收请求信息
TLS/DTLS
10161
接收通知信息
TLS/DTLS
10162
首先需要在系统中安装 SNMP 客户端, 对于 Linux 平台来说只需要执行如下配置过程即可.
[root@localhost ~]# yum install -y net-snmp
[root@localhost ~]# cat /etc/snmp/snmpd.conf |grep -vE "^#|^$"
com2sec notConfigUser default public
?
group notConfigGroup v1 notConfigUser
group notConfigGroup v2c notConfigUser
?
view systemview included .1
view systemview included .1
?
access notConfigGroup "" any noauth exact systemview none none
?
[root@localhost ~]# systemctl restart snmpd
[root@localhost ~]# systemctl enable snmpd
?
?
如果是 Windows 系统则需要在客户机服务列表,开启 SNMP 支持,并设置好一个团体名称,如下图。
当我们配置好客户端后,服务端就客户获取数据了,我们以一个 OID 序号为例,我们查询特定序号对应的名称,然后将其记录下来,例如下面这样。
首先我们不适用 PySNMP 模块直接开线程调用看看, 该代码如下所示.
import os,re,time
?
# 通过SNMP收集主机CPU利用率: 通过SNMP协议,收集目标主机的CPU利用率(百分比),并返回JSON字符串.
def Get_CPU_Info(addr):
try:
Head = ["HostName","CoreLoad","CpuUser","CpuSystem","CpuIdle"]
CPU = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
CPU.append(ret.read().split(":")[3].strip())
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.25.3.3.1.2")
CPU.append(ret.read().split(":")[3].strip())
?
for i in [9,10,11]:
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " 1.3.6.1.4.1.2021.11.{}.0".format(i))
ret = ret.read()
Info = ret.split(":")[3].strip()
CPU.append(Info)
return dict(zip(Head,CPU))
except Exception:
return 0
?
# 通过SNMP获取系统CPU负载信息: 分别获取到系统的1,5,15分钟的负载信息,并返回JSON格式.
def Get_Load_Info(addr):
try:
Head = ["HostName","Load1","Load5","Load15"]
SysLoad = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
SysLoad.append(ret.read().split(":")[3].strip())
?
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.4.1.2021.10.1.3")
load = list(re.sub(".*STRING: ", "", ret.read()).split("\n"))
SysLoad.append(load[0])
SysLoad.append(load[1])
SysLoad.append(load[2])
return dict(zip(Head,SysLoad))
except Exception:
return 0
?
# 通过SNMP获取系统内存占用: 内存利用率,获取到之后,将其转化为字典格式保存。
def Get_Mem_Info(addr):
try:
Head = ["HostName","memTotalSwap","memAvailSwap","memTotalReal","memTotalFree"]
SysMem = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
SysMem.append(ret.read().split(":")[3].strip())
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.4.1.2021.4")
mem = ret.read().split("\n")
for i in [2,3,4,6]:
SysMem.append(re.sub(".*INTEGER: ","",mem[i]).split(" ")[0])
return dict(zip(Head,SysMem))
except Exception:
return 0
?
# 通过SNMP获取系统磁盘数据: 这个案例并不完整,我只写了一点,后面有个问题一直没有解决.
def Get_Disk_Info(addr):
try:
dic = {}
list = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageDescr")
DiskName = ret.read().split("\n")
ret =os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageUsed")
DiskUsed = ret.read().split("\n")
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageSize")
DiskSize = ret.read().split("\n")
?
for i in range(1,len(DiskName) - 7):
dic["Name"]= DiskName[i + 5].split(":")[3]
dic["Used"]= DiskUsed[i + 5].split(":")[3]
dic["Size"]= DiskSize[i + 5].split(":")[3]
list.append(dic)
return list
except Exception:
return 0
?
if __name__ == '__main__':
for i in range(100):
dic = Get_CPU_Info("192.168.1.20")
print(dic)
time.sleep(1)
?
?
通过 SNMP 收集主机 CPU 利用率 通过 SNMP 协议, 收集目标主机的 CPU 利用率 (百分比), 并返回 JSON 字符串.
import os,re,time
?
def Get_CPU_Info(addr):
try:
Head = ["HostName","CoreLoad","CpuUser","CpuSystem","CpuIdle"]
CPU = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
CPU.append(ret.read().split(":")[3].strip())
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.25.3.3.1.2")
CPU.append(ret.read().split(":")[3].strip())
?
for i in [9,10,11]:
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " 1.3.6.1.4.1.2021.11.{}.0".format(i))
ret = ret.read()
Info = ret.split(":")[3].strip()
CPU.append(Info)
return dict(zip(Head,CPU))
except Exception:
return 0
?
if __name__ == '__main__':
for i in range(100):
dic = Get_CPU_Info("192.168.1.20")
print(dic)
time.sleep(1)
?
?
通过 SNMP 获取系统 CPU 负载信息 分别获取到系统的 1,5,15 分钟的负载信息, 并返回 JSON 格式.
import os,re,time
?
def Get_Load_Info(addr):
try:
Head = ["HostName","Load1","Load5","Load15"]
SysLoad = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
SysLoad.append(ret.read().split(":")[3].strip())
?
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.4.1.2021.10.1.3")
load = list(re.sub(".*STRING: ", "", ret.read()).split("\n"))
SysLoad.append(load[0])
SysLoad.append(load[1])
SysLoad.append(load[2])
return dict(zip(Head,SysLoad))
except Exception:
return 0
?
if __name__ == '__main__':
dic = Get_Load_Info("192.168.1.20")
print(dic)
?
?
通过 SNMP 获取系统内存占用 内存利用率,获取到之后,将其转化为字典格式保存。
import os,re,time
?
def Get_Mem_Info(addr):
try:
Head = ["HostName","memTotalSwap","memAvailSwap","memTotalReal","memTotalFree"]
SysMem = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.2.1.1.5")
SysMem.append(ret.read().split(":")[3].strip())
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " .1.3.6.1.4.1.2021.4")
mem = ret.read().split("\n")
for i in [2,3,4,6]:
SysMem.append(re.sub(".*INTEGER: ","",mem[i]).split(" ")[0])
return dict(zip(Head,SysMem))
except Exception:
return 0
?
if __name__ == '__main__':
dic = Get_Mem_Info("192.168.1.20")
print(dic)
?
?
通过 SNMP 获取系统磁盘数据 这个案例并不完整, 我只写了一点, 后面有个问题一直没有解决.
import os,re,time
?
def Get_Disk_Info(addr):
try:
dic = {}
list = []
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageDescr")
DiskName = ret.read().split("\n")
ret =os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageUsed")
DiskUsed = ret.read().split("\n")
ret = os.popen("snmpwalk -v 2c -c nmap " + addr + " HOST-RESOURCES-MIB::hrStorageSize")
DiskSize = ret.read().split("\n")
?
for i in range(1,len(DiskName) - 7):
dic["Name"]= DiskName[i + 5].split(":")[3]
dic["Used"]= DiskUsed[i + 5].split(":")[3]
dic["Size"]= DiskSize[i + 5].split(":")[3]
list.append(dic)
return list
except Exception:
return 0
?
if __name__ == '__main__':
list = Get_Disk_Info("192.168.1.20")
print(list)
?
?
接下来,我们使用 pysnmp 模块来做,安装 pysnmp 很简单,执行命令pip install pysnmp
即可,安装后,使用以下代码执行即可获取到目标数据,网上的那些转载的都是坑,没一个能用的,这个案例是官方案例,可以使用。
from pysnmp.hlapi import *
?
iterator = getCmd(SnmpEngine(),
CommunityData('public'),
UdpTransportTarget(('192.168.1.113', 161)),
ContextData(),
ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)))
?
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
?
if errorIndication:
print(errorIndication)
else:
if errorStatus:
print('%s at %s' % (errorStatus.prettyPrint(), varBinds[int(errorIndex)-1] if errorIndex else '?'))
else:
for varBind in varBinds:
print(' = '.join([x.prettyPrint() for x in varBind]))
?
?
首先我们以一个 OID 序号为例,我们查询特定序号对应的名称,然后将其记录下来,例如下面这样。
在客户机上面,需要在服务列,开启 SNMP 支持,并设置好一个团体名称,如下图。
然后我们简单的封装一个类,先来测试一下是否能通。
# snmpwalk -v 2c -c public 192.168.1.113 .1.3.6.1.2.1.1.5
from pysnmp.hlapi import *
?
?
class NetSNMP():
def __init__(self,address,region):
self.region = region
self.address = address
?
# 获取指定数据的方法
def GetNumber(self,oid,sub_oid,sub_id):
iterator = getCmd(SnmpEngine(),
CommunityData(self.region),
UdpTransportTarget((self.address, 161)),
ContextData(),
ObjectType(ObjectIdentity(oid, sub_oid, sub_id)))
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
?
if errorIndication:
return False
else:
if errorStatus:
return False
else:
for varBind in varBinds:
return [x.prettyPrint() for x in varBind]
?
if __name__ == "__main__":
?
# 初始化
ptr = NetSNMP("192.168.1.101","public")
?
# 设置OID数据集
ret = ptr.GetNumber("HOST-RESOURCES-MIB","hrMemorySize",0)
print("类型: {} --> 返回结果: {} --> 解析: {}".format(type(ret),ret,ret[1]))
?
?
运行后,即可读取到内存数据,如下。
Python SNMP 获取交换机ARP表和FDB表(MAC和端口对应表)
import sys
import pysnmp.hlapi as hlapi
import pysnmp.proto.rfc1902 as rfc1902
def snmp_walk(host, oid, format='str', strip_prefix=True, community='public'):
res = []
for (errorIndication,
errorStatus,
errorIndex,
varBinds) in hlapi.nextCmd(hlapi.SnmpEngine(),
hlapi.CommunityData(community),
hlapi.UdpTransportTarget((host, 161), timeout=4.0, retries=3),
hlapi.ContextData(),
hlapi.ObjectType(hlapi.ObjectIdentity(oid)),
lookupMib=False,
lexicographicMode=False):
if errorIndication:
raise ConnectionError(f'SNMP error: "{str(errorIndication)}". Status={str(errorStatus)}')
elif errorStatus:
raise ConnectionError('errorStatus: %s at %s' % (errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
else:
for x in varBinds:
k, v = x
if strip_prefix:
k = str(k)[len(str(oid)) + 1:]
if isinstance(v, rfc1902.Integer):
res.append((str(k), int(v)))
else:
if format == 'numbers':
res.append((str(k), v.asNumbers()))
elif format == 'hex':
res.append((str(k), v.asOctets().hex()))
elif format == 'raw':
res.append((str(k), v))
elif format == 'bin':
res.append((str(k), v.asOctets()))
elif format == 'int':
res.append((str(k), int(v)))
elif format == 'preview':
res.append((str(k), str(v)))
elif format == 'any':
try:
res.append((str(k), v.asOctets().decode('utf-8')))
except UnicodeDecodeError:
res.append((str(k), '0x' + v.asOctets().hex()))
elif format == 'str':
res.append((str(k), v.asOctets().decode(v.encoding)))