1. 说明
要使用Agent++库开发snmp agent,首先要编译snmp++库,二者编译的方法在另一篇文章中有说明。
SNMP++及Agent++交叉编译
2. Agent++框架
agent++主要是有MIB类及其派生类组成,其结构如下图所示。
agent++简单使用可分为如下几步:
- 初始化snmp socket网络。
- 通过Mib中成员函数add创建任意所需的MIB节点,通过remove来移除。
- 创建request列表,将创建的mib对象关联上。(v2 和v3版本有区别)
- 通过receive来监听NSM管理端消息。
3.简单的SNMP Agent代码
如下是一个用Agent++开发的agent代理的例子,此例子可用于SNMP v1和v2版本,v2版本只需设置下读写社区,v3需要用到安全认证,将在下一篇文章中介绍。
#include <stdio.h>
#include <signal.h>
#include "agent_pp/agent++.h"
#include "agent_pp/snmp_group.h"
#include "agent_pp/system_group.h"
#include "agent_pp/snmp_target_mib.h"
#include "agent_pp/snmp_notification_mib.h"
#include "agent_pp/notification_originator.h"
#include "agent_pp/mib_complex_entry.h"
#include "agent_pp/v3_mib.h"
#include "agent_pp/vacm.h"
#include "snmp_pp/oid_def.h"
#include "snmp_pp/mp_v3.h"
#include "snmp_pp/log.h"
#ifdef SNMP_PP_NAMESPACE
using namespace Snmp_pp;
#endif
#ifdef AGENTPP_NAMESPACE
using namespace Agentpp;
#endif
bool run = TRUE;
static void sig(int signo)
{
if ((signo == SIGTERM) || (signo == SIGINT) ||
(signo == SIGSEGV)) {
printf ("\n");
switch (signo) {
case SIGSEGV: {
printf ("Segmentation fault, aborting.\n");
exit(1);
}
case SIGTERM:
case SIGINT: {
if (run) {
printf ("User abort\n");
run = FALSE;
}
}
}
}
}
void init_signals()
{
signal (SIGTERM, sig);
signal (SIGINT, sig);
signal (SIGSEGV, sig);
}
int main()
{
int status;
Snmp::socket_startup(); //初始化socket子系统
unsigned short port = 161; //161为监听端口 162为Trap端口
Snmpx snmp(status, port);
if( SNMP_CLASS_SUCCESS == status)
{
Mib mib;
//mib节点创建 使用的是MibLeaf构造函数生成
MibEntry* mib100 = mib.add(new MibLeaf("1.3.6.1.4.1.100", READWRITE, new SnmpInt32(3)));
MibEntry* mib101 = mib.add(new MibLeaf("1.3.6.1.4.1.101", READWRITE, new OctetStr("haioneuw12")));
mib.add(new MibLeaf("1.3.6.1.4.1.102", READWRITE, new SnmpInt32(-3434)));
mib.add(new MibLeaf("1.3.6.1.4.1.103", READWRITE, new SnmpInt32(-55)));
//获取值
int _int_32 = 0;
((MibLeaf*)mib100)->get_value(_int_32);
std::string str_;
((MibLeaf*)mib101)->get_value((char*)str_.c_str());
printf("----111------100 value is %s------------\n", _int_32);
printf("----111------101 value is %s------------\n", str_.c_str());
//修改值
SnmpInt32* inttmp = new SnmpInt32(-92);
((MibLeaf*)mib100)->set_value(*((SnmpSyntax*)inttmp));
OctetStr* strtmp = new OctetStr("hello");
((MibLeaf*)mib101)->set_value(*((SnmpSyntax*)strtmp));
//创建RequestList
RequestList* reqList = new RequestList(&mib);
mib.set_request_list(reqList);
init_signals();
reqList->set_snmp(&snmp);
//v2版本 设置读写社区
reqList->set_read_community("public");
reqList->set_write_community("public");
mib.init(); //mib 初始化
//监听循环 此处循环何时退出可根据实际情况来做修改
Request* req;
while (run)
{
req = reqList->receive(2);
if (req)
{
mib.process_request(req);
}
else
{
mib.cleanup();
}
}
delete reqList;
Snmp::socket_cleanup();
}
else
{
printf("snmp port init failed!\n");
}
return 0;
}
4. 重载MibLeaf实现读写过程处理
之前简单介绍了如何使用agent++来实现一个代理,其中mib是直接调用add添加oid节点的,那如果想要在nsm get或者set时处理一些事物,要如何做呢?
以下是一个简单示例
#include <agent_pp/agent++.h>
#include <agent_pp/snmp_request.h>
#include <agent_pp/snmp_textual_conventions.h>
#include <snmp_pp/snmp_pp.h>
using namespace Snmp_pp;
using namespace Agentpp;
// 定义一个简单的MIB对象
class MyMibObject : public MibLeaf {
public:
MyMibObject(const Oidx& id) : MibLeaf(id, READWRITE, new SnmpInt32(0)) {}
void get_request(Request* req, int index) override {
// 这里可以添加获取请求的处理逻辑
MibLeaf::get_request(req, index);
}
int set(const Vbx& vb) override {
// 这里可以添加设置请求的处理逻辑
return MibLeaf::set(vb);
}
};
int main() {
// 初始化SNMP++库
Snmp::socket_startup();
// 创建代理实例
Mib mib;
// 创建并注册MIB对象
mib.add(new MyMibObject("1.3.6.1.4.1.99999.1"));
// 设置代理的配置(端口号、版本等)
RequestList reqList(&mib);
UdpAddress address("0.0.0.0/161"); // 监听所有接口,端口161
Snmpx snmpx(SNMP_VERSION_2c, address, "public");
// 主循环,监听并处理请求
while (true) {
snmpx.receive();
reqList.process();
}
// 清理SNMP++库资源
Snmp::socket_cleanup();
}
当实现使用Agent++来创建SNMP代理时,获取请求的处理逻辑是一个关键环节。在SNMP代理中,获取请求主要指的是对SNMP GET、GETNEXT、和GETBULK操作的响应。
你需要为每个你想管理的MIB(管理信息库)对象实现特定的方法来处理获取请求。通常,你会从 MibLeaf
或类似的类派生你的MIB对象类。
4.1 定义MIB对象
首先,你需要定义一个MIB对象。这个对象将代表SNMP树中的一个节点(一个OID)。
class MyMibObject : public MibLeaf {
public:
MyMibObject(const Oidx& id) : MibLeaf(id, READONLY, new SnmpInt32(0)) {}
void get_request(Request* req, int index) override {
// 在这里添加处理GET请求的逻辑
MibLeaf::get_request(req, index);
}
};
4.2 实现GET请求的处理逻辑
在 get_request
方法中,你需要实现处理GET请求的逻辑。这通常涉及读取或计算该MIB对象的当前值,并将这个值返回给请求者。
void MyMibObject::get_request(Request* req, int index) {
// 假设我们只是返回当前的值
// 在实际应用中,这里可能会有更复杂的逻辑,比如读取硬件状态或进行计算
MibLeaf::get_request(req, index);
}
在这个简单的例子中,我们只是调用了父类 MibLeaf
的 get_request
方法,它会自动处理返回MIB对象的当前值。在实际的应用中,你可能需要在这里添加更复杂的逻辑。
4.3 注册MIB对象
在你的主函数或代理的初始化代码中,你需要创建并注册你的MIB对象。
Mib mib;
mib.add(new MyMibObject("1.3.6.1.4.1.99999.1"));
在这里,你创建了一个 MyMibObject
实例,并将其添加到MIB中。这样,当代理收到对应的OID(在这个例子中是 "1.3.6.1.4.1.99999.1"
)的GET请求时,你的 get_request
方法就会被调用。
4.4 补充说明
- 对于GETNEXT和GETBULK请求,处理逻辑类似,但可能需要额外的逻辑来确定返回哪个OID的值。
- 确保你的代理能够正确地处理并发请求和异常情况。
- 考虑到安全性,确保你的代理不会泄露敏感信息,并且正确处理不同类型的请求。
4.5 SNMP SET请求的处理阶段
-
预处理阶段 (
prepare_set_request
):- 这是处理SET请求的第一阶段。
- 在这个阶段,通常进行参数有效性检查,例如检查提供的值是否在允许的范围内。
- 这个阶段不应该修改任何实际的状态或数据。
-
提交阶段 (
commit_set_request
):- 在预处理阶段成功后,紧接着是提交阶段。
- 这个阶段用于实际应用更改。
- 如果这个阶段成功,更改将被保留;如果失败,将触发回滚机制。
-
应用阶段 (
set
方法):- 这是最终阶段,用于实际设置值。
- 这个阶段通常在
commit_set_request
成功之后调用。 - 这个阶段应该保证操作的不可逆性,确保更改被应用且不会回滚。
-
回滚阶段 (
undo_set_request
):- 如果在提交阶段发生错误,将调用此阶段来撤销之前的更改。
- 这是异常处理的一部分,确保系统返回到初始状态。
5.SNMP调试
调试可以使用MIB Browser或者 SNMP测试工具snmp tester 等对节点进行读写值