SNMP 独自エージェントを作成してスクリプトの結果を監視する

「Ubuntu に SNMP をインストール」
https://qiita.com/nanbuwks/items/3632b3180e587152bfc7

で設定した SNMP を使って、独自の結果を表示したい。

「SNMP でスクリプトの実行結果を表示する」
https://qiita.com/nanbuwks/items/b23cc954ff1ddef000e5

において、 snmpd.conf に exec デレクティブなどを書く方法を使いました。

今回は、SNMP 独自エージェントを作成して独自の値を監視する方法にチャレンジしてみます。


# 環境 

以下で動作確認しています
- Ubuntu 22.04 LTS AMD64
- Raspberry Pi OS Lite Release date: April 4th 2022.04 

# しくみ

net-snmp を使って独自にエージェントを作成するにはいくつか方法があります。
そのうち、今回は以下に説明されている AgentX プロトコルを使うやり方にします。
https://net-snmp.sourceforge.io/tutorial/tutorial-5/toolkit/demon/index.html

この資料には、AgentX は以下のように説明されています。

> 拡張可能なSNMPエージェントの標準化されたフレームワークを定義します。これは、マスターエージェントとサブエージェントと呼ばれる処理エンティティ、それらの間の通信に使用されるプロトコル（AgentX）と、拡張可能エージェントがSNMPプロトコルメッセージを処理するプロトコルの要素を定義します。

これを使い、今回は以下のように実現します。

- net-snmp のマスターエージェントとして snmpd を動かしておく
- 独自の MIB を処理するだけのサブエージェントプログラムを作成する
- サブエージェントプログラムは AgentX プロトコルを使用してマスターエージェントである snmpd と通信できるようになる
- 必要な処理だけサブエージェントプログラムが返す

# 準備 


必要なソフトウェアをインストールしておきます。

```
$ sudo apt install git 

```
など、他に c や make などの開発環境も必要となります。

また、今回は使いませんでしたが mib2c を使う場合は以下も必要となります。

```
$ sudo apt install libsnmp-dev
$ sudo apt-get install libsnmp-perl
```



# snmpd.conf の設定
「Ubuntu に snmp をインストール」
https://qiita.com/nanbuwks/items/3632b3180e587152bfc7
で設定した /etc/snmp/snmpd.conf に更に以下を追加します

```
master agentx
```
変更を反映しておきます
```
$ sudo service snmpd restart
```
# サンプルプログラムをダウンロード

https://net-snmp.sourceforge.io/tutorial/tutorial-5/toolkit/demon/index.html

以降、このページの説明を読んで、やっていきます。

```
$ git clone https://github.com/net-snmp/subagent-example.git
$ cd subagent-example/
```

Makefile を以下のように作りかえます。

```
#
# Warning: you may need more libraries than are included here on the
# build line.  The agent frequently needs various libraries in order
# to compile pieces of it, but is OS dependent and we can't list all
# the combinations here.  Instead, look at the libraries that were
# used when linking the snmpd master agent and copy those to this
# file.
#

CC=gcc

TARGET=example-demon

NET_SNMP_CONFIG=net-snmp-config
CFLAGS=`$(NET_SNMP_CONFIG) --cflags` -Wall -Wextra -Werror \
        -Wno-unused-parameter
BUILDLIBS=`$(NET_SNMP_CONFIG) --libs`
BUILDAGENTLIBS=`$(NET_SNMP_CONFIG) --agent-libs`

# shared library flags (assumes gcc)
DLFLAGS=-fPIC -shared

example-demon: example-demon.o nstAgentSubagentObject.o
        $(CC) -o $@ $@.o nstAgentSubagentObject.o $(BUILDAGENTLIBS)

clean:
        rm -f -- *.o $(TARGETS)

example-daemon.o: example-daemon.c nstAgentSubagentObject.h
        $(CC) $(CFLAGS) $(DLFLAGS) -c -o $@ example-daemon.c

nstAgentPluginObject.o: nstAgentPluginObject.c Makefile
        $(CC) $(CFLAGS) $(DLFLAGS) -c -o $@ nstAgentPluginObject.c

nstAgentPluginObject.so: nstAgentPluginObject.o Makefile
        $(CC) $(CFLAGS) $(DLFLAGS) -o $@ nstAgentPluginObject.o

```

必要最小限の example-daemon のみ作ってみることにしました。
makeします


