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文件中有如下定义:

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

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

MIB/OID定义

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

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


+--test()
|
+-- -R-- Integer32 readObject()
+-- -RW- OctStr writeObject()

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

1、头文件test.h的编写

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

具体过程是:

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

test.h源代码如下:

 #ifndef __TEST_H__
#define __TEST_H__ //1、声明init函数
void init_test(void);
//2、声明两个OID对应的handler函数
Netsnmp_Node_Handler handle_readObject;
Netsnmp_Node_Handler handle_writeObject; #endif //!__TEST_H__

2、test.c的编写

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

init_test函数编写

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

 void
init_test(void)
{
//oid可能是unsigned char类型,也可能是unsigned long类型
//具体是那种类型,由宏定义EIGHBIT_SUBIDS控制
//具体可见include/net-snmp/library目录下的oid.h文件
const oid readObject_oid[] = { ,,,,,,, };
const oid writeObject_oid[] = { ,,,,,,, }; //这是debug消息输出的,具体可以看net-snmp源码目录下
//的include/net-snmp/library/目录下的snmp_debug.h和
// include/net-snmp/目录下的output_api.h文件
DEBUGMSGTL(("test", "Initializing\n")); //注册readObject节点的处理程序
netsnmp_register_scalar(
netsnmp_create_handler_registration(
"readObject", /*节点名*/
handle_readObject, /*处理函数*/
readObject_oid, /*oid字符串*/
OID_LENGTH(readObject_oid), /*节点长度*/
HANDLER_CAN_RONLY /*读写权限-只读*/
));
//注册writeObject节点的处理程序
netsnmp_register_scalar(
netsnmp_create_handler_registration(
"writeObject",
handle_writeObject,
writeObject_oid,
OID_LENGTH(writeObject_oid),
HANDLER_CAN_RWRITE /*读写权限*/
));
}

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 = ;
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中获取。

 typedef union {
long *integer;
u_char *string;
oid *objid;
u_char *bitstring;
struct counter64 *counter64;
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
float *floatVal;
double *doubleVal;
#endif /*NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */
} 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[];
/** (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程序也是正常的,不会进入出错处理)

 int handle_writeObject(
netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/*用来接收set请求过来的值,当前给了一个初始值*/
static char buf[] = "writeObject"; switch(reqinfo->mode) {
/*Get请求处理*/
case MODE_GET:
snmp_set_var_typed_value(
requests->requestvb,
ASN_OCTET_STR, /*OctStr类型*/
buf, /*传出数据*/
strlen(buf));
break; /* SET REQUEST 下面都是Set请求相关的模式 */
case MODE_SET_RESERVE1:
//检查变量绑定类型
ret = netsnmp_check_vb_type(
requests->requestvb,
ASN_OCTET_STR);
//出错处理
if ( ret != SNMP_ERR_NOERROR ) {
netsnmp_set_request_error(reqinfo,
requests, ret );
}
break; case MODE_SET_RESERVE2:
if () {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_RESOURCEUNAVAILABLE);
}
break; case MODE_SET_FREE:
/*释放在RESERVE1或RESERVE2下分配的资源*/
/*或者在某些地方出错的情况*/
break; case MODE_SET_ACTION:
/*在这里执行对set请求处理的操作,来获取传过来的值*/
/*直接将requests->requestvb->val中的数据拷贝到buf*/
if(requests->requestvb->val_len > ){
/*太长了,不要*/
ret = ;
}else if(requests->requestvb->val.string == NULL){
/*val指向NULL也不能要*/
ret = ;
}else{
ret = requests->requestvb->val_len;
memcpy(buf,requests->requestvb->val.string,ret);
buf[ret] = '\0';
}
if ( ret == ) {
/*set 请求出错*/
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_BADVALUE);/*坏值*/
}
break; case MODE_SET_COMMIT:
/*这里用于删除临时存储数据*/
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_COMMITFAILED);
}
break; case MODE_SET_UNDO:
/*撤消并返回到对象以前的值*/
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_UNDOFAILED);
}
break; default:
snmp_log(LOG_ERR,
"unknown mode (%d) in handle_writeObject\n",
reqinfo->mode );
return SNMP_ERR_GENERR;
} return SNMP_ERR_NOERROR;
}

