« ◆カーナビに左寄せアプローチアルゴリズムを | トップページ | javadocでジェネリックメソッドの@linkを書く、他 »

SNMPのhello-worldレベル具体例

メモです。

SNMP(Simple Network Management Protocol)はネットワーク機器管理 を行うプロトコルで、マネージャと装置(エージェント)との 間の通信の取り決めです。

SNMPプロトコルの解説は目にしますが今ひとつ

パッと分かる記述がなかなか見つかりません。で、メモ

この記事は 通信形式の説明 と、  具体的な送信電文データ と、  具体的な受信電文データ と、  実際に動く単純 マネージャプログラム を載せます。

 通信はUDPポートは装置側161、マネージャ側162

電文にはマネージャから装置への要求とそれに対する応答の他Trapと呼ぶ 装置からマネージャへの通知があります。

装置はUDPポート161でマネージャからの要求電文を受けます。応答は 要求を出したマネージャのポートに送り返されます。

マネージャは必要があればUDPポート162で装置からのtrap電文を受け取ります。

trapに関してはこの記事では省略します。

 電文形式(ASN.1)

マネージャから装置へ送る電文は次のASN.1記述で定義されます。
Message構造体を送ります。
ASN.1はデータ構造の定義で同時に電文の形式も定められるものです。電文を定める ため [0] などの特別な書き方が構造定義に混ざっています。
REF1157-SNMP DEFINITIONS ::= BEGIN
IMPORTS
   ObjectName,ObjectSyntax,NetworkAddress,IpAddress,TimeTicks
   FROM REF1155-SMI;
-- EXPORTS every thing 
Message ::= SEQUENCE{
   version   INTEGER { version-1(0) },
   community OCTET STRING,
   data      ANY
   }
PDUs ::= CHOICE {
   get-request       GetRequest-PDU,
   get-next-request  GetNextRequest-PDU,
   get-response      GetResponse-PDU,
   set-request       SetRequest-PDU,
   trap              Trap-PDU
   }
GetRequest-PDU     ::= [0] IMPLICIT PDU
GetNextRequest-PDU ::= [1] IMPLICIT PDU
GetResponse-PDU    ::= [2] IMPLICIT PDU
SetRequest-PDU     ::= [3] IMPLICIT PDU
PDU ::= SEQUENCE {
   request-id INTEGER,
   error-status INTEGER {
      noError(0),
      tooBig(1),
      noSuchName(2),
      badValue(3),
      readOnly(4),
      genErr(5)
      },
   error-index INTEGER,
   variable-bindings VarBindList
   }
Trap-PDU           ::= [4] IMPLICIT SEQUENCE {
   enterprise         OBJECT IDENTIFIER,
   agent-addr         NetworkAddress,
   generic-trap       INTEGER {
      coldStart(0),
      warmStart(1),
      linkDown(2),
      linkUp(3),
      authenticationFailure(4),
      egpNeighborLoss(5),
      enterpriseSpecific(6)
      },
   specific-trap      INTEGER,
   time-stamp         TimeTicks,
   variable-bindings  VarBindList
   }
VarBind ::= SEQUENCE {
   name  ObjectName,   -- ここに取得するデータを指定する
   value ObjectSyntax  -- 応答ではここに情報が載る
   }
VarBindList ::= SEQUENCE OF VarBind
END
REF1155-SMI DEFINITIONS ::= BEGIN
--EXPORTS everything
IMPORTS; -- nothing
-- the path to the root
internet      OBJECT IDENTIFIER ::= { iso org(3) dod(6) 1 }
directory     OBJECT IDENTIFIER ::= { internet 1 }
mgmt          OBJECT IDENTIFIER ::= { internet 2 }
experimental  OBJECT IDENTIFIER ::= { internet 3 }
private       OBJECT IDENTIFIER ::= { internet 4 }
enterprises   OBJECT IDENTIFIER ::= { private 1 }
-- definition of object types
ObjectName   ::= OBJECT IDENTIFIER
ObjectSyntax ::= CHOICE {
   simple           SimpleSyntax,
   application-wide ApplicationSyntax
   }
SimpleSyntax ::= CHOICE{
   number   INTEGER,
   string   OCTET STRING,
   object   OBJECT IDENTIFIER,
   empty    NULL
   }
ApplicationSyntax ::= CHOICE {
   address  NetworkAddress,
   counter  Counter,
   gauge    Gauge, 
   ticks    TimeTicks,
   arbitary Opaque
   }
NetworkAddress ::= CHOICE {
   internet IpAddress
   }
IpAddress ::= [APPLICATION 0] IMPLICIT OCTET STRING --(SIZE(4))
Counter   ::= [APPLICATION 1] IMPLICIT INTEGER -- (0..294967295)
Gauge     ::= [APPLICATION 2] IMPLICIT INTEGER -- (0..294967295)
TimeTicks ::= [APPLICATION 3] IMPLICIT INTEGER -- (0..294967295)
Opaque    ::= [APPLICATION 4] IMPLICIT OCTET STRING
END