```
$ make
gcc `net-snmp-config --cflags` -Wall -Wextra -Werror -Wno-unused-parameter   -c -o example-demon.o example-demon.c
gcc `net-snmp-config --cflags` -Wall -Wextra -Werror -Wno-unused-parameter   -c -o nstAgentSubagentObject.o nstAgentSubagentObject.c
gcc -o example-demon example-demon.o nstAgentSubagentObject.o `net-snmp-config --agent-libs`

```

なお、以下のようにエラーが出てくる場合はコピペでタブがスペースに変換されているのが原因ですので、エディターで字下げのスペースをタブに変更して下さい。
```
Makefile:24: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.
```


先の Makefile には共有ライブラリを揃えてくださいと書いています。コンパイルした example-daemon に必要なライブラリが足りているかどうかチェックしてみます。
```
$ ldd example-demon
	linux-vdso.so.1 (0x00007fff405ec000)
	libnetsnmpagent.so.35 => /lib/x86_64-linux-gnu/libnetsnmpagent.so.35 (0x00007fefb914f000)
	libnetsnmp.so.35 => /lib/x86_64-linux-gnu/libnetsnmp.so.35 (0x00007fefb8fd1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fefb8ddf000)
	libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007fefb8dd3000)
	libperl.so.5.30 => /lib/x86_64-linux-gnu/libperl.so.5.30 (0x00007fefb8a7e000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fefb8a5b000)
	libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fefb8783000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fefb91e1000)
	libnsl.so.1 => /lib/x86_64-linux-gnu/libnsl.so.1 (0x00007fefb8766000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fefb8760000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fefb8611000)
	libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fefb85d6000)
```
うまくできているみたいです

# 起動

そのまま起動するとマスターエージェントである snmpd に接続ができませんでした。
```
$ ./example-demon
Warning: Failed to connect to the agentx master agent ([NIL]): 
example-demon is up and running.
```
一度 Ctrl+C で終了し、sudo で動かします。

```
$ sudo ./example-demon
unknown snmp version 193
NET-SNMP version 5.8 AgentX subagent connected
example-demon is up and running.
```
問題無さそうなので再度 Ctrl+C で終了し、デーモンで動かします。

```
$ sudo ./example-demon &
[1] 182452
nanbuwks@nanbuwks-PRIMERGY-TX1310-M3:~/Downloads/subagent-example$ unknown snmp version 193
NET-SNMP version 5.8 AgentX subagent connected
example-demon is up and running.
```


# 動作チェック

説明ページに記されているやりかただとうまくいきませんでした。

```
$ snmpget localhost NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0
MIB search path: /home/nanbuwks/.snmp/mibs:/usr/share/snmp/mibs:/usr/share/snmp/mibs/iana:/usr/share/snmp/mibs/ietf
Cannot find module (NET-SNMP-TUTORIAL-MIB): At line 1 in (none)
NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0: Unknown Object Identifier
```

しかしながら以下のようにすると情報が取れました。

```
$ snmpwalk -v2c -c "public" localhost 1.3.6.1.4.1.8072.2.4.1.1.2.0
iso.3.6.1.4.1.8072.2.4.1.1.2.0 = INTEGER: 2
```

構成要素を見てみます。
```
$ snmpwalk -v2c -c "public" 127.0.0.1 .1.3.6.1.4.1.8072.2 -Ona
.1.3.6.1.4.1.8072.2.4.1.1.2.0 = INTEGER: 2
```

# 独自要素を作ってみる

nstAgentSubagentObject.h を書き換えます。
書き換え前

```
/*
 * Note: this file originally auto-generated by mib2c using
 *        : mib2c.int_watch.conf,v 5.0 2002/04/20 07:30:13 hardaker Exp $
 */
#ifndef NSTAGENTSUBAGENTOBJECT_H
#define NSTAGENTSUBAGENTOBJECT_H

/*
 * function declarations 
 */
void            init_nstAgentSubagentObject(void);

#endif                          /* NSTAGENTSUBAGENTOBJECT_H */
```
書き換え後