完整的代码如下

 /*
* Note: this file originally auto-generated by mib2c using
* $
*/ #include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "test.h" /** Initializes the test module */
void
init_test(void)
{
const oid readObject_oid[] = { ,,,,,,, };
const oid writeObject_oid[] = { ,,,,,,, }; DEBUGMSGTL(("test", "Initializing\n")); netsnmp_register_scalar(
netsnmp_create_handler_registration("readObject", handle_readObject,
readObject_oid, OID_LENGTH(readObject_oid),
HANDLER_CAN_RONLY
));
netsnmp_register_scalar(
netsnmp_create_handler_registration("writeObject", handle_writeObject,
writeObject_oid, OID_LENGTH(writeObject_oid),
HANDLER_CAN_RWRITE
));
} int
handle_readObject(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int value = ;
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; //正常返回
}
int handle_writeObject(
netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/*用来接收set请求过来的值,当前给了一个初始值*/
static char buf[] = "writeObject"; switch(reqinfo->mode) {
/*Get请求处理*/
case MODE_GET:
snmp_set_var_typed_value(
requests->requestvb,
ASN_OCTET_STR, /*OctStr类型*/
buf, /*传出数据*/
strlen(buf));
break; /* SET REQUEST 下面都是Set请求相关的模式 */
case MODE_SET_RESERVE1:
//检查变量绑定类型
ret = netsnmp_check_vb_type(
requests->requestvb,
ASN_OCTET_STR);
//出错处理
if ( ret != SNMP_ERR_NOERROR ) {
netsnmp_set_request_error(reqinfo,
requests, ret );
}
break; case MODE_SET_RESERVE2:
if () {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_RESOURCEUNAVAILABLE);
}
break; case MODE_SET_FREE:
/*释放在RESERVE1或RESERVE2下分配的资源*/
/*或者在某些地方出错的情况*/
break; case MODE_SET_ACTION:
/*在这里执行对set请求处理的操作,来获取传过来的值*/
/*直接将requests->requestvb->val中的数据拷贝到buf*/
if(requests->requestvb->val_len > ){
/*太长了,不要*/
ret = ;
}else if(requests->requestvb->val == NULL){
/*val指向NULL也不能要*/
ret = ;
}else{
ret = requests->requestvb->val_len;
memncpy(buf,requests->requestvb->val,ret);
buf[ret] = '\0';
}
if ( ret == ) {
/*set 请求出错*/
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_BADVALUE);/*坏值*/
}
break; case MODE_SET_COMMIT:
/*这里用于删除临时存储数据*/
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_COMMITFAILED);
}
break; case MODE_SET_UNDO:
/*撤消并返回到对象以前的值*/
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_UNDOFAILED);
}
break; default:
snmp_log(LOG_ERR,
"unknown mode (%d) in handle_writeObject\n",
reqinfo->mode );
return SNMP_ERR_GENERR;
} return SNMP_ERR_NOERROR;
}

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完整代码

 #include <net-snmp/net-snmp-config.h>
#include <signal.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "test.h"
const char *app_name = "test";
static int reconfig = ; extern int netsnmp_running; #ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif RETSIGTYPE
stop_server(UNUSED int a) {
puts("捕捉到信号 SIGTERM/SIGINT");
netsnmp_running = ;
} #ifdef SIGHUP
RETSIGTYPE
hup_handler(int sig)
{
puts("捕捉到信号 SIGHUP");
reconfig = ;
signal(SIGHUP, hup_handler);
}
#endif void reg_sig()
{
#ifdef SIGTERM
signal(SIGTERM, stop_server);
#endif
#ifdef SIGINT
signal(SIGINT, stop_server);
#endif
#ifdef SIGHUP
signal(SIGHUP, hup_handler);
#endif
} int
main (int argc, char **argv)
{
int arg;
char* cp = NULL;
int dont_fork = , do_help = ; puts(" 1 we are a subagent");
netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_ROLE, ); puts("2 initialize tcpip, if necessary");
SOCK_STARTUP; puts("3 initialize the agent library");
init_agent(app_name); puts("4 initialize your mib code here");
init_test(); puts("5 test will be used to read test.conf files");
init_snmp("test"); /* In case we received a request to stop (kill -TERM or kill -INT) */
puts("设置信号捕捉函数(SIGINT/SIGTERM/SIGHUP)");
reg_sig(); netsnmp_running = ;
puts("6 main loop here...");
while(netsnmp_running) {
if (reconfig) {
free_config();
read_configs();
reconfig = ;
}
agent_check_and_process();
} puts("7 at shutdown time");
snmp_shutdown(app_name); puts("8 shutdown the agent library");
shutdown_agent();
SOCK_CLEANUP;
exit();
}

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=
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}

