SNMPパケットの構造を勉強したので、メモ。
■SNMPパケットはBERによって成り立っている
SNMP(ここではoverIP)パケットは、フィールドのすべてがBER(Basic Encoding Rule)というルールのもとで記述されています。これは
- タグ (Tag)
- データ部の長さ (Length)
- データ部/値 (Value)
というTLV形式とも呼ばれる構造の組み合わせで成り立っている、と言い換えられます。タグ部(T)にはコマンドや値の型も引っ括めて定義されていて、SNMPv1では以下の情報が存在します。
- INTEGER(0x02) 整数型
- OCTET STRING(0x04) 可変長のバイト列、文字型、MACアドレス等
- NULL(0x05) 値なし
- OBJECT ID(0x06) オブジェクトID
- SEQUENCE(0x30) TLVが入れ子になっている
- Ip Address(0x40) IPアドレス
- Counter(0x41) カウンタ値
- Gauge(0x42) ゲージ値
- Time Ticks(0x43) TimeTick値
- Get-Request(0xA0) SNMP Get-PDU
- Get-Next(0xA1) SNMP Get-NextPDU
- Get-Response(0xA2) SNMP Get-ResponsePDU
- SET-Request(0xA3) SNMP Set-RequestPDU
- TRAP(0xA4) SNMP Trap-PDU
データ部の長さ(L部)とオブジェクトIDは最上位ビットがデータの連続性を表す7bitエンコーディングで定義され、それ以外のデータ部(V)は原則8bitエンコーディングで表されます。
例えば「20という数値(Integer)」は、TはIntegerなので0x02、Lは255以下で1バイトで表現できるので0x01、Vは20の16進表記で0x14、ということで
【0x02 0x01 0x14】
というように表現します。「'20'という文字」であればOCTET STRINGでASCII表現をするだけなので、
【0x04 0x02 0x42 0x40】
です。ちなみにOCTET STRINGはLがしっかり定義されているので、途中でヌル文字(0x00)が混じっていてもよく、またヌル終端である必要もありません。そのためMIBではビット列の表現によく使われています。
SEQUENCEの例も挙げておきます。「上の2つをまとめた集合」とした場合に、
【0x30 0x07 0x02 0x01 0x14 0x04 0x02 0x42 0x40】
となるだけです。「T:0x30/L:0x07(3+4)/V:データの羅列」となっているのが見て取れるでしょうか。
■SNMPパケットフォーマット
SNMPパケットは、徹頭徹尾このTLVの組み合わせで構成されています。GetRequestの場合の構造を例示します。
SEQUENCE
| INTEGER (バージョン(SNMPv1は0固定))
| OCTET STRING (コミュニティ("public"))
| Get-Request (コマンドの種類)
| | INTEGER (ユニークなID)
| | INTEGER (error status。エラーコード)
| | INTEGER (error index。何番目のOIDがエラーか)
| | SEQUENCE
| | | SEQUENCE
| | | |Object-ID
| | | |Null (0x05 0x00)
上記のとおりです。縦棒「|」は上のタグで示すLengthの範囲を表します。レスポンスや設定時のときもほとんど同じフォーマットです。フォーマットの例外はありません。
したがってBERさえ分かってしまえば、ダンプデータからSNMP情報を読み取ることも可能になります。
思っていたよりも簡単なルールで成り立っているんですね。
あ、ちなみにSNMP over IPはUDPパケットなので
|IPヘッダ|UDPヘッダ|SNMPパケット|
という構造になっています。ポート番号は161です。
−−−
え?なぜこんなことを調べているかって?それは、前回pySNMPのexe化で挫折したことが悔しいので、自分でSNMPライブラリを作ろうとしているからです。
Getは出来るようになったので、あとはSetさえ実装すれば動くようになってくれるはずです。ただ、後悔する場合にはwalkやGetNextも実装しておかないと。
また進捗報告をさせてください。
最近のコメント