Pure Go SNMP v2c/v3 Library
Author: Volkov Oleg Contact: oleg.powerc@gmail.com License: MIT Commercial support and custom development available.
- SNMPv2c and SNMPv3 support
- Authentication: MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512
- Encryption: DES, AES-128, AES-192, AES-256 (including AGENT++ variants)
- SNMP operations: Get, GetMulti, Set, SetMulti, Walk, BulkWalk
- Streaming operations via channels: Walk_WChan, BulkWalk_WChan
- TRAP/INFORM reception with automatic ACK
- Minimal dependencies: only
github.com/OlegPowerC/asn1modsnmp - Partial error handling support
go get github.com/OlegPowerC/powersnmpv3package main
import (
"fmt"
"log"
snmp "github.com/OlegPowerC/powersnmpv3"
)
func main() {
device := snmp.NetworkDevice{
IPaddress: "192.168.1.1",
Port: 161,
SNMPparameters: snmp.SNMPUserParameters{
SNMPversion: 3,
Username: "snmpuser",
AuthProtocol: "sha",
AuthKey: "authpass123",
PrivProtocol: "aes",
PrivKey: "privpass123",
RetryCount: 3,
TimeoutBtwRepeat: 500,
},
}
sess, err := snmp.SNMP_Init(device)
if err != nil {
log.Fatal(err)
}
defer sess.Close()
// sysDescr.0
oid, _ := snmp.ParseOID("1.3.6.1.2.1.1.1.0")
result, err := sess.SNMP_Get(oid)
if err != nil {
log.Fatal(err)
}
for _, vb := range result {
fmt.Printf("%s = %s\n",
snmp.Convert_OID_IntArrayToString_RAW(vb.RSnmpOID),
snmp.Convert_Variable_To_String(vb.RSnmpVar))
}
}
device := snmp.NetworkDevice{
IPaddress: "192.168.1.1",
Port: 161,
SNMPparameters: snmp.SNMPUserParameters{
SNMPversion: 2,
Community: "public",
RetryCount: 3,
TimeoutBtwRepeat: 500,
},
}
sess, err := snmp.SNMP_Init(device)
// ...
| Parameter | Default | Feature |
|---|---|---|
| TimeoutBtwRepeat | 300ms | base × (attempt+1): 300→600→900ms backoff |
| MaxMsgSize | 1360 bytes | Like TCP MSS — prevents IP fragmentation |
| MaxRepetitions | 25 | Maximum number of table rows returned per iteration in GetBulk requests |
Device configuration for session initialization.
type NetworkDevice struct {
IPaddress string // Device IP address
Port int // UDP port (usually 161)
SNMPparameters SNMPUserParameters // SNMP parameters
DebugLevel uint8 // Debug level (0-3)
}
SNMP session parameters.
type SNMPUserParameters struct {
SNMPversion int // 2 for v2c, 3 for v3
Username string // Username (v3)
AuthKey string // Authentication password (v3)
AuthProtocol string // "md5", "sha", "sha224", "sha256", "sha384", "sha512"
PrivKey string // Privacy password (v3)
PrivProtocol string // "des", "aes", "aes192", "aes256", "aes192a", "aes256a"
ContextName string // SNMPv3 context (optional)
RetryCount int // Retry count (1-10, default 3)
TimeoutBtwRepeat int // Timeout between retries in ms (default 300)
MaxRepetitions uint16 // Max repetitions for BulkWalk (default 25)
MaxMsgSize uint16 // Max SNMP v3 message size
Community string // Community string (v2c)
}
| Parameter | Type | description |
|---|---|---|
| SNMPversion | int | SNMP version, 2 for v2c, 3 for v3 |
| Username | string | USM User for SNMP v3 |
| AuthKey | string | Authentication password for v3 |
| AuthProtocol | string | Authentication protocol "md5", "sha", "sha224", "sha256", "sha384", "sha512" |
| PrivKey | string | Privacy password for v3 |
| PrivProtocol | string | "des", "aes", "aes192", "aes256", "aes192a", "aes256a" |
| ContextName | string | Optional SNMP v3 context name. For example, to retrieve a MAC address table for VLAN 10 on a Cisco switch, specify the vlan-10 context |
| RetryCount | int | Retry count (1-10, default 3) |
| TimeoutBtwRepeat | int | Timeout between retries in ms (default 300). Uses linear backoff: timeout = base × (attempt + 1) |
| MaxRepetitions | uint16 | Max repetitions for BulkWalk (default 25) |
| MaxMsgSize | Uint16 | limits the maximum size of SNMP messages sent to and received from the device. It functions similarly to the MSS (Maximum Segment Size) parameter in TCP, defining the upper limit for the payload to avoid IP fragmentation and improve delivery reliability. |
Active SNMP session. Created via SNMP_Init().
type SNMPv3Session struct {
IPaddress string
Port int
Debuglevel uint8
SNMPparams SNMPParameters
// ... internal fields
}
SNMP request result - OID and value pair.
type SNMP_Packet_V2_Decoded_VarBind struct {
RSnmpOID []int // OID as int array
RSnmpVar SNMPVar // Variable value
}
SNMP variable with type and value.
type SNMPVar struct {
ValueType int // Value type (ASN.1 tag)
ValueClass int // Class (Universal, Application, Context, Private)
IsCompound bool // Compound type (SEQUENCE, etc.)
Value []byte // Raw value
}
Result for streaming operations via channels.
type ChanDataWErr struct {
Data SNMP_Packet_V2_Decoded_VarBind // Data
Error error // Error (nil if OK)
ValidData bool // Data is valid
}
Parameters for TRAP/INFORM reception.
type SNMPTrapParameters struct {
SNMPversion int // 2 or 3
Username string // Username (v3)
AuthKey string // Authentication password
AuthProtocol string // Authentication protocol
PrivKey string // Privacy password
PrivProtocol string // Privacy protocol
Community string // Community (v2c)
}
Creates and initializes SNMP session. Performs Engine Discovery for SNMPv3.
func SNMP_Init(Ndev NetworkDevice) (*SNMPv3Session, error)
Parameters:
Ndev- device configuration
Returns:
*SNMPv3Session- pointer to sessionerror- initialization error or nil
Example:
sess, err := snmp.SNMP_Init(device)
if err != nil {
log.Fatal("Init failed:", err)
}
defer sess.Close()
Closes UDP connection and releases resources.
func (s *SNMPv3Session) Close() error
Performs SNMP GET request for a single OID.
func (s *SNMPv3Session) SNMP_Get(Oid []int) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Parameters:
Oid- OID as int array (useParseOID)
Returns:
[]SNMP_Packet_V2_Decoded_VarBind- array with single resulterror- fatal error or nil
Example:
oid, _ := snmp.ParseOID("1.3.6.1.2.1.1.1.0")
result, err := sess.SNMP_Get(oid)
Performs SNMP GET request for multiple OIDs in a single PDU.
func (s *SNMPv3Session) SNMP_GetMulti(OidVar []SNMP_Packet_V2_Decoded_VarBind) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Parameters:
OidVar- VarBind array with filled OIDs (RSnmpOIDfield)
Returns:
[]SNMP_Packet_V2_Decoded_VarBind- resultserror- can be partial error (SNMPne_Errors) or fatal
Example:
oids := []snmp.SNMP_Packet_V2_Decoded_VarBind{
{RSnmpOID: oid1},
{RSnmpOID: oid2},
{RSnmpOID: oid3},
}
results, err := sess.SNMP_GetMulti(oids)
Performs complete SNMP Walk of subtree using GETNEXT.
func (s *SNMPv3Session) SNMP_Walk(oid []int) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Parameters:
oid- root OID of subtree
Returns:
[]SNMP_Packet_V2_Decoded_VarBind- all values in subtreeerror- error or nil
Example:
// Walk ifDescr (1.3.6.1.2.1.2.2.1.2)
oid, _ := snmp.ParseOID("1.3.6.1.2.1.2.2.1.2")
interfaces, err := sess.SNMP_Walk(oid)
for _, iface := range interfaces {
fmt.Println(snmp.Convert_Variable_To_String(iface.RSnmpVar))
}
Performs high-performance SNMP Walk using GETBULK.
func (s *SNMPv3Session) SNMP_BulkWalk(oid []int) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Parameters and returns are similar to SNMP_Walk.
Uses MaxRepetitions to get multiple values per request.
Streaming Walk via channel. Results are sent as received.
func (s *SNMPv3Session) SNMP_Walk_WChan(ctx context.Context,oid []int, CData chan<- ChanDataWErr)
Parameters:
ctx- context with timeoutoid- root OIDCData- channel for results (closed on completion)
Recommended: buffered channel prevents blocking, context adds timeout
Example:
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
ch := make(chan snmp.ChanDataWErr,30000)
go sess.SNMP_Walk_WChan(ctx,oid, ch)
for result := range ch {
if result.Error != nil {
log.Println("Error:", result.Error)
continue
}
fmt.Printf("%s = %s\n",
snmp.Convert_OID_IntArrayToString_RAW(result.Data.RSnmpOID),
snmp.Convert_Variable_To_String(result.Data.RSnmpVar))
}
Streaming BulkWalk via channel.
func (s *SNMPv3Session) SNMP_BulkWalk_WChan(ctx context.Context,oid []int, CData chan<- ChanDataWErr)
Similar to SNMP_Walk_WChan, but uses GETBULK.
Performs SNMP SET for a single OID.
func (s *SNMPv3Session) SNMP_Set(Oid []int, VBvalue SNMPVar) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Parameters:
Oid- OID to writeVBvalue- value (create usingSetSNMPVar_*functions)
Returns:
[]SNMP_Packet_V2_Decoded_VarBind- SET resulterror- error or nil
Example:
oid, _ := snmp.ParseOID("1.3.6.1.2.1.1.6.0") // sysLocation
value := snmp.SetSNMPVar_OctetString("Server Room A1")
result, err := sess.SNMP_Set(oid, value)
Performs SNMP SET for multiple OIDs in a single PDU.
func (s *SNMPv3Session) SNMP_SetMulti(OidVar []SNMP_Packet_V2_Decoded_VarBind) ([]SNMP_Packet_V2_Decoded_VarBind, error)
Creates SNMP OctetString.
func SetSNMPVar_OctetString(str string) SNMPVar
Creates SNMP Integer.
func SetSNMPVar_Int(ival int32) SNMPVar
Creates SNMP IpAddress.
func SetSNMPVar_IpAddr(ipval net.IP) (SNMPVar, error)
Converts OID string to BER-encoded int array.
func Convert_OID_StringToIntArray(OIDStr string) ([]int, error)
Example:
oid, err := snmp.Convert_OID_StringToIntArray("1.3.6.1.2.1.1.1.0")
// oid = []int{43, 6, 1, 2, 1, 1, 1, 0} (BER encoded)
Converts OID string to "raw" int array (without BER encoding). Use for convert string OID to []int for us in the Get/Set/Walk function
func ParseOID(OIDStr string) ([]int, error)
Example:
oid, err := snmp.ParseOID("1.3.6.1.2.1.1.1.0")
// oid = []int{1, 3, 6, 1, 2, 1, 1, 1, 0}
Converts int array back to OID string.
func Convert_OID_IntArrayToString_RAW(OIDIntArray []int) string
Example:
str := snmp.Convert_OID_IntArrayToString_RAW([]int{1, 3, 6, 1, 2, 1, 1, 1, 0})
// str = "1.3.6.1.2.1.1.1.0"
Converts SNMPVar to human-readable string.
func Convert_Variable_To_String(Var SNMPVar) string
Automatically detects type and formats:
- OctetString → string
- Integer → number
- Counter32/64, Gauge32 → number
- TimeTicks → number
- IpAddress → "x.x.x.x"
- OID → "1.3.6.1..."
Returns ASN.1/SNMP type name.
func Convert_ClassTag_to_String(Var SNMPVar) string
Example:
typeStr := snmp.Convert_ClassTag_to_String(varbind.RSnmpVar)
// "Universal OCTET STRING", "Application COUNTER32", etc.
Functions for identifying SNMP variable types. Return true if the variable matches the specified ASN.1 type.
Checks if SNMPVar is Universal Class BOOLEAN (Tag 1).
func IsBoolean(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=1 (0x01)
Example:
if snmp.IsBoolean(varbind.RSnmpVar) {
fmt.Println("Boolean value:", varbind.RSnmpVar.Value[0] != 0)
}
Checks if SNMPVar is Universal Class INTEGER (Tag 2).
func IsInteger(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=2 (0x02)
Example:
if snmp.IsInteger(varbind.RSnmpVar) {
value := snmp.Convert_snmpint_to_int32(varbind.RSnmpVar.Value)
fmt.Println("Integer:", value)
}
Checks if SNMPVar is Universal Class BIT STRING (Tag 3).
func IsBitstring(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=3 (0x03)
Checks if SNMPVar is Universal Class OCTET STRING (Tag 4).
func IsOctetString(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=4 (0x04)
Used in: sysName, sysLocation
Example:
if snmp.IsOctetString(varbind.RSnmpVar) {
str := string(varbind.RSnmpVar.Value)
fmt.Println("String:", str)
}
Checks if SNMPVar is Universal Class NULL (Tag 5).
func IsNull(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=5 (0x05)
Example:
if snmp.IsNull(varbind.RSnmpVar) {
fmt.Println("NULL value")
}
Checks if SNMPVar is Universal Class OID (Tag 6).
func IsOid(Val SNMPVar) bool
Matches: Class=0, Constructed=0, Tag=6 (0x06)
Used in: OID value processing
Example:
if snmp.IsOid(varbind.RSnmpVar) {
oidArray := snmp.Convert_bytearray_to_intarray(varbind.RSnmpVar.Value)
oidStr := snmp.Convert_OID_IntArrayToString_DER(oidArray)
fmt.Println("OID:", oidStr)
}
Checks if SNMPVar is Application Class IPADDRESS (Tag 0).
func IsIpaddr(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=0 (0x40)
Used in: ipAdEntAddr, IP address fields
Checks if SNMPVar is Application Class Counter32 (Tag 1).
func IsCounter32(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=1 (0x41)
Example:
if snmp.IsCounter32(varbind.RSnmpVar) {
counter := snmp.Convert_snmpint_to_int32(varbind.RSnmpVar.Value)
fmt.Printf("Counter32: %d\n", counter)
}
Checks if SNMPVar is Application Class Gauge32 (Tag 2).
func IsGauge32(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=2 (0x42)
Example:
if snmp.IsGauge32(varbind.RSnmpVar) {
gauge := snmp.Convert_snmpint_to_int32(varbind.RSnmpVar.Value)
fmt.Printf("Gauge32: %d\n", gauge)
}
Checks if SNMPVar is Application Class Timeticks (Tag 3).
func IsTimetick(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=3 (0x43)
Used in: sysUpTime.0, time measurements
Example:
if snmp.IsTimetick(varbind.RSnmpVar) {
ticks := snmp.Convert_bytearray_to_int(varbind.RSnmpVar.Value)
duration := time.Duration(ticks * 10 * time.Millisecond)
fmt.Println("Uptime:", duration.String())
}
Checks if SNMPVar is Application Class Opaque (Tag 4).
func IsOpaque(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=4 (0x44)
Used in: Binary data, vendor-specific encodings
Example:
if snmp.IsOpaque(varbind.RSnmpVar) {
hexStr := hex.EncodeToString(varbind.RSnmpVar.Value)
fmt.Println("Opaque (hex):", hexStr)
}
Checks if SNMPVar is Application Class Counter64 (Tag 6).
func IsCounter64(Val SNMPVar) bool
Matches: Class=1, Constructed=0, Tag=6 (0x46)
Used in: high-capacity counters
Example:
if snmp.IsCounter64(varbind.RSnmpVar) {
counter := snmp.Convert_bytearray_to_uint(varbind.RSnmpVar.Value)
fmt.Printf("Counter64: %d\n", counter)
}
Supports simultaneous reception of SNMP v2c and SNMP v3 TRAPs/INFORMs
Quick extraction of username or community from TRAP packet.
func ParseTrapUsername(packet []byte) (version int, username string, v3secdata SNMPv3_SecSeq, err error)
Returns:
version- SNMP version (1 for v2c, 3 for v3)username- username (v3) or community (v2c)v3secdata- v3 security data (EngineID, Boots, Time)err- parsing error
Full TRAP/INFORM parsing with authentication verification and decryption.
func ParseTrapWithCredentials(
SenderIp string,
SenderPort int,
packet []byte,
UserData SNMPTrapParameters,
debuglevel uint8,
) (decodedversion int, messagetype int, decryptedData SNMP_Packet_V2_decoded_PDU, err error)
Parameters:
SenderIp,SenderPort- sender addresspacket- raw UDP packetUserData- credentials for decryptiondebuglevel- debug level
Returns:
decodedversion- SNMP versionmessagetype- type (1=REPORT, 2=TRAP, 3=INFORM)decryptedData- decrypted PDU dataerr- error
Message type constants:
const (
REPORT_MESSAGE = 1
TRAP_MESSAGE = 2
INFORM_MESSAGE = 3
)
Workflow:
After initial packet parsing extracts Username from SNMPv3 header, matching credentials can be looked up in a map by Username for decryption/authentication.
Checks if OID is within subtree.
func InSubTreeCheck(OidMain []int, OidCurrent []int) bool
Example:
main := []int{1, 3, 6, 1, 2, 1}
current := []int{1, 3, 6, 1, 2, 1, 1, 1}
inTree := snmp.InSubTreeCheck(main, current) // true
Converts SNMP error code to text.
func SNMPErrorIntToText(code int) string
Converts PDU error code to text.
func SNMPPDUErrorIntToText(code int) string
The library distinguishes between fatal and partial errors.
Operation completely failed. Results are unavailable.
type SNMPfe_Errors struct {
ErrorStatusRaw int32 // SNMP error code
ErrorIndexRaw int32 // Failed OID index
FailedOID []int // OID that caused error
RequestType uint32 // Request type
}
Examples: network timeout, authentication error, notWritable.
Part of request completed successfully. Results are available if AllOIDsFail=false
type SNMPne_Errors struct {
AllOIDsFail bool
Failedoids []PowerSNMPv3_Errors_FailedOids_Error
}
type PowerSNMPv3_Errors_FailedOids_Error struct {
Failedoid []int // Failed OID
Error_id int // Error code (noSuchInstance, etc.)
}
Examples: noSuchInstance, noSuchObject for some OIDs in GetMulti.
The ParseError function helps determine error type:
func ParseError(err error) (SNMPerr SNMPud_Errors, CommonError error)
Returns:
type SNMPud_Errors struct {
IsFatal bool // true = fatal, false = partial
Oids []SNMPud_OidError // List of failed OIDs
}
type SNMPud_OidError struct {
Failedoid []int // Failed OID
Error_id int32 // Error code
ErrorDescription string // Error description
}
Usage example:
results, err := sess.SNMP_GetMulti(oids)
if err != nil {
snmpErr, commonErr := snmp.ParseError(err)
if commonErr != nil {
// Network error, cryptography, etc.
log.Fatal("Network/system error:", commonErr)
}
if snmpErr.IsFatal {
// Fatal SNMP error
log.Fatal("Fatal SNMP error:", snmpErr.Oids[0].ErrorDescription)
}
// Partial error - results are available!
log.Printf("Partial error, %d OIDs failed:", len(snmpErr.Oids))
for _, oidErr := range snmpErr.Oids {
log.Printf(" - %s: %s",
snmp.Convert_OID_IntArrayToString_RAW(oidErr.Failedoid),
oidErr.ErrorDescription)
}
}
// Process successful results
for _, vb := range results {
// ...
}
Sample code:
fmt.Println("=== MULTI GET ===")
oids := []string{
"1.3.6.1.2.1.1.5.0", // sysName
"1.3.6.1.2.1.1.6.0", // sysLocation
"1.3.6.1.2.1.1.99.0", // invalid oid (will be noSuchObject)
"1.3.6.1.2.1.1.100.0", // invalid oid (will be noSuchObject)
}
result, err := params.Get(oids)
fmt.Println("--- Check errors ---")
if err != nil {
fmt.Printf("MultiGet ERROR: %v\n", err)
} else {
fmt.Println("- NO Errors -")
fmt.Println("--- Print result ---")
for i, pdu := range result.Variables {
fmt.Printf("OID[%d]: %s = ", i, pdu.Name)
switch pdu.Type {
case gosnmp.OctetString:
fmt.Printf("%s\n", string(pdu.Value.([]byte)))
case gosnmp.NoSuchObject:
fmt.Printf("NoSuchObject\n")
default:
fmt.Printf("%v (%s)\n", pdu.Value, pdu.Type)
}
}
}
fmt.Println("=== MULTI SET ===")
vars := []gosnmp.SnmpPDU{
{
Name: "1.3.6.1.2.1.1.6.0", // sysLocation
Type: gosnmp.OctetString,
Value: []byte("Test from gosnmp"),
},
{
Name: "1.3.6.1.2.1.1.99.0", // Wrong OID
Type: gosnmp.OctetString,
Value: []byte("Should fail"),
},
}
resultSet, err := params.Set(vars)
fmt.Println("--- Check errors ---")
if err != nil {
fmt.Printf("MultiSet ERROR: %v\n", err)
} else {
fmt.Println("- NO Errors -")
fmt.Println("--- Print result ---")
for i, pdu := range resultSet.Variables {
fmt.Printf("OID[%d]: %s = ", i, pdu.Name)
switch pdu.Type {
case gosnmp.OctetString:
fmt.Printf("%s\n", string(pdu.Value.([]byte)))
case gosnmp.NoSuchObject:
fmt.Printf("NoSuchObject\n")
default:
fmt.Printf("%v (%s)\n", pdu.Value, pdu.Type)
}
}
}
fmt.Println("explicit check errors (resultSet.Error,resultSet.ErrorIndex)", resultSet.Error, resultSet.ErrorIndex)
fmt.Println("=== Get 1.3.6.1.2.1.1.6.0 ===")
oids = []string{
"1.3.6.1.2.1.1.6.0", // sysLocation
}
result, err = params.Get(oids)
if err != nil {
fmt.Printf("Get ERROR: %v\n", err)
} else {
for i, pdu := range result.Variables {
fmt.Println("- NO Errors -")
fmt.Println("--- Print result ---")
fmt.Printf("OID[%d]: %s = ", i, pdu.Name)
switch pdu.Type {
case gosnmp.OctetString:
fmt.Printf("%s\n", string(pdu.Value.([]byte)))
case gosnmp.NoSuchObject:
fmt.Printf("NoSuchObject\n")
default:
fmt.Printf("%v (%s)\n", pdu.Value, pdu.Type)
}
}
}
Result:
=== MULTI GET ===
--- Check errors ---
- NO Errors -
--- Print result ---
OID[0]: .1.3.6.1.2.1.1.5.0 = powercsw01.powerc
OID[1]: .1.3.6.1.2.1.1.6.0 = Test 6.0
OID[2]: .1.3.6.1.2.1.1.99.0 = NoSuchObject
OID[3]: .1.3.6.1.2.1.1.100.0 = NoSuchObject
=== MULTI SET ===
--- Check errors ---
- NO Errors -
--- Print result ---
OID[0]: .1.3.6.1.2.1.1.6.0 = Test from gosnmp
OID[1]: .1.3.6.1.2.1.1.99.0 = Should fail
explicit check errors (resultSet.Error,resultSet.ErrorIndex) NoCreation 2
=== Get 1.3.6.1.2.1.1.6.0 ===
- NO Errors -
--- Print result ---
OID[0]: .1.3.6.1.2.1.1.6.0 = Test 6.0
Sample code:
fmt.Println("=== MULTI GET ===")
OidsStrings := []string{"1.3.6.1.2.1.1.6.0", "1.3.6.1.2.1.1.99.0", "1.3.6.1.2.1.1.5.0", "1.3.6.1.2.1.1.100.0"}
OidsConverted := []PowerSNMP.SNMP_Packet_V2_Decoded_VarBind{}
for _, OidSting := range OidsStrings {
Ioid, IoidErr := PowerSNMP.Convert_OID_StringToIntArray_RAW(OidSting)
if IoidErr != nil {
fmt.Println(IoidErr)
return
}
OidsConverted = append(OidsConverted, PowerSNMP.SNMP_Packet_V2_Decoded_VarBind{Ioid, PowerSNMP.SNMPvbNullValue})
}
GetRes2, verr2 := Ssess.SNMP_GetMulti(OidsConverted)
fmt.Println("--- Check errors ---")
if verr2 != nil {
snmpErr, commonErr := PowerSNMP.ParseError(verr2)
if commonErr != nil {
// Network error, cryptography, etc.
fmt.Println("Network/system error:", commonErr)
}
if snmpErr.IsFatal {
// Fatal SNMP error
fmt.Println("Fatal SNMP error, results are not available")
for _, descr := range snmpErr.Oids {
fmt.Println("Error description:", descr.ErrorDescription, descr)
}
}
// Partial error - results are available!
fmt.Printf("Partial error, %d OIDs failed:", len(snmpErr.Oids))
for _, oidErr := range snmpErr.Oids {
fmt.Printf(" | %s", oidErr.ErrorDescription)
}
fmt.Println("")
} else {
fmt.Println("- NO Errors -")
}
fmt.Println("--- Print result ---")
for _, wl := range GetRes2 {
fmt.Println(PowerSNMP.Convert_OID_IntArrayToString_RAW(wl.RSnmpOID), "=", PowerSNMP.Convert_Variable_To_String(wl.RSnmpVar), ":", PowerSNMP.Convert_ClassTag_to_String(wl.RSnmpVar))
}
fmt.Println("=== MULTI SET ===")
VarData := []PowerSNMP.SNMPVar{PowerSNMP.SetSNMPVar_OctetString("Test 6.0"), PowerSNMP.SetSNMPVar_OctetString("Test 99.0"), PowerSNMP.SetSNMPVar_OctetString("Test 5.0")}
SetStringOids := []string{"1.3.6.1.2.1.1.6.0", "1.3.6.1.2.1.1.99.0", "1.3.6.1.2.1.1.5.0"}
SetDataVB := []PowerSNMP.SNMP_Packet_V2_Decoded_VarBind{}
if len(VarData) == len(SetStringOids) {
for VdataInd, StoidS := range SetStringOids {
IoidS, IoidErrS := PowerSNMP.Convert_OID_StringToIntArray_RAW(StoidS)
if IoidErrS != nil {
fmt.Println(IoidErrS)
return
}
SetDataVB = append(SetDataVB, PowerSNMP.SNMP_Packet_V2_Decoded_VarBind{IoidS, VarData[VdataInd]})
}
} else {
fmt.Println("Mismatch oids and data")
return
}
sdata, verres3 := Ssess.SNMP_SetMulti(SetDataVB)
fmt.Println("--- Check errors ---")
if verres3 != nil {
snmpErr, commonErr := PowerSNMP.ParseError(verres3)
if commonErr != nil {
// Network error, cryptography, etc.
fmt.Println("Network/system error:", commonErr)
}
if snmpErr.IsFatal {
// Fatal SNMP error
fmt.Println("Fatal SNMP error, results are not available")
for _, descr := range snmpErr.Oids {
fmt.Println("Error description:", descr.ErrorDescription)
}
} else {
// Partial error - results are available!
fmt.Printf("Partial error, %d OIDs failed:", len(snmpErr.Oids))
for _, oidErr := range snmpErr.Oids {
fmt.Printf(" | %s", oidErr.ErrorDescription)
}
}
fmt.Println("")
} else {
fmt.Println("- NO Errors -")
}
fmt.Println("--- Print result ---")
for _, wl := range sdata {
fmt.Println(PowerSNMP.Convert_OID_IntArrayToString_RAW(wl.RSnmpOID), "=", PowerSNMP.Convert_Variable_To_String(wl.RSnmpVar), ":", PowerSNMP.Convert_ClassTag_to_String(wl.RSnmpVar))
}
Result:
=== MULTI GET ===
--- Check errors ---
Partial error, 2 OIDs failed: | 1.3.6.1.2.1.1.99.0 (status=128): NoSuchObject | 1.3.6.1.2.1.1.100.0 (status=128): NoSuchObject
--- Print result ---
1.3.6.1.2.1.1.6.0 = Test 6.0 : Universal OCTET STRING
1.3.6.1.2.1.1.5.0 = powercsw01.powerc : Universal OCTET STRING
=== MULTI SET ===
--- Check errors ---
Fatal SNMP error, results are not available
Error description: 1.3.6.1.2.1.1.99.0 (status=11): CannotCreateVariable
--- Print result ---
In Get operation:
-
gosnmp returns all VarBinds in result and the user must check for noSuchObject manually
-
PowerSNMPv3 returns a partial error which contains information about which OIDs are noSuchObject and returns result without failed OIDs
In Set operation:
-
gosnmp returns result with VarBinds which it tried to set and no error Result may be incorrectly interpreted by user. User must explicitly check resultSet.Error,resultSet.ErrorIndex to detect error
-
PowerSNMPv3 returns a fatal error which contains the first failed OID and type of error and no result. It is really a fatal error, because the SET operation is atomic
Command-line utility for SNMP Walk.
# SNMPv3 Walk
go run cmd/SNMPWalk.go -h 192.168.1.1 -v 3 \
-u snmpuser -a sha -A authpass -x aes -X privpass \
-o 1.3.6.1.2.1.2.2.1.2
# SNMPv3 BulkWalk
go run cmd/SNMPWalk.go -h 192.168.1.1 -v 3 \
-u snmpuser -a sha -A authpass -x aes -X privpass \
-o 1.3.6.1.2.1.2.2.1.2 -bulk
# SNMPv2c Walk
go run cmd/SNMPWalk.go -h 192.168.1.1 -v 2 -c public \
-o 1.3.6.1.2.1.2.2.1.2TRAP and INFORM message receiver with SNMPv3 support.
// Configure credentials
Userv3Map := make(map[string]*snmp.SNMPTrapParameters)
Userv3Map["snmpuser"] = &snmp.SNMPTrapParameters{
Username: "snmpuser",
AuthProtocol: "sha",
AuthKey: "authpass123",
PrivProtocol: "aes",
PrivKey: "privpass123",
}
// Receive packet
conn, _ := net.ListenPacket("udp", ":162")
n, addr, _ := conn.ReadFrom(buff)
// Parse
version, msgtype, data, err := snmp.ParseTrapWithCredentials(
addr.IP.String(), addr.Port, buff[:n], credentials, 0)
Testing with net-snmp:
# SNMPv3 INFORM
snmpinform -v 3 -u snmpuser -a sha -A authpass123 \
-l authPriv -x aes -X privpass123 \
-e 0x80001f8880f7996d5a41965d69 192.168.1.100 42 coldStart.0
# SNMPv2c INFORM
snmpinform -v 2c -c public 192.168.1.100 42 coldStart.0
const (
DESCRIPTION_OID = "1.3.6.1.2.1.1.1.0"
LOCATION_OID = "1.3.6.1.2.1.1.6.0"
NAME_OID = "1.3.6.1.2.1.1.5.0"
)
func GetSwData(SNMPsession *PowerSNMPv3.SNMPv3Session) (SwDescription string, SwName string, SwLocation string, err error) {
SwNm := ""
SwDsc := ""
SwLoc := ""
if !SNMPsession.SNMPparams.DiscoveredEngineId.Load() && SNMPsession.SNMPparams.SNMPversion == 3 {
return SwDsc, SwNm, SwLoc, errors.New(DISCOVERY_ERR)
}
seqoid := [3]string{NAME_OID, DESCRIPTION_OID, LOCATION_OID}
seoidi := []PowerSNMPv3.SNMP_Packet_V2_Decoded_VarBind{}
for _, so := range seqoid {
ioc, oierr := PowerSNMPv3.ParseOID(so)
if oierr != nil {
return SwDsc, SwNm, SwLoc, oierr
}
seoidi = append(seoidi, PowerSNMPv3.SNMP_Packet_V2_Decoded_VarBind{ioc, PowerSNMPv3.SNMPvbNullValue})
}
SwdataGm, SwdataGerr := SNMPsession.SNMP_GetMulti(seoidi)
if SwdataGerr != nil {
pe, ce := PowerSNMPv3.ParseError(SwdataGerr)
if ce != nil {
return SwDsc, SwNm, SwLoc, ce
}
if pe.IsFatal {
return SwDsc, SwNm, SwLoc, SwdataGerr
}
}
for _, rdata := range SwdataGm {
if PowerSNMPv3.IsOctetString(rdata.RSnmpVar) && len(seoidi) == 3 {
if slices.Equal(rdata.RSnmpOID, seoidi[0].RSnmpOID) {
SwNm = string(rdata.RSnmpVar.Value)
}
if slices.Equal(rdata.RSnmpOID, seoidi[1].RSnmpOID) {
SwDsc = string(rdata.RSnmpVar.Value)
}
if slices.Equal(rdata.RSnmpOID, seoidi[2].RSnmpOID) {
SwLoc = string(rdata.RSnmpVar.Value)
}
}
}
return SwDsc, SwNm, SwLoc, nil
}
Full testing requires a real SNMP device (switch, router).
go test -v -args \
-h <DEVICE_IP> \
-u <SNMP_USER> \
-a <AUTH_PROTO> \
-A <AUTH_PASS> \
-x <PRIV_PROTO> \
-X <PRIV_PASS> \
-c <COMMUNITY>Example:
go test -v -args -h 192.168.1.252 -u SNMPv3-U -a sha -A test123 -x aes -X test321 -c private123-
Unit tests (no device required):
TestConvert_Variable_To_String- value conversionTestConvert_ClassTag_to_String- type detectionTestConvert_bytearray_to_int- number conversionTestConvert_bytearray_to_uint- unsigned conversionTestCovert_OID_IntArrayToString- OID conversionTestCovert_OID_IntArrayToString_RAW- RAW OID conversionTestConvert_bytearray_to_intarray_with_multibyte_data- multibyte OIDTestConvert_snmpint_to_int32- SNMP int32TestConvert_snmpint_to_uint32- SNMP uint32TestConvert_Covert_OID_StringToIntArray- string to OIDTest_PKCS5Padding- PKCS5 padding/unpadding
-
Integration tests (device required):
TestSNMPv3Session_SNMP_Get_Set_Walk:- SNMPv2c GET (read sysLocation)
- SNMPv2c SET (write sysLocation)
- SNMPv3 initialization with Engine Discovery
- SNMPv3 GET (read sysLocation)
- SNMPv3 SET (write sysLocation)
- V2→V3 verification (check that v2 SET is visible from v3)
- SNMPv3 Walk (ifDescr)
- SNMPv3 BulkWalk (ifDescr)
=== RUN TestConvert_Variable_To_String
--- PASS: TestConvert_Variable_To_String (0.00s)
...
=== RUN TestSNMPv3Session_SNMP_Get_Set_Walk
1.3.6.1.2.1.1.6.0 = Test location : Universal OCTET STRING
snmpfunc_test.go:55: 59 <nil>
1.3.6.1.2.1.1.6.0 = Test location from V2 : Universal OCTET STRING
snmpfunc_test.go:88: 67 <nil>
snmpfunc_test.go:93: V2→V3 VERIFICATION PASS! 'Test location from V2'
1.3.6.1.2.1.2.2.1.2.1 = Vlan1 : Universal OCTET STRING
...
--- PASS: TestSNMPv3Session_SNMP_Get_Set_Walk (0.75s)
PASS
ok github.com/OlegPowerC/powersnmpv3 2.273s
| Constant | Value | String |
|---|---|---|
| AUTH_PROTOCOL_NONE | 0 | - |
| AUTH_PROTOCOL_MD5 | 1 | "md5" |
| AUTH_PROTOCOL_SHA | 2 | "sha" |
| AUTH_PROTOCOL_SHA224 | 3 | "sha224" |
| AUTH_PROTOCOL_SHA256 | 4 | "sha256" |
| AUTH_PROTOCOL_SHA384 | 5 | "sha384" |
| AUTH_PROTOCOL_SHA512 | 6 | "sha512" |
| Constant | Value | String |
|---|---|---|
| PRIV_PROTOCOL_NONE | 0 | - |
| PRIV_PROTOCOL_DES | 2 | "des" |
| PRIV_PROTOCOL_AES128 | 1 | "aes" |
| PRIV_PROTOCOL_AES192 | 3 | "aes192" |
| PRIV_PROTOCOL_AES256 | 4 | "aes256" |
| PRIV_PROTOCOL_AES192A | 5 | "aes192a" (AGENT++) |
| PRIV_PROTOCOL_AES256A | 6 | "aes256a" (AGENT++) |
| Constant | Value | Description |
|---|---|---|
| SNMP_MAXIMUMWALK | 1000000 | Max Walk entries |
| SNMP_BUFFERSIZE | 2048 | UDP buffer size |
| SNMP_MAXTIMEOUT_MS | 1000 | Max timeout |
| SNMP_DEFAULTTIMEOUT_MS | 300 | Default timeout |
| SNMP_MAXIMUM_RETRY | 10 | Max retries |
| SNMP_DEFAULTRETRY | 3 | Default retries |
| SNMP_MAXREPETITION | 80 | Max repetitions |
| SNMP_DEFAULTREPETITION | 25 | Default repetitions |
github.com/OlegPowerC/asn1modsnmp- fork of standardencoding/asn1with SNMP optimizations:ShortLengthAllow- support for short-length encoding (some switches, e.g., Moxa) and indefinite length (include nested)FindSNMPv3AuthParamsOffset()- find AuthParams position for USM verificationExtractDataWOTagAndLen()- extract data without TLV wrapper for cryptography
Benchmarked on 15,381 OIDs (SNMPv3 AES128+SHA, 1Gbps network, Cisco S8000v target):
| Implementation | Time | Throughput | Memory |
|---|---|---|---|
| PowerSNMPv3 (Channel) ⭐ | 4.02s | 3,827 OID/s | 12.4 MB |
| PowerSNMPv3 (Callback) | 4.46s | 3,449 OID/s | 8.8 MB |
| gosnmp | 4.43s | 3,472 OID/s | 10.0 MB |
| net-snmp (C) | 6.43s | 2,393 OID/s | 8.8 MB |
| snmp4j (Java) | 8.27s | 1,860 OID/s | 145.6 MB |
PowerSNMP is 37% faster than net-snmp and uses 12x less memory than snmp4j.
- Channel (
SNMP_BulkWalk_WChan): Maximum speed for large tables (5000+ OIDs) - Callback (
SNMP_BulkWalk_WCallback): Memory-efficient for scripts (100-5000 OIDs) - Sync (
SNMP_Walk): Debugging and small queries (<100 OIDs)
MIT License
Commercial version with extended support available.
Contact: oleg.powerc@gmail.com
SNMPv2 buffers:
The RX buffer size is now fixed at 65535 bytes, while the TX buffer defaults to 1500 bytes (can be modified via a constant).
SNMPv3 buffers:
The RX buffer size now dynamically matches the MaxMsgSize value negotiated with the agent.
Dynamic discovery:
During the initial exchange, the library automatically discovers the agent’s MaxMsgSize and adjusts the session configuration. Subsequent messages are validated to ensure they do not exceed this limit.
These changes make message exchange more robust across different devices and agents, especially those with non‑standard or limited buffer sizes. They also reduce the risk of transmission errors and improve performance when communicating with high‑capacity agents. Impact
Better interoperability with diverse SNMP implementations.
Automatic buffer sizing for SNMPv3.
Configurable TX buffer for SNMPv2 to match specific deployment needs.
Key Improvement
- Discovery Agent EngineID: Now performed in main get/set functions when proper REPORT is received
- Automatic EngineID update without additional calls
PowerSNMPv3 provides RFC 3414-compliant handling of security level mismatches with optimal network efficiency and zero unnecessary retries.
The behavior comparison was conducted using simple SNMP Walk operations against the same SNMP agent with identical misconfigurations. Test scenario: client configured with authentication (authNoPriv or authPriv), agent has the same username configured with noAuthNoPriv (no authentication). This represents a common misconfiguration in production environments.
All three libraries (PowerSNMPv3, SNMP4J, gosnmp) were tested with identical parameters, and network traffic was captured via PCAP for analysis.
Security level mismatches occur when the client is configured with authentication/encryption (authNoPriv or authPriv) but the agent has the same username configured with a different security level (e.g., noAuthNoPriv). According to RFC 3414 §3.2, the agent responds with a REPORT PDU containing usmStatsUnsupportedSecLevels (OID 1.3.6.1.6.3.15.1.1.1.0). Critically, the REPORT is sent without authentication or encryption, even if the request was authenticated/encrypted.
Client → Agent: GetRequest (EngineID discovery, noAuthNoPriv)
Agent → Client: Report (usmStatsUnknownEngineIDs)
Returns: EngineID, Boots, Time
Client → Agent: GetRequest/GetBulk
Flags: 0x05 (Auth, Reportable) or 0x07 (Auth, Priv, Reportable)
Auth Params: HMAC digest present
Priv Params: AES/DES IV (if authPriv)
Agent → Client: Report
Flags: 0x00 (None) ← NO authentication!
Report OID: 1.3.6.1.6.3.15.1.1.1.0 (usmStatsUnsupportedSecLevels)
Counter: Increments on each mismatch
PowerSNMPv3 Response:
✓ Accepts REPORT (checks packet flags, not client config)
✓ Parses report OID
✓ Returns error: "unsupported security levels"
✓ NO RETRY - immediate error return
PowerSNMPv3 correctly implements RFC 3414 requirements:
- Accepts REPORT PDUs without authentication - Even when client expects authenticated responses, REPORT PDUs with msgFlags=0x00 are accepted as valid error indications
- No retry on configuration errors - UnsupportedSecLevels is a non-recoverable configuration error; retrying with the same credentials won't help
- Checks packet flags, not client config - Authentication verification is based on actual packet flags, not expected client configuration
PowerSNMPv3 distinguishes between recoverable and non-recoverable errors:
Recoverable (auto-retry with recovery):
usmStatsNotInTimeWindows- Syncs time, then retriesusmStatsUnknownEngineIDs- Performs discovery with key re-localization, then retries
Non-recoverable (immediate error return):
usmStatsUnsupportedSecLevels- Configuration mismatchusmStatsUnknownUserNames- Unknown userusmStatsWrongDigests- Wrong passwordusmStatsDecryptionErrors- Wrong encryption key
| Library | Total Packets | Retry Count | RFC 3414 Compliant | Automatic REPORT Handling |
|---|---|---|---|---|
| PowerSNMPv3 | 4 | 0 | ✅ | ✅ Yes |
| SNMP4J | 6 | 1 | ✅ | ❌ Manual |
| gosnmp | 10 | 3 | ❌ |
Network efficiency: PowerSNMPv3 typically uses 60% fewer packets than gosnmp and 33% fewer than SNMP4J when handling misconfigurations.
PowerSNMPv3:
Error: unsupported security levels
✅ Clear, actionable error message
✅ Immediate feedback
✅ Obvious what to fix (check security level configuration)
gosnmp:
Walk Error: incoming packet is not authentic, discarding
❌ Confusing error message - suggests authentication problem, not configuration mismatch
❌ Multiple retries before error
❌ Unclear root cause - could be wrong password, wrong protocol, or security level mismatch
SNMP4J (without manual PDU type check):
1.3.6.1.6.3.15.1.1.1.0 = 17
1.3.6.1.6.3.15.1.1.1.0 = 18
❌ Returns REPORT OID and counter as data
❌ Requires manual PDU type checking: if (response.getType() == PDU.REPORT)
❌ User must interpret OID meaning and handle appropriately