PowerSNMPv3: A New Pure Go SNMP Library with Better Error Handling
Source: Dev.to
Introduction
I created a pure Go SNMP v2c/v3 library that is smaller than gosnmp and is based on a slightly modified ASN.1 parser from the Go standard library.
The stdlib ASN.1 parser handles pure DER, but SNMP requires BER for unmarshaling, so I forked it with minimal changes.
Core Features
- GET multiple OIDs – Returns all successful OIDs even if some fail, along with a partial error describing the failed OID(s).
- SET multiple OIDs – Follows SNMP spec: the operation is atomic; any failure aborts the whole request.
- Handles security‑level mismatches gracefully (no unnecessary retries).
- Automatic recovery for:
usmStatsNotInTimeWindows– synchronizes time and retries.usmStatsUnknownEngineIDs– discovers EngineID with key re‑localization and retries.
- Supported authentication protocols: MD5, SHA, SHA‑224, SHA‑256, SHA‑384, SHA‑512.
- Supported privacy protocols: DES, AES‑128, AES‑192, AES‑256 (including AGENT++ variants).
- Key expansion compliant with RFC 3826 (AES‑192/256).
- Tested with Cisco, Huawei, Moxa, Eltex devices.
Pro Tips
- Avoid Bulk requests with Moxa devices (BER encoding issues).
- Use Bulk with Eltex but reduce repetitions to 8 and increase timeouts.
Installation
go get github.com/OlegPowerC/powersnmpv3
Basic Usage
Initializing a Session
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"time"
PowerSNMP "github.com/OlegPowerC/powersnmpv3"
)
func main() {
// Command‑line flags
Host := flag.String("h", "", "Switch or router IP")
SNMPVersion := flag.Int("v", 3, "SNMP version (2 or 3), default 3")
SNMPuser := flag.String("u", "", "SNMP v3 USER")
SNMPcommunity := flag.String("c", "", "Community name (mandatory for v2)")
SNMPv3Context := flag.String("context", "", "SNMP v3 context")
SNMPauthProtocol := flag.String("a", "", "SNMP auth protocol")
SNMPauthPassword := flag.String("A", "", "SNMP auth password")
SNMPprivProtocol := flag.String("x", "", "SNMP priv protocol")
SNMPprivPassword := flag.String("X", "", "SNMP priv password")
Bulk := flag.Bool("bulk", false, "Use SNMP Bulk")
DebugLevel := flag.Int("debug", 0, "Debug level")
StrOid := flag.String("o", "1.3.6", "SNMP OID")
RawToo := flag.Bool("r", false, "Show raw data")
flag.Parse()
// Device definition
var RouterDev PowerSNMP.NetworkDevice
RouterDev.IPaddress = *Host
RouterDev.Port = 161
RouterDev.SNMPparameters.Username = *SNMPuser
RouterDev.SNMPparameters.Community = *SNMPcommunity
RouterDev.SNMPparameters.SNMPversion = *SNMPVersion
RouterDev.SNMPparameters.AuthProtocol = *SNMPauthProtocol
RouterDev.SNMPparameters.AuthKey = *SNMPauthPassword
RouterDev.SNMPparameters.PrivProtocol = *SNMPprivProtocol
RouterDev.SNMPparameters.PrivKey = *SNMPprivPassword
RouterDev.SNMPparameters.ContextName = *SNMPv3Context
RouterDev.SNMPparameters.RetryCount = 5
RouterDev.SNMPparameters.MaxRepetitions = 50
RouterDev.SNMPparameters.TimeoutBtwRepeat = 800
RouterDev.DebugLevel = uint8(*DebugLevel)
// Create session
sess, err := PowerSNMP.SNMP_Init(RouterDev)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer sess.Close()
// Convert OID string to internal representation
iArOID, _ := PowerSNMP.Convert_OID_StringToIntArray_RAW(*StrOid)
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
ch := make(chan PowerSNMP.ChanDataWErr, 3000)
if *Bulk {
go sess.SNMP_BulkWalk_WChan(ctx, iArOID, ch)
} else {
go sess.SNMP_Walk_WChan(ctx, iArOID, ch)
}
// Process results
for gdata := range ch {
if gdata.Error != nil {
fmt.Println(gdata.Error)
os.Exit(1)
}
if gdata.ValidData {
if *RawToo {
fmt.Println(
PowerSNMP.Convert_OID_IntArrayToString_RAW(gdata.Data.RSnmpOID),
"=",
PowerSNMP.Convert_Variable_To_String(gdata.Data.RSnmpVar),
":",
PowerSNMP.Convert_ClassTag_to_String(gdata.Data.RSnmpVar),
gdata.Data.RSnmpVar.Value,
)
} else {
fmt.Println(
PowerSNMP.Convert_OID_IntArrayToString_RAW(gdata.Data.RSnmpOID),
"=",
PowerSNMP.Convert_Variable_To_String(gdata.Data.RSnmpVar),
":",
PowerSNMP.Convert_ClassTag_to_String(gdata.Data.RSnmpVar),
)
}
}
}
}
Simple GET Example
package main
import (
"log"
"github.com/OlegPowerC/powersnmpv3"
)
func main() {
device := powersnmpv3.NetworkDevice{
IPaddress: "192.168.1.1",
Port: 161,
SNMPparameters: powersnmpv3.SNMPUserParameters{
SNMPversion: 3,
Username: "snmpuser",
AuthProtocol: "sha",
AuthKey: "authpass",
PrivProtocol: "aes",
PrivKey: "privpass",
},
}
sess, err := powersnmpv3.SNMP_Init(device)
if err != nil {
log.Fatal(err)
}
defer sess.Close()
oid, _ := powersnmpv3.ParseOID("1.3.6.1.2.1.1.1.0")
result, err := sess.SNMP_Get(oid)
if err != nil {
log.Fatal(err)
}
log.Printf("Result: %v", result)
}
Error Handling Example (GET Multi)
results, err := sess.SNMP_GetMulti(oids)
// Even if err is non‑nil, `results` contains successful OIDs
snmpErr, _ := powersnmpv3.ParseError(err)
for _, oidErr := range snmpErr.Oids {
log.Printf("Failed: %s - %s", oidErr.Failedoid, oidErr.ErrorDescription)
}
Benchmarks
| Library | Time (15,381 OIDs) | OIDs/s |
|---|---|---|
| PowerSNMPv3 | 4.02 s | 3,827 |
| gosnmp | 4.43 s | 3,472 |
| net‑snmp | 6.43 s | 2,393 |
PowerSNMPv3 is ~37 % faster than net‑snmp and reduces packet traffic in mis‑configured environments (4 vs 10 packets).
License
powersnmpv3 is released under the MIT License.