• 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
    "));
    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
    ",
                 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操作,会导致这个函数调用多次。一般的顺序是:

    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] = '';
    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
    ",
    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
    "));
     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
    ",
     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] = '';
    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
    ",
    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}
  • 相关阅读:
    边工作边刷题:70天一遍leetcode: day 52
    边工作边刷题:70天一遍leetcode: day 53-1
    边工作边刷题:70天一遍leetcode: day 53
    边工作边刷题:70天一遍leetcode: day 54
    边工作边刷题:70天一遍leetcode: day 55
    JavaScript 组件化开发之路(一)
    Promise
    HTML5 API 之 history
    时隔一年,window.scroll
    sublimeLinter-jshint 配置
  • 原文地址:https://www.cnblogs.com/oloroso/p/4708581.html
Copyright © 2020-2023  润新知