コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

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でインストールしても良いけどなんとなく)

net-snmp.sourceforge.net


Downloadのリンクから辿りソースコード(tarball)をダウンロードする。

f:id:segmentation-fault:20170727221254p:plain

f:id:segmentation-fault:20170727221339p:plain

f:id:segmentation-fault:20170727221401p:plain


ダウンロードできたら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


[参考]
net-snmp.sourceforge.net

www.valinux.co.jp