1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語のSNMPパッケージgosnmpでsnmpwalkを作る方法

Posted at

はじめに

Go言語のSNMPパッケージgosnmpを紹介するシリーズの6回目です。

gosnmpパッケージのサンプルプログラムは、NET-SNMPのsnmpgetコマンドのような動作でした。今回はsnmpwalkコマンドの動作の作り方です。

の記事で紹介したOIDと名前の変換も使っています。

snmpwalkコマンドについて

snmpwalkは、NET-SNMPのコマンドです。グループ単位でMIBを取得できます。SNMPエージェントが対応しているMIBを全て取得する場合にも利用します。

$snmpwalk -v 2c -c public 192.168.1.210 system
SNMPv2-MIB::sysDescr.0 = STRING: Linux minipc 4.19.0-26-amd64 1 SMP Debian 4.19.304-1 (2024-01-09) x86_64
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (17244672) 1 day, 23:54:06.72
SNMPv2-MIB::sysContact.0 = STRING: twsnmp@gmail.com
SNMPv2-MIB::sysName.0 = STRING: minipc
SNMPv2-MIB::sysLocation.0 = STRING: YMI2F
SNMPv2-MIB::sysServices.0 = INTEGER: 72
SNMPv2-MIB::sysORLastChange.0 = Timeticks: (1) 0:00:00.01

のようにsystemグループのMIBを1つのコマンドで取得できます。

SNMPのGetNextコマンドを繰り返してMIBツリーのsystem配下にあるMIB取得しています。
systemと指定している最初のMIBをisoとすれば、対象のSNMPエージェントが対応している全MIBを取得することができます。

snmpwalkをgosnmpを使って作る

snmpwalkをgosnmpで作ると

package main

import (
	"fmt"
	"log"

	g "github.com/gosnmp/gosnmp"
	mibdb "github.com/twsnmp/go-mibdb"
)

