Hello mates!
I made one more pure Go SNMP v2c/v3 library, but smaller than gosnmp and based on a slightly modified ASN.1 parser from Go stdlib.
The stdlib ASN.1 parser is pure DER but we need BER for unmarshaling, so I forked it with minimal changes.
Key Differences with gosnmp
1. Partial Error Handling
GET multiple OIDs: If one fails, you get all OK OIDs in result + partial error with failed OID and reason
results, err := sess.SNMP_GetMulti(oids)
// Even with error, results contains successful OIDs
snmpErr, _ := powersnmpv3.ParseError(err)
for _, oidErr := range snmpErr.Oids {
log.Printf("Failed: %s - %s", oidErr.Failedoid, oidErr.ErrorDescription)
}
SET multiple OIDs: If one fails, you get total error (SET is atomic per SNMP spec)
2. Async Walk with Channels (cli tool)
package main
import (
"context"
"flag"
"fmt"
"os"
"time"
PowerSNMP "github.com/OlegPowerC/powersnmpv3"
)
func main() {
Host := flag.String("h", "", "Switch or routers IP")
SNMPVersion := flag.Int("v", 3, "SNMP version, 2 or 3, default is 3")
SNMPuser := flag.String("u", "", "SNMP v3 USER")
SNMPcommunity := flag.String("c", "", "Mandatory for version 2, SNMP read community name")
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, "SNMP Bukl")
DebugLevel := flag.Int("debug", 0, "Debug lebel")
StrOid := flag.String("o", "1.3.6", "SNMP OID")
RawToo := flag.Bool("r", false, "RAW data")
flag.Parse()
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)
Ssess, SsessError := PowerSNMP.SNMP_Init(RouterDev)
if SsessError != nil {
fmt.Println(SsessError)
os.Exit(1)
}
if Ssess == nil {
fmt.Println("Session is nil")
os.Exit(1)
}
defer Ssess.Close()
iArOID, _ := PowerSNMP.Convert_OID_StringToIntArray_RAW(*StrOid)
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
ChIn := make(chan PowerSNMP.ChanDataWErr, 3000)
if *Bulk {
go Ssess.SNMP_BulkWalk_WChan(ctx, iArOID, ChIn)
} else {
go Ssess.SNMP_Walk_WChan(ctx, iArOID, ChIn)
}
ResultNumber := 0
for gdata := range ChIn {
if gdata.Error != nil {
fmt.Println(gdata.Error)
os.Exit(1)
}
ResultNumber++
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))
}
}
}
}
3. RFC 3414-Compliant REPORT Handling
This is where it gets interesting. When security levels mismatch (client expects auth, agent configured without), libraries behave differently:
gosnmp: Checks authentication based on client config, not packet flags
- Agent sends REPORT without auth (valid per RFC 3414)
- gosnmp rejects: "incoming packet is not authentic, discarding"
- Makes 3 unnecessary retries
- 10 packets total ❌
PowerSNMPv3: Checks packet flags, accepts REPORT without auth
- Immediate error: "unsupported security levels"
- Zero retries on config errors (non-recoverable)
- 4 packets total ✅
60% fewer packets in misconfigured environments!
Also handles recoverable errors automatically:
-
usmStatsNotInTimeWindows→ syncs time, retries -
usmStatsUnknownEngineIDs→ discovers EngineID with key re-localization, retries
4. Full Crypto Support
- Auth: MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512
- Priv: DES, AES-128, AES-192, AES-256 (including AGENT++ variants)
- Key expansion: RFC 3826 compliant (AES-192/256)
5. Real Hardware Tested
Cisco / Huawei / Moxa / Eltex
Pro tips:
- Don't use Bulk with Moxa (BER encoding issues)
- Use Bulk with Eltex but reduce repetitions to 8 and increase timeouts
Performance
Benchmarked on 15,381 OIDs (SNMPv3 AES+SHA):
- PowerSNMPv3: 4.02s (3,827 OID/s)
- gosnmp: 4.43s (3,472 OID/s)
- net-snmp: 6.43s (2,393 OID/s)
37% faster than net-snmp 🚀
Quick Start
go get github.com/OlegPowerC/powersnmpv3
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)
GitHub
License: MIT
Production use: Monitoring systems with 1000+ devices
Top comments (0)