1. はじめに

アプリケーションを独自MIBで監視させたいと思いながらも、「SNMPエージェントを作るのは少し難しい」と考えている方は、少なくないと思われます。日本語で解説しているWebサイト等も多くはありませんし。
そこで、本稿では、NET-SNMPをベースとしたSNMPサブエージェントの作成概要を紹介していきたいと思います。
NET-SNMPを使って独自MIBを実装する方式には、チュートリアル(※1)を見る限りは何種類かあるようですが、本稿では、NET-SNMPのエージェントsnmpd(マスタエージェント)にAgentX(※2)接続するサブエージェントという形で独自MIBを実装する例を中心に解説します。

本稿で作成するSNMPサブエージェントは【図1-1 イメージ】のように動作します。
SNMPサブエージェントがアプリケーションと情報を共有することで独自MIBを形成するようにしています。安直に共有メモリを用いた形で、アプリケーションとサブエージェント間の情報を共有させましたので、情報更新時の排他等は考慮していません。
また、本稿でのアプリケーションはチュートリアル的な役割でしかありませんので、
・共有メモリ作成
・共有メモリ設定
を行なうだけのものを別々に用意しました。
SNMPサブエージェントは、概ね【図1-2 SNMPサブエージェントの実装過程】のような過程を経て実装します。
本稿では、【図1-1 イメージ】となるSNMPサブエージェントの簡単な実例をもとに、これら各過程を紹介します。
なお、本稿記載の内容は、以下の内容を基に検証いたしました。

 
  • Debian GNU/Linux 4.0r3(etch)
  • NET-SNMP のバージョンは 5.2.3-7

2. MIB定義ファイル作成編

まず、はじめに、独自MIBと言えば定番の.iso.org.dod.internet.private.enterprisesの下に独自定義を作っていきます。
MIB定義ファイル自体は、単なるテキストファイルです。

 

  • — はコメント
  • DEFINITIONS ::= BEGIN で定義開始
  • END で定義終了
  • IMPORTS は他の定義をinclude

といったような項目が基本的なところです。詳細につきましては、各種書籍、Web公開情報類が充実していますのでそちらを参照して下さい。
では早速、カウンタ、ゲージを各々一つずつと、トラップ二種類を持たせるようにVAJ-TEST-MIB.txtというテキストファイル【MIB定義ファイル】を作成してみました。
【図2-1 MIB定義ファイル】を参照してください。

図2-1 MIB定義ファイル

$ cat VAJ-TEST-MIB.txt <Enter>
-- SNMP Subagent for a test
--      Copyright(C) 2008 VA Linux Systems Japan K.K.
VAJ-TEST-MIB DEFINITIONS ::= BEGIN
IMPORTS
    MODULE-IDENTITY, OBJECT-TYPE, enterprises, Counter32, Gauge32
    NOTIFICATION-TYPE FROM SNMPv2-SMI;
vaj             OBJECT IDENTIFIER ::= { enterprises 14327 }
vajTestMIB      MODULE-IDENTITY
    LAST-UPDATED "200807010000Z"
    ORGANIZATION "VA Linux Systems Japan K.K."
    CONTACT-INFO "Postal: VA Linux Systems Japan K.K.
                          Harumi Island Triton Square Office Tower W 16F
                          1-8-8 Harumi Chuo-ku,
                          Tokyo 104-0053 Japan
                  Email:  test@valinux.co.jp"
    DESCRIPTION  "The VAJ Test MIB."
    ::= { vaj 38 }
vajTestObjects  OBJECT IDENTIFIER ::= { vajTestMIB 38 }
vajTestCount    OBJECT-TYPE
    SYNTAX      Counter32
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
    "This is a counter32 for a test."
    ::= { vajTestObjects 1 }
vajTestGauge    OBJECT-TYPE
    SYNTAX      Gauge32
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
    "This is a gauge32 for a test."
    ::= { vajTestObjects 2 }