net-snmp子代理(SubAgent)编写详述的更多相关文章

  1. snmp agent 表格实现(子代理方式实现)

    前奏參见例如以下: http://blog.sina.com.cn/s/blog_8f3de3250100xhao.html http://blog.csdn.net/hepeng597/articl ...

  2. snmpd 子代理模式编译测试

    1.参考链接 1)Net-snmp添加子代理示例 https://blog.csdn.net/eyf0917/article/details/39546651   2.操作步骤 1)网络拷贝下面的文件 ...

  3. Atitit  代理与分销系统(1)  子代理 充值总额功能设计概览 sum() groubpy subagt

    Atitit  代理与分销系统(1)  子代理 充值总额功能设计概览 sum() groubpy subagt Keyword 分组与聚合操作. 一个for做分组...里面的做聚合... 数据g操作查 ...

  4. SONiC架构分析

    目录 系统架构 设计原则 核心组件 SWSS 容器 syncd 容器 网络应用容器 内部通信模型 SubscriberStateTable NotificationProducer/Consumer ...

  5. snmp理论篇

    SNMP协议入门 1.引言 基于TCP/IP的网络管理包含3个组成部分: 1) 一个管理信息库MIB(Management Information Base).管理信息库包含所有代理进程的所有可被查询 ...

  6. SNMP消息传输机制

    1.引言 基于TCP/IP的网络管理包含3个组成部分: 1) 一个管理信息库MIB(Management Information Base).管理信息库包含所有代理进程的所有可被查询和修改的参数.RF ...

  7. 前端学HTTP之代理

    前面的话 Web代理(proxy)服务器是网络的中间实体,位于客户端和服务器之间,扮演“中间人”的角色,在各端点之间来回传送HTTP报文.本文将介绍HTTP代理服务器相关内容 中间实体 Web上的代理 ...

  8. SNMP 原理与实战详解

    原文地址:http://freeloda.blog.51cto.com/2033581/1306743 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法 ...

  9. db2代理和优化

    DB2 的代理 (agent) 是位于 DB2 服务器中的服务于应用程序请求的一些进程或线程.当有外部应用程序连接至 DB2 实例提出访问请求时,DB2 的代理就会被激活去应答这些请求.一般 DB2 ...

随机推荐

  1. llinux 查看自己的公网ip

    如何在LINUX服务器下查看公网IP地址,可以使用下面的方法: [root@web ~]#curl http://members.3322.org/dyndns/getip [root@web ~]# ...

  2. JavaWeb学习笔记——jsp:setproperty和getproperty

  3. JPG / TGA

    JPG http://www.openjpeg.org http://libjpeg.sourceforge.net TGA http://tgalib.sourceforge.net

  4. 免费SSL证书Let’s Encrypt

    由于我们公司测试环境使用的这个.自己没有亲手搭建使用,但是知道有这个东西.以后使用的话自己直接搞起. 连接文档:http://www.5icool.org/a/201512/a15271.html   ...

  5. Nginx for Windows 使用笔记

    一.常见启动错误 1. "No mapping for the Unicode character exists in the target multi-byte code page&quo ...

  6. 工具介绍 - VSCommands

    VSCommands 一个Visual Studio的轻量级扩展工具 地址:http://vscommands.squaredinfinity.com/home 1.可以设置自动隐藏显示主菜单栏,设置 ...

  7. 装X之写博客

    博客作用: 为了温习以前的知识,记录下 前几天和一个前辈聊天,说起看书总是前面学後面忘点的事情· 写个博客试试?

  8. web性能调优

    http://blog.csdn.net/chengzhezhijian/article/details/50680250 Java Web应用调优线程池:没你想的那么复杂 标签: java 线程池 ...

  9. Linq查询操作语句学习

    对于一个集合,我们通常会用foreach或者for循环来判断查找里面的元素. 但这种方法通常会看起来比较复杂,我们可以使用linq. Linq允许编写C#代码以查询数据库相同的方式操作内存数据(写法类 ...

  10. R语言 常见模型

    转自 雪晴网 [R]如何确定最适合数据集的机器学习算法 抽查(Spot checking)机器学习算法是指如何找出最适合于给定数据集的算法模型.本文中我将介绍八个常用于抽查的机器学习算法,文中还包括各 ...