net-snmp的MIBs扩展(linux下)
2015年06月25日[TOC] 博客园文章地址 http://www.cnblogs.com/oloroso/archive/2015/06/25/4599501.html
net-snmp的MIBs扩展
-
MIB的相关概念
在SNMP网络管理中,管理信息库MIB(Management Information Base)是对于通过网络管理协议可以访问的信息。这些信息更具体的理解为网管中被管资源,而网络管理中的资源是以对象来表示,每一个对象表示被管资源某一方面的属性,这些对象的集合形成管理信息库。 -
先说一下系统环境
o@o-pc:~$ uname -a Linux o-pc 3.19.0-21-generic #21-Ubuntu SMP Sun Jun 14 18:31:11 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
1 编译安装net-snmp
具体的就不说了,见此处: net-snmp-5.7.3配置编译安装 http://www.cnblogs.com/oloroso/p/4595123.html
2 编写MIB文件
MIB文件描述
MIB文件是用
ASN.1
语法来描述的,所以为了精确定义MIB中各管理对象,用户不得不参考一些ASN.1语法的有关文档如RFC1155、RFC1212等等来定义设备自己的MIB。ASN.1是抽象句法表示法一
(Abstract Syntax Notation One)
的简称,对于每个管理对象它都用文本来描述,一般文件的后缀名都用“.mib”。
关于MIB文件示例,可以见编译安装后的net-snmp目录,一般是
/usr/local/net-snmp/share/snmp/mibs/
。
ls /usr/local/net-snmp/share/snmp/mibs/ AGENTX-MIB.txt IPV6-TCP-MIB.txt SNMP-NOTIFICATION-MIB.txt … IPV6-MIB.txt SNMP-MPD-MIB.txt UDP-MIB.txt
一个简单的示例
这里我们写一个MIB文件,含有两个节点,一个是只读权限的,一个是读写权限的。然后把这个文件命名为 myTest.mib
然后保存到 /usr/local/net-snmp/share/snmp/mibs/
目录。
–开始 TEST-MIB DEFINITIONS ::= BEGIN –引入部分 IMPORTS enterprises FROM RFC1155-SMI
Integer32,OBJECT-TYPE FROM SNMPv2-SMI
DisplayString FROM SNMPv2-TC TEXTUAL-CONVENTION FROM SNMPv2-TC; –引用结束,用分号 –定义节点 –enterprises的OID是1.3.6.1.4 test OBJECT IDENTIFIER ::= {enterprises 77587}readObject OBJECT IDENTIFIER ::
= {test 1}
writeObject OBJECT IDENTIFIER ::= {test 2}–结束定义 ENDreadobject OBJECT</span>-TYPE --<span style="color: #000000;">对象名称 SYNTAX Integer32 </span>--<span style="color: #000000;">类型 MAX</span>-ACCESS read-only --<span style="color: #000000;">访问方式 STATUS current </span>--<span style="color: #000000;">状态 DESCRIPTION </span><span style="color: #800000;">"</span><span style="color: #800000;">test read</span><span style="color: #800000;">"</span> --<span style="color: #000000;">描述 ::</span>= {test <span style="color: #800080;">1</span>} --<span style="color: #000000;">父节点 writeObject OBJECT</span>-TYPE --<span style="color: #000000;">对象名称 SYNTAX DisplayString </span>--<span style="color: #000000;">类型 MAX</span>-ACCESS read-<span style="color: #0000ff;">write</span> --<span style="color: #000000;">访问方式 STATUS current </span>--<span style="color: #000000;">状态 DESCRIPTION </span><span style="color: #800000;">"</span><span style="color: #800000;">test write</span><span style="color: #800000;">"</span> --<span style="color: #000000;">描述 ::</span>= {test <span style="color: #800080;">2</span>} --<span style="color: #000000;">父节点
3 使自定义的MIB文件生效
要使得这个自定义的MIB生效,最简单的办法就是把它的内容添加到已有的某个MIB文件中就是了。比如 cat myTest.mib >> 原有MIB文件
,然后重启一下 snmpd
服务就是了。不过这是一种投机的做法。
正常一点的做法是:
-
将自定义的MIB文件的开始处定义的名称加入到环境变量
MIBS
中,譬如这里TEST-MIB DEFINITIONS ::= BEGIN
中的TEST-MIB
。
还可以添加到snmpd
的配置文件中。因为本机上是采取的添加到bash
的环境变量中的,所以在~/.bashrc
文件中添加了一行
export MIBS=+TEST-MIB
-
杀死
snmpd
进程,然后重启它。 -
使用
snmptranslate
查看自定义的TEST-MIB
是否被加载了。
o@o-pc:~/snmp/mibs$ /usr/local/net-snmp/bin/snmptranslate -Tp -IR test +–test(77587) | +– -R– Integer32 readObject(1) +– -RW- String writeObject(2) Textual Convention: DisplayString Size: 0..255
4 实现agent代理程序
我们可以先来获取一下前面定义的 readObject
节点的值试试。
因为
enterprises
的OID是
1.3.6.1.4
,而
test
是
enterprises
的叶子(77585),而
readObject
又是
test
的叶子节点(1)。所以其OID为
1.3.6.1.4.77585.1
。
下面使用snmpget来测试一下
o@o-pc:~/snmp/mibs$ /usr/local/net-snmp/bin/snmpget -c public -v 2c localhost 1.3.6.1.4.1.77587.1 TEST-MIB::readObject = No Such Object available on this agent at this OID
结果是No Such Object available on this agent at this OID,即
使用mib2c程序来生成 .c
和 .h
文件。
使用以下命令来生成,两条命令的 -c
后跟的配置文件可以是不同的,因为节点的类型可能不一样。第一个是(只读的)数字(integer32,counter,time)的配置,第二个是其它类型的。
mib2c -c mib2c.int_watch.conf readObject
mib2c -c mib2c.scalar.conf writeObject
只读节点readObject.c和readObject.h的修改。
readObject.h文件就不改了,这里没什么必要。
先来看一下readObject.c文件。(这里我把不重要的删掉了)
所做的修改就是在
init_readObject
函数中添加了一句
readObject = 12345;
。这个是随性而为的。我们只需要知道,当使用
snmpget
来获取
readObject
节点值的时候,获取的就是这个变量的值。这里还要注意一点,这个值一旦确定就不会再更改了。如果需要可以更改的,前面生成代码模板的时候应该使用 mib2c -c mib2c.scalar.conf readObject
#include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> #include “readObject.h“//这一句是mib2c生成的,默认值设为0 long readObject = 0; / XXX: set default value /
void init_readObject(void) { netsnmp_handler_registration *reg;
</span><span style="color: #0000ff;">const</span> oid readObject_oid[] = { <span style="color: #800080;">1</span>,<span style="color: #800080;">3</span>,<span style="color: #800080;">6</span>,<span style="color: #800080;">1</span>,<span style="color: #800080;">4</span>,<span style="color: #800080;">1</span>,<span style="color: #800080;">77587</span>,<span style="color: #800080;">1</span><span style="color: #000000;"> };
static netsnmp_watcher_info readObject_winfo;
DEBUGMSGTL((“readObject“, “Initializing the readObject module\n“));
/****************************************************/ //这里是我修改的,以便于验证其有效 readObject = 12345;
/****************************************************/ DEBUGMSGTL((“readObject“, “Initializing readObject scalar integer. Default value = %ld\n“, readObject)); reg = netsnmp_create_handler_registration( “readObject“, NULL, readObject_oid, OID_LENGTH(readObject_oid), HANDLER_CAN_RONLY); netsnmp_init_watcher_info(&readObject_winfo, &readObject, sizeof(long), ASN_INTEGER, WATCHER_FIXED_SIZE); if (netsnmp_register_watched_scalar( reg, &readObject_winfo ) < 0 ) { snmp_log( LOG_ERR, “Failed to register watched readObject“ ); }
DEBUGMSGTL((“readObject“, “Done initalizing readObject module\n“)); }
编译一下
编译的时候需要使用到另一个工具 net-snmp-config
。这个工具用来做两件事,一个是生成中间代码,然后使用gcc来编译它。
为什么要生成中间代码呢?你看上面的生存的
readObject.c
中没有main函数就知道了吧。
具体使用如下
net-snmp-config –compile-subagent readObject readObject.c
- –compile-subagent的意思是编译为subagent程序。
- readObject是编译后输出程序名
- readObject.c是要编译的文件
还可以加上 –norm
参数来阻止编译后删除生成的中间代码文件。我们可以试一下。
o@o-pc:~/snmp/mibs$ net-snmp-config –compile-subagent –norm readObject readObject.c generating the tmporary code file: netsnmptmp.25506.c void init_readObject(void); checking for init_readObject in readObject.c init_readObject(void) checking for shutdown_readObject in readObject.c running: gcc -fno-strict-aliasing -g -O2 -Ulinux -Dlinux=linux -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/lib/x86_64-linux-gnu/perl/5.20/CORE -I. -I/usr/local/net-snmp/include -o readObject netsnmptmp.25506.c readObject.c -L/usr/local/net-snmp/lib -lnetsnmpmibs -lnetsnmpagent -lnetsnmp -lnetsnmpmibs -ldl -lnetsnmpagent -Wl,-E -lnetsnmp
leaving the tmporary code file: netsnmptmp.25506.c subagent program readObject created
编译后我们可以看到,当前目录下出现了编译后的程序 readObject
和一个 netsnmptmp.25506.c
的文件。你把它打开,就可以看到这是一个具有 main
函数的.c源文件了。简化之后的代码如下了。
#include “readObject.h“ const char *app_name = “readObject“; static int reconfig = 0; extern int netsnmp_running; int main (int argc, char **argv) { … while ((arg = getopt(argc, argv, …)) != EOF) { … } … /* 初始化agent库 * /initialize the agent library / init_agent(app_name); / i初始化你的mib代码 / / initialize your mib code here / init_readObject(); / readObject will be used to read readObject.conf files. / / readObject will be used to read readObject.conf files. / init_snmp(“readObject“); … exit(0); }
运行测试一下
这里我们先启动这个 readObject
的程序,然后再去重新 snmpget
获取节点 readObeject
的值。
注意,这个
readObject
默认是守护进程。
获取结果如下,可以看到成功获取了。
o@o-pc:~/snmp/mibs$ /usr/local/net-snmp/bin/snmpget -c public -v 2c localhost 1.3.6.1.4.1.77587.1 …TEST
-MIB::readObject.0 = INTEGER: 12345
读写节点writeObject.c的修改
下面是修改后的writeObject.c文件
#include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> #include “writeObject.h“//这里是添加的,buf用于保存控制端设置的值,也用于返回。 #define BUFSIZE 1024 static char buf[BUFSIZE] = “test Write“; //给一个默认值
/ Initializes the writeObject module */ void init_writeObject(void) { const oid writeObject_oid[] = { 1,3,6,1,4,1,77587,2 };
DEBUGMSGTL((“writeObject“, “Initializing\n“));
netsnmp_register_scalar( netsnmp_create_handler_registration(</span><span style="color: #800000;">"</span><span style="color: #800000;">writeObject</span><span style="color: #800000;">"</span><span style="color: #000000;">, handle_writeObject, writeObject_oid, OID_LENGTH(writeObject_oid), HANDLER_CAN_RWRITE ));
}
int handle_writeObject(netsnmp_mib_handler handler, netsnmp_handler_registration reginfo, netsnmp_agent_request_info reqinfo, netsnmp_request_info requests) { int ret;
</span><span style="color: #0000ff;">switch</span>(reqinfo-><span style="color: #000000;">mode) { </span><span style="color: #008000;">//</span><span style="color: #008000;">是获取操作</span> <span style="color: #0000ff;">case</span><span style="color: #000000;"> MODE_GET: snmp_set_var_typed_value(requests</span>-><span style="color: #000000;">requestvb, ASN_OCTET_STR, </span><span style="color: #008000;">/*</span><span style="color: #008000;">这里填buf,用于返回数据给控制端</span><span style="color: #008000;">*/</span> buf <span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: a pointer to the scalar's data </span><span style="color: #008000;">*/</span><span style="color: #000000;">, </span><span style="color: #008000;">/*</span><span style="color: #008000;">这里是buf数据字节数,注意writeObject类型</span><span style="color: #008000;">*/</span> strlen(buf) <span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: the length of the data in bytes </span><span style="color: #008000;">*/</span><span style="color: #000000;">); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #008000;">/*</span><span style="color: #008000;"> * SET REQUEST * * multiple states in the transaction. See: * </span><span style="color: #008000; text-decoration: underline;">http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg</span> <span style="color: #008000;">*/</span> <span style="color: #008000;">//</span><span style="color: #008000;">下面是设置操作的,也就是snmpset</span> <span style="color: #0000ff;">case</span> MODE_SET_RESERVE1: <span style="color: #008000;">//</span><span style="color: #008000;">这个不管它</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> or you could use netsnmp_check_vb_type_and_size instead </span><span style="color: #008000;">*/</span><span style="color: #000000;"> ret </span>= netsnmp_check_vb_type(requests-><span style="color: #000000;">requestvb, ASN_OCTET_STR); </span><span style="color: #0000ff;">if</span> ( ret !=<span style="color: #000000;"> SNMP_ERR_NOERROR ) { netsnmp_set_request_error(reqinfo, requests, ret ); } </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">case</span> MODE_SET_RESERVE2: <span style="color: #008000;">//</span><span style="color: #008000;">这个也不管它</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> XXX malloc "undo" storage buffer </span><span style="color: #008000;">*/</span> <span style="color: #008000;">//</span><span style="color: #008000;">我们不需要动态申请内存,直接略过</span> <span style="color: #0000ff;">if</span> ( <span style="color: #800080;">0</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> XXX if malloc, or whatever, failed: </span><span style="color: #008000;">*/</span><span style="color: #000000;">) { netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE); } </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MODE_SET_FREE: </span><span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: free resources allocated in RESERVE1 and/or RESERVE2. Something failed somewhere, and the states below won't be called. </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #008000;">/*</span><span style="color: #008000;">**************************************************************</span><span style="color: #008000;">*/</span> <span style="color: #008000;">//</span><span style="color: #008000;"> 这里是我们的重点,控制端传过来的数据就在这里获取</span> <span style="color: #0000ff;">case</span><span style="color: #000000;"> MODE_SET_ACTION: </span><span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: perform the value change here </span><span style="color: #008000;">*/</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> 获取控制端使用snmpset传来的数据 </span><span style="color: #008000;">*/</span><span style="color: #000000;"> memcpy(buf,requests</span>->requestvb->buf,requests->requestvb-><span style="color: #000000;">val_len); </span><span style="color: #0000ff;">if</span> (<span style="color: #800080;">0</span><span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: error? </span><span style="color: #008000;">*/</span>) { <span style="color: #008000;">//</span><span style="color: #008000;">这个先不管了</span> netsnmp_set_request_error(reqinfo, requests, <span style="color: #800080;">0</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> some error </span><span style="color: #008000;">*/</span><span style="color: #000000;">); } </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
//下面的都不管了 case MODE_SET_COMMIT: / XXX: delete temporary storage / if (0 / XXX: error? /) { / try _reallyreally hard to never get to this point / netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED); } break;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MODE_SET_UNDO: </span><span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: UNDO and return to previous value for the object </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">if</span> (<span style="color: #800080;">0</span> <span style="color: #008000;">/*</span><span style="color: #008000;"> XXX: error? </span><span style="color: #008000;">*/</span><span style="color: #000000;">) { </span><span style="color: #008000;">/*</span><span style="color: #008000;"> try _really_really_ hard to never get to this point </span><span style="color: #008000;">*/</span><span style="color: #000000;"> netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED); } </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">default</span><span style="color: #000000;">: </span><span style="color: #008000;">/*</span><span style="color: #008000;"> we should never get here, so this is a really bad error </span><span style="color: #008000;">*/</span><span style="color: #000000;"> snmp_log(LOG_ERR, </span><span style="color: #800000;">"</span><span style="color: #800000;">unknown mode (%d) in handle_writeObject\n</span><span style="color: #800000;">"</span>, reqinfo-><span style="color: #000000;">mode ); </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> SNMP_ERR_GENERR; } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> SNMP_ERR_NOERROR;
}
编译运行测试一下
编译还是和前面说的一样,就不详细说了。
一样运行后进行获取一次试试。这里使用的snmpbulkget(批量获取)而不是snmpget。是因为任性而已。
o@o-pc:~/snmp/mibs$ snmpbulkget -c public -v 2c localhost writeObject.0 MY-TEST-MIB::writeObject.0 = STRING: test Writeo@o
-pc:~/snmp/mibs$ snmpget -c public -v 2c localhost writeObject.0 MY-TEST-MIB::writeObject.0 = STRING: test Write
可以看大获取是没有问题的,那么设置一下试试。设置要使用到 snmpset
工具,使用方式和 snmpget
类似。只是需要在最后加上要设置的数据的类型和数据。
snmpset -c public -v 2c localhost writeObject.0 s “nihao”
数据类型参数可以使用 snmpset –help
来查看,其结果如下
TYPE: one of i, u, t, a, o, s, x, d, b i: INTEGER, u: unsigned INTEGER, t: TIMETICKS, a: IPADDRESS o: OBJID, s: STRING, x: HEX STRING, d: DECIMAL STRING, b: BITS U: unsigned int64, I: signed int64, F: float, D: double
设置完成之后再次获取试试。
o@o-pc:~/snmp/mibs$ snmpget -c public -v 2c localhost writeObject.0 MY-TEST-MIB::writeObject.0 = STRING: nihao
如果测试的时候碰到设置不成功的情况,检查一下snmpd的配置文件(通常是snmpd.conf)中权限设置的问题。
o@o-pc:~/snmp/mibs$ snmpset -c public -v 2c localhost writeObject.0 s “nihao“ Error in packet. Reason: noAccess Failed object: MY-TEST-MIB::writeObject.0