一、写在前面
本章将给出具体示例,包括自定义MIB库的编写、mib2c自动生成代码的改写、桌面工具MIB Browser软件的监控使用等,具体细节部分有ASN.1的数据类型简单介绍、get和set命令如何使用、snmpd.conf文件如何配置。
二、准备工作
本示例以Linux系统环境编写,net-snmp库需自行下载,我这里使用的是5.9.4版本。
snmpd --version (终端输入命令)
(显示版本号)
NET-SNMP version: 5.9.4.pre2
Web: http://www.net-snmp.org/
Email: net-snmp-coders@lists.sourceforge.net
在下载好net-snmp库后,snmp.conf文件应该是有的,我没找到我的snmpd,conf文件,可以自己在/usr/local/etc/snmp/路径下新建一个文件,如果这个路径没有就把路径也新建好。
snmpd.conf文件内容如下:
rocommunity public
rocommunity private
master agentx
三、自定义mib文件
假如,要新增MIB文件MYMIB-MIB.txt
-- MYMIB-MIB.txt
MYMIB-MIB DEFINITIONS ::= BEGIN
IMPORTS
MODULE-IDENTITY,OBJECT-TYPE,enterprises,Gauge32
FROM SNMPv2-SMI
DisplayString
FROM SNMPv2-TC;
-- start MODULE-IDENTITY
Mymib MODULE-IDENTITY
LAST-UPDATED "202409231400Z"
ORGANIZATION "XXX"
CONTACT-INFO "email: <123123123@qq.com>"
DESCRIPTION "i"
::= { enterprises 54321 }
-- end MODULE-IDENTITY
-- start OBJECT-IDENTITY
Mid OBJECT-IDENTITY
STATUS current
DESCRIPTION
"The object identity used to test"
::= { Mymib 1 }
-- end OBJECT-IDENTITY
-- start OBJECT IDENTIFIER
UAV OBJECT IDENTIFIER ::= { Mid 1 }
-- end OBJECT IDENTIFIER
-- start OBJECT-TYPE
-- 1.3.6.1.4.1.54321.1.1.1
dataOneUAV OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-write
STATUS current
DESCRIPTION "The first data"
::= { UAV 1 }
--1.3.6.1.4.1.54321.1.1.2
dataTwoUAV OBJECT-TYPE
SYNTAX Gauge32
MAX-ACCESS read-write
STATUS current
DESCRIPTION "The second data"
::= { UAV 2 }
-- end OBJECT-TYPE
END
-- MYMIB-MIB.txt
使用snmptranslate -Tp -IR MYMIB-MIB::Mymib获取文件结构
+--Mymib(54321)
+--Mid(1)
|
+--UAV(1)
|
+-- -RW- String dataOneUAV(1)
| Textual Convention: DisplayString
| Size: 0..255
+-- -RW- Gauge dataTwoUAV(2)
四、mib2c自动生成代码
使用mib2c自动生成.c和.h代码,这里需要root权限下执行
env MIBS="+/usr/local/share/snmp/mibs/MYMIB-MIB.txt" mib2c Mymib
选2,再选1。这种自动生成代码的.c可以让我们自定义注册处理程序。(PS:选2再选2,适用于简单的int类型的变量监测,这种会直接都写好,不用自己再改了)。
自动生成后的Mymib.c代码如下:
/*
* Note: this file originally auto-generated by mib2c
* using mib2c.scalar.conf
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "Mymib.h"
/** Initializes the Mymib module */
void
init_Mymib(void)
{
const oid dataOneUAV_oid[] = { 1,3,6,1,4,1,54321,1,1,1 };
const oid dataTwoUAV_oid[] = { 1,3,6,1,4,1,54321,1,1,2 };
DEBUGMSGTL(("Mymib", "Initializing\n"));
netsnmp_register_scalar(
netsnmp_create_handler_registration("dataOneUAV", handle_dataOneUAV,
dataOneUAV_oid, OID_LENGTH(dataOneUAV_oid),
HANDLER_CAN_RWRITE
));
netsnmp_register_scalar(
netsnmp_create_handler_registration("dataTwoUAV", handle_dataTwoUAV,
dataTwoUAV_oid, OID_LENGTH(dataTwoUAV_oid),
HANDLER_CAN_RWRITE
));
}
int
handle_dataOneUAV(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/* We are never called for a GETNEXT if it's registered as a
"instance", as it's "magically" handled for us. */
/* a instance handler also only hands us one request at a time, so
we don't need to loop over a list of requests; we'll only get one. */
switch(reqinfo->mode) {
case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
/* XXX: a pointer to the scalar's data */,
/* XXX: the length of the data in bytes */);
break;
/*
* SET REQUEST
*
* multiple states in the transaction. See:
* http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
*/
case MODE_SET_RESERVE1:
/* or you could use netsnmp_check_vb_type_and_size instead */
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:
/* XXX malloc "undo" storage buffer */
if (/* XXX if malloc, or whatever, failed: */) {
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
}
break;
case MODE_SET_FREE:
/* XXX: free resources allocated in RESERVE1 and/or
RESERVE2. Something failed somewhere, and the states
below won't be called. */
break;
case MODE_SET_ACTION:
/* XXX: perform the value change here */
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests, /* some error */);
}
break;
case MODE_SET_COMMIT:
/* XXX: delete temporary storage */
if (/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
}
break;
case MODE_SET_UNDO:
/* XXX: UNDO and return to previous value for the object */
if (/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
}
break;
default:
/* we should never get here, so this is a really bad error */
snmp_log(LOG_ERR, "unknown mode (%d) in handle_dataOneUAV\n", reqinfo->mode );
return SNMP_ERR_GENERR;
}
return SNMP_ERR_NOERROR;
}
int
handle_dataTwoUAV(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/* We are never called for a GETNEXT if it's registered as a
"instance", as it's "magically" handled for us. */
/* a instance handler also only hands us one request at a time, so
we don't need to loop over a list of requests; we'll only get one. */
switch(reqinfo->mode) {
case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
/* XXX: a pointer to the scalar's data */,
/* XXX: the length of the data in bytes */);
break;
/*
* SET REQUEST
*
* multiple states in the transaction. See:
* http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
*/
case MODE_SET_RESERVE1:
/* or you could use netsnmp_check_vb_type_and_size instead */
ret = netsnmp_check_vb_type(requests->requestvb, ASN_GAUGE);
if ( ret != SNMP_ERR_NOERROR ) {
netsnmp_set_request_error(reqinfo, requests, ret );
}
break;
case MODE_SET_RESERVE2:
/* XXX malloc "undo" storage buffer */
if (/* XXX if malloc, or whatever, failed: */) {
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
}
break;
case MODE_SET_FREE:
/* XXX: free resources allocated in RESERVE1 and/or
RESERVE2. Something failed somewhere, and the states
below won't be called. */
break;
case MODE_SET_ACTION:
/* XXX: perform the value change here */
if (/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests, /* some error */);
}
break;
case MODE_SET_COMMIT:
/* XXX: delete temporary storage */
if (/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
}
break;
case MODE_SET_UNDO:
/* XXX: UNDO and return to previous value for the object */
if (/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
}
break;
default:
/* we should never get here, so this is a really bad error */
snmp_log(LOG_ERR, "unknown mode (%d) in handle_dataTwoUAV\n", reqinfo->mode );
return SNMP_ERR_GENERR;
}
return SNMP_ERR_NOERROR;
}
五、分析自动生成代码
1、头文件
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "Mymib.h"
这四个include包含了net-snmp库的相关头文件,以便于使用下面用到的函数。
2、init_Mymib自定义模块
在这里,会注册SNMP变量,并让代理知道它能够处理哪些请求。
DEBUGMSGTL函数
作用:输出一条调试信息,用于表示XXX模块正在初始化
下面是netsnmp_register_scalar函数和netsnmp_create_handler_registration函数。我们所定义的子代理,就是靠这俩函数,来完成自定义的SET和GET操作处理的,可以先看netsnmp_create_handler_registration函数的使用,再看netsnmp_register_scalar函数的使用。
netsnmp_register_scalar函数
作用:将处理程序和变量信息注册到SNMP代理中,在此过程中,它还创建一个netsnmp_variable_list节点,使得这个新的SNMP变量能够参与到SNMP请求和响应中
netsnmp_create_handler_registration函数
作用:主要用于创建和准备一个处理程序注册对象,它并不直接将变量注册到SNMP代理中。而是返回一个包含所有相关信息的对象
这个两个函数的存在意义要有一个简单理解,一个是创建好处理程序和注册对象,一个是将创建好的处理程序和注册对象,这些东西注册到SNMP代理中。我们所创建的信息,最终会放到子代理的netsnmp_variable_list链表中,这个链表是由netsnmp_register_scalar函数给新的变量,注册一个新的节点,而不是由netsnmp_create_handler_registration函数在链表中注册新的节点。
为什么要对变量的存放赘述,因为SNMP协议的通信数据以pdu传输,要想解析从子代理中的数据,需要找到pdu中存放变量信息的地方在哪。 有关结构体存储的信息,我放在文章的最后。当然,你也可以在代码中按住ctrl找到相应的结构体,分析存放的内容。
3、补全.c文件
这里我直接给出补全后的文件代码
/*
* Note: this file originally auto-generated by mib2c
* using mib2c.scalar.conf
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "Mymib.h"
static unsigned char dataOneUAV[10]="hello";
static int dataTwoUAV=1;
/** Initializes the Mymib module */
void
init_Mymib(void)
{
const oid dataOneUAV_oid[] = { 1,3,6,1,4,1,54321,1,1,1 };
const oid dataTwoUAV_oid[] = { 1,3,6,1,4,1,54321,1,1,2 };
DEBUGMSGTL(("Mymib", "Initializing\n"));
netsnmp_register_scalar(
netsnmp_create_handler_registration("dataOneUAV", handle_dataOneUAV,
dataOneUAV_oid, OID_LENGTH(dataOneUAV_oid),
HANDLER_CAN_RWRITE
));
netsnmp_register_scalar(
netsnmp_create_handler_registration("dataTwoUAV", handle_dataTwoUAV,
dataTwoUAV_oid, OID_LENGTH(dataTwoUAV_oid),
HANDLER_CAN_RWRITE
));
}
int
handle_dataOneUAV(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/* We are never called for a GETNEXT if it's registered as a
"instance", as it's "magically" handled for us. */
/* a instance handler also only hands us one request at a time, so
we don't need to loop over a list of requests; we'll only get one. */
switch(reqinfo->mode) {
case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
dataOneUAV /* XXX: a pointer to the scalar's data */,
strlen(dataOneUAV) /* XXX: the length of the data in bytes */);
break;
/*
* SET REQUEST
*
* multiple states in the transaction. See:
* http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
*/
case MODE_SET_RESERVE1:
/* or you could use netsnmp_check_vb_type_and_size instead */
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:
/* XXX malloc "undo" storage buffer */
if (0/* XXX if malloc, or whatever, failed: */) {
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
}
break;
case MODE_SET_FREE:
/* XXX: free resources allocated in RESERVE1 and/or
RESERVE2. Something failed somewhere, and the states
below won't be called. */
break;
case MODE_SET_ACTION:
/* XXX: perform the value change here */
memcpy(dataOneUAV, requests->requestvb->buf, requests->requestvb->val_len);
if (0/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests, 0/* some error */);
}
break;
case MODE_SET_COMMIT:
/* XXX: delete temporary storage */
if (0/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
}
break;
case MODE_SET_UNDO:
/* XXX: UNDO and return to previous value for the object */
if (0/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
}
break;
default:
/* we should never get here, so this is a really bad error */
snmp_log(LOG_ERR, "unknown mode (%d) in handle_dataOneUAV\n", reqinfo->mode );
return SNMP_ERR_GENERR;
}
return SNMP_ERR_NOERROR;
}
int
handle_dataTwoUAV(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
int ret;
/* We are never called for a GETNEXT if it's registered as a
"instance", as it's "magically" handled for us. */
/* a instance handler also only hands us one request at a time, so
we don't need to loop over a list of requests; we'll only get one. */
switch(reqinfo->mode) {
case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
&dataTwoUAV /* XXX: a pointer to the scalar's data */,
sizeof(dataTwoUAV) /* XXX: the length of the data in bytes */);
break;
/*
* SET REQUEST
*
* multiple states in the transaction. See:
* http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
*/
case MODE_SET_RESERVE1:
/* or you could use netsnmp_check_vb_type_and_size instead */
ret = netsnmp_check_vb_type(requests->requestvb, ASN_GAUGE);
if ( ret != SNMP_ERR_NOERROR ) {
netsnmp_set_request_error(reqinfo, requests, ret );
}
break;
case MODE_SET_RESERVE2:
/* XXX malloc "undo" storage buffer */
if (0/* XXX if malloc, or whatever, failed: */) {
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_RESOURCEUNAVAILABLE);
}
break;
case MODE_SET_FREE:
/* XXX: free resources allocated in RESERVE1 and/or
RESERVE2. Something failed somewhere, and the states
below won't be called. */
break;
case MODE_SET_ACTION:
/* XXX: perform the value change here */
dataTwoUAV = *requests->requestvb->val.integer;
if (0/* XXX: error? */) {
netsnmp_set_request_error(reqinfo, requests, 0/* some error */);
}
break;
case MODE_SET_COMMIT:
/* XXX: delete temporary storage */
if (0/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_COMMITFAILED);
}
break;
case MODE_SET_UNDO:
/* XXX: UNDO and return to previous value for the object */
if (0/* XXX: error? */) {
/* try _really_really_ hard to never get to this point */
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_UNDOFAILED);
}
break;
default:
/* we should never get here, so this is a really bad error */
snmp_log(LOG_ERR, "unknown mode (%d) in handle_dataTwoUAV\n", reqinfo->mode );
return SNMP_ERR_GENERR;
}
return SNMP_ERR_NOERROR;
}
对比一下,自己应该基本上能看明白改了哪些地方,不再赘述。
4、自动生成可执行文件
在终端运行命令
net-snmp-config --compile-subagent Mymib Mymib.c
出现以下信息,证明成功生成可执行文件
generating the temporary code file: netsnmptmp.874626.c
void init_Mymib(void);
checking for init_Mymib in Mymib.c
init_Mymib(void)
checking for shutdown_Mymib in Mymib.c
running: gcc -g -O2 -DNETSNMP_ENABLE_IPV6 -fno-strict-aliasing -DNETSNMP_REMOVE_U64 -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.30/CORE -I. -I/usr/local/include -o Mymib netsnmptmp.874626.c Mymib.c -L/usr/local/lib -lnetsnmpmibs -lnetsnmpagent -lnetsnmp -lnetsnmpmibs -lnl-3 -ldl -lm -lpcre -lnetsnmpagent -lpcre -Wl,-E -lnetsnmp -lm -lssl -lssl -lcrypto
netsnmptmp.874626.c: In function ‘main’:
netsnmptmp.874626.c:228:7: warning: implicit declaration of function ‘unlink’ [-Wimplicit-function-declaration]
228 | unlink(pid_file);
| ^~~~~~
netsnmptmp.874626.c:238:65: warning: implicit declaration of function ‘getpid’; did you mean ‘getpt’? [-Wimplicit-function-declaration]
238 | int len = snprintf(buf, sizeof(buf), "%ld\n", (long int)getpid());
| ^~~~~~
| getpt
netsnmptmp.874626.c:239:9: warning: implicit declaration of function ‘write’; did you mean ‘fwrite’? [-Wimplicit-function-declaration]
239 | write(fd, buf, len);
| ^~~~~
| fwrite
netsnmptmp.874626.c:240:9: warning: implicit declaration of function ‘close’; did you mean ‘pclose’? [-Wimplicit-function-declaration]
240 | close(fd);
| ^~~~~
| pclose
removing the temporary code file: netsnmptmp.874626.c
subagent program Mymib created
六、运行子代理,实验GET和SET指令
在root权限下,执行命令。启动SNMP代理服务
snmpd -Le -f
运行可执行文件,启动子代理
./Mqri
退出root权限,使用snmpget和snmpset命令实验是否能正常运行
第一个是执行命令,第二个是打印显示
snmpget -v2c -c public localhost 1.3.6.1.4.1.54321.1.1.1.0
SNMPv2-SMI::enterprises.54321.1.1.1.0 = STRING: "hello"
snmpset -v2c -c private localhost 1.3.6.1.4.1.54321.1.1.1.0 s IamSNMP
SNMPv2-SMI::enterprises.54321.1.1.1.0 = STRING: "IamSNMP"
snmpget -v2c -c public localhost 1.3.6.1.4.1.54321.1.1.1.0
SNMPv2-SMI::enterprises.54321.1.1.1.0 = STRING: "IamSNMP"
snmpget -v2c -c public localhost 1.3.6.1.4.1.54321.1.1.2.0
SNMPv2-SMI::enterprises.54321.1.1.2.0 = Gauge32: 1
snmpset -v2c -c private localhost 1.3.6.1.4.1.54321.1.1.2.0 u 2
SNMPv2-SMI::enterprises.54321.1.1.2.0 = Gauge32: 2
snmpget -v2c -c public localhost 1.3.6.1.4.1.54321.1.1.2.0
SNMPv2-SMI::enterprises.54321.1.1.2.0 = Gauge32: 2
七、总结不足
这基本上就是一个简单示例,其中还有好多没有说到的地方,比如说pdu的消息中包含哪些内容,在源码中如何体现、MIB文件的数据类型对应自动生成代码中的数据类型是什么,如何在.c文件中准确设置数据类型、snmpset命令中为什么要是有u,u代表什么,s代表什么,其他的数据类型用什么字母代表。上述只是一个简单例子了解net-snmp库如何运行起来,在实际使用中,需要根据需求,在GET和SET处理函数中添加自己需要的功能,比如说与监测设备之间的udp通信。一般来说不会使用net-snmp-config --compile-subagent Mymib Mymib.c自动生成可执行文件,自己在代码中补全main函数即可,SNMP代理的监听会使用agent_check_and_process函数。本来写的时候想一口气写完整,写一半的时候感觉码字太繁琐,第一篇博客,比较懒,理解一下。如果看的人多,可以后续写一下与设备监控的简单示例,我会用一个c++程序跑起来作为启动的设备,与子代理进行UDP通信交换共享内存数据,应用层做一个简单的自定义标志,只需要在子代理的GET和SET中加个UDP通信即可。废话到此结束,有什么问题大家及时指正,互相学习