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" } } }
| 固定リンク


コメント