1背景
在上一篇snmp v1 get请求响应c++实现里简单实现了一个队oid为system.sysNam的响应,但是实际上不能这么使用,不然就一直是Demo。 在这一篇,计划去借鉴net-snmp的源码,对之前的进行改造优化。
2 net-snmp 源码
github 地址可以从官网的首页找到 www.net-snmp.org/
或者直接访问 github.com/net-snmp/ne...
前面 snmp wireshark 抓包 下载的二进制是5.5.0的,这里用tag为5.9.4的源码版本(5.5的基本是2011年,还是用个最新的吧)
tag所在地址 github.com/net-snmp/ne...
3 分析net-snmp源码
3.1 目录整理
lua
agent ---服务器
apps ---客户端
3.2 从main到初始化agent
以下所有的都是在agent目录 main函数 (snmpd.c)
c++
//snmpd.c
int __cdecl
main(int argc, TCHAR * argv[])
{
LPCTSTR lpszServiceName = app_name_long; /* Service Registry Name */
LPCTSTR lpszServiceDisplayName = _T("Net-SNMP Agent"); /* Display Name */
LPCTSTR lpszServiceDescription =
InputParams InputOptions;
enum net_snmp_cmd_line_action nRunType = RUN_AS_CONSOLE;
int quiet = 0;
nRunType = ParseCmdLineForServiceOption(argc, argv, &quiet);
switch (nRunType) {
case REGISTER_SERVICE:
/*
* Register As service
*/
InputOptions.Argc = argc;
InputOptions.Argv = argv;
return RegisterService(lpszServiceName,
lpszServiceDisplayName,
lpszServiceDescription, &InputOptions, quiet);
case UN_REGISTER_SERVICE:
/*
* Unregister service
*/
return UnregisterService(lpszServiceName, quiet);
case RUN_AS_SERVICE:
/*
* Run as service
*/
/*
* Register Stop Function
*/
RegisterStopFunction(StopSnmpAgent);
return RunAsService(SnmpDaemonMain);
default:
/*
* Run in console mode
*/
return SnmpDaemonMain(argc, argv);
}
}
上面的过滤掉其他用不到的,默认执行的是RUN_AS_CONSOLE ,最终执行的就一个 SnmpDaemonMain。继续往下找
c++
#ifdef WIN32SERVICE
static int
SnmpDaemonMain(int argc, TCHAR * argv[])
#else
int
main(int argc, char *argv[])
#endif
{
if (init_agent(app_name) != 0) {
snmp_log(LOG_ERR, "Agent initialization failed\n");
goto out;
}
init_mib_modules();
/*
* start library
*/
init_snmp(app_name);
if ((ret = init_master_agent()) != 0) {
/*
* Some error opening one of the specified agent transports.
*/
snmp_log(LOG_ERR, "Server Exiting with code 1\n");
goto out;
}
}
上面的处理了解析命令行等等,重点关注 init_master_agent 其他的不在关注的范围内
3.3 绑定udp端口
c++
//snmp_agent.c
int
init_master_agent(void)
{
char *cptr;
char *buf = NULL;
char *st;
/* default to a default cache size */
netsnmp_set_lookup_cache_size(-1);
if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_ROLE) != MASTER_AGENT) {
DEBUGMSGTL(("snmp_agent",
"init_master_agent; not master agent\n"));
netsnmp_assert("agent role !master && !sub_agent");
return 0; /* No error if ! MASTER_AGENT */
}
#ifndef NETSNMP_NO_LISTEN_SUPPORT
/*
* Have specific agent ports been specified?
*/
cptr = netsnmp_ds_get_string(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_PORTS);
if (cptr) {
buf = strdup(cptr);
if (!buf) {
snmp_log(LOG_ERR,
"Error processing transport \"%s\"\n", cptr);
return 1;
}
} else {
/*
* No, so just specify the default port.
*/
buf = strdup("");
}
DEBUGMSGTL(("snmp_agent", "final port spec: \"%s\"\n", buf));
st = buf;
do {
/*
* Specification format:
*
* NONE: (a pseudo-transport)
* UDP:[address:]port (also default if no transport is specified)
* TCP:[address:]port (if supported)
* Unix:pathname (if supported)
* AAL5PVC:itf.vpi.vci (if supported)
* IPX:[network]:node[/port] (if supported)
*
*/
cptr = st;
st = strchr(st, ',');
if (st)
*st++ = '\0';
DEBUGMSGTL(("snmp_agent", "installing master agent on port %s\n",
cptr));
if (strncasecmp(cptr, "none", 4) == 0) {
DEBUGMSGTL(("snmp_agent",
"init_master_agent; pseudo-transport \"none\" "
"requested\n"));
break;
}
if (-1 == netsnmp_agent_listen_on(cptr)) {
SNMP_FREE(buf);
return 1;
}
} while(st && *st != '\0');
SNMP_FREE(buf);
#endif /* NETSNMP_NO_LISTEN_SUPPORT */
#ifdef USING_AGENTX_MASTER_MODULE
if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_AGENTX_MASTER) == 1)
real_init_master();
#endif
#ifdef USING_SMUX_MODULE
if(should_init("smux"))
real_init_smux();
#endif
#ifndef NETSNMP_NO_PDU_STATS
_pdu_stats_init();
#endif /* NETSNMP_NO_PDU_STATS */
return 0;
}
这里除了netsnmp_agent_listen_on,还有两种USING_AGENTX_MASTER_MODULE和USING_SMUX_MODULE,可能是给多线程下使用的,跳过
c++
//snmp_agent.c
int netsnmp_agent_listen_on(const char *port)
{
netsnmp_transport *transport;
int handle;
if (NULL == port)
return -1;
transport = netsnmp_transport_open_server("snmp", port);
if (transport == NULL) {
snmp_log(LOG_ERR, "Error opening specified endpoint \"%s\"\n", port);
return -1;
}
handle = netsnmp_register_agent_nsap(transport);
if (handle < 0) {
snmp_log(LOG_ERR, "Error registering specified transport \"%s\" as an "
"agent NSAP\n", port);
return -1;
} else {
DEBUGMSGTL(("snmp_agent",
"init_master_agent; \"%s\" registered as an agent NSAP\n",
port));
}
return handle;
}
这里的netsnmp_register_agent_nsap查看实现
c++
//snmp_agent.c
int
netsnmp_register_agent_nsap(netsnmp_transport *t)
{
netsnmp_session *s, *sp = NULL;
agent_nsap *a = NULL, *n = NULL, **prevNext = &agent_nsap_list;
int handle = 0;
void *isp = NULL;
if (t == NULL) {
return -1;
}
DEBUGMSGTL(("netsnmp_register_agent_nsap", "fd %d\n", t->sock));
n = (agent_nsap *) malloc(sizeof(agent_nsap));
if (n == NULL) {
return -1;
}
s = (netsnmp_session *) malloc(sizeof(netsnmp_session));
if (s == NULL) {
SNMP_FREE(n);
return -1;
}
snmp_sess_init(s);
/*
* Set up the session appropriately for an agent.
*/
s->version = SNMP_DEFAULT_VERSION;
s->callback = handle_snmp_packet;
s->authenticator = NULL;
s->flags = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_FLAGS);
s->isAuthoritative = SNMP_SESS_AUTHORITATIVE;
/* Optional supplimental transport configuration information and
final call to actually open the transport */
if (netsnmp_sess_config_transport(s->transport_configuration, t)
!= SNMPERR_SUCCESS) {
SNMP_FREE(s);
SNMP_FREE(n);
return -1;
}
if (t->f_open)
t = t->f_open(t);
if (NULL == t) {
SNMP_FREE(s);
SNMP_FREE(n);
return -1;
}
t->flags |= NETSNMP_TRANSPORT_FLAG_OPENED;
sp = snmp_add(s, t, netsnmp_agent_check_packet,
netsnmp_agent_check_parse);
if (sp == NULL) {
SNMP_FREE(s);
SNMP_FREE(n);
return -1;
}
isp = snmp_sess_pointer(sp);
if (isp == NULL) { /* over-cautious */
SNMP_FREE(s);
SNMP_FREE(n);
return -1;
}
n->s = isp;
n->t = t;
if (main_session == NULL) {
main_session = snmp_sess_session(isp);
}
for (a = agent_nsap_list; a != NULL && handle + 1 >= a->handle;
a = a->next) {
handle = a->handle;
prevNext = &(a->next);
}
if (handle < INT_MAX) {
n->handle = handle + 1;
n->next = a;
*prevNext = n;
SNMP_FREE(s);
DEBUGMSGTL(("netsnmp_register_agent_nsap", "handle %d\n", n->handle));
return n->handle;
} else {
SNMP_FREE(s);
SNMP_FREE(n);
return -1;
}
}
netsnmp_session 像是内部封装的一个处理类。这里有三个地方需要关注,在3.3 重点解析,handle_snmp_packet,netsnmp_agent_check_packet,netsnmp_agent_check_parse
ini
s->callback = handle_snmp_packet;
sp = snmp_add(s, t, netsnmp_agent_check_packet,
netsnmp_agent_check_parse);
3.3 数据解析
netsnmp_agent_check_packet 好像就缓存了下地址,以及追加统计计数。 在 Net-SNMP 中,NETSNMP_USE_LIBWRAP
是一个 编译时宏定义 ,用于启用对 TCP Wrappers (libwrap
)的支持。其核心作用是允许 SNMP 守护进程(snmpd
)通过 TCP Wrappers 实现基于主机的访问控制,即通过 /etc/hosts.allow
和 /etc/hosts.deny
文件限制客户端访问
c++
//snmp_agent.c
int
netsnmp_agent_check_packet(netsnmp_session * session,
netsnmp_transport *transport,
void *transport_data, int transport_data_length)
{
char *addr_string = NULL;
#ifdef NETSNMP_USE_LIBWRAP
char *tcpudpaddr = NULL, *name;
short not_log_connection;
name = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE);
/* not_log_connection will be 1 if we should skip the messages */
not_log_connection = netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_DONT_LOG_TCPWRAPPERS_CONNECTS);
/*
* handle the error case
* default to logging the messages
*/
if (not_log_connection == SNMPERR_GENERR) not_log_connection = 0;
#endif
/*
* Log the message and/or dump the message.
* Optionally cache the network address of the sender.
*/
if (transport != NULL && transport->f_fmtaddr != NULL) {
/*
* Okay I do know how to format this address for logging.
*/
addr_string = transport->f_fmtaddr(transport, transport_data,
transport_data_length);
/*
* Don't forget to free() it.
*/
}
#ifdef NETSNMP_USE_LIBWRAP
/* Catch udp,udp6,tcp,tcp6 transports using "[" */
if (addr_string)
tcpudpaddr = strstr(addr_string, "[");
if ( tcpudpaddr != 0 ) {
char sbuf[64];
char *xp;
strlcpy(sbuf, tcpudpaddr + 1, sizeof(sbuf));
xp = strstr(sbuf, "]");
if (xp)
*xp = '\0';
if (hosts_ctl(name, STRING_UNKNOWN, sbuf, STRING_UNKNOWN)) {
if (!not_log_connection) {
snmp_log(allow_severity, "Connection from %s\n", addr_string);
}
} else {
snmp_log(deny_severity, "Connection from %s REFUSED\n",
addr_string);
SNMP_FREE(addr_string);
return 0;
}
} else {
/*
* don't log callback connections.
* What about 'Local IPC', 'IPX' and 'AAL5 PVC'?
*/
if (0 == strncmp(addr_string, "callback", 8))
;
else if (hosts_ctl(name, STRING_UNKNOWN, STRING_UNKNOWN, STRING_UNKNOWN)){
if (!not_log_connection) {
snmp_log(allow_severity, "Connection from <UNKNOWN> (%s)\n", addr_string);
};
SNMP_FREE(addr_string);
addr_string = strdup("<UNKNOWN>");
} else {
snmp_log(deny_severity, "Connection from <UNKNOWN> (%s) REFUSED\n", addr_string);
SNMP_FREE(addr_string);
return 0;
}
}
#endif /*NETSNMP_USE_LIBWRAP */
snmp_increment_statistic(STAT_SNMPINPKTS);
if (addr_string != NULL) {
netsnmp_addrcache_add(addr_string);
SNMP_FREE(addr_string);
}
return 1;
}
netsnmp_agent_check_parse 主要功能是 解析并记录 SNMP 请求的调试信息。它在 Agent 处理 SNMP 请求时被调用,用于在日志中输出详细的协议操作信息,帮助开发者调试或监控 SNMP 请求的流程
c++
//snmp_agent.c
int
netsnmp_agent_check_parse(netsnmp_session * session, netsnmp_pdu *pdu,
int result)
{
if (result == 0) {
if (snmp_get_do_logging() &&
netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_VERBOSE)) {
netsnmp_variable_list *var_ptr;
switch (pdu->command) {
case SNMP_MSG_GET:
snmp_log(LOG_DEBUG, " GET message\n");
break;
case SNMP_MSG_GETNEXT:
snmp_log(LOG_DEBUG, " GETNEXT message\n");
break;
case SNMP_MSG_RESPONSE:
snmp_log(LOG_DEBUG, " RESPONSE message\n");
break;
#ifndef NETSNMP_NO_WRITE_SUPPORT
case SNMP_MSG_SET:
snmp_log(LOG_DEBUG, " SET message\n");
break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
case SNMP_MSG_TRAP:
snmp_log(LOG_DEBUG, " TRAP message\n");
break;
case SNMP_MSG_GETBULK:
snmp_log(LOG_DEBUG, " GETBULK message, non-rep=%ld, max_rep=%ld\n",
pdu->errstat, pdu->errindex);
break;
case SNMP_MSG_INFORM:
snmp_log(LOG_DEBUG, " INFORM message\n");
break;
case SNMP_MSG_TRAP2:
snmp_log(LOG_DEBUG, " TRAP2 message\n");
break;
case SNMP_MSG_REPORT:
snmp_log(LOG_DEBUG, " REPORT message\n");
break;
#ifndef NETSNMP_NO_WRITE_SUPPORT
case SNMP_MSG_INTERNAL_SET_RESERVE1:
snmp_log(LOG_DEBUG, " INTERNAL RESERVE1 message\n");
break;
case SNMP_MSG_INTERNAL_SET_RESERVE2:
snmp_log(LOG_DEBUG, " INTERNAL RESERVE2 message\n");
break;
case SNMP_MSG_INTERNAL_SET_ACTION:
snmp_log(LOG_DEBUG, " INTERNAL ACTION message\n");
break;
case SNMP_MSG_INTERNAL_SET_COMMIT:
snmp_log(LOG_DEBUG, " INTERNAL COMMIT message\n");
break;
case SNMP_MSG_INTERNAL_SET_FREE:
snmp_log(LOG_DEBUG, " INTERNAL FREE message\n");
break;
case SNMP_MSG_INTERNAL_SET_UNDO:
snmp_log(LOG_DEBUG, " INTERNAL UNDO message\n");
break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
default:
snmp_log(LOG_DEBUG, " UNKNOWN message, type=%02X\n",
pdu->command);
snmp_increment_statistic(STAT_SNMPINASNPARSEERRS);
return 0;
}
for (var_ptr = pdu->variables; var_ptr != NULL;
var_ptr = var_ptr->next_variable) {
size_t c_oidlen = 256, c_outlen = 0;
u_char *c_oid = (u_char *) malloc(c_oidlen);
if (c_oid) {
if (!sprint_realloc_objid
(&c_oid, &c_oidlen, &c_outlen, 1, var_ptr->name,
var_ptr->name_length)) {
snmp_log(LOG_DEBUG, " -- %s [TRUNCATED]\n",
c_oid);
} else {
snmp_log(LOG_DEBUG, " -- %s\n", c_oid);
}
SNMP_FREE(c_oid);
}
}
}
return 1;
}
return 0; /* XXX: does it matter what the return value
* is? Yes: if we return 0, then the PDU is
* dumped. */
}
handle_snmp_packet 的实现如下
c++
int
handle_snmp_packet(int op, netsnmp_session * session, int reqid,
netsnmp_pdu *pdu, void *magic)
{
netsnmp_agent_session *asp;
int status, access_ret, rc;
/*
* We only support receiving here.
*/
if (op != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) {
return 1;
}
/*
* RESPONSE messages won't get this far, but TRAP-like messages
* might.
*/
if (pdu->command == SNMP_MSG_TRAP || pdu->command == SNMP_MSG_INFORM ||
pdu->command == SNMP_MSG_TRAP2) {
DEBUGMSGTL(("snmp_agent", "received trap-like PDU (%02x)\n",
pdu->command));
pdu->command = SNMP_MSG_TRAP2;
snmp_increment_statistic(STAT_SNMPUNKNOWNPDUHANDLERS);
return 1;
}
/*
* send snmpv3 authfail trap.
*/
if (pdu->version == SNMP_VERSION_3 &&
session->s_snmp_errno == SNMPERR_USM_AUTHENTICATIONFAILURE) {
send_easy_trap(SNMP_TRAP_AUTHFAIL, 0);
return 1;
}
if (magic == NULL) {
asp = init_agent_snmp_session(session, pdu);
status = SNMP_ERR_NOERROR;
} else {
asp = (netsnmp_agent_session *) magic;
status = asp->status;
}
#if defined(NETSNMP_DISABLE_SET_SUPPORT) && !defined(NETSNMP_NO_WRITE_SUPPORT)
if (pdu->command == SNMP_MSG_SET) {
/** Silvercreek protocol tests send set with 0 varbinds */
if (NULL == pdu->variables)
return netsnmp_wrap_up_request(asp, SNMP_ERR_NOERROR);
asp->index = 1;
return netsnmp_wrap_up_request(asp, SNMP_ERR_NOTWRITABLE);
}
#endif /* NETSNMP_DISABLE_SET_SUPPORT && !NETSNMP_NO_WRITE_SUPPORT */
if ((access_ret = check_access(asp->pdu)) != 0) {
if (access_ret == VACM_NOSUCHCONTEXT) {
/*
* rfc3413 section 3.2, step 5 says that we increment the
* counter but don't return a response of any kind
*/
/*
* we currently don't support unavailable contexts, as
* there is no reason to that I currently know of
*/
snmp_increment_statistic(STAT_SNMPUNKNOWNCONTEXTS);
/*
* drop the request
*/
netsnmp_remove_and_free_agent_snmp_session(asp);
return 0;
} else {
/*
* access control setup is incorrect
*/
send_easy_trap(SNMP_TRAP_AUTHFAIL, 0);
#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C)
#if defined(NETSNMP_DISABLE_SNMPV1)
if (asp->pdu->version != SNMP_VERSION_2c) {
#else
#if defined(NETSNMP_DISABLE_SNMPV2C)
if (asp->pdu->version != SNMP_VERSION_1) {
#else
if (asp->pdu->version != SNMP_VERSION_1
&& asp->pdu->version != SNMP_VERSION_2c) {
#endif
#endif
asp->pdu->errstat = SNMP_ERR_AUTHORIZATIONERROR;
asp->pdu->command = SNMP_MSG_RESPONSE;
snmp_increment_statistic(STAT_SNMPOUTPKTS);
if (!snmp_send(asp->session, asp->pdu))
snmp_free_pdu(asp->pdu);
asp->pdu = NULL;
netsnmp_remove_and_free_agent_snmp_session(asp);
return 1;
} else {
#endif /* support for community based SNMP */
/*
* drop the request
*/
netsnmp_remove_and_free_agent_snmp_session(asp);
return 0;
#if !defined(NETSNMP_DISABLE_SNMPV1) || !defined(NETSNMP_DISABLE_SNMPV2C)
}
#endif /* support for community based SNMP */
}
}
rc = netsnmp_handle_request(asp, status);
/*
* done
*/
DEBUGMSGTL(("snmp_agent", "end of handle_snmp_packet, asp = %8p\n",
asp));
return rc;
}