vajTestTraps    OBJECT IDENTIFIER ::= { vajTestMIB 83 }
vajTestStart    NOTIFICATION-TYPE
    STATUS      current
    DESCRIPTION
    "This trap is sent when a test is online."
    ::= { vajTestTraps 1 }
vajTestStop     NOTIFICATION-TYPE
    STATUS      current
    DESCRIPTION
    "This trap is sent when a test is offline."
    ::= { vajTestTraps 2 }
END
$

snmptranslateというコマンドで、MIB定義ファイルからMIBツリーを表示させることもできます。(【図2-2 snmptranslateの実行結果】を参照)
定義等で誤りがあるとエラーになるので、簡単なチェックには使い勝手が良いかもしれませんが、コンパイラのような精度はなさそうですので注意が必要です。

【 図2-2 snmptranslateの実行結果 】

$ snmptranslate -M “.:/usr/share/snmp/mibs” -Tp VAJ-TEST-MIB::vaj <Enter>
+–vaj(14327)
|
+–vajTestMIB(38)
|
+–vajTestObjects(38)
| |
| +– -R– Counter vajTestCount(1)
| +– -R– Gauge vajTestGauge(2)
|
+–vajTestTraps(83)
|
+–vajTestStart(1)
+–vajTestStop(2)
$

3. プログラムテンプレート生成編

先の【 2.MIB定義ファイル作成編 】で作成したMIB定義ファイルからプログラムテンプレートを生成します。 mib2cというコマンドを使うと、MIB定義ファイルからプログラムテンプレートを生成することができます。【図3-1 mib2c】の実行結果を参照してください。

図3-1 mib2c

 $ env MIBDIRS=".:/usr/share/snmp/mibs" MIBS="+VAJ-TEST-MIB" mib2c vajTestObjects <Enter> (※)
writing to -
mib2c has multiple configuration files depending on the type of
code you need to write. You must pick one depending on your need.
You requested mib2c to be run on the following part of the MIB tree:
  OID:                              vajTestObjects
  numeric translation:              .1.3.6.1.4.1.14327.38.38
  number of scalars within:         2
  number of tables within:          0
  number of notifications within:   0
First, do you want to generate code that is compatible with the
ucd-snmp 4.X line of code, or code for the newer Net-SNMP 5.X code
base (which provides a much greater choice of APIs to pick from):
  1) ucd-snmp style code
  2) Net-SNMP style code
Select your choice : 2 <Enter> (※)
**********************************************************************
GENERATING CODE FOR SCALAR OBJECTS:
**********************************************************************
  It looks like you have some scalars in the mib you requested, so I
  will now generate code for them if you wish. You have two choices
  for scalar API styles currently. Pick between them, or choose not
  to generate any code for the scalars:
  1) If you're writing code for some generic scalars
     (by hand use: "mib2c -c mib2c.scalar.conf vajTestObjects")
  2) If you want to magically "tie" integer variables to integer
     scalars
     (by hand use: "mib2c -c mib2c.int_watch.conf vajTestObjects")
  3) Don't generate any code for the scalars
Select your choice: 2 <Enter> (※)
      using the mib2c.int_watch.conf configuration file to generate your code.
*** Warning: only generating code for nodes of MIB type INTEGER
writing to vajTestObjects.h
writing to vajTestObjects.c
**********************************************************************
* NOTE WELL: The code generated by mib2c is only a template.  *YOU*  *
* must fill in the code before it'll work most of the time.  In many *
* cases, spots that MUST be edited within the files are marked with  *
* /* XXX */ or /* TODO */ comments.                                  *
**********************************************************************
running indent on vajTestObjects.h
running indent on vajTestObjects.c
$
$ env MIBDIRS=".:/usr/share/snmp/mibs" MIBS="+VAJ-TEST-MIB" mib2c vajTestTraps <Enter> (※)
writing to -
mib2c has multiple configuration files depending on the type of
code you need to write. You must pick one depending on your need.
You requested mib2c to be run on the following part of the MIB tree:
  OID:                              vajTestTraps
  numeric translation:              .1.3.6.1.4.1.14327.38.83
  number of scalars within:         0
  number of tables within:          0
  number of notifications within:   2
