风吹过


以前,晚餐后在学校田径场的大榕树下,散散步吹吹风,累了就去图书馆看看书,感觉真好。


net-snmp子代理(SubAgent)编写详述

[TOC] 博客园文章地址 http://www.cnblogs.com/oloroso/archive/2015/08/06/4708581.html

net-snmp子代理(SubAgent)编写

可以使用mib2c生成子代理的代码来编写子代理程序,但是这个方式并不利于我们来学习这个开发过程。

本文由乌合之众 lym瞎编,欢迎转载 blog.cnblogs.net/oloroso
本文由乌合之众 lym瞎编,欢迎转载 my.oschina.net/oloroso

Netsnmp_Node_Handler

先来看一个类型定义

net-snmp源码目录include/net-snmp/agent/下的agent_handler.h文件中有如下定义:

1 typedef int (Netsnmp_Node_Handler) (netsnmp_mib_handler handler,
2     /* pointer to registration struct /
3     /* 指针,指向注册结构体 */
4     netsnmp_handler_registration reginfo,
5     /* pointer to current transaction /
6     /* 指针,指向当前处理信息 */
7     netsnmp_agent_request_info *reqinfo,
8     netsnmp_request_info requests);

这个类型就是一个函数指针,它是用来声明OID对应的handler函数的。

MIB/OID定义

先假设我们的MIB定义为以下形式

可以看这里面的http://www.cnblogs.com/oloroso/p/4599501.html


+–test(77587)
   |
   +– -R– Integer32 readObject(1)
   +– -RW- OctStr    writeObject(2)

 

这只是举个例子,这个test节点下面有两个节点,分别是只读的readObject和读写的writeObject。我们要实现的就是这个两个节点的agent程序。

1、头文件test.h的编写

先写头文件,这是比较机械的过程。

具体过程是:

  • 1、声明init_xxx函数(初始化)
  • 2、声明handle_xxx函数(处理请求)

test.h源代码如下:

 1 #ifndef TEST_H
 2 #define TEST_H
 3
 4 //1、声明init函数
 5 void init_test(void);
 6 //2、声明两个OID对应的handler函数
 7 Netsnmp_Node_Handler handle_readObject;
 8 Netsnmp_Node_Handler handle_writeObject;
 9
10 #endif //!TEST_H

 

2、test.c的编写

test.c的编写是为了实现test.h中定义的三个函数。

init_test函数编写

init_test函数主要用来注册相关OID的处理程序。这个函数将在main函数中被调用。

 1 void
 2 init_test(void)
 3 {
 4     //oid可能是unsigned char类型,也可能是unsigned long类型
 5     //具体是那种类型,由宏定义EIGHBIT_SUBIDS控制
 6     //具体可见include/net-snmp/library目录下的oid.h文件
 7     const oid readObject_oid[] = { 1,3,6,1,4,1,77587,1 };
 8     const oid writeObject_oid[] = { 1,3,6,1,4,1,77587,2 };
 9
10     //这是debug消息输出的,具体可以看net-snmp源码目录下
11     //的include/net-snmp/library/目录下的snmp_debug.h和
12     // include/net-snmp/目录下的output_api.h文件
13     DEBUGMSGTL((test, Initializing\n));
14
15     //注册readObject节点的处理程序
16     netsnmp_register_scalar(
17         netsnmp_create_handler_registration(
18             readObject,           /节点名/
19             handle_readObject,      /处理函数/
20             readObject_oid,     /oid字符串/
21             OID_LENGTH(readObject_oid), /节点长度/
22             HANDLER_CAN_RONLY   /读写权限-只读/
23         ));
24     //注册writeObject节点的处理程序
25     netsnmp_register_scalar(
26         netsnmp_create_handler_registration(
27             writeObject,
28             handle_writeObject,
29             writeObject_oid,
30             OID_LENGTH(writeObject_oid),
31             HANDLER_CAN_RWRITE  /读写权限/
32     ));
33 }

 

