Net-SNMPのサブエージェントを作ってみる
プライベートMIBで監視するためのサブエージェントをNet-SNMPをベースに作成してみた。
作成&実行環境はLinux-CentOS7(64bit)、言語はC。
はじめに
Net-SNMPはオープンソースソフトウェアであり、その名の通りSNMPプロトコル関連の機能を提供している。
SNMPを使うとサーバやネットワーク機器のリソース状況(CPU使用率、メモリ使用率、ネットワークトラフィック)などを外部からネットワーク経由で取得することができる。SNMP関連のソフトウェアではTWSNMPマネージャをよく使う(ネコちゃんのアニメーションが印象的)
SNMPでは標準MIB(MIB-2)と呼ばれる管理情報の他にユーザが独自に定義した情報(プライベートMIB)を監視をすることができ、その実現方式の1つとしてサブエージェント経由で情報をやり取りする方法がある。今回はNET-SNMPのマスターエージェント(snmpd)にAgentX接続するサブエージェント(sub_agent)を作成して情報を監視してみる。
Net-SNMPのインストール
ソースコードのダウンロード
まずは下記からソースコードを取ってきてLinux環境にインストールしていく。
(yumでインストールしても良いけどなんとなく)
Downloadのリンクから辿りソースコード(tarball)をダウンロードする。
ダウンロードできたらLinux環境へもっていく。
configure / make / make install
ソースコード取ってこれたら解凍/展開して./configureを実行する。
今回のインストール先は/usr/local/とする。
$ tar zxvf net-snmp-5.7.3.tar.gz $ cd net-snmp-5.7.3 $ ./configure --prefix=/usr/local
ここで、5回ほどShellから質問されるが下記の通り回答した。(全部Enter連打でもOK)
<enter>はEnterキーの意。
*** Default SNMP Version: Default version of SNMP to use (3): 2<enter> *** System Contact Information: System Contact Information (@@no.where): root@localhost.localdomain<enter> *** System Location: System Location (Unknown): <enter> *** Logfile location: Location to write logfile (/var/log/snmpd.log): <enter> *** snmpd persistent storage location: Location to write persistent information (/var/net-snmp): <enter>
configureがエラーなく無事完了したらmakeする。
$ make ...(略) Can't locate ExtUtils/MakeMaker.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at Makefile.PL line 1. BEGIN failed--compilation aborted at Makefile.PL line 1. make: *** [perlmakefiles] Error 2
が、ここでエラーが発生して怒られる...
エラーメッセージからPerlの何かが足りなくて怒られている、調べると下記パッケージのインストールで解消されるらしい。
$ sudo yum install perl-ExtUtils-MakeMaker $ make
インストール後、再度makeすると確かにエラーなくコンパイルが完了。
最後にmake isntallを実行してインストールが完了。
$ sudo make install
コンフィグ(snmpd.conf)の作成
このままsnmpdを起動してもSNMPで情報取得を取得することが出来ないため、必要なコンフィグを記載したファイル(snmpd.conf)を作成する。
$ sudo mkdir -p /usr/local/etc/snmp $ sudo vim /usr/local/etc/snmp/snmpd.conf
snmpd.confの記載内容
# sec.name source community com2sec public localhost public # sec.model sec.name group public v2c public # incl/excl subtree mask view all included .1 80 # context sec.model sec.level prefix read write notify access public "" any noauth exact all none none # for sub_agent master agentx
とりあえず最低限のコンフィグだけ記載する、"master agentx"を記載するとサブエージェント間の通信ができるようになる。
マスターエージェント(snmpd)の起動
snmpd.confを作成したら下記のように引数を渡してsnmpdを起動する。
実行後に一応プロセスが起動しているかpsコマンドで確認。
$ sudo /usr/local/sbin/snmpd -c /usr/local/etc/snmpd.conf -p /var/run/snmpd.pid -M /usr/local/share/snmp/mibs $ ps aux |grep snmpd
snmpdが起動したら、試しにSNMPによって管理情報が取得できるかsystem(OID=.1.3.6.1.2.1.1)の情報にアクセスしてみる。
$ snmpwalk -v2c -c "public" 127.0.0.1 .1.3.6.1.2.1.1 SNMPv2-MIB::sysDescr.0 = STRING: Linux localhost.localdomain 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10 DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (8763647) 1 day, 0:20:36.47 SNMPv2-MIB::sysContact.0 = STRING: root@localhost.localdomain SNMPv2-MIB::sysName.0 = STRING: localhost.localdomain SNMPv2-MIB::sysLocation.0 = STRING: Unknown SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00 ...(略)
サブエージェントのプログラミング
Net-SNMPがインストールできたので、サブエージェントを作成していく。
ソースコードはNet-SNMPのサンプルコードをベースにする。
サブエージェントが監視するプライベートMIBは簡単のため下記の仕様とした。
・OIDは1.3.6.1.4.1.99999.1
・返却値は3つの固定値(systemのパクリ)
・READ-ONLY
ソースコード
作成するファイル
example-daemon.c
nstAgentSubagentObject.c
nstAgentSubagentObject.h
Makefile
example-daemon.c
#include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> #include <signal.h> #include <nstAgentSubagentObject.h> extern void init_usmUser(void); extern void init_vacm_vars(void); static int keep_running; RETSIGTYPE stop_server(int a) { keep_running = 0; } int main (int argc, char **argv) { int agentx_subagent=1; /* change this if you want to be a SNMP master agent */ int background = 0; /* change this if you want to run in the background */ int syslog = 0; /* change this if you want to use syslog */ /* print log errors to syslog or stderr */ if (syslog) snmp_enable_calllog(); else snmp_enable_stderrlog(); /* we're an agentx subagent? */ if (agentx_subagent) { /* make us a agentx client. */ netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1); } /* run in background, if requested */ if (background && netsnmp_daemonize(1, !syslog)) exit(1); /* initialize tcpip, if necessary */ SOCK_STARTUP; /* initialize the agent library */ init_agent("example-sub_agent"); /* initialize mib code here */ /* mib code: init_nstAgentSubagentObject from nstAgentSubagentObject.C */ init_nstAgentSubagentObject(); /* initialize vacm/usm access control */ if (!agentx_subagent) { init_vacm_vars(); init_usmUser(); } /* example-demon will be used to read example-demon.conf files. */ init_snmp("example-sub_agent"); /* If we're going to be a snmp master agent, initial the ports */ if (!agentx_subagent) init_master_agent(); /* open the port to listen on (defaults to udp:161) */ /* In case we recevie a request to stop (kill -TERM or kill -INT) */ keep_running = 1; signal(SIGTERM, stop_server); signal(SIGINT, stop_server); snmp_log(LOG_INFO,"example-demon is up and running.\n"); #ifdef TRAP_TEST send_easy_trap(SNMP_TRAP_ENTERPRISESPECIFIC, 99); #endif /* your main loop here... */ while(keep_running) { /* if you use select(), see snmp_select_info() in snmp_api(3) */ /* --- OR --- */ agent_check_and_process(1); /* 0 == don't block */ } /* at shutdown time */ snmp_shutdown("example-demon"); SOCK_CLEANUP; return 0; }
nstAgentSubagentObject.c
/* * Note: this file originally auto-generated by mib2c using * : mib2c.scalar.conf 17337 2009-01-01 14:28:29Z magfr $ */ #include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <net-snmp/agent/net-snmp-agent-includes.h> #include "nstAgentSubagentObject.h" /* Instance Declear */ char sysDscr[] = "My System Information Group"; oid sysObjectID[] = { 1, 3, 6, 1, 4, 1, 99999, 1, 2 }; uint64_t sysUpTime = 1483196400; /* oid Declear */ oid myMib_sysDscr_oid[] = { 1, 3, 6, 1, 4, 1, 99999, 1, 1 }; oid myMib_sysObjectID_oid[] = { 1, 3, 6, 1, 4, 1, 99999, 1, 2 }; oid myMib_sysUpTime_oid[] = { 1, 3, 6, 1, 4, 1, 99999, 1, 3 }; typedef struct { char* type; int (*handler)(netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests); oid* myMib_system_oid; size_t oid_len; int modes; } myMib_t; myMib_t myMib[] = { { .type = "myMib-sysDscr", .handler = handle_myMib_sysDscr, .myMib_system_oid = myMib_sysDscr_oid, .oid_len = OID_LENGTH(myMib_sysDscr_oid), .modes = HANDLER_CAN_RONLY, }, { .type = "myMib-sysObjectID", .handler = handle_myMib_sysObjectID, .myMib_system_oid = myMib_sysObjectID_oid, .oid_len = OID_LENGTH(myMib_sysObjectID_oid), .modes = HANDLER_CAN_RONLY, }, { .type = "myMib-sysUpTime", .handler = handle_myMib_sysUpTime, .myMib_system_oid = myMib_sysUpTime_oid, .oid_len = OID_LENGTH(myMib_sysUpTime_oid), .modes = HANDLER_CAN_RONLY, }, }; #define MYMIB_SYSTEM_MIBREG_MAX sizeof(myMib)/sizeof(myMib[0]) /** Initializes the mymib module */ void init_nstAgentSubagentObject(void) { DEBUGMSGTL(("my sub_agent", "initializing\n")); for (int i = 0; i < MYMIB_SYSTEM_MIBREG_MAX; i++) { netsnmp_register_scalar(netsnmp_create_handler_registration (myMib[i].type, myMib[i].handler, myMib[i].myMib_system_oid, myMib[i].oid_len, myMib[i].modes)); } } int handle_myMib_sysDscr( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests) { /* * 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, (u_char *) sysDscr, strlen(sysDscr)+1); break; default: /* * we should never get here, so this is a really bad error */ snmp_log(LOG_ERR, "unknown mode (%d) in handle_myMib-sysObjectID\n", reqinfo->mode); return SNMP_ERR_GENERR; } return SNMP_ERR_NOERROR; } int handle_myMib_sysObjectID( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests) { /* * 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_OBJECT_ID, (u_char *) sysObjectID, sizeof(sysObjectID)); break; default: /* * we should never get here, so this is a really bad error */ snmp_log(LOG_ERR, "unknown mode (%d) in handle_myMib-sysObjectID\n", reqinfo->mode); return SNMP_ERR_GENERR; } return SNMP_ERR_NOERROR; } int handle_myMib_sysUpTime( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests) { /* * 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_TIMETICKS, (u_char *) &sysUpTime, sizeof(sysUpTime)); break; default: /* * we should never get here, so this is a really bad error */ snmp_log(LOG_ERR, "unknown mode (%d) in handle_myMib-sysUpTime\n", reqinfo->mode); return SNMP_ERR_GENERR; } return SNMP_ERR_NOERROR; }
nstAgentSubagentObject.h
/* * Note: this file originally auto-generated by mib2c using * : mib2c.scalar.conf 17337 2009-01-01 14:28:29Z magfr $ */ #ifndef MYMIB_H #define MYMIB_H /* * function declarations */ extern int handle_myMib_sysDscr( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests); extern int handle_myMib_sysObjectID( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests); extern int handle_myMib_sysUpTime( netsnmp_mib_handler* handler, netsnmp_handler_registration* reginfo, netsnmp_agent_request_info* reqinfo, netsnmp_request_info* requests); Netsnmp_Node_Handler handle_myMib_sysDscr; Netsnmp_Node_Handler handle_myMib_sysObjectID; Netsnmp_Node_Handler handle_myMib_sysUpTime; extern void init_nstAgentSubagentObject(void); #endif /* MYMIB_H */
Makefile
CFLAGS = -g -O2 -std=gnu99 -I/usr/local/include/ -I./ LFLAGS = -L/usr/local/lib SNMPLIB = -lnetsnmp -lnetsnmpagent -lnetsnmphelpers -lnetsnmpmibs TARGET = sub_agent sub_agent: example-daemon.o nstAgentSubagentObject.o gcc -o $(TARGET) $(CFLAGS) nstAgentSubagentObject.o example-daemon.o $(SNMPLIB) example-daemon.o: example-daemon.c nstAgentSubagentObject.h gcc -c $(CFLAGS) example-daemon.c $(SNMPLIB) nstAgentSubagentObject.o: nstAgentSubagentObject.c nstAgentSubagentObject.h gcc -c $(CFLAGS) nstAgentSubagentObject.c $(SNMPLIB) clean: rm -f $(TARGET) *.o
サブエージェント起動
ソースコードを作成したらmakeをかけてコンパイルをする。
$ make gcc -c -g -O2 -std=gnu99 -I/usr/local/include/ -I./ example-daemon.c -lnetsnmp -lnetsnmpagent -lnetsnmphelpers -lnetsnmpmibs gcc -c -g -O2 -std=gnu99 -I/usr/local/include/ -I./ nstAgentSubagentObject.c -lnetsnmp -lnetsnmpagent -lnetsnmphelpers -lnetsnmpmibs gcc -o sub_agent -g -O2 -std=gnu99 -I/usr/local/include/ -I./ nstAgentSubagentObject.o example-daemon.o -lnetsnmp -lnetsnmpagent -lnetsnmphelpers -lnetsnmpmibs
これでサブエージェント(sub_agent)が出来上がったが、SNMP関連のライブラリのインストール先を/usr/local配下にしたため共有ライブラリのパスが通っていないので解消する。
共有ライブラリの確認はlddコマンドで行う。not foundのライブラリが未解決。
$ ldd sub_agent linux-vdso.so.1 => (0x00007ffc497e1000) libnetsnmp.so.30 => not found libnetsnmpagent.so.30 => not found libnetsnmphelpers.so.30 => not found libnetsnmpmibs.so.30 => not found libc.so.6 => /lib64/libc.so.6 (0x00007f0346b02000) /lib64/ld-linux-x86-64.so.2 (0x00007f0346ece000)
/etc/ld.so.conf.dの下にライブラリパスを記載したファイルを配置してldconfigコマンドを実行するとライブラリパスが反映される。
$ sudo vim /etc/ld.so.conf.d/net-snmp.conf $ sudo cat /etc/ld.so.conf.d/net-snmp.conf /usr/local/lib $ sudo ldconfig
再度、lddコマンドで確認しパスが表示されていればOK。
$ ldd sub_agent linux-vdso.so.1 => (0x00007ffe4dfd0000) libnetsnmp.so.30 => /usr/local/lib/libnetsnmp.so.30 (0x00007f5fa0fe0000) libnetsnmpagent.so.30 => /usr/local/lib/libnetsnmpagent.so.30 (0x00007f5fa0d7d000) libnetsnmphelpers.so.30 => /usr/local/lib/libnetsnmphelpers.so.30 (0x00007f5fa0b7b000) libnetsnmpmibs.so.30 => /usr/local/lib/libnetsnmpmibs.so.30 (0x00007f5fa0713000) libc.so.6 => /lib64/libc.so.6 (0x00007f5fa0352000) libm.so.6 => /lib64/libm.so.6 (0x00007f5fa004f000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f5f9fe4b000) /lib64/ld-linux-x86-64.so.2 (0x00007f5fa12cf000) $
その後、サブエージェント(sub_agent)を起動し"AgentX subagent connected"のログが画面に出れば起動完了。(befferが小さいとかいうエラーはとりあえず無視。)
$ sudo ./sub_agent & buffer too small to read octet string (17 < 17) NET-SNMP version 5.7.3 AgentX subagent connected example-demon is up and running.
プライベートMIBの取得
マスターエージェント、サブエージェントの両方を起動したら実際に定義したプライベートMIBにアクセスできるか試してみる。snmpwalkを実行して値が表示されれば成功。
$ snmpwalk -v2c -c "public" 127.0.0.1 .1.3.6.1.4.1.99999 -Ona .1.3.6.1.4.1.99999.1.1.0 = STRING: "My System Information Group." .1.3.6.1.4.1.99999.1.2.0 = OID: .1.3.6.1.4.1.99999.1.2 .1.3.6.1.4.1.99999.1.3.0 = Timeticks: (1483196400) 171 days, 15:59:24.00