First, do you want to generate code that is compatible with the
ucd-snmp 4.X line of code, or code for the newer Net-SNMP 5.X code
base (which provides a much greater choice of APIs to pick from):
  1) ucd-snmp style code
  2) Net-SNMP style code
Select your choice : 2 <Enter> (※)
**********************************************************************
GENERATING CODE FOR NOTIFICATIONS:
**********************************************************************
Would you like to generate code for sending notifications from within
the agent?
"y" or "n": y <Enter> (※)
   using mib2c.notify.conf to generate code for sending notifications
writing to vajTestTraps.h
writing to vajTestTraps.c
#  GENERATING HEADER FILE DEFINITIONS
#
#    To generate just a header with a define for each column number in
#    your table:
#
#      mib2c -c mib2c.column_defines.conf vajTestTraps
#
#    To generate just a header with a define for each enum for any
#    column containing enums:
#
#      mib2c -c mib2c.column_enums.conf vajTestTraps
**********************************************************************
* NOTE WELL: The code generated by mib2c is only a template.  *YOU*  *
* must fill in the code before it'll work most of the time.  In many *
* cases, spots that MUST be edited within the files are marked with  *
* /* XXX */ or /* TODO */ comments.                                  *
**********************************************************************
running indent on vajTestTraps.h
running indent on vajTestTraps.c
$
(※)入力を行なった行です。

今回はvajTestObjectsとvajTestTrapsを指定し、Net-SNMPスタイルで各々のプログラムテンプレートを生成させていますが、別の指定等もいろいろと試してみて下さい。ただし、生成される内容は異なってくることもありますので、その点は注意しましょう。

4. プログラムテンプレート編集編

ここまでの過程でプログラムテンプレートができ上がりました。このままの状態ではアプリケーションと連動していませんので、アプリケーションとの情報を共有するための仕組みを作り込んでいく必要があります。
プログラムテンプレートでは、MIB登録や各値に該当する変数あるいは関数の関連づけを行なう内容になっていますので、この関連づけを行なっている部分に変更あるいは追加等を施し、アプリケーションと共有している情報を関連づけるように編集します。

(1)vajTestObjects.c (vajTestObjects.h)

カウンタ、ゲージのMIB登録を行ない、各値に該当する静的変数の関連づけを行なうプログラムになっているので、静的変数の代わりに各共有メモリのアドレスを関連づけるよう変更します。なお、共有メモリのアドレスは引数で受けるように変更します。(【図4-1 vajTestObjects 主な変更点】を参照)また、共有メモリへの接続等は、呼出側で処理します。

【 図4-1 vajTestObjects 主な変更点 】

--- vajTestObjects.c ---
・アプリケーションとの共通ヘッダファイル定義を追加
     +#include "app.h"
・静的変数は使用しないのでコメント化
     -u_long    vajTestCount = 0;  /* XXX: set default value */
     -u_long    vajTestGauge = 0;  /* XXX: set default value */
     +// u_long    vajTestCount = 0;  /* XXX: set default value */
     +// u_long    vajTestGauge = 0;  /* XXX: set default value */
・共有メモリのアドレスを引数で受ける
     void
     -init_vajTestObjects(void)
     +init_vajTestObjects(void *mibval_ptr)
     + mibval  *appinfo = mibval_ptr;
・静的変数の代わりに共有メモリのアドレス(カウンタ)
          winfo = netsnmp_create_watcher_info(
     -                &vajTestCount, sizeof(u_long),
     +                &appinfo->count, sizeof(u_long),
                       ASN_COUNTER, WATCHER_FIXED_SIZE);
