net-snmp子代理(SubAgent)编写详述
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) { case MODE_GET: /*读取值模式*/ /*这里将值传给snmpd,再由它组成snmp协议包发送出去*/ snmp_set_var_typed_value( requests->requestvb,/*vb=>var bind变量绑定*/ ASN_INTEGER, /*值类型*/ &value, /*传出去的值*/ sizeof(value) /*传出去值的字节数*/ ); break; default: /*如果进入了这里,表明发生了非常严重的错误*/ snmp_log(LOG_ERR, /*日志类型*/ "unknown mode (%d) in handle_readObject\n", reqinfo->mode ); return SNMP_ERR_GENERR; } return SNMP_ERR_NOERROR; //正常返回 }
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
操作,会导致这个函数调用多次。一般的顺序是:
- 第一此调用的的时候
reqinfo->mode
为MODE_SET_RESERVE1
- 第二次为
MODE_SET_RESERVE2
如果前两个有一个失败了,那么将变为MODE_SET_FREE
模式,并不再继续下面的调用。 - 第三次为
MODE_SET_ACTION
如果失败,将变为MODE_SET_UNDO
模式。 - 第四次为
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 }
3、main函数的编写
写完上面两个文件,其实就可以使用net-snmp-conf
工具来生成带有main
函数的文件了,但是这里不说这种方式。
简述一下这个main
函数的流程,当然,这只是最基本的,还可以添加一些自己需要的。
- 声明本程序是一个子代理
SubAgent
调用函数netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1);
来实现。 - 如果有必要,先初始化
socket
函数库,这是在windows下需要的。
SOCK_STARTUP;
- 初始化net-snmp的
agent
库
init_agent(app_name);
//app_name是一个字符串,通常使用程序的名称。你可以自定义一个名称。这个是用于给snmpd
进程作为标识子代理程序使用的。如果没有这个操作,snmpd
进程将不知道子代理程序的存在。 - 初始化我们自己的
mib
代码
这个就简单了,就是调用我们前面写的init_test()
函数即可 - 调用
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
文件。但是其实质的意思我也不知道。这个函数的参数,也可以自定义填写,貌似也会正常运行。 - 无限循环,等待处理请求
使用while(1){agent_check_and_process(1);}
来处理请求 - 告知
snmpd
,子代理结束了
这里是告知snmpd
,本程序将退出了。调用函数snmp_shutdown(app_name);
来操作。
这里还要说明一点,通常这个程序还应该捕捉三个信号,来进行退出前的处理。一个是SIGTREM
,kill默认。第二个是SIGINT
,这个不用说了,软中断信号,结束代理。第三个是SIGHUP
信号,这个也是一样,防止退出终端时调用其默认处理(退出进程)。
如果进程捕捉到了前两个个信号,则认为snmpd
结束了,那么就可以退出了。如果是后一个,那么可以按需处理。 - 程序结束前的清理工作
先调用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 }
4、写一个简单的makefile
注意,下面代码中的INDIR
和LIBSDIR
需要根据实际路径填写
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}