目录
管理信息库MIB指明了网络元素所维持的变量(即能够被管理进程查询和设置的信息)。MIB给出了一个网络中所有可能的被管理对象的集合的数据结构。SNMP的管理信息库采用和域名系统DNS相似的树型结构,它的根在最上面,根没有名字。下图画的是管理信息库的一部分,它又称为对象命名树(objectnamingtree)。(图片来自网络)
对象命名树的顶级对象有三个,即ISO、ITU-T和这两个组织的联合体。在ISO的下面有4个结点,其中的一个(标号3)是被标识的组织。在其下面有一个美国国防部(Department of Defense)的子树(标号是6),再下面就是Internet(标号是1)。在只讨论Internet中的对象时,可只画出Internet以下的子树(图中带阴影的虚线方框),并在Internet结点旁边标注上{1.3.6.1}即可。
在Internet结点下面的第二个结点是mgmt(管理),标号是2。再下面是管理信息库,原先的结点名是mib。1991年定义了新的版本MIB-II,故结点名现改为mib-2,其标识为{1.3.6.1.2.1},或{Internet(1) .2.1}。这种标识为对象标识符。
最初的结点mib将其所管理的信息分为8个类别,见表4(图片来自网络)。现在的mib-2所包含的信息类别已超过40个。
应当指出,MIB的定义与具体的网络管理协议无关,这对于厂商和用户都有利。厂商可以在产品(如路由器)中包含SNMP代理软件,并保证在定义新的MIB项目后该软件仍遵守标准。用户可以使用同一网络管理客户软件来管理具有不同版本的MIB的多个路由器。当然,一个没有新的MIB项目的路由器不能提供这些项目的信息。
管理信息结构SMI
SNMP中,数据类型并不多。这里我们就讨论这些数据类型,而不关心这些数据类型在实际中是如何编码的。
一个变量虽然定义为整型,但也有多种形式。有些整型变量没有范围限制,有些整型变量定义为特定的数值(例如,IP的转发标志就只有允许转发时的或者不允许转发时的这两种),有些整型变量定义一个特定的范围(例如,UDP和TCP的端口号就从0到65535)。
0或多个8bit字节,每个字节值在0~255之间。对于这种数据类型和下一种数据类型的BER编码,字符串的字节个数要超过字符串本身的长度。这些字符串不是以NULL结尾的字符串。
0或多个8bit字节,但是每个字节必须是ASCII码。在MIB-II中,所有该类型的变量不能超过255个字符(0个字符是可以的)。
代表相关的变量没有值。例如,在get或get-next操作中,变量的值就是NULL,因为这些值还有待到代理进程处去取。
4字节长度的OCTERSTRING,以网络序表示的IP地址。每个字节代表IP地址的一个字段。
OCTERSTRING类型,代表物理地址(例如以太网物理地址为6个字节长度)。
非负的整数,可从0递增到232—1(4294976295)。达到最大值后归0。
非负的整数,取值范围为从0到4294976295(或增或减)。达到最大值后锁定直到复位。例如,MIB中的tcpCurrEstab就是这种类型的变量的一个例子,它代表目前在ESTABLISHED或CLOSE_WAIT状态的TCP连接数。
时间计数器,以0.01秒为单位递增,但是不同的变量可以有不同的递增幅度。所以在定义这种类型的变量的时候,必须指定递增幅度。例如,MIB中的sysUpTime变量就是这种类型的变量,代表代理进程从启动开始的时间长度,以多少个百分之一秒的数目来表示。
这一数据类型与C程序设计语言中的“structure”类似。一个SEQUENCE包括0个或多个元素,每一个元素又是另一个ASN.1数据类型。例如,MIB中的UdpEntry就是这种类型的变量。它代表在代理进程侧目前“激活”的UDP数量(“激活”表示目前被应用程序所用)。在这个变量中包含两个元素:
IpAddress类型中的udpLocalAddress,表示IP地址。
INTEGER类型中的udpLocalPort,从0到65535,表示端口号。
这是一个向量的定义,其所有元素具有相同的类型。如果每一个元素都具有简单的数据类型,例如是整数类型,那么我们就得到一个简单的向量(一个一维向量)。但是我们将看到,SNMP在使用这个数据类型时,其向量中的每一个元素是一个SEQUENCE(结构)。因而可以将它看成为一个二维数组或表。
SNMP规定了5种协议数据单元PDU(也就是SNMP报文),用来在管理进程和代理之间交换。
get-request操作:从代理进程处提取一个或多个参数值
get-next-request操作:从代理进程处提取紧跟当前参数值的下一个参数值
set-request操作:设置代理进程的一个或多个参数值
get-response操作:返回的一个或多个参数值。这个操作是由代理进程发出的,它是前面三种操作的响应操作。
trap操作:代理进程主动发出的报文,通知管理进程有某些事情发生。
前面的3种操作是由管理进程向代理进程发出的,后面的2个操作是代理进程发给管理进程的,为了简化起见,前面3个操作今后叫做get、get-next和set操作。图4描述了SNMP的这5种报文操作。请注意,在代理进程端是用熟知端口161来接收get或set报文,而在管理进程端是用熟知端口162来接收trap报文。(图片来自网络)
下图是封装成UDP数据报的5种操作的SNMP报文格式。可见一个SNMP报文共有三个部分组成,即公共SNMP首部、get/set首部trap首部、变量绑定。(图片来自网络)
共三个字段:
版本:写入版本字段的是版本号减1,对于SNMP(即SNMPV1)则应写入0。
共同体(community):共同体就是一个字符串,作为管理进程和代理进程之间的明文口令,常用的是6个字符“public”。
PDU类型:根据PDU的类型,填入0~4中的一个数字,其对应关系如下图所示:
请求标识符(request ID):这是由管理进程设置的一个整数值。代理进程在发送get-response报文时也要返回此请求标识符。管理进程可同时向许多代理发出get报文,这些报文都使用UDP传送,先发送的有可能后到达。设置了请求标识符可使管理进程能够识别返回的响应报文对于哪一个请求报文
差错状态(error status):由代理进程回答时填入0~5中的一个数字,见下图中的描述
差错索引(error index):当出现noSuchName、badValue或readOnly的差错时,由代理进程在回答时设置的一个整数,它指明有差错的变量在变量列表中的偏移。
企业(enterprise):填入trap报文的网络设备的对象标识符。此对象标识符肯定是在图3的对象命名树上的enterprise结点{1.3.6.1.4.1}下面的一棵子树上。
trap类型:此字段正式的名称是generic-trap,共分为下图中的7种。3、5时,在报文后面变量部分的第一个变量应标识响应的接口。
特定代码(specific-code):指明代理自定义的时间(若trap类型为6),否则为0。
时间戳(timestamp):指明自代理进程初始化到trap报告的事件发生所经历的时间,单位为10ms。例如时间戳为1908表明在代理初始化后1908ms发生了该时间。
指明一个或多个变量的名和对应的值。在get或get-next报文中,变量的值应忽略。
Lwip中自带的是snmp v1的客户端。该客户端使用raw-api类型的UDP接口。Lwip中管理的信息库基于MIB-2,MIB-2信息库中几乎所有的对象都不是可写的,除了以下几个:sysName,sysLocation,sysContact,snmpEnableAuthenTraps,写或者改变arp表router表以及ip地址都是不可能的。另外,MIB库的信息只能在编译的时候添加,而不能在运行时添加,所以额外的信息库需要手动编写代码实现。
要将lwip自带的snmp协议添加到工程中,需要作如下几步工作:
首先需要在头文件中定义对lwip_snmp的支持
#define LWIP_SNMP 1
然后将源代码和头文件添加到工作目录中。
注意:可能需要根据具体驱动的实现更新MIB-2中有关接口的变量
要使snmp客户端运行起来,下列函数需要被调用:
在启动客户端之前,应当为sysContact,sysLocation以及snmpEnableAuthenTraps变量提供指向非易失性存储器的指针。这一点可以通过调用下面几个函数做到:
snmp_set_syscontact()
snmp_set_syslocation()
snmp_set_snmpenableauthentraps()
另外,可能还需要设置:
snmp_set_sysdescr()
snmp_set_sysobjid() (if you have a private MIB)
snmp_set_sysname()
在启动客户端之前还需要设置一个或者多个trap destination,这可以通过下面的调用做到:
snmp_trap_dst_enable();
snmp_trap_dst_ip_set();
在lwip的初始化序列中,snmp_init应当在udp_init调用之后。
最后还需要实现一个定时器,每隔10毫秒调用snmp_inc_sysuptime()来更新snmp的更新时间戳
如果想要添加私有的MIB来扩展snmp的客户端,则需要完成如下一些工作:
首先,需要在本地的lwipopt.h头文件中进行如下的定义,表示支持私有MIB
#define SNMP_PRIVATE_MIB 1
另外,需要提供一个private_mib.h文件并与自己的文件相关联。因为并不提供一个MIB compiler为某个MIB生成相应的c代码,所以这需要自己来完成相应的编码工作。
如果要创建自己的MIB,还需要向iana申请自己的Enterprise id。
注意,变量的标识对象符号和自己私有的MIB树必须按照字典序的升序来排列,这样才能保证get next操作的正确执行。
私有MIB的一个例子就是最小unix工程的一部分,。。。
这部分简要讨论lwip中关于snmp网络管理协议客户端程序的实现
在tcp/ip中,snmp是通过UDP包来承载的,所以在初始化时创建一个全局的UDP控制块snmp1_udp,并注册接收函数snmp_recv和snmp知名端口161。这样当有snmp包到达时就可以调用snmp_recv进行相应的处理。
在正式的snmp规范中都采用ASN.1语法,并且在snmp报文中比特的编码采用BER。ASN.1是一种描述数据和数据特征的正式语言。它和数据的存储及编码无关。MIB和snmp报文中所有的字段都是用ASN.1描述的。
<1>编码
有关snmp中所用数据的编码在文件asn1_enc.c中实现。
<2>解码
有关snmp中所用抽象符号的解码在文件asn1_dec.c中实现。
<1>信息库
在mib2.c文件中定义了管理信息库-2的对象和操作接口
<2>操作(主要是信息树的加入删除查找等)
在mib_structs.c文件中实现了MIB树的构造和访问接口。
<1>输入操作接口
在初始化部分已经介绍了,输入的数据包都首先经由snmp_recv处理。Snmp_recv对输入的数据包进行分解,针对不通的请求调用相应的处理函数进一步处理。
输入包的处理从snmp_recv开始。输入的数据被存放在pbuf中,所以有关数据的操作都要根据pbuf的结构来进行。首先调用pbuf_header将pbuf的数据指针payload调整到UDP头部分,并获取UDP头部的数据。程序中定义了一个数据结构struct snmp_msg_pstat用来保存输入包承载的与snmp有关的信息,比如版本、共同体、pdu类型等等。后续对输入包进行解析获得的所有信息都会保存到该数据结构中。另外,该结构还保存了有关UDP控制块的一些信息,比如ip地址,端口号等。
在获得snmp数据包后,就对其进行解析。Snmp头部信息的解析是通过snmp_pdu_header_check函数来完成的。该函数根据asn1编解码的特征对数据进行解码处理。如之前所述,解析获得的信息保存在snmp_msg_pstat结构中,以便后续进一步处理。对snmp变量部分的解析是通过接口snmp_pdu_dec_varbindlist完成的,相应的信息也保存在snmp_msg_pstat结构体中。最后,调用snmp_msg_event处理该输入事件。
Snmp_msg_event函数处理get-request、get-next-request以及set-request请求,并对需要发送response的事件发送相应的response给管理进程。对应的请求处理函数为:snmp_msg_get_event、snmp_msg_getnext_event、snmp_msg_set_event。
<2>输出操作接口
所有输出的包通过snmp_send_response和snmp_send_trap两个接口来完成。Snmp_send_trap在客户端有一些事件发生时被调用来向管理进程进行通告。Snmp_send_response用于响应管理进程的请求。另外,发送部分的处理可以分为三类:对trap响应的处理,对response头的处理以及对varbind的处理。对每一类,分别有两个处理函数,一个是以_sum结束的用于计算长度的接口,一个是以_enc结束的用于进行编码的接口。六个接口分别是:{snmp_trap_header_sum,snmp_trap_header_enc };{snmp_resp_header_sum ,snmp_resp_header_enc};{snmp_varbind_list_sum,snmp_varbind_list_enc}
<3>变量部分的处理
变量部分是被保存在一个struct snmp_varbind类型的双向链表数据结构中进行处理的。该链表结构和其头结构struct snmp_varbind_root同样都被保存在snmp_msg_pstat结构中。
snmp_varbind_alloc:该接口分配一个变量节点结构,用于保存从输入数据包中获得的一个变量信息。
snmp_varbind_free:用于释放一个变量节点占用的资源。
snmp_varbind_tail_add:该接口用于将一个变量节点添加到变量双向链表的尾巴。
snmp_varbind_tail_remove:该接口用于将一个变量节点从变量双向链表的尾部移除。
snmp_varbind_list_free:该接口从链表的尾巴开始,从后向前,依次释放链表中的各个节点,也就是保存有各个变量的数据结构,知道最后只剩根节点。根节点只保存链表的头尾节点的指针,当节点全被释放后就变为空。