2010年07月17日

実践SNMP+Java Get

ここではSNMP4Jを利用したSNMP Getオペレーションについて説明します。

<Javaコード>

  1.     String[] oids = {
  2.         ".1.3.6.1.2.1.1.1.0", // system.sysDescr.0
  3.         ".1.3.6.1.2.1.1.3.0", // system.sysUpTime.0
  4.         ".1.3.6.1.2.1.1.5.0"  // system.sysName.0
  5.     };
  6.     // SNMP通信パラメータを設定するクラス。
  7.     CommunityTarget comTgt = new CommunityTarget();
  8.     comTgt.setAddress( new UdpAddress("192.168.0.225/161") ); // UDPアドレス(ターゲット・アドレス/SNMPポート)
  9.     comTgt.setCommunity( new OctetString("public") ); // SNMPコミュニティ名
  10.     comTgt.setTimeout(1000); // タイムアウト時間(ミリ秒)
  11.     comTgt.setRetries(1); // リトライ数
  12.     comTgt.setVersion(SnmpConstants.version1); // SNMPバージョン
  13.     // PDUを生成します。
  14.     PDU pdu = new PDU();
  15.     pdu.setType(PDU.GET);
  16.     for (int i=0; i<oids.length; i++) {
  17.       // PDUにバーバインドを格納します。
  18.       OID oid = new OID(oids[i]);
  19.       pdu.add(new VariableBinding(oid));
  20.     }
  21.     // SNMP操作の基本になるクラス。
  22.     Snmp snmp = null;
  23.     // DefaultTcpTransportMappingクラスもあります
  24.     DefaultUdpTransportMapping utm = null;
  25.     try {
  26.       utm = new DefaultUdpTransportMapping();
  27.       snmp = new Snmp(utm);
  28.       snmp.listen(); // ローカルUDPポートのオープンと待機
  29.       // SNMP Get
  30.       ResponseEvent response = snmp.get(pdu, comTgt);
  31.       // レスポンスPDU
  32.       PDU resPdu = response.getResponse();
  33.       if ( resPdu != null && resPdu.getErrorStatus() == SnmpConstants.SNMP_ERROR_SUCCESS ) {
  34.         // レスポンスPDUに含まれるバーバインド数
  35.         int numVarBinds = resPdu.size();
  36.         for (int i=0; i<numVarBinds; i++) {
  37.           VariableBinding var = resPdu.get(i);
  38.           if ( !var.isException() ) {
  39.             System.out.println(var.getOid() + " -> " + var.getVariable().toString());
  40.           } else {
  41.             System.out.println(var.getOid() + " -> " + "has exception syntax!");
  42.           }
  43.         }
  44.       } else {
  45.         if (resPdu == null) {
  46.           // レスポンスPDUがnullの場合はタイムアウトです。
  47.           System.out.println("request timeout.");
  48.         } else {
  49.           System.out.println(resPdu.getErrorStatusText());
  50.         }
  51.       }
  52.       // ローカルUDPポートのclose
  53.       snmp.close();
  54.     } catch (IOException e) {
  55.       e.printStackTrace();
  56.     }


<実行結果サンプル> 

1.3.6.1.2.1.1.1.0 -> Hardware: x86 Family 6 Model 15 Stepping 6 AT/AT COMPATIBLE - Software: Windows 2000 Version 5.1 (Build 2600 Multiprocessor Free)
1.3.6.1.2.1.1.3.0 -> 2:53:20.12
1.3.6.1.2.1.1.5.0 -> PANZERII
ちなみに、SNMPエージェント(ターゲット)は
SNMPサービスを有効にしたWindowsXPです。


<説明>

1)OIDの定義(1
~5行目)
  このサンプルではsystem.sysDescr.0、system.sysUpTime.0、sysName.0を定義していますが、
  最後の.0はインスタンス識別子と呼ばれるもので、正確な表現をするとOID+インスタンス
  識別子で指定します。
  インスタンス識別子が0の場合はエージェント側で1つしかインスタンスが存在しない
  ことを意味しますが、0以上の場合、複数存在する
(しうる)ことを表します。
  MIBテーブルにエントリされているOIDには存在するインスタンス分だけ識別子が
  生成されます。
  #例えば、ifTable.ifDescrはその機器がもつインタフェース分だけ識別子(ifIndex)を
  持っています。

2)CommunityTarget(7~12行目)
  このオブジェクトにSNMP通信パラメータを設定します。
  僕としてはアドレスとポートはStringとintで別々に設定させてくれた方が直感的で
  嬉しいと思うのですが、ターゲット(SNMPエージェント)のUDPアドレスの設定には
  UdpAddressクラスを使います。(SNMPコミュニティ名も同じ意見です)
  リトライ、タイムアウト値は見た通りの作業ですが、このままだと実践(現場)で苦労する
  ことがあります。
  別の機会に説明しますね。

3)PDUの生成(14~20行目)
  低(Low)レベルな部分を提供しているのでしょうがないのですが、PDUを生成して、
  そこにOIDをセットしたバーバインド(VariableBinding)を追加していきます。
  SNMP PDUフォーマット、Variable Bindingの詳細は解説書が色々とありますので
  割愛しますが、OIDとその値(Value)をペアにした単位で、1PDUに複数格納出来ます。
  実践的な注意点ですが、1PDUにあまりに多くのバーバインドを設定するとTooBigエラー
  になります。
  TooBigエラーは1PDUサイズが上限を超えた場合に発生するエラーです。
  仕様上、下限は定義(485 bytes)されておりエージェント側のサポートも約束される
  のですが、面倒な事に上限は定義されてされていない為、エージェント毎に変わってきます。
  #CISCO IOSだと、snmp-server packetsize ?(byte)で設定できますね。
  #1000~1500 bytes程度はサポートしてくれるエージェントが多いように感じますが。
  ですので、バーバインド(OID)が多い場合には、どこかでPDUをスプリットしてリクエストを
  送信する必要があります。