ASN.1のデータ構造は、

型情報-長さ-内容

の形で電文化されます。

SETやSEQUENCEなどの構造を持つものは、内容部にその要素がさらに入った 階層を構成します。内部に要素を持つかどうかは型情報の第3bitで示され ます。

注意しなくてはならないのが、本来ASN.1ではこの階層化した構造の場合 長さを指定せず、代わりに最後にEOCタグ(0x0000)を置くことができるの ですが、SNMPではそれを許していないことです。

 マネージャから装置に送る電文GetRequest

電文は全てMessage型を全体の入れ物として、その中のdataメンバ に電文のタイプ(GetRequest/GetResponseなど)毎のデータを入れます。
例えばGetRequestの場合

   Message { の中の
      data に
      }
   PDUs {の
      get-request { の
         variable-bindings { の一個の(必要なら複数)
            {
               nameに 取得したいデータのidを書く
               }}}}
といった形です。

nameに設定するidはOBJECT IDENTIFIERで、整数の並びで構成 されています。
どの値が何を指すかを決めたものをSNMP-MIB定義と言います。

例えば次のようなものがあります。

オブジェクト/ノード名 説明
1.3.6.1.2.1.1.1 sysDescr sysDescr(ツリー)
1.3.6.1.2.1.1.1.0 sysDescr.0 システム概要
1.3.6.1.2.1.1.3.0 sysUpTime.0 アップタイム
1.3.6.1.2.1.1.5.0 sysName.0 システム名
1.3.6.1.2.1.2.2.1 if インターフェース(ツリー)
1.3.6.1.2.1.2.2.1.6 ifPhysAddress マックアドレス

メッセージの直下にあるcommunityはパスワードのようなもので デフォルトは"public"です。
セキュリティー上の問題で"public"は受け付けなくしてある システムも多くあります。

 マネージャから装置に送る電文例(GetRequest)

前項のASN.1定義の、MessageのdataにGetRequestをセットした ものです。
example Message ::= {
   version	version-1,
   community	'7075626C6963'H, -- "public"
   data	PDUs get-request:{
      request-id	1,
      error-status	noError,
      error-index	0,
      variable-bindings	{
         {
            name	{ iso 3 6 1 2 1 1 1 0 },  -- sysDescr.0
            value	simple:empty:NULL } } } } -- 要求時は空を指定
この電文を16進で表すと次のようになります。
30 26
   02 01 00
   04 06 7075 626C 6963
   A0 19
      02 01 01
      02 01 00
      02 01 00
      30 0E
         30 0C
            06 08 2B06 0102 0101 0100
            05 00
青が型情報緑がデータ長
この例では一つの情報のみ問い合わせていますが、variable-bindingsには 複数の要素を入れることができ、一度に複数の問い合わせを実行することが できます。

 実際に動くプログラム(Java版)

前項の電文を送ってデータを取得する単体動作可能なプログラムを載せます。
プログラミングの最初の壁を乗り越えるSNMPのhello-worldレベルプログラム です。

// SNMPのhelloプログラム
import java.net.*;
public class SnmpHello {
   final static byte request[]={ // GetRequest電文(最外郭はMessage)
      0x30 ,0x26
        ,0x02 ,0x01 ,0x00
        ,0x04 ,0x06 ,0x70,0x75 ,0x62,0x6C ,0x69,0x63 
          ,(byte)0xA0 ,0x19
          ,0x02 ,0x01 ,0x01
          ,0x02 ,0x01 ,0x00
          ,0x02 ,0x01 ,0x00
          ,0x30 ,0x0E
             ,0x30 ,0x0C
               ,0x06 ,0x08 ,0x2B,0x06 ,0x01,0x02 ,0x01,0x01 ,0x01,0x00
               ,0x05 ,0x00};
   public static void main(String[] args_){
      try{
         InetSocketAddress _addr
                = new InetSocketAddress(args_[0], 161);// UDPアドレス
         DatagramSocket _socket 
                = new DatagramSocket();
         DatagramPacket _packet
                = new DatagramPacket(request, 0, request.length,_addr);
         _socket.send(_packet);
         System.err.println("send request to "+args_[0]);
         byte _buf[]=new byte[1024];
         DatagramPacket _rpacket = new DatagramPacket(_buf,_buf.length);
         _socket.setSoTimeout(5000);
         System.err.println("wait responce");
         _socket.receive(_rpacket);
         System.err.println("response come");
         for(int _idx=0;_idx<_rpacket.getLength();++_idx){
            if( (_idx%16)==0 ) System.out.println("");
            System.out.printf("%02x ",_buf[_idx]);
            }
         System.out.println("");
         }
      catch(Exception _e){
         _e.printStackTrace(System.err);
         System.exit(1);
         }
      }
   }

このプログラムは動作確認済みですが、対象となる装置がcommunity= "public"でアクセスを受け付ける必要があります。

 実際に動かした結果(取得したGetResponse)

実際に動かした結果を載せます。
send request printer1
wait responce
response come

