DEV Community

Volkov Oleg
Volkov Oleg

Posted on

PowerSNMPv3: A New Pure Go SNMP Library with Better Error Handling

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)
}
Enter fullscreen mode Exit fullscreen mode

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))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode

GitHub

OlegPowerC/powersnmpv3

License: MIT

Production use: Monitoring systems with 1000+ devices

Top comments (0)