・静的変数の代わりに共有メモリのアドレス(ゲージ)
          winfo = netsnmp_create_watcher_info(
     -                &vajTestGauge, sizeof(u_long),
     +                &appinfo->gauge, sizeof(u_long),
                       ASN_GAUGE, WATCHER_FIXED_SIZE);
--- vajTestObjects.h ---
・共有メモリのアドレスを引数で受ける
      /* function declarations */
     -void init_vajTestObjects(void);
     +void init_vajTestObjects(void *);

(2)vajTestTraps.c

このままでも良いのですが、コンパイル時にワーニングが出るのでキャストしておきます。【図4-2 vajTestTraps.c 変更点】を参照してください。

【 図4-2 vajTestTraps.c 変更点 】

       snmp_varlist_add_variable(&var_list,
          snmptrap_oid, OID_LENGTH(snmptrap_oid),
          ASN_OBJECT_ID,
-         vajTestStart_oid, sizeof(vajTestStart_oid));
+         (u_char *)vajTestStart_oid, sizeof(vajTestStart_oid));

     snmp_varlist_add_variable(&var_list,
         snmptrap_oid, OID_LENGTH(snmptrap_oid),
         ASN_OBJECT_ID,
-        vajTestStop_oid, sizeof(vajTestStop_oid));
+        (u_char *)vajTestStop_oid, sizeof(vajTestStop_oid));

主な変更点は以上です。

5. メイン作成編

メインについては自動生成されないので一から作る必要があります。ただし、基本的なパターンは決まっていますので、ポイントを踏まえて作成を進めれば、さほど難しくはないでしょう。

(1)インクルードヘッダ

次の4つ(※3)が必要となります。

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "vajTestObjects.h" // (※3)

トラップ出力の必要に応じて、以下を加えます。

#include "vajTestTraps.h"

(2)main()の流れ

次の主要関数を順に呼出すイメージになります。(順序変更不可)

以上の2点が基本的なポイントになります。これらポイントに、

 

  • agent_check_and_process()のループとそれを抜ける仕組み
  • アプリケーションとの連動の仕組み
  • トラップ発射

等を肉づけすれば完成です。

6. サブエージェント完成編

これまでの過程で一通りのプログラムソースはでき上がっています。あとはサブエージェントとしての基本動作の確認で完成ですが、その前に下記の2点に注意しましょう。

(1)ビルドTips

ビルド時に結合しなければいけないライブラリも多いので「何を指定すれば?」で困惑するケースもあるかと思います。
net-snmp-configというコマンドで情報を得ることができますので、これを参考にビルドすれば良いでしょう。
(【図6-1 net-snmp-config】の実行結果を参照)

図6-1 net-snmp-config

$ net-snmp-config –cflags <Enter>
-DINET6 -O2 -D_REENTRANT -DNETSNMP_USE_INLINE -Wall -Dlinux -I. -I/usr/include
$
$ net-snmp-config –agent-libs <Enter>
-L/usr/lib -lnetsnmpmibs -lnetsnmpagent -lnetsnmphelpers -lnetsnmp -lm -ldl -lse
nsors -lwrap -lwrap
$

(2)動作環境Tips

サブエージェントは、マスタエージェントsnmpdが稼働していないとSNMPマネージャとのやり取りができませんし、追加の設定も必要です。

ファイルsnmpd.confに
master agentx
という行を追加して下さい。また、トラップを出力するために
trapcommunity xxxxxx // public等のコミュニティ
trap2sink トラップを受けるホスト // ホスト名
の指定も必要です。

それでは、基本動作の確認を開始します。まずは、アプリケーション(共有メモリ作成)を動かします。

$ ./application/app <Enter>
This is an application for the SAMPLE-SUBAGENT.
Create a SHARED-MEMORY…succeeded.
initial contents: count[0] gauge[0]
Wait for the contents update…(Push Enter to exit)…

アプリケーション(共有メモリ設定)で、カウンタを123、ゲージを55に設定します。

$ ./application/up 123 55 <Enter>
This is a utility for the application.
Update the SHARED-MEMORY.
count —> 123
gauge —> 55
Attach the SHARED-MEMORY…succeeded.
Update the SHARED-MEMORY…count[123] gauge[55]
Detach the SHARED-MEMORY…normal ended.
$

SNMPトラップを見られる様にします。

# env MIBDIRS=”.:/usr/share/snmp/mibs” MIBS=”+VAJ-TEST-MIB” snmptrapd -P <Enter>

更に、facility LOG_DAEMONのsyslogも見られる様に設定します。

# tail -f /var/log/daemon.log <Enter>

これで、準備が完了です。次に、サブエージェント起動します。

# ./vajTest -D”vajTestObjects” -d <Enter>

ここから確認を開始します。

図6-2 起動とSNMPトラップの確認

— SNMPトラップ —
2008-08-08 10:10:30 localhost [UDP: [127.0.0.1]:45235]:
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (9289082) 1 day, 1:48:10.82
SNMPv2-MIB::snmpTrapOID.0 = OID: VAJ-TEST-MIB::vajTestStart
— syslog —
Aug 8 10:10:30 hogehoge vajTest[20961]: vajTestObjects:
Aug 8 10:10:30 hogehoge vajTest[20961]: Initializing the vajTestObjects module
Aug 8 10:10:30 hogehoge vajTest[20961]: vajTestObjects:
Aug 8 10:10:30 hogehoge vajTest[20961]: Initializing vajTestCount scalar integer.
Default value = 123
Aug 8 10:10:30 hogehoge vajTest[20961]: vajTestObjects:
Aug 8 10:10:30 hogehoge vajTest[20961]: Initializing vajTestGauge scalar integer.
Default value = 55
Aug 8 10:10:30 hogehoge vajTest[20961]: vajTestObjects:
Aug 8 10:10:30 hogehoge vajTest[20961]: Done initalizing vajTestObjects module
Aug 8 10:10:30 hogehoge vajTest[20961]: Starting vajTest.

起動とSNMPトラップに問題がないことを確認できました。次にSNMP GETとWALK(GETNEXT)をかけてみます。

図6-3 SNMP GETとWALK(GETNEXT)の確認

— GET確認 —
$ env MIBDIRS=”.:/usr/share/snmp/mibs” MIBS=”+VAJ-TEST-MIB”
> snmpget -c public -v 2c localhost
> enterprises.vaj.vajTestMIB.vajTestObjects.vajTestCount.0 <Enter>
VAJ-TEST-MIB::vajTestCount.0 = Counter32: 123
$
$ env MIBDIRS=”.:/usr/share/snmp/mibs” MIBS=”+VAJ-TEST-MIB”
> snmpget -c public -v 2c localhost
> enterprises.vaj.vajTestMIB.vajTestObjects.vajTestGauge.0 <Enter>
VAJ-TEST-MIB::vajTestGauge.0 = Gauge32: 55
$
— WALK確認 —
$ env MIBDIRS=”.:/usr/share/snmp/mibs” MIBS=”+VAJ-TEST-MIB”
> snmpwalk -c public -v 2c localhost
> enterprises.vaj <Enter>
VAJ-TEST-MIB::vajTestCount.0 = Counter32: 123
VAJ-TEST-MIB::vajTestGauge.0 = Gauge32: 55
$

ここでも、問題は見られません。最後に終了とトラップを確認します。

図6-4 終了とトラップの確認

# kill 20961 <Enter>
#
— SNMPトラップ —
2008-08-08 10:22:47 localhost [UDP: [127.0.0.1]:45235]:
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (9362812) 1 day, 2:00:28.12
SNMPv2-MIB::snmpTrapOID.0 = OID: VAJ-TEST-MIB::vajTestStop
— syslog —
Aug 8 10:22:47 hogehoge vajTest[20961]: Stopping vajTest.

以上で完了です。

7. 補足

(1)tarボール

本稿で作成したものは、全てtarボールsample-subagent.tar.gzにまとめられています。内容は次の通りです。

 

  • MIB定義ファイル:
    ./sample-subagent/VAJ-TEST-MIB.txt
  • プログラムテンプレート:
    ./sample-subagent/TEMPLATE/vajTestObjects.c
    ./sample-subagent/TEMPLATE/vajTestObjects.h
    ./sample-subagent/TEMPLATE/vajTestTraps.c
    ./sample-subagent/TEMPLATE/vajTestTraps.h
  • サブエージェント:
    ./sample-subagent/Makefile
    ./sample-subagent/main.c
    ./sample-subagent/vajTestObjects.c (編集済)
    ./sample-subagent/vajTestObjects.h (編集済)
    ./sample-subagent/vajTestTraps.c (編集済)
    ./sample-subagent/vajTestTraps.h
  • アプリケーション:
    ./sample-subagent/application/Makefile
    ./sample-subagent/application/app.h
    ./sample-subagent/application/app.c
    ./sample-subagent/application/up.c

sample-subagent/Makefile と sample-subagent/application/Makefileは連動していません。ご了承ください。

(2)アプリケーション

本稿で使用したアプリケーションは、

 

  • 共有メモリ作成:app (app.c)
  • 共有メモリ設定:up (up.c)

となっており、各々の動作は、次の通りのシンプルなものです。

共有メモリ作成

起動すると、共有メモリを作成し入力待ちとなります。

$ ./application/app <Enter>
This is an application for the SAMPLE-SUBAGENT.
Create a SHARED-MEMORY…succeeded.
initial contents: count[0] gauge[0]
Wait for the contents update…(Push Enter to exit)…

入力待ちの状態からEnter入力で終了します。

$ ./application/app
This is an application for the SAMPLE-SUBAGENT.
Create a SHARED-MEMORY…succeeded.
initial contents: count[0] gauge[0]
Wait for the contents update…(Push Enter to exit)…
<Enter>
Going to end…normal ended.
$

共有メモリ設定

指定されたカウンタ値、ゲージ値を共有メモリ内に設定します。(指定が無い場合は0に設定されます。)

application/up [カウンタ値 [ゲージ値]]

他ターミナルで共有メモリ作成が入力待ちの状態である際に実行します。

$ ./application/up 123 55 <Enter>
This is a utility for the application.
Update the SHARED-MEMORY.
count —> 123
gauge —> 55
Attach the SHARED-MEMORY…succeeded.
Update the SHARED-MEMORY…count[123] gauge[55]
Detach the SHARED-MEMORY…normal ended.
$

入力待ちの状態である共有メモリ作成の方では、設定された値が出力され、再び入力待ちの状態になります。

$ ./application/app
This is an application for the SAMPLE-SUBAGENT.
Create a SHARED-MEMORY…succeeded.
initial contents: count[0] gauge[0]
Wait for the contents update…(Push Enter to exit)…
contents updated: count[123] gauge[55]
Wait for the contents update…(Push Enter to exit)…
  • ※1 http://www.net-snmp.org/wiki/index.php/Tutorials
  • ※2 RFC2741で定義されているマスタ・サブエージェント間プロトコル
  • ※3 プログラムテンプレートを生成(編集)したものになりますので、最低4つとなり、トラップに関するものを除いた分全てが対象となります。
  • ※4 “エージェント名”はinit_agent()、init_snmp()、snmp_shutdown()の3つで揃えます。
  • ※5 “ブロック”とは言っても、NET-SNMPの内部タイマ間隔で抜けてきますので、呼出し側でループと抜けるための仕組みが必要となります。