func main() {
	m, err := mibdb.NewMIBDB("./mib.txt")
	if err != nil {
		fmt.Printf("NewMIBDB failed err=%v", err)
		return
	}
	g.Default.Target = "192.168.1.210"
	err = g.Default.Connect()
	if err != nil {
		log.Fatalf("Connect() err: %v", err)
	}
	defer g.Default.Conn.Close()
	err = g.Default.Walk(m.NameToOID("system"), func(variable g.SnmpPDU) error {
		fmt.Printf("oid: %s name:%s = ", variable.Name, m.OIDToName(variable.Name))
		switch variable.Type {
		case g.ObjectIdentifier:
			fmt.Printf("oid: %s(%s)\n", m.OIDToName(variable.Value.(string)), variable.Value)
		case g.OctetString:
			bytes := variable.Value.([]byte)
			fmt.Printf("string: %s\n", string(bytes))
		default:
			fmt.Printf("number: %d\n", g.ToBigInt(variable.Value))
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Walk() err: %v", err)
	}
}

gosnmpには、Walkという関数があります。Walkは、GetNextで取得したMIBを1つづつCallバック関数に渡してくれます。

Callバック関数

err = g.Default.Walk(m.NameToOID("system"), func(variable g.SnmpPDU) error {
		fmt.Printf("oid: %s name:%s = ", variable.Name, m.OIDToName(variable.Name))
		switch variable.Type {
		case g.ObjectIdentifier:
			fmt.Printf("oid: %s(%s)\n", m.OIDToName(variable.Value.(string)), variable.Value)
		case g.OctetString:
			bytes := variable.Value.([]byte)
			fmt.Printf("string: %s\n", string(bytes))
		default:
			fmt.Printf("number: %d\n", g.ToBigInt(variable.Value))
		}
		return nil
	})

がCallバック関数です。この中で取得したMIBを表示すれば、snnmpwalkコマンドと同じような動作を実現できます。

実行結果

$go run main.go
oid: .1.3.6.1.2.1.1.1.0 name:sysDescr.0 = string: Linux minipc 4.19.0-26-amd64 1 SMP Debian 4.19.304-1 (2024-01-09) x86_64
oid: .1.3.6.1.2.1.1.2.0 name:sysObjectID.0 = oid: netSnmpAgentOIDs.10(.1.3.6.1.4.1.8072.3.2.10)
oid: .1.3.6.1.2.1.1.3.0 name:sysUpTimeInstance = number: 16366034
oid: .1.3.6.1.2.1.1.4.0 name:sysContact.0 = string: twsnmp@gmail.com
oid: .1.3.6.1.2.1.1.5.0 name:sysName.0 = string: minipc
oid: .1.3.6.1.2.1.1.6.0 name:sysLocation.0 = string: YMI2F
oid: .1.3.6.1.2.1.1.7.0 name:sysServices.0 = number: 72
oid: .1.3.6.1.2.1.1.8.0 name:sysORLastChange.0 = number: 1

snmptableコマンドについて

NET-SNMPにはテーブル形式のMIBを取得するsnmptableというコマンドがあります。
インターフェイスの情報ifTableをsnmpwalkで取得すると

$snmpwalk -v 2c -c public 192.168.1.210 ifTable
IF-MIB::ifIndex.1 = INTEGER: 1
IF-MIB::ifIndex.2 = INTEGER: 2
IF-MIB::ifIndex.3 = INTEGER: 3
IF-MIB::ifIndex.4 = INTEGER: 4
IF-MIB::ifIndex.5 = INTEGER: 5
IF-MIB::ifDescr.1 = STRING: lo
IF-MIB::ifDescr.2 = STRING: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
IF-MIB::ifDescr.3 = STRING: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
IF-MIB::ifDescr.4 = STRING: Qualcomm Atheros QCA9377 802.11ac Wireless Network Adapter
IF-MIB::ifDescr.5 = STRING: docker0
IF-MIB::ifType.1 = INTEGER: softwareLoopback(24)
IF-MIB::ifType.2 = INTEGER: ethernetCsmacd(6)
IF-MIB::ifType.3 = INTEGER: ethernetCsmacd(6)
IF-MIB::ifType.4 = INTEGER: ethernetCsmacd(6)
IF-MIB::ifType.5 = INTEGER: ethernetCsmacd(6)
IF-MIB::ifMtu.1 = INTEGER: 65536
IF-MIB::ifMtu.2 = INTEGER: 1500
IF-MIB::ifMtu.3 = INTEGER: 1500
IF-MIB::ifMtu.4 = INTEGER: 1500
IF-MIB::ifMtu.5 = INTEGER: 1500
IF-MIB::ifSpeed.1 = Gauge32: 10000000
IF-MIB::ifSpeed.2 = Gauge32: 1000000000

のように見やすくありません。snmptableで取得すると

$snmptable -v 2c -c public 192.168.1.210 ifTable
SNMP table: IF-MIB::ifTable

 ifIndex                                                                                   ifDescr           ifType ifMtu    ifSpeed     ifPhysAddress ifAdminStatus ifOperStatus ifLastChange ifInOctets ifInUcastPkts ifInNUcastPkts ifInDiscards ifInErrors ifInUnknownProtos ifOutOctets ifOutUcastPkts ifOutNUcastPkts ifOutDiscards ifOutErrors ifOutQLen              ifSpecific
       1                                                                                        lo softwareLoopback 65536   10000000                              up           up 0:0:00:00.00  614573050       3237520              0            0          0                 0   614573050        3237520               0             0           0         0 SNMPv2-SMI::zeroDotZero
       2 Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller   ethernetCsmacd  1500 1000000000 5c:85:7e:4a:9a:58            up           up 0:0:00:00.00   30076806      64586983          75750      9840636          0                 0  2996661367      121366896               0             0           0         0 SNMPv2-SMI::zeroDotZero
       3 Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller   ethernetCsmacd  1500 1000000000 5c:85:7e:4a:9a:59            up           up 0:0:00:00.00  317343533    2127902930       22268130        78808          0                 0    93935144        1114655               0             0           0         0 SNMPv2-SMI::zeroDotZero
       4                                Qualcomm Atheros QCA9377 802.11ac Wireless Network Adapter   ethernetCsmacd  1500          0  0:e9:3a:89:8d:ff          down         down 0:0:00:00.00          0             0              0            0          0                 0           0              0               0             0           0         0 SNMPv2-SMI::zeroDotZero
       5                                                                                   docker0   ethernetCsmacd  1500          0   2:42:89:3:c4:e2            up         down 0:0:00:00.00          0             0              0            0          0                 0           0              0               0             0           0         0 SNMPv2-SMI::zeroDotZero

ようにテーブル形式で表示できます。

TWSNMP FKの場合

TWSNMP FKで取得すると

image.png

のように表示できます。

gosnmpでsnmptableを作る

gosnmpでsnmptableを作ると

package main

import (
	"fmt"
	"log"
	"strings"

	g "github.com/gosnmp/gosnmp"
	mibdb "github.com/twsnmp/go-mibdb"
)

func main() {
	m, err := mibdb.NewMIBDB("./mib.txt")
	if err != nil {
		fmt.Printf("NewMIBDB failed err=%v", err)
		return
	}
	g.Default.Target = "192.168.1.210"
	err = g.Default.Connect()
	if err != nil {
		log.Fatalf("Connect() err: %v", err)
	}
	defer g.Default.Conn.Close()
	tableMap := make(map[string]map[string]string)
	nameMap := make(map[string]bool)
	nameList := []string{}
	err = g.Default.Walk(m.NameToOID("ifTable"), func(variable g.SnmpPDU) error {
		name := m.OIDToName(variable.Name)
		a := strings.SplitN(name, ".", 2)
		if len(a) != 2 {
			log.Fatalln("no index")
		}
		name = a[0]
		index := a[1]
		val := ""
		switch variable.Type {
		case g.ObjectIdentifier:
			val = fmt.Sprintf("%s(%s)", m.OIDToName(variable.Value.(string)), variable.Value)
		case g.OctetString:
			if name == "ifPhysAddress" {
				bytes := variable.Value.([]byte)
				for i, b := range bytes {
					if i > 0 {
						val += ":"
					}
					val += fmt.Sprintf("%02X", b)
				}
			} else {
				bytes := variable.Value.([]byte)
				val = string(bytes)
			}
		default:
			val = fmt.Sprintf("%d", g.ToBigInt(variable.Value))
		}
		if _, ok := nameMap[name]; !ok {
			nameMap[name] = true
			nameList = append(nameList, name)
		}
		if _, ok := tableMap[index]; ok {
			tableMap[index][name] = val
		} else {
			tableMap[index] = make(map[string]string)
			tableMap[index][name] = val
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Walk() err: %v", err)
	}
	fmt.Printf("index")
	for _, n := range nameList {
		fmt.Printf("\t%s", n)
	}
	fmt.Println("")
	for i, m := range tableMap {
		fmt.Printf("%s", i)
		for _, n := range nameList {
			if v, ok := m[n]; ok {
				fmt.Printf("\t%s", v)
			} else {
				fmt.Printf("\t")
			}
		}
		fmt.Println("")
	}
}

Walk関数で取得したMIBをGo言語のMAPを使って並べ替えています。

$go run main.go
index	ifIndex	ifDescr	ifType	ifMtu	ifSpeed	ifPhysAddress	ifAdminStatus	ifOperStatus	ifLastChange	ifInOctets	ifInUcastPkts	ifInNUcastPkts	ifInDiscards	ifInErrors	ifInUnknownProtos	ifOutOctets	ifOutUcastPkts	ifOutNUcastPkts	ifOutDiscards	ifOutErrors	ifOutQLen	ifSpecific
5	5	docker0	6	1500	0	02:42:89:03:C4:E2	1	2	0	0	0	0	0	0	0	0	0	00	0	0	zeroDotZero(.0.0)
1	1	lo	24	65536	10000000		1	1	0	614573050	3237520	0	0	0	0	614573050	3237520	0	0	0	0	zeroDotZero(.0.0)
2	2	Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller	6	1500	1000000000	5C:85:7E:4A:9A:58	1	1	0	30524249	64592573	75761	9841794	0	0	3001538382	121384914	0	0	0	0	zeroDotZero(.0.0)
3	3	Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller	6	1500	1000000000	5C:85:7E:4A:9A:59	1	1	0	430705774	2128102302	22271244	78808	0	0	93945512	1114781	0	0	0	0	zeroDotZero(.0.0)
4	4	Qualcomm Atheros QCA9377 802.11ac Wireless Network Adapter	6	1500	0	00:E9:3A:89:8D:FF	2	2	0	0	00	0	0	0	0	0	0	0	0	0	zeroDotZero(.0.0)

ifPhysAddressは、そのまま文字列にすると文字化けするので

if name == "ifPhysAddress" {
	bytes := variable.Value.([]byte)
	for i, b := range bytes {
    	if i > 0 {
			val += ":"
		}
		val += fmt.Sprintf("%02X", b)
	}
} else {
	bytes := variable.Value.([]byte)
	val = string(bytes)
}

のようにMACアドレスのように表示する工夫をしています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?