4)リクエストの送信とレスポンスの受信(22行目~51行目)
  SnmpクラスはSNMP操作の基本になるクラスで、Javadocを見るとサンプルコードも
  記載されています。
  Snmpクラスのコンストラクタでトランスポート層で利用するプロトコルを指定します。
  TransportMappingのサブクラスであるDefaultTcpTransportMapping又は
  DefaultUdpTransportMapping使うことになるのですが、TCPを使う機会は
  少ないんじゃないかと思います。
  snmp.listen()でローカルのポートが待機状態になります。
  #時々、’値が取れないんだけど・・・’という質問が出ますがsnmp.listen()忘れが多いです。
  
  やっと、snmp.getです。getした結果はResponseEventで受け取り、レスポンスPDUを
  取り出します。このresPDUがnullの場合はタイムアウトで、resPDUs.getErrorStatus()が
  0(
SnmpConstants.SNMP_ERROR_SUCCESS)以外の場合は、何かしらのSNMPエラーが
  発生しています。
  SNMP PDUに含まれるError Statusには以下のような種類があります(v2で拡張されています)。

             <The SNMPv2 error status is mapped to an SNMPv1 error-status using this table(RFC2089抜粋)> 
             SNMPv2 error-status              SNMPv1 error-status
             ===================    ===================
             noError                                   noError
             tooBig                                    tooBig
             noSuchName                           noSuchName
             badValue                                badValue
             readOnly                                readOnly
             genErr                                    genErr
             wrongValue                             badValue
             wrongEncoding                        badValue
             wrongType                              badValue
             wrongLength                           badValue
             inconsistentValue                     badValue
             noAccess                                noSuchName
             notWritable                             noSuchName
             noCreation                              noSuchName
             inconsistentName                     noSuchName
             resourceUnavailable                  genErr
             commitFailed                           genErr
             undoFailed                              genErr
             authorizationError                     noSuchName
 
  SNMPエラーが発生した場合の処理(49行目)をみると、
  ’レスポンスPDU自体は受信しているのだから、複数OIDをgetした時には、
  正常な値がセットされているバーバインドもあるんじゃ無いの?’
  と思うかもしれませんが、値を見ると全てNullオクテット列(”Null”)になっています。
  確かにPDUにはError Indexというエラーの発生したバーバインドの場所(Index)を
  通知する領域もあるのだから、エラーの発生したバーバインドと正常なバーバインドを
  判断できるはずで、Error StatusがnoError(0)以外の場合でも、正常な値は取れそう
  なものだと想像したくなりますが、バーバインズ(Variable Bindings)は全てNullに
  なってしまうんです(v1)。
  #RFC1157/4.1.2(4)を見るとこのような場合には、エラー情報(Index,Status)
  #以外は受信時と同一のGetレスポンスPDUを送信するよう定義されています。  

  ただ、救い(v1の反省)もありましてSNMPのバージョンをv2cにすると、一部OIDで
  エラーが発生しても、Error StatusはnoError(0)で、エラーのバーバインドのみValueが
  Nullになります(38~42行目)。
  この理由ですが、RFC1905(SNMPv2)でGetRequest-PDUに関する仕様が変更され、
  noSuchObject、noSuchInstanceというSyntax(バーバインドで宣言する型情報)を
  利用して、各バーバインド単位でエラーがハンドリングできるようになっています。
  sysDescr.0を".1.3.6.1.2.1.1.1.1", // system.sysDescr.1という存在しないOIDに
  変更し、v1、v2cでget結果を比較してみるとよく分かります。
  #僕のWindowsXPはv1、v2双方でErrorStatusがnoSuchName(2)でバーバインズ
  全滅しました。
  #WindowsXPのSNMPサービスはSNMPv2準拠ではなくv1でしか動かないみたいです。
  #(真っ当なネットワーク機器であればこういう事はまず無いです)
 
  エラーに関する話しが長くなりましたが、最後に正常受信の説明です。
  resPDUの中にはバーバインドが格納されているので、順番に取り出します(35~43行目)。
  39行目で全てStringで値を出力していますが、バーバインドにはSyntax(型)情報があるので、
  Syntaxに応じてデータ型を変えることも出来ます。

            if (var.getSyntax() == SMIConstants.SYNTAX_TIMETICKS) {
              System.out.println(var.getOid() + " -> " + var.getVariable().toInt());
            } else {
              System.out.println(var.getOid() + " -> " + var.getVariable().toString());
            }
 
  この例だと、sysUpTime.0がStringではなく、intで表示されます。
  #sysUpTime.0のString(文字列)変換はSNMP4Jが独自のフォーマットに基づいて
  #行っているので、API依存性を低くするのであればintで取得して、アプリケーション側で
  #フォーマットした方が良いでしょう。

 
  まだまだ実践的なテクニックはあるのですが、まずはここまでにしておきます。


入門SNMP
入門SNMP
posted with amazlet at 13.01.12
ダグラス・R. マウロ ケビン・J. シュミット
オライリー・ジャパン
売り上げランキング: 257,278

Java ネットワーク プログラミング 基礎からわかる 完全入門
永嶋 浩
技術評論社
売り上げランキング: 228,941

Posted by netbuffalo at 03:56│TrackBack(0) 実践SNMP+Java 


この記事へのトラックバックURL