1 、 SNMP 代理需求
(1) 提供设备相关的信息节点,供 SNMP 管理端查询,可支持 get 、 get next 和 walk 查询命令。提供 SN 、 WAN 口 IP 、 CPU 占用率、内存使用率、进程个数、网络接口状态、网络接口当前流量、软件版本信息等节点信息。
(2) 提供对特俗事件到来时的发往 SNMP 管理端得 TRAP 告警功能。包括 CPU 使用率超阈值、内存超阈值、网口流量超阈值、 WAN 口 IP 改变、设备重启等告警功能(暂未做)。
提供对 snmp v1 、 v2 版本的支持( v3 版本将在后期实现)
2 、总体设计思想
(1) 开发环境:基于开源软件 net-snmp- 5.4.2 。
(2) MIB 库来源:根据管理端的功能需求和节点的定义自行开发。
(3) 程序实现:利用 mib2c 工具将 MIB 库转化为代理程序框架,然后基于此框架进行进一步开发。最后将开发的代理程序静态编译进 net-snmp 框架中,它将注册到 net-snmp 框架中。注册的方法是再特定目录下(以 agent/mibgroup/bamboo 目录为例)分别建立一个 .c 文件和头文件,例如,名称分别取为 bamboo.c 和 bamboo.h ,在 bamboo 中存放自己开发的所有 c 程序和头文件 ( 如 xxx.c 和 xxx.h) ,然后在 bamboo.h 中添加 module_require(xxx) ,并在 configure 时跟上:— with-mib_modules= ” bamboo ”来配置 Makefile ,使之在编译时加载 bamboo 文件下面的 xxx 文件(可以有多个 xxx 文件)。
3 、 get 信息节点的实现,以 cpu 占用率信息节点为例
(1) 自己编写该节点 MIB 库,由于 eX110 只有一个 CPU ,所以编写一个节点即可:
hr_proc OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS read-only
STATUS current
DESCRIPTION "cpu usage"
::= { my_cpu 2 }
(2) 利用 mib2c 工具 mib2c_old-api.conf 将该 mib 库转化为 get 信息节点的程序框架,然后基于该框架,添加需要实现的功能,部分代码如下:
/* 由系统基于 MIB 库生成的与节点相关的信息的结构体 */
#define HRPROC_LOAD 2
struct variable4 hrproc_variables[] = {
{HRPROC_LOAD, ASN_INTEGER, RONLY, var_hrproc, 3, {1, 2, 768}}
};
oid hrproc_variables_oid[] = { 1, 3, 6, 1, 2, 1, 25, 3, 3 };
/* 由系统生成的该节点初始化函数,在该初始化函数中注册了针对该节点的处理函数 */
void init_hr_proc(void)
{
REGISTER_MIB("bamboo/hr_proc", hrproc_variables, variable4,
hrproc_variables_oid);
}
/*
* header_hrproc :自己编写实现的函数,该函数将已注册到 net-snmp 的节点信息和管理端与访问的节点信息作比较。由于规定第一个 CPU 是用节点编号 768 开始,并且 eX110 只有一个 CPU , 即要求该节点为 1.3.6 .1.2.1.25.3.3.1.2 . 768 ,所以将 netsnmp 中提供 head_generic() 函数中将已注册节点最后加“ 0 ”这步去掉, 直接将注册的该节点和跟管理端欲访问的节点作对比。
* vp IN - 指向已注册节点数组的指针
* name IN/OUT - 指向管理端欲访问节点的指针
* length IN/OUT - length of IN/OUT oid's
* exact IN - TRUE if an exact match was requested
* var_len OUT - length of variable or 0 if function returned
* write_method
*/
Int header_hrproc(struct variable *vp,
oid * name,
size_t * length,
int exact, size_t * var_len, WriteMethod ** write_method)
{
oid newname[MAX_OID_LEN];
int result;
memcpy((char *) newname, (char *) vp->name,
(int) vp->namelen * sizeof(oid));
//newname[vp->namelen] = 0; // 不需要在已注册节点后加“ 0 ” .
result = snmp_oid_compare(name, *length, newname, vp->namelen);
if ((exact && (result != 0)) || (!exact && (result >= 0)))
return (MATCH_FAILED);
memcpy((char *) name, (char *) newname,
((int) vp->namelen) * sizeof(oid));
*length = vp->namelen;
*write_method = 0;
*var_len = sizeof(long); /* default to 'long' results */
return (MATCH_SUCCEEDED);
}
/* System specific implementation functions 注册的处理函数 */
u_char *var_hrproc(struct variable * vp,
oid * name,
size_t * length,
int exact, size_t * var_len, WriteMethod ** write_method)
{
int proc_idx;
static unsigned char string[SPRINT_MAX_LEN];
FILE *file;
proc_idx =
header_hrproc(vp, name, length, exact, var_len, write_method);
if (proc_idx == MATCH_FAILED)
return NULL;
switch (vp->magic) {
case HRPROC_LOAD:
get_cpu_usage(cpu_usage); // 获取 CPU 使用率
snprintf(string, sizeof(string), cpu_usage); /* ++by Bamboo */
long_return = atoi(string);
if ((file = fopen("/var/run/snmp.log", "w")) == NULL)
return;
fprintf(file, "string is:%s , value is :%d/n", string, long_return);
fclose(file);
return (u_char *) &long_return ; // 返回 long_return
default:
DEBUGMSGTL(("host/hr_proc", "unknown sub-id %d in var_hrproc/n",
vp->magic));
}
return NULL;
}
/*get cpu usage information for file loadavg5 and save it in string[] */
void
get_cpu_usage (char *string)
{
FILE *file;
char *str;
int i = 0;
char tmp[16];
if ((file = fopen("/proc/loadavg5", "r")) == NULL)
return;
fread(tmp, 1, sizeof(tmp), file);
fclose(file);
str = tmp;
while (*str != '/n') {
if (*str == '.') {
str++;
}
else
*(string + i++) = *str++;
}
*(string + i) = '/0';
return;
}
3 、 trap 告警的实现,以 cpu 使用率超标 Trap 的实现为例
(1) 该 Trap 包需包含信息: a 、 cpuoverflow 的 trap 节点 1.3.6 .1.4.1.3902.151.11.10.3.5.1 (自己定义)
b 、携带的其他信息携带变量:
SN oid : 1.3.6 .1.2.1.47.1.1.1.1.11 ,和对应的值
CPU 使用率 oid : 1.3.6 .1.2.1.25.3.3.1.2 ,和其对应的值
阈值 oid: 1.3.6.1.4.1.3902.151.11.10.3.3.9.1 ,和其对应的值
(2) 实现方法:参考示例 notfication.c ,并进行信号注册函数的修改,最终实现自己的功能。
(3) 程序实现:
/*cpu 超阈值告警初始化,即注册新号处理函数,每 30 秒执行一次 */
Void init_cpuoverflow(void)
{
snmp_alarm_register(30 , /* seconds ,可自行设置时间间隔 */
SA_REPEAT, /* repeat (every 30 seconds). */
send_cpuoverflow, /* our callback */
NULL /* no callback data needed */
);
}
/* 要调用的回调函数的实现 */
Void send_cpuoverflow(unsigned int clientreg, void *clientarg)
{
/*
* define the OID for the cpuoverflow we're going to send
* NET-SNMP-EXAMPLES-MIB::netSnmpExampleHeartbeatNotification
*/
FILE *file;
oid cpuoverflow_oid[] =
{ 1, 3, 6, 1, 4, 1, 3902, 151, 11, 10, 3, 5, 1};
size_t cpuoverflow_oid_len = OID_LENGTH(cpuoverflow_oid);
static u_long count = 0;
u_char sn[64];
u_char cpu_usage_str[8];
unsigned cpu_usage;
u_char cpu_threshold_str[8];
get_cpu_usage(cpu_usage_str);
cpu_usage = atoi(cpu_usage_str);
snprintf(cpu_threshold_str, sizeof(cpu_threshold_str), nvram_safe_get("cpu_threshold"));
/*if cpu percentage is small than threshold, do not trap*/
if (cpu_usage < atoi(cpu_threshold_str)) // 若 CPU 没超阈值,则返回。
return;
snprintf(sn, sizeof(sn), nvram_safe_get("dev_sn"));
/*
* In the cpuoverflow, we have to assign our cpuoverflow OID to
* the snmpTrapOID.0 object. Here is it's definition.
*/
oid objid_snmptrap[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
size_t objid_snmptrap_len = OID_LENGTH(objid_snmptrap);
oid devicesn_oid[] = { 1, 3, 6, 1, 2, 1, 47, 1, 1, 1, 1, 11, 1 };
size_t devicesn_oid_len = OID_LENGTH(devicesn_oid);
oid hrproc_load_oid[] = { 1, 3, 6, 1, 2, 1, 25, 3, 3, 1, 2, 768 };
size_t hrproc_load_oid_len = OID_LENGTH(hrproc_load_oid);
oid cpu_threshold_oid[] = { 1, 3, 6, 1, 4, 1, 3902, 151, 11, 10, 3, 3, 9, 1, 0 };
size_t cpu_threshold_oid_len = OID_LENGTH(cpu_threshold_oid);
/* here is where we store the variables to be sent in the trap */
netsnmp_variable_list *cpuoverflow_vars = NULL; // 所有的绑定变量都将保存在该链表中
/*add in the trap definition object 依次手动加载要绑定的变量到链表中 */
snmp_varlist_add_variable(&cpuoverflow_vars,
objid_snmptrap, objid_snmptrap_len,
ASN_OBJECT_ID,
(u_char *) cpuoverflow_oid,
cpuoverflow_oid_len * sizeof(oid));
snmp_varlist_add_variable(&cpuoverflow_vars,
devicesn_oid, devicesn_oid_len,
ASN_OCTET_STR,
sn,
strlen(sn));
snmp_varlist_add_variable(&cpuoverflow_vars,
hrproc_load_oid, hrproc_load_oid_len,
ASN_INTEGER,
(u_char *)&cpu_usage,
sizeof(cpu_usage));
snmp_varlist_add_variable(&cpuoverflow_vars,
cpu_threshold_oid, cpu_threshold_oid_len,
ASN_OCTET_STR,
cpu_threshold_str,
strlen(cpu_threshold_str));
++count; // 该 trap 每执行一次,计数器加 1
send_v2trap(cpuoverflow_vars); // 发送 snmpv2 trap
/* free the created cpuoverflow variable list */
snmp_free_varbind(cpuoverflow_vars);
}
4 、 SNMP 移植到 eX110 的过程
(1) 在 eX110 下的 Makefile 中添加:
build: user snmp romfs linux image
snmp:: armeb-linux-strip $(IXPDIR)/user/net-snmp/sbin/snmpd
cp $(IXPDIR)/user/net-snmp/sbin/snmpd $(IXPDIR)/user/romfs/sbin
(2) 在 user 下添加
snmp:: make -C snmp
snmp-install:: make -C snmp install
snmp-clean:: make -C snmp clean
(3) 在 defaulte.c 的 router_default[] 中添加:
{"dev_sn", "123456789abcdefg", 0},
{"oui", "cdsf", 0},
{"cpu_threshold", "20", 0},
{"mem_threshold", "20", 0},
{"recv_octet_threshold", "102400", 0},
{"send_octet_threshold", "102400", 0},
{"snmp_community", "public", 0},
{"snmp_version", "v2c", 0},
{"snmp_enable", "1", 0},
{"snmp_server_ip", "0.0.0.0", 0},
{"wan_alter_trap_enable", "1", 0},
{"cpu_trap_enable", "1", 0},
{"mem_trap_enable", "1", 0},
{"recv_trap_enable", "1", 0},
{"send_trap_enable", "1", 0},
{"snmpv3_user", "navigator", 0},
{"snmpv3_md5_auth", "navigator", 0},
{"snmpv3_des_passwd", "navigator", 0},
(4) 在 basic.c 中的 apply_action 中添加:
{ "snmpd_setup", "snmpd", 16, SERVICE_RESTART, NULL},
在 variable[] 中添加:
/*for snmpd*/
{"dev_sn", "Dvice SN", NULL, NULL, FALSE},
{"oui", "OUI Number", NULL, NULL, FALSE},
{"cpu_threshold", "CPU Threshold Value", NULL, NULL, FALSE},
{"mem_threshold", "MEM Threshold Value", NULL, NULL, FALSE},
{"recv_octet_threshold", "Recive Octet Threshold Value", NULL, NULL, FALSE},
{"send_octet_threshold", "Send Octet Threshold Value", NULL, NULL, FALSE},
{"snmp_community", "Read Only COMMUNITY", NULL, NULL, FALSE},
{"snmp_version", "SNMP Version", NULL, NULL, FALSE},
{"snmp_enable", "SNMP Enable", validate_choice, ARGV("0", "1"), FALSE},
{"snmp_server_ip", "Mamager Server Number", NULL, NULL, FALSE},
{"wan_alter_trap_enable", "WAN IP ALTER NOTIFY", validate_choice, ARGV("0", "1"), FALSE},
{"cpu_trap_enable", "CPU TRAP ENABLE", validate_choice, ARGV("0", "1"), FALSE},
{"mem_trap_enable", "MEMORY TRAP ENABLE", validate_choice, ARGV("0", "1"), FALSE},
{"recv_trap_enable", "RECV OCTETS TRAP ENABLE", validate_choice, ARGV("0", "1"), FALSE},
{"send_trap_enable", "SEND OCTETS TRAP ENABLE", validate_choice, ARGV("0", "1"), FALSE},
{"snmpv3_user", "SNMPV3 USER NAME", NULL, NULL, FALSE},
{"snmpv3_md5_auth", "SNMPV3 MD5 AUTHENTICATION CODEI", NULL, NULL, FALSE},
{"snmpv3_des_passwd", "SNMPV3 DES PASSWORD", NULL, NULL, FALSE},
(5) 在 service.c 中编写 start_snmpd() 和 stop_snmpd()
int start_snmpd(void)
{
FILE *fp;
char ser_ip[16];
char comnt[32];
char version[8];
char user[32];
char md5_auth[32];
char des_passwd[32];
int snmp_enable;
snmp_enable = atoi(nvram_safe_get("snmp_enable"));
if (snmp_enable == 0)
return 0;
if (!nvram_invmatch("snmp_enable", "0"))
return -1;
strncpy(ser_ip, nvram_safe_get("snmp_server_ip"), sizeof(ser_ip));
strncpy(comnt, nvram_safe_get("snmp_community"), sizeof(comnt));
strncpy(version, nvram_safe_get("snmp_version"), sizeof(version));
strncpy(user, nvram_safe_get("snmpv3_user"), sizeof(user));
strncpy(md5_auth, nvram_safe_get("snmpv3_md5_auth"), sizeof(md5_auth));
strncpy(des_passwd, nvram_safe_get("snmpv3_des_passwd"), sizeof(des_passwd));
/* Touch snmp config file */
if (!(fp = fopen(SNMP_CONFIG_FILE, "w"))) {
perror(SNMP_CONFIG_FILE);
return errno;
}
fprintf(fp, "com2sec snmpserver default %s/n", comnt);
if (!strcmp("v3",version))
fprintf(fp, "group ROGroup usm snmpserver/n");
else
fprintf(fp, "group ROGroup %s snmpserver/n", version);
fprintf(fp, "view all included .1 80/n");
fprintf(fp, "access ROGroup /"/" any noauth exact all none none/n");
fprintf(fp, "syscontact Me <me@ssnsm.org>/n");
fprintf(fp, "proc mountd/n");
fprintf(fp, "proc ntalkd 4/n");
fprintf(fp, "proc sendmail 10 1/n");
fprintf(fp, "trap2sink %s:162 %s/n", ser_ip, comnt);
if (!strcmp("v3", version)) {
fprintf(fp, "rwuser %s/n", user);
fprintf(fp, "createUser %s MD5 %s DES %s/n", user, md5_auth, des_passwd);
}
fclose(fp);
eval("snmpd", "-c", SNMP_CONFIG_FILE);
return 0;
}
int stop_snmpd(void)
{
int ret = 0;
if (find_pid_by_name("snmpd") < 0)
return ret;
ret = eval("killall", "-9", "snmpd");
dprintf("done/n");
return ret;
}
(6) 在 rc.hz 中添加 extern int start_snmpd(void); extern int stop_snmpd(void);
(7) 在 start_single_service() 中添加:
else if (!strcmp(service, "snmpd"))
{
stop_snmpd();
start_snmpd();
}
在 start_servcies() 中添加:
start_snmpd(); //++ Bamboo
( 7 )写该 snmpd 顶层 Makefile 中的 install:
install::
armeb-linux-strip ……/snmpd
cp ……/snmpd …..../user/romfs/sbin
5 、 SNMPv3 的实现
Snmp DES 加密功能的实现需要 openssl 的支持,所以应首先编译 openssl 开源软件包,最后需要两个静态库 libcrypto.a 和 libssl.a ,以及它的头文件头文件。并在 snmpd 的配置文件 cross 中指定静态库的目录路径和静态库( -lcrypto –lssl 要按这个顺序),指定头文件的目录路径。
(1) openssl 的编译,主要需要处理一些错误方能通过编译:
a 、 将 openssl-0.9.8d/crypto/bio/bss_file.c 中以下代码注释掉
//#ifndef _FILE_OFFSET_BITS
//#define _FILE_OFFSET_BITS 64
//#endif
#endif
b 、 报错没有 timeb.h 时,在报错的 .c 文件中 #undef TIMEB
c 、 去掉 openssl 顶层 Makefile 中“ build_all :”后面的 build_app build_test (我们要的只是两个静态库 libcrypto.a 和 libssl.a ),并去掉 ”DIRS=” 后面的 app 和 tests 。
( 2 )在配置 net-snmp 时,指定静态库的目录路径和静态库( -lcrypto –lssl 要按这个顺序),指定头文件的目录路径。
这样,便可以是 SNMP 加载进 DES 加密算法和 SHA1 认证(暂时不用该认证)。
( 3 ) SNMPv3 的配置文件 snmpd.conf 配置示例如下:
rwuser navigator
createUser navigator MD5 navigator DES navigator
group ROGroup usm navigator
group RWGroup usm navigator
view all included .1 80
access ROGroup "" any noauth exact all none none
access RWGroup "" any noauth exact all all none
syscontact Me <me@ssnsm.org>
syslocation Chengdu
trap2sink 192.168.16.8:162 public