SNMP协议学习笔记
SNMP是用于管理网络中设备的一种应用层协议,它使用简单的request/response通信模型。被管理的设备可能包括:路由器、终端服务器、打印机等,这些设备的共同特点是运行TCP/IP协议族。SNMP协议为所有这些设备定义了统一的访问接口,简化了网络管理工作。
基于SNMP的网络管理,包含的节点分为两种类型:
- 网络管理站:管理站一般都是带有监视器的工作站,可以显示所有被管设备的状态(Manager)
- 被管网络单元(受管设备),被管设备上运行代理程序(Agent)
管理进程和代理进程之间的通信可以有两种方式:
- 管理进程向代理进程发出请求,询问一个具体的变量值,或者修改某一变量值(Get/Set)
- 代理进程主动向管理进程报告有某些重要的事件发生(Trap)
基于SNMP的网络管理,需要关注以下三个方面的内容:
- 管理信息结构(SMI):关于MIB的一套公用的结构和表示符号(数据类型)。RFC 1155定义了SMI
- 管理信息库(MIB):管理信息库包含所有代理进程的所有可被查询和修改的变量。RFC1213定义了第二版MIB,称为MIB-II
- 简单网络管理协议(SNMP):管理进程和代理进程之间的通信协议
SNMP中定义了以下数据类型(其中全大写的是来自ASN.1的内建类型):
数据类型 | 说明 |
INTEGER | 整数类型。某些变量还具有额外的范围限制,例如UDP端口号必须在0-65535之间 |
OCTET STRING | 0或者多个8bit的字节构成的字符串,每个字节的值在0-255之间 |
OBJECT IDENTIFIER | 即对象标识符(OID),在一个全世界范围树状结构中注册 |
NULL | 表示相关的变量没有值 |
SEQUENCE | 序列,一个序列可以包含0-N个元素,每个元素可以具有不同的数据类型,类似与C语言的结构体 |
SEQUENCE OF |
向量,其中所有元素具有相同的类型。 如果元素是简单类型,例如整数,那么就是一维向量。 如果元素是Sequence,那么可以看做二维数组或者表 |
DisplayString | 即OCTET STRING |
IpAddress | 4字节的OcterString,以网络序表示的IP地址 |
PhysAddress | OCTET STRING,物理地址,以太网物理地址6字节长 |
Counter | 0-2^32-1之间的整数,达到最大值后归0 |
Gauge | 0-2^32-1之间的整数,达到最大值后锁定,直到复位 |
TimeTicks | 时间计数器,以0.01秒为单位递增 |
对象标识符(OID)用来识别一个“授权的”命名对象。所谓“授权”是指OID必须由权威机构进行管理和分配,而不是随意使用。
OID的形式是点号分割的整数序列,这些整数序列形成树形结构,类似与DNS或者UNIX文件系统的结构。另外,每一个整数还对应了一个文字的名字,这个名字是为了便于阅读的。OID树的结构示例如下图:
可以看到,所有SNMP MIB-2公共对象都定义在1.3.6.1.2.1下。而厂商自定义MIB对象都定义在1.3.6.1.4.1下。
所谓管理信息库,就是所有代理进程包含的、并且能够被管理进程进行查询和设置的信息的集合。管理信息库整体上以OID树为基础进行组织,它定义了信息(树节点)的详细规格——例如OID、数据类型、描述等。
管理信息库是通过ASN.1语言来定义的,对应的文件一般称为MIB(定义)文件。有关MIB定义文件的细节,我们在后续章节进行说明。
这里以1.3.6.1.2.1.7即udp组(定义在RFC1213-MIB中)为例来阐述管理信息库的运行时结构,该组比较简单,包含4简单变量、以及一个由两个简单变量组成的表格:
udp组下的4个简单变量可以用如下格式来描述:
名称 | 数据类型 | R/W | 描述 |
udpInDatagrams | Counter | UDP输入数据报个数 | |
udpNoPort | Counter | 没有发送到有效端口的UDP数据报个数 | |
udpInErrors | Counter | 接收到的有错误的UDP数据报个数 | |
udpOutDatagrams | Counter | UDP数据报输出数 |
R/W表示是否可读写,为空表示只读。如果变量可写,则使用点号(.)标注。如果数据类型有范围限制,则用 [lower, upper]形式标注。
udpTable(UDP监听表)下定义包含的两个简单变量我们可以用如下格式描述:
UDP监听表,索引=<udpLocalAddress>.<udpLocalPort> | |||
名称 | 数据类型 | R/W | 描述 |
udpLocalAddress | IpAddress | 监听的本地地址 | |
udpLocalPort | [0..65535] | 监听的端口号 |
注意我们在第一行描述了表的索引,即说明了列的先后顺序。
当对MIB变量进行读写时,必须对每个MIB变量进行标识(而不是仅仅使用OID)。需要注意的是,只有叶子节点是可操作的,SNMP没法操作表格的一整行或者一整列。
简单变量
对于简单变量,我们通过在OID后面添加.0来作为实例标识。例如udpInDatagrams的OID是1.3.6.1.2.1.7.1,那么它的实例标识则是1.3.6.1.2.1.7.1.0。这个实例标识会用在SNMP报文中。
表格
对于表格,实例标识比较复杂。 假设监听表中包含三个条目:0.0.0.0:67、0.0.0.0:161、0.0.0.0:520。那么表格实例标识是如何组织的呢?
SNMP使用OID.点号分隔的值列表的形式来表示表格中每一个单元格的实例标识,对于表格的每一列,OID是一致的;对于表格的每一行,值列表是一致的。按照这一规则:
- 条目0.0.0.0:67的udpLocalAddress的实例标识为:1.3.6.1.2.1.7.5.1.1.0.0.0.0.67
- 条目0.0.0.0:67的udpLocalPort的实例标识为: 1.3.6.1.2.1.7.5.1.2.0.0.0.0.67
在SNMP代理内部,对象标识(OID)是按字典式排序的,并且对于表格,是按照先列后行的顺序排列,因此三个条目(6个变量)的排列顺序将如下:
get-next操作是基于上述排序的,我们可以使用snmpi命令来多次get-next操作,并观察它如何遍历UDP监听表格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
snmpi -a 127.0.0.1 -c ****** #udpTable不是叶子节点,不需要也不能指定实例,但是get-next操作仍然能返回表格中下一个对象 snmpi > next udpTable #输出 udpLocalAddress.0.0.0.0.67=0.0.0.0 第1个条目,返回第1列第1行的值 snmpi > next udpLocalAddress.0.0.0.0.67 #输出 udpLocalAddress.0.0.0.0.161=0.0.0.0 第2个条目,返回第1列第2行的值 snmpi > next udpLocalAddress.0.0.0.0.161 #输出 udpLocalAddress.0.0.0.0.250=0.0.0.0 第3个条目,返回第1列第3行的值 snmpi > next udpLocalAddress.0.0.0.0.250 #输出 udpLocalPort.0.0.0.0.67=67 第1个条目,返回第2列第1行的值 snmpi > next udpLocalPort.0.0.0.0.67 #输出 udpLocalPort.0.0.0.0.161=161 第1个条目,返回第2列第2行的值 snmpi > next udpLocalPort.0.0.0.0.161 #输出 udpLocalPort.0.0.0.0.520=520 第1个条目,返回第2列第3行的值 snmpi > next udpLocalPort.0.0.0.0.520 #输出 snmpInPkts.0=59,变量名称前缀发生变化,提示遍历结束 |
ASN.1(Abstract Syntax Notation 1)是一种描述数据和数据特征的正式语言,它和数据的存储及编码无关。在正式的SNMP规范中,MIB和SNMP报文中所有字段的规格都是用ASN.1来描述的。下面的代码示例了ASN.1抽象语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
-- 类型定义语法:[新类型的名字] ::= [类型描述] -- 类型名字一般以大写字母开头 Married ::= BOOLEAN StaffEntry ::= SEQUENCE { staffCode OCTET STRING, dob INTEGER } -- 值定义语法:[新的值的名字] [该值的类型] ::= [值描述] system OBJECT IDENTIFIER ::= { mib-2 1 } pair Coordinates ::= { x 5, y -3 } counter Counter ::=0 -- 模块定义语法: -- [模块名] DEFINITIONS [缺省标记] ::= -- BEGIN -- EXPORTS [导出描述] -- IMPORTS [导入描述] -- [模块体] -- END RFC1213-MIB DEFINITIONS ::= BEGIN IMPORTS OBJECT-TYPE FROM RFC-1212; DisplayString ::= OCTET STRING END |
有了这样的一个抽象化定义,可以设计不同的编码方式把数据编码为比特流。SNMP使用的方法是BER(Basic Encoding Rule),BER是SNMP报文中比特的编码方式,对于10这个简单的整数,BER需要3个字节来表示:
- 第一个字节说明类型是一个整数
- 第二个字节说明后面有多少字节用来存放这个数(这里是1)
- 第三个字节存放真正的数值
ASN.1、BER是SNMP协议的实现者需要关心的内容。
最初版本的SNMP协议在1990年,由RFC 1157发布,它定义了5种报文,来表示管理进程与代理进程之间的交互信息(其中前三个由管理进程发出,后二个由代理进程发出):
- get-request:从代理进程处提取一个或多个变量值
- get-next-request:从代理进程处提取一个或多个变量的“下一个”变量值
- set-request:设置代理进程的一个或多个变量值
- get-response:返回的一个或多个变量值
- trap:通知管理进程有某些事情发生
这些报文的关系如下图所示:
这些报文的结构如下:
除了trap报文的结构描述如下:
- 版本:SNMP版本号-1得到
- 共同体(团体名):一个字符串。这是管理进程和代理进程之间的口令,明文格式。默认的值是public
- PDU类型:即协议数据单元类型,如下表:在TCP/IP协议族中,表示某个协议的对等实体之间进行交换的单位信息(即单个报文)。SNMP定义了多种报文格式,因此它使用PDU类型来区分不同的报文
- 请求标识:对于get*、get-next*、set操作,请求标识由管理进程设置,然后由代理进程在get-response中返回。用于匹配查询与应答
- 差错状态:是由代理进程标注指明有差错发生,如下表:
- 差错索引:一个整数偏移量,指明当有差错发生时,差错发生在哪个变量。它是由代理进程标注的,并且只有在发生noSuchName、readOnly和badValue差错时才进行标注
- 对于get、get-next、set请求数据报,包含变量名称、变量值的一张表。其中get、get-next的变量值不用填写
trap报文的结构描述如下:
- 代理地址:即发送trap报文的代理进程的地址
- trap类型:如下表:
1993年新的SNMP协议被发布,它包含以下改进:
- 定义了新的报文:get-bulk-request,可以高效率的从代理进程获取大量数据。可以用来代替迭代式的GetNextRequests
- 定义了新的报文:inform-request,使一个管理进程可以向另一个管理进程发送信息
- 定义了两个新的MIB:SNMP v2 MIB以及SNMP v2-M2M MIB
- 提高了安全性:SNMP v1中团体名是以明文方式发送的,现在可以提高身份验证和加密强度
SNMP v2还包含一些变体:
- SNMPv2c:即基于团体名的SNMP v2(Community-Based Simple Network Management Protocol version 2)。它剔除了SNMP v2有争议的新安全模型,仍然使用SNMP v1的基于团体名的安全模型。该版本被认为是实际上的SNMP v2标准
- SNMPv2u:即基于用户的SNMP v2(User-Based Simple Network Management Protocol version 2)。它尝试在提高安全性的同时,避免引入SNMP v2的复杂性
SNMP v2c和SNMP v1存在兼容性问题:
- SNMP v2c使用了不同格式的报文头,以及PDU格式。特别是,在SNMPv1中被作为特殊报文格式看待的trap,现在格式其它PDU例如get-request一样
- 引入了SNMP v1没有的新的协议报文
这是SNMP协议的最新版本,通过对数据进行身份验证和加密,SNMP v3确保了防篡改、保密等安全特性。
除了密码学安全性的增强外,SNMP3还增加了新的PDU类型,例如report
下面是MIB文件RFC1213-MIB中的片段,包含了本文用到的一些节点的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
-- -- 注释以两个小横线开头 -- -- 整个MIB文件呈:[模块名] DEFINITIONS := BEGIN [模块定义] END的形式 RFC1213-MIB DEFINITIONS ::= BEGIN -- 声明从其它模块导入的数据类型、宏和节点定义 IMPORTS OBJECT-TYPE -- 这是一个宏 FROM RFC-1212 mgmt, TimeTicks, IpAddress, Counter, Gauge, -- 这是数据类型 NetworkAddress -- 这是一个节点 FROM RFC1155-SMI; -- -- 模块定义的类型 -- DisplayString ::= OCTET STRING PhysAddress ::= OCTET STRING -- -- 模块定义的节点 -- -- 这是一个简单的OID声明,类似于名字空间 -- 1.3.6.1.2.1 mib-2 OBJECT IDENTIFIER ::= { mgmt 1 } -- 花括号中的部分属于类型描述,说明了父节点的名字,以及当前节点的编号 -- 1.3.6.1.2.1.1 system OBJECT IDENTIFIER ::= { mib-2 1 } -- 这是一个标量定义 -- 1.3.6.1.2.1.1.1 sysDescr OBJECT-TYPE -- OBJECT-TYPE宏用于定义标量、表格或者表格条目 -- SYNTAX指明节点的数据类型,这里指明类型为DisplayString,尺寸限制为0-255个字符 SYNTAX DisplayString (SIZE (0..255)) -- 最大访问权限 ACCESS read-only -- 节点状态 STATUS mandatory DESCRIPTION "A textual description of the entity. This value should include the full name and version identification of the system's hardware type, software operating-system, and networking software. It is mandatory that this only contain printable ASCII characters." ::= { system 1 } -- UDP监听表格的定义 -- 1.3.6.1.2.1.7.5 udpTable OBJECT-TYPE SYNTAX SEQUENCE OF UdpEntry --该表格是UdpEntry类型的序列 ACCESS not-accessible STATUS mandatory DESCRIPTION "A table containing UDP listener information." ::= { udp 5 } -- UDP监听表格条目定义 -- 1.3.6.1.2.1.7.5.1 udpEntry OBJECT-TYPE SYNTAX UdpEntry -- 一个SEQUENCE类型 ACCESS not-accessible STATUS mandatory DESCRIPTION "Information about a particular current UDP listener." INDEX { udpLocalAddress, udpLocalPort } -- 索引,确定了列顺序,列顺序则确定了表格单元格实例OID的编码方式 ::= { udpTable 1 } -- 声明UdpEntry类型,这是一个包含2字段的序列 UdpEntry ::= SEQUENCE { udpLocalAddress IpAddress, udpLocalPort INTEGER } -- 1.3.6.1.2.1.7.5.1.1 udpLocalAddress OBJECT-TYPE SYNTAX IpAddress ACCESS read-only STATUS mandatory DESCRIPTION "The local IP address for this UDP listener. In the case of a UDP listener which is willing to accept datagrams for any IP interface associated with the node, the value 0.0.0.0 is used." ::= { udpEntry 1 } -- 1.3.6.1.2.1.7.5.1.2 udpLocalPort OBJECT-TYPE SYNTAX INTEGER (0..65535) -- 端口,具有数据范围限制 ACCESS read-only STATUS mandatory DESCRIPTION "The local port number for this UDP listener." ::= { udpEntry 2 } END -- 这里表示模块定义结束 |
Leave a Reply