```
#ifndef NSTAGENTSUBAGENTOBJECT_H
#define NSTAGENTSUBAGENTOBJECT_H

extern int
handle_myMib_sysDscr(
    netsnmp_mib_handler*            handler,
    netsnmp_handler_registration*   reginfo,
    netsnmp_agent_request_info*     reqinfo,
    netsnmp_request_info*           requests);

Netsnmp_Node_Handler handle_myMib_examdata;

void            init_nstAgentSubagentObject(void);

#endif                          /* NSTAGENTSUBAGENTOBJECT_H */

```
nstAgentSubagentObject.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 "nstAgentSubagentObject.h"
oid     nstAgentSubagentObject_oid[] =
        { 1,3,6,1,4,1,8072,2,4,1,1,2,0 };
char    sysDscr[20]  = "                   ";
oid      examdata_oid[]     = { 1,3,6,1,4,1,8072,2,4,1,1,2,0, 1,1 };

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_examdata,
        .myMib_system_oid  = examdata_oid,
        .oid_len           = OID_LENGTH(examdata_oid),
        .modes             = HANDLER_CAN_RONLY,
    },
};
#define MYMIB_SYSTEM_MIBREG_MAX 1
void
init_nstAgentSubagentObject(void)
{
    DEBUGMSGTL(("nstAgentSubagentObject",
                "Initializing the nstAgentSubagentObject module\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_examdata(
    netsnmp_mib_handler*            handler,
    netsnmp_handler_registration*   reginfo,
    netsnmp_agent_request_info*     reqinfo,
    netsnmp_request_info*           requests)
{
    switch (reqinfo->mode) {
    case MODE_GET:
        snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
                                 (u_char *) sysDscr,
                                 strlen(sysDscr)+1);
        break;
    default:  // bad error
        snmp_log(LOG_ERR, "unknown mode (%d) in handle_myMib-sysObjectID\n", reqinfo->mode);
        return SNMP_ERR_GENERR;
    }
    return SNMP_ERR_NOERROR;
}


```

1.3.6.1.4.1.8072.2.4.1.1.2.0.1.1.0 に  sysDscr[20] を示すようにしました。

これをコンパイル、実行して snmpwalk で値が得られるかどうか見てみます。

```
$ snmpwalk -v1 localhost -c public 1.3.6.1.4.1.8072.2
iso.3.6.1.4.1.8072.2.4.1.1.2.0.1.1.0 = Hex-STRING: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 
20 20 20 00 
```
なんか Hex-STRING になっていますが取得できました。

# スクリプトの値を閲覧できるようにする

上に示した nstAgentSubagentObject.c のうちの、handle_myMib_examdata を以下に入れ替えます。

```
int
handle_myMib_examdata(
    netsnmp_mib_handler*            handler,
    netsnmp_handler_registration*   reginfo,
    netsnmp_agent_request_info*     reqinfo,
    netsnmp_request_info*           requests)
{
    char  buffer[512],*ptr;
    FILE  *fp;
    char  *exec= "/bin/echo 1024";
    int   buf2int;
    switch (reqinfo->mode) {
    case MODE_GET:
            if ((fp=popen(exec,"r"))==NULL) {
                snmp_log(LOG_ERR, "exec error (%s) in handle_myMib-examdata\n", exec);
                buffer[0]=0;
            } else {
                if (NULL == fgets(buffer,512,fp))
                    buffer[0]='\n';
                pclose(fp);
                ptr=strchr(buffer,'\n');
                if(ptr!=NULL){
                  *ptr='\0';
                }
        // printf("%s\n",buffer);
            }
            buf2int=atoi(buffer);
            snmp_set_var_typed_integer(requests->requestvb, ASN_INTEGER, buf2int);
        break;
    default:  // bad error
        snmp_log(LOG_ERR, "unknown mode (%d) in handle_myMib-sysObjectID\n", reqinfo->mode);
        return SNMP_ERR_GENERR;
    }
    return SNMP_ERR_NOERROR;
}

```
make し直してデーモンを起動し、snmpwalk で見てみます。


```
$ snmpwalk -v1 localhost -c public 1.3.6.1.4.1.8072.2
iso.3.6.1.4.1.8072.2.4.1.1.2.0.1.1.0 = INTEGER: 1024

```

`char  *exec= "/bin/echo 1024";`
とコマンドを指定していて、この値が取得できました。この箇所を適当なスクリプトに変更すればいいですね。


# 参考資料
「Net-SNMPのサブエージェントを作ってみる｜コアダンプの数だけ強くなれるよ」
https://segmentation-fault.xyz/2017/07/27/54/#rtoc-6

「net-snmpについて（独自監視項目の追加） - 後編 (1/5)：CodeZine（コードジン）」
https://codezine.jp/article/detail/2991