handle_readObject函数实现(只读节点)

对于只读节点,处理是比较简单。只需要从reqinfo参数中获取请求的类型,然后对MODE_GET类型的请求进行处理,将对应类型的值传出去即可。

int handle_readObject(
        netsnmp_mib_handler handler,
        netsnmp_handler_registration reginfo,
        netsnmp_agent_request_info   reqinfo,
        netsnmp_request_info         *requests)
{
    int value = 1234;
    switch(reqinfo->mode) {

</span><span style="color: #0000ff;">case</span> MODE_GET:  <span style="color: #008000;">/*</span><span style="color: #008000;">读取值模式</span><span style="color: #008000;">*/</span>
    <span style="color: #008000;">/*</span><span style="color: #008000;">这里将值传给snmpd,再由它组成snmp协议包发送出去</span><span style="color: #008000;">*/</span><span style="color: #000000;">
    snmp_set_var_typed_value(
    requests</span>-&gt;requestvb,<span style="color: #008000;">/*</span><span style="color: #008000;">vb=&gt;var bind变量绑定</span><span style="color: #008000;">*/</span><span style="color: #000000;">
    ASN_INTEGER,    </span><span style="color: #008000;">/*</span><span style="color: #008000;">值类型</span><span style="color: #008000;">*/</span>
    &amp;value,         <span style="color: #008000;">/*</span><span style="color: #008000;">传出去的值</span><span style="color: #008000;">*/</span>
    <span style="color: #0000ff;">sizeof</span>(value)   <span style="color: #008000;">/*</span><span style="color: #008000;">传出去值的字节数</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: #0000ff;">default</span><span style="color: #000000;">:
    </span><span style="color: #008000;">/*</span><span style="color: #008000;">如果进入了这里,表明发生了非常严重的错误</span><span style="color: #008000;">*/</span><span style="color: #000000;">
    snmp_log(LOG_ERR,   </span><span style="color: #008000;">/*</span><span style="color: #008000;">日志类型</span><span style="color: #008000;">*/</span>
         <span style="color: #800000;">"</span><span style="color: #800000;">unknown mode (%d) in handle_readObject\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,
         reqinfo</span>-&gt;<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> SNMP_ERR_NOERROR; <span style="color: #008000;">//</span><span style="color: #008000;">正常返回</span>

}

 

handle_writeObject函数实现(读写节点)

读写节点的读取部分就不说了,这里只说说set操作的部分。
set操作主要是从requests->requestvb->val获取传过来的数据,关于requests->requestvb->val,这是一个union netsnmp_vardata类型。传过来值的长度可以从requests->requestvb->val_len中获取。

 1 typedef union {
 2     long            *integer;
 3     u_char          *string;
 4     oid             *objid;
 5     u_char          *bitstring;
 6     struct counter64    *counter64;
 7 #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
 8     float       *floatVal;
 9     double      *doubleVal;
10 #endif  /*NETSNMP_WITH_OPAQUE_SPECIAL_TYPES /
11 } netsnmp_vardata;

 

typedef struct variable_list {
    /* NULL for last variable */
    struct variable_list next_variable;
/
* Object identifier of variable / oid name;
/ number of subid’s in name / size_t name_length;
/
* ASN type of variable / u_char type;
/
* value of variable / netsnmp_vardata val; /* the length of the value to be copied into buf / size_t val_len; /* buffer to hold the OID / oid name_loc[MAX_OID_LEN];
/
* 90 percentile < 40. / u_char buf[40]; /* (Opaque) hook for additional data */ void data; /* callback to free above */ void (*dataFreeHook)(void *);
int index; } netsnmp_variable_list;

 

具体内容可见net-snmp源码目录下的include/net-snmp/agent/目录下的snmp_agent.h文件和include/net-snmp/目录下的types.h文件。

include/net-snmp/library目录下的snmp.h文件中定义了大量的宏,包括一些错误处理的。

关于reqinfo->mode中的几个SET相关的模式,在net-snmp源代码目录下agent/mibgroup/agentx/目录下的subagent.c文件中可以看到对其的处理。

具体大致是这样的,如果reqinfo->mode的模式是RESERVE1或者RESERVE2模式,那么在这两个操作失败的时候就会将其修改为FREE模式并再调用函数。如果是ACTION模式,则失败时修改为UNDO模式并再调用函数。
一次普通的snmpset操作,会导致这个函数调用多次。一般的顺序是:

  1. 第一此调用的的时候reqinfo->modeMODE_SET_RESERVE1
  2. 第二次为MODE_SET_RESERVE2
    如果前两个有一个失败了,那么将变为MODE_SET_FREE模式,并不再继续下面的调用。
  3. 第三次为MODE_SET_ACTION
    如果失败,将变为MODE_SET_UNDO模式。
  4. 第四次为MODE_SET_COMMIT

上述过程中的出错,主要是指调用了netsnmp_set_request_error函数,并且该函数的最后一个参数不是SNMP_ERR_NOERROR(实际测试,值为SNMP_ERR_COMMITFAILED程序也是正常的,不会进入出错处理)

 1 int handle_writeObject(
 2         netsnmp_mib_handler *handler,
 3         netsnmp_handler_registration *reginfo,
 4         netsnmp_agent_request_info   *reqinfo,
 5         netsnmp_request_info         requests)
 6 {
 7     int ret;
 8     /用来接收set请求过来的值,当前给了一个初始值/
 9     static char buf[1024] = writeObject;
10
11     switch(reqinfo->mode) {
12     /Get请求处理/
13     case MODE_GET:
14         snmp_set_var_typed_value(
15             requests->requestvb,
16             ASN_OCTET_STR,  /OctStr类型/
17             buf,            /传出数据/
18             strlen(buf));
19         break;
20
21     / SET REQUEST 下面都是Set请求相关的模式 /
22     case MODE_SET_RESERVE1:
23             //检查变量绑定类型
24             ret = netsnmp_check_vb_type(
25                         requests->requestvb,
26                         ASN_OCTET_STR);
27             //出错处理
28             if ( ret != SNMP_ERR_NOERROR ) {
29                 netsnmp_set_request_error(reqinfo,
30                                 requests, ret );
31             }
32         break;
33
34         case MODE_SET_RESERVE2:
35             if (0) {
36                 netsnmp_set_request_error(reqinfo, requests,
37                         SNMP_ERR_RESOURCEUNAVAILABLE);
38             }
39         break;
40
41     case MODE_SET_FREE:
42         /释放在RESERVE1或RESERVE2下分配的资源/
43         /或者在某些地方出错的情况/
44         break;
45
46     case MODE_SET_ACTION:
47         /在这里执行对set请求处理的操作,来获取传过来的值/
48         /直接将requests->requestvb->val中的数据拷贝到buf/
49         if(requests->requestvb->val_len > 1023){
50             /太长了,不要/
51             ret = 0;
52         }else if(requests->requestvb->val.string == NULL){
53             /val指向NULL也不能要/
54             ret = 0;
55         }else{
56             ret = requests->requestvb->val_len;
57             memcpy(buf,requests->requestvb->val.string,ret);
58             buf[ret] = \0;
59         }
60         if ( ret == 0) {
61             /set 请求出错/
62             netsnmp_set_request_error(reqinfo, requests,
63                      SNMP_ERR_BADVALUE);/坏值/
64         }
65         break;
66
67     case MODE_SET_COMMIT:
68         /这里用于删除临时存储数据/
69         if (0/ XXX: error? /) {
70             netsnmp_set_request_error(reqinfo, requests,
71                     SNMP_ERR_COMMITFAILED);
72         }
73         break;
74
75     case MODE_SET_UNDO:
76         /撤消并返回到对象以前的值/
77         if (0/ XXX: error? /) {
78             netsnmp_set_request_error(reqinfo, requests,
79                     SNMP_ERR_UNDOFAILED);
80         }
81         break;
82
83     default:
84         snmp_log(LOG_ERR,
85                 unknown mode (%d) in handle_writeObject\n,
86                 reqinfo->mode );
87         return SNMP_ERR_GENERR;
88     }
89
90     return SNMP_ERR_NOERROR;
91 }

完整的代码如下

  1 /
  2  * Note: this file originally auto-generated by mib2c using
  3  *        $
  4  /
  5
  6 #include <net-snmp/net-snmp-config.h>
  7 #include <net-snmp/net-snmp-includes.h>
  8 #include <net-snmp/agent/net-snmp-agent-includes.h>
  9 #include test.h
 10
 11 /* Initializes the test module */
 12 void
 13 init_test(void)
 14 {
 15     const oid readObject_oid[] = { 1,3,6,1,4,1,77587,1 };
 16     const oid writeObject_oid[] = { 1,3,6,1,4,1,77587,2 };
 17
 18   DEBUGMSGTL((test, Initializing\n));
 19
 20     netsnmp_register_scalar(
 21         netsnmp_create_handler_registration(readObject, handle_readObject,
 22                                readObject_oid, OID_LENGTH(readObject_oid),
 23                                HANDLER_CAN_RONLY
 24         ));
 25     netsnmp_register_scalar(
 26         netsnmp_create_handler_registration(writeObject, handle_writeObject,
 27                                writeObject_oid, OID_LENGTH(writeObject_oid),
 28                                HANDLER_CAN_RWRITE
 29         ));
 30 }
 31
 32 int
 33 handle_readObject(netsnmp_mib_handler *handler,
 34                           netsnmp_handler_registration *reginfo,
 35                           netsnmp_agent_request_info   *reqinfo,
 36                           netsnmp_request_info         requests)
 37 {
 38     int value = 1234;
 39     switch(reqinfo->mode) {
 40
 41     case MODE_GET:  /读取值模式/
 42         /这里将值传给snmpd,再由它组成snmp协议包发送出去/
 43         snmp_set_var_typed_value(
 44         requests->requestvb,/vb=>var bind变量绑定/
 45         ASN_INTEGER,    /值类型/
 46         &value,         /传出去的值/
 47         sizeof(value)   /传出去值的字节数/
 48         );
 49         break;
 50     default:
 51         /如果进入了这里,表明发生了非常严重的错误/
 52         snmp_log(LOG_ERR,   /日志类型*/
 53              unknown mode (%d) in handle_readObject\n,
 54              reqinfo->mode );
 55             return SNMP_ERR_GENERR;
 56     }
 57
 58     return SNMP_ERR_NOERROR; //正常返回
 59 }
 60 int handle_writeObject(
 61         netsnmp_mib_handler *handler,
 62         netsnmp_handler_registration *reginfo,
 63         netsnmp_agent_request_info   *reqinfo,
 64         netsnmp_request_info         requests)
 65 {
 66     int ret;
 67     /用来接收set请求过来的值,当前给了一个初始值/
 68     static char buf[1024] = writeObject;
 69
 70     switch(reqinfo->mode) {
 71     /Get请求处理/
 72     case MODE_GET:
 73         snmp_set_var_typed_value(
 74             requests->requestvb,
 75             ASN_OCTET_STR,  /OctStr类型/
 76             buf,            /传出数据/
 77             strlen(buf));
 78         break;
 79
 80     / SET REQUEST 下面都是Set请求相关的模式 /
 81     case MODE_SET_RESERVE1:
 82             //检查变量绑定类型
 83             ret = netsnmp_check_vb_type(
 84                         requests->requestvb,
 85                         ASN_OCTET_STR);
 86             //出错处理
 87             if ( ret != SNMP_ERR_NOERROR ) {
 88                 netsnmp_set_request_error(reqinfo,
 89                                 requests, ret );
 90             }
 91         break;
 92
 93         case MODE_SET_RESERVE2:
 94             if (0) {
 95                 netsnmp_set_request_error(reqinfo, requests,
 96                         SNMP_ERR_RESOURCEUNAVAILABLE);
 97             }
 98         break;
 99
100     case MODE_SET_FREE:
101         /释放在RESERVE1或RESERVE2下分配的资源/
102         /或者在某些地方出错的情况/
103         break;
104
105     case MODE_SET_ACTION:
106         /在这里执行对set请求处理的操作,来获取传过来的值/
107         /直接将requests->requestvb->val中的数据拷贝到buf/
108         if(requests->requestvb->val_len > 1023){
109             /太长了,不要/
110             ret = 0;
111         }else if(requests->requestvb->val == NULL){
112             /val指向NULL也不能要/
113             ret = 0;
114         }else{
115             ret = requests->requestvb->val_len;
116             memncpy(buf,requests->requestvb->val,ret);
117             buf[ret] = \0;
118         }
119         if ( ret == 0) {
120             /set 请求出错/
121             netsnmp_set_request_error(reqinfo, requests,
122                      SNMP_ERR_BADVALUE);/坏值/
123         }
124         break;
125
126     case MODE_SET_COMMIT:
127         /这里用于删除临时存储数据/
128         if (0/ XXX: error? /) {
129             netsnmp_set_request_error(reqinfo, requests,
130                     SNMP_ERR_COMMITFAILED);
131         }
132         break;
133
134     case MODE_SET_UNDO:
135         /撤消并返回到对象以前的值/
136         if (0/ XXX: error? */) {
137             netsnmp_set_request_error(reqinfo, requests,
138                     SNMP_ERR_UNDOFAILED);
139         }
140         break;
141
142     default:
143         snmp_log(LOG_ERR,
144                 unknown mode (%d) in handle_writeObject\n,
145                 reqinfo->mode );
146         return SNMP_ERR_GENERR;
147     }
148
149     return SNMP_ERR_NOERROR;
150 }
test.c

 

3、main函数的编写

写完上面两个文件,其实就可以使用net-snmp-conf工具来生成带有main函数的文件了,但是这里不说这种方式。

简述一下这个main函数的流程,当然,这只是最基本的,还可以添加一些自己需要的。

  1. 声明本程序是一个子代理SubAgent
    调用函数netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1);来实现。
  2. 如果有必要,先初始化socket函数库,这是在windows下需要的。
    SOCK_STARTUP;
  3. 初始化net-snmp的agent
    init_agent(app_name);//app_name是一个字符串,通常使用程序的名称。你可以自定义一个名称。这个是用于给snmpd进程作为标识子代理程序使用的。如果没有这个操作,snmpd进程将不知道子代理程序的存在。
  4. 初始化我们自己的mib代码
    这个就简单了,就是调用我们前面写的init_test()函数即可
  5. 调用init_snmp(“test”)函数
    这个函数声明在include/net-snmp/library/snmp_api.h文件中,定义在snmplib/snmp_api.c中。
    如果没有调用这个函数,snmpd将不知道这个子代理的存在。
    这里有一个注释test will be used to read test.conf files.测试将用于读取test.conf文件。但是其实质的意思我也不知道。这个函数的参数,也可以自定义填写,貌似也会正常运行。
  6. 无限循环,等待处理请求
    使用while(1){agent_check_and_process(1);}来处理请求
  7. 告知snmpd,子代理结束了
    这里是告知snmpd,本程序将退出了。调用函数snmp_shutdown(app_name);来操作。
    这里还要说明一点,通常这个程序还应该捕捉三个信号,来进行退出前的处理。一个是SIGTREM,kill默认。第二个是SIGINT,这个不用说了,软中断信号,结束代理。第三个是SIGHUP信号,这个也是一样,防止退出终端时调用其默认处理(退出进程)。
    如果进程捕捉到了前两个个信号,则认为snmpd结束了,那么就可以退出了。如果是后一个,那么可以按需处理。
  8. 程序结束前的清理工作
    先调用shutdown_agent();来结束agent库使用。
    再调用SOCK_CLEANUP;来清理Socket库调用。

main.c完整代码

 1 #include <net-snmp/net-snmp-config.h>
 2 #include <signal.h>
 3 #include <net-snmp/net-snmp-includes.h>
 4 #include <net-snmp/agent/net-snmp-agent-includes.h>
 5 #include test.h
 6 const char *app_name = test;
 7 static int reconfig = 0;
 8
 9 extern int netsnmp_running;
10
11 #ifdef GNUC
12 #define UNUSED attribute((unused))
13 #else
14 #define UNUSED
15 #endif
16
17 RETSIGTYPE
18 stop_server(UNUSED int a) {
19     puts(捕捉到信号 SIGTERM/SIGINT);
20     netsnmp_running = 0;
21 }
22
23 #ifdef SIGHUP
24 RETSIGTYPE
25 hup_handler(int sig)
26 {
27     puts(捕捉到信号 SIGHUP);
28     reconfig = 1;
29     signal(SIGHUP, hup_handler);
30 }
31 #endif
32
33 void reg_sig()
34 {
35 #ifdef SIGTERM
36     signal(SIGTERM, stop_server);
37 #endif
38 #ifdef SIGINT
39     signal(SIGINT, stop_server);
40 #endif
41 #ifdef SIGHUP
42     signal(SIGHUP, hup_handler);
43 #endif
44 }
45
46 int
47 main (int argc, char *argv)
48 {
49     int arg;
50     char cp = NULL;
51     int dont_fork = 0, do_help = 0;
52
53     puts( 1 we are a subagent);
54     netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID,
55                 NETSNMP_DS_AGENT_ROLE, 1);
56
57     puts(2 initialize tcpip, if necessary);
58     SOCK_STARTUP;
59
60
61     puts(3 initialize the agent library);
62     init_agent(app_name);
63
64     puts(4 initialize your mib code here);
65     init_test();
66
67     puts(5 test will be used to read test.conf files);
68     init_snmp(test);
69
70
71     / In case we received a request to stop (kill -TERM or kill -INT) /
72     puts(设置信号捕捉函数(SIGINT/SIGTERM/SIGHUP));
73     reg_sig();
74
75     netsnmp_running = 1;
76     puts(6 main loop here…);
77     while(netsnmp_running) {
78         if (reconfig) {
79             free_config();
80             read_configs();
81             reconfig = 0;
82         }
83         agent_check_and_process(1);
84     }
85
86     puts(7 at shutdown time);
87     snmp_shutdown(app_name);
88
89     puts(8 shutdown the agent library);
90     shutdown_agent();
91     SOCK_CLEANUP;
92     exit(0);
93 }
main.c

 

4、写一个简单的makefile

注意,下面代码中的INDIRLIBSDIR需要根据实际路径填写

CFLAGS=-fno-strict-aliasing -g -O2 -Ulinux -Dlinux=linux  -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv -fno-strict-aliasing -pipe -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
INDIR=-I/usr/local/include -I. -I/usr/local/net-snmp/include -I/usr/lib/x86_64-linux-gnu/perl/5.20/CORE
LIBSDIR=-L/usr/local/net-snmp/lib
LIBS=-lnetsnmpmibs -lnetsnmpagent -lnetsnmp -lnetsnmpmibs -ldl  -lnetsnmpagent  -Wl,-E -lnetsnmp

CC=gcc

test:main.c test.c ${CC} ${CFLAGS} ${INDIR} -o $@ $^ ${LIBSDIR} ${LIBS}