30 3c 02 01 00 04 06 70 75 62 6c 69 63 a2 2f 02
01 01 02 01 00 02 01 00 30 24 30 22 06 08 2b 06
01 02 01 01 01 00 04 16 45 50 53 4f 4e 20 4c 50
2d 38 37 30 30 50 53 33 20 30 31 2e 30 30
これは次の構造をしています。
30 3C 
   02 01  00
   04 06  7075 626C 6963
   A2 2F 
      02 01  01
      02 01  00
      02 01  00
      30 24 
         30 22 
            06 08  2B06 0102 0101 0100
            04 16  4550 534F 4E20 4C50 2D38 3730 3050 5333 2030 312E 3030
次のASN.1値を表しています。
example Message ::= {
   version	version-1,
   community	'7075626C6963'H, -- "public"
   data PDUs get-response:{
      request-id 1,
      error-status 0,
      error-index 0,
      variable-bindings {
         {
            name { 10 3 6 1 2 1 1 1 0 },
            value simple:string:
                  '4550534f4e204c502d383730305053332030312e3030'H } } }
--                "EPSON LP-8700PS3 01.00"

 受信データ(GetResponse)の形

GetResponseメッセージは送信したメッセージのGetRequestをGetReqponse に置き換えたものとなります。
置き換えるといってもタグが異なるだけで形式は同じです。

通常、vraiable-bindingsの要素のvalueに値がセットされます。

問い合わせ項目によっては一つの問い合わせに対して複数の値が 返ってくる場合もあります。

 ASN.1の取り扱い

この記事は具体的な電文に関する説明を目的としているので ASN.1の取り扱いを実際のプログラムでどのようにするかは 述べていません。

もちろんここでの例のように16進の書き下ろしをすること はありません。
この記事のデータも実際にはASN.1のツールを用いてSNMP アクセスしたものを取り出したものです。

このツールはオツアンドサンズで"オツライブラリ/asDur"といいます。
オツライブリは3つのパートからなっており、その中の一つです。 2つは仮公開していますが、asDurに関しては公開法を検討中です。

ライブラリ全体の説明は にあります。
オツアンドサンズのホームページは です。

プログラムをこのツールを使う形にしたものを載せます。

import otsu.asDur.*;
import REF1157_SNMP.*;
import REF1155_SMI.*;
import java.io.*;
import java.net.*;
public class Test {
   public static void main(String[] args_){
       try{
         // 要求電文の作成
         AsnPDUs           _pdu= new AsnPDUs();
         _pdu.get_request().request_id = 1; // get-requestを有効化
         _pdu.get_request.error_status = 0;
         _pdu.get_request.error_index  = 0;
         int _oid[]       = { 1,3,6,1,2,1,1,1,0 };
         AsnVarBind _vb=_pdu.get_request.variable_bindings.cre();// OF要素生成
         _vb.name=_oid; 
         _vb.value.simple().empty(); // CHOICE要素の選択
         _vb.value.simple.empty=true;// 値(NULLの場合値は仮のもの)設定

         AsnMessage _msg= new AsnMessage();
         _msg.version   = 0;
         _msg.community = AsnOCTET_STRING.getBytes("public");
         _msg.data.set(_pdu);

         ByteArrayOutputStream _baos= new ByteArrayOutputStream();
         _msg.encode(_baos);                           // エンコード
         byte _request[] = _baos.toByteArray();

         // 要求の送信
         InetSocketAddress _addr= new InetSocketAddress(args_[0], 161);
         DatagramSocket _socket = new DatagramSocket();
         _socket.send(new DatagramPacket(_request,0,_request.length,_addr));
         // 応答の受信
         byte _buf[]=new byte[1024];
         DatagramPacket _rpacket = new DatagramPacket(_buf,_buf.length);
         _socket.receive(_rpacket);

         // 応答電文の解析
         AsnMessage _msg2= new AsnMessage();
         _msg2.decode(new ByteArrayInputStream(_buf)); // デコード
         AsnPDUs           _pdu2= new AsnPDUs();
         _msg2.data.getTo(_pdu2);    // ANYの内容をAsnPDUs型に取り込む
         _pdu2.print(System.out,_pdu2.PRINT_AS_STRING); // 印刷
         }
      catch(Exception _e){
         _e.printStackTrace(System.err);
         System.exit(1);
         }
      }
   }
実際に動かした結果は次のようになります。 PRINT_AS_STRING指定があるためOCTET STRING が16進数ではなく文字列で表示されています。
get-response:{
   request-id 1,
   error-status 0,
   error-index 0,
   variable-bindings {
      {
         name { 1 3 6 1 2 1 1 1 0 },
         value simple:string:"EPSON LP-8700PS3 01.00" } } }

|

« ◆カーナビに左寄せアプローチアルゴリズムを | トップページ | javadocでジェネリックメソッドの@linkを書く、他 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: SNMPのhello-worldレベル具体例:

« ◆カーナビに左寄せアプローチアルゴリズムを | トップページ | javadocでジェネリックメソッドの@linkを書く、他 »