GoSNMPにSNMP agent機能を追加する(前編)
TWSNNMPの開発のためのgithubでforkしたGOSNMPにSNMP agentのための機能を追加した話です。関連する記事は
です。
SNMP Agentのために必要な機能
GoSNMPは、
GoSNMP is an SNMP client library fully written in Go. It provides Get, GetNext, GetBulk, Walk, BulkWalk, Set and Traps.
と書いてあるようにGetなどのリクエストを送信してMIB情報を取得する機能とTRAPの送受信の機能に対応しています。SNMP Agentそのものの機能はありません。SNMP Agentは、マネージャからのGetなどのリクエストを受信してGet Responseを返信する機能とTRAPの送信機能があれば実現できます。TRAPの送信機能はあるので、まずは受信したリクエストの解釈と応答の組み立てができる機能を調べました。ドキュメントとソースコードを探すと
SnmpDecodePacket():
UDPで受信したデータをSNMPのパケットとして解釈する関数
SnmpEncodePacket():
SNMPのパケットをUDPで送信するデータへ変換する関数
を見つけました。これは使えそうです。
次にGet Responseを返信するためには、返信するMIBデータを検索して取得する機能が必要です。これは、GOSNMPの中にないので自分で作るしかありません。
テストプログラムで実験
調べたことが本当に使えるのか試してみることは大事です。テストプログラムを作って試してみました。作ったプログラムの仕様は、
・UDPのポートをオープンしてデータを受信する
・受信したUDPのデータをSnmpDecodePacketで解析する
・応答パケットのデータを作ってSnmpEncodePacketで返信データを作る
・返信データをUDPポートから送信する
SNMPリクエストはTWSNMPから送ればよいのですが、GO言語の特徴であるマルチスレッドを生かしてテストプログラムの中から送信するようにしました。これは、GoSNMPのサンプルプログラムをそのまま使っています。
まずは、受信して解析するまで
// 受信するアドレスとポート
udpAddr := &net.UDPAddr{
IP: net.ParseIP("0.0.0.0"),
Port: 161,
}
// 受信待ち開始
updLn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Fatalln(err)
}
// 受信バッファを作成
buf := make([]byte, 4096)
log.Println("Starting snmpd ...")
for {
// UDPデータを受信
n, addr, err := updLn.ReadFromUDP(buf)
if err != nil {
log.Fatal(err)
}
// パケットを解析
p, err := g.Default.SnmpDecodePacket(buf[:n])
if err != nil {
log.Fatal(err)
}
// 変換して解析したデータを表示
log.Printf("p=%#v",p)
のようなプログラムです。ここまでは快調でした。しかし、落とし穴がありました。応答のデータを作成してSnmpEncodePacket()関数で返信データを作ろうとした時に問題が見つかりました。
・リクエストIDが1つ増える
・エラーの応答が作れない
この瞬間、このパッケージのままではできないことがわかりました。これがきっかけでリポジトリをforkして自分でメンテナンスするモチベーションが湧いてきました。
SnmpEncodeGetResponsePacket()関数を追加
リポジトリをfork した後、SnmpEncodePacket関数の問題点を改善したSnmpEncodeGetResponsePacket関数をパッケージに追加しました。
とは言っても作ったのは、
func (x *GoSNMP) SnmpEncodeGetResponsePacket(reqID uint32, errorIndex int32, pdus []SnmpPDU) ([]byte, error) {
err := x.validateParameters()
if err != nil {
return []byte{}, err
}
pkt := x.mkSnmpPacket(GetResponse, pdus, 0, 0)
pkt.RequestID = reqID
if errorIndex >= 0 {
pkt.Error = NoSuchName
pkt.ErrorIndex = uint8(errorIndex)
}
if x.Version == Version3 {
pkt.MsgID = reqID
err = x.initPacket(pkt)
if err != nil {
return []byte{}, err
}
}
var out []byte
out, err = pkt.marshalMsg()
if err != nil {
return []byte{}, err
}
return out, nil
}
だけです。でも、大きく前進です。テストプログラム組み込んでみました。
out, err := g.Default.SnmpEncodeGetResponsePacket(p.RequestID, int32(errIndex), pdus)
if err != nil {
log.Fatal(err)
}
updLn.WriteTo(out, addr)
うまく応答を返しました。
この関数だけあれば、SNMP Agentを実現するライブラリとしては十分です。でも、返信するMIBデータを検索して取得する機能やUDPサーバー機能もパッケージに組み込もうという欲がでてきました。
この話は長くなるので後編で書きます。
つづく