Skip to content

Commit

Permalink
feat(inputs.snmp): Convert octet string with invalid data to hex (#15439
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Hipska authored Jun 4, 2024
1 parent c2a67ec commit c1bbce3
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 81 deletions.
32 changes: 30 additions & 2 deletions internal/snmp/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package snmp

import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"net"
"strconv"
"strings"
"unicode/utf8"

"github.com/gosnmp/gosnmp"
)
Expand Down Expand Up @@ -94,6 +96,10 @@ func (f *Field) Init(tr Translator) error {
// fieldConvert converts from any type according to the conv specification
func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
if f.Conversion == "" {
// OctetStrings may contain hex data that needs its own conversion
if ent.Type == gosnmp.OctetString && !utf8.ValidString(string(ent.Value.([]byte)[:])) {
return hex.EncodeToString(ent.Value.([]byte)), nil
}
if bs, ok := ent.Value.([]byte); ok {
return string(bs), nil
}
Expand Down Expand Up @@ -188,7 +194,29 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
case []byte:
v = net.HardwareAddr(vt).String()
default:
return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", v)
return nil, fmt.Errorf("invalid type (%T) for hwaddr conversion", vt)
}
return v, nil
}

if f.Conversion == "hex" {
switch vt := ent.Value.(type) {
case string:
switch ent.Type {
case gosnmp.IPAddress:
ip := net.ParseIP(vt)
if ip4 := ip.To4(); ip4 != nil {
v = hex.EncodeToString(ip4)
} else {
v = hex.EncodeToString(ip)
}
default:
return nil, fmt.Errorf("unsupported Asn1BER (%#v) for hex conversion", ent.Type)
}
case []byte:
v = hex.EncodeToString(vt)
default:
return nil, fmt.Errorf("unsupported type (%T) for hex conversion", vt)
}
return v, nil
}
Expand Down Expand Up @@ -242,7 +270,7 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (interface{}, error) {
case []byte:
ipbs = vt
default:
return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", v)
return nil, fmt.Errorf("invalid type (%T) for ipaddr conversion", vt)
}

switch len(ipbs) {
Expand Down
243 changes: 243 additions & 0 deletions internal/snmp/field_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package snmp

import (
"testing"

"github.com/gosnmp/gosnmp"
"github.com/stretchr/testify/require"
)

func TestConvertDefault(t *testing.T) {
tests := []struct {
name string
ent gosnmp.SnmpPDU
expected interface{}
errmsg string
}{
{
name: "integer",
ent: gosnmp.SnmpPDU{
Type: gosnmp.Integer,
Value: int(2),
},
expected: 2,
},
{
name: "octet string with valid bytes",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64},
},
expected: "Hello world",
},
{
name: "octet string with invalid bytes",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff, 0xfd, 0x38, 0x54, 0xc1},
},
expected: "84c807fffd3854c1",
},
}

f := Field{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := f.Convert(tt.ent)

if tt.errmsg != "" {
require.ErrorContains(t, err, tt.errmsg)
} else {
require.NoError(t, err)
}

require.Equal(t, tt.expected, actual)
})
}

t.Run("invalid", func(t *testing.T) {
f.Conversion = "invalid"
actual, err := f.Convert(gosnmp.SnmpPDU{})

require.Nil(t, actual)
require.ErrorContains(t, err, "invalid conversion type")
})
}

func TestConvertHex(t *testing.T) {
tests := []struct {
name string
ent gosnmp.SnmpPDU
expected interface{}
errmsg string
}{
{
name: "octet string with valid bytes",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64},
},
expected: "48656c6c6f20776f726c64",
},
{
name: "octet string with invalid bytes",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff, 0xfd, 0x38, 0x54, 0xc1},
},
expected: "84c807fffd3854c1",
},
{
name: "IPv4",
ent: gosnmp.SnmpPDU{
Type: gosnmp.IPAddress,
Value: "192.0.2.1",
},
expected: "c0000201",
},
{
name: "IPv6",
ent: gosnmp.SnmpPDU{
Type: gosnmp.IPAddress,
Value: "2001:db8::1",
},
expected: "20010db8000000000000000000000001",
},
{
name: "oid",
ent: gosnmp.SnmpPDU{
Type: gosnmp.ObjectIdentifier,
Value: ".1.2.3",
},
errmsg: "unsupported Asn1BER (0x6) for hex conversion",
},
{
name: "integer",
ent: gosnmp.SnmpPDU{
Type: gosnmp.Integer,
Value: int(2),
},
errmsg: "unsupported type (int) for hex conversion",
},
}

f := Field{Conversion: "hex"}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := f.Convert(tt.ent)

if tt.errmsg != "" {
require.ErrorContains(t, err, tt.errmsg)
} else {
require.NoError(t, err)
}

require.Equal(t, tt.expected, actual)
})
}
}

func TestConvertHextoint(t *testing.T) {
tests := []struct {
name string
conversion string
ent gosnmp.SnmpPDU
expected interface{}
errmsg string
}{
{
name: "empty",
conversion: "hextoint:BigEndian:uint64",
ent: gosnmp.SnmpPDU{},
expected: nil,
},
{
name: "big endian uint64",
conversion: "hextoint:BigEndian:uint64",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff, 0xfd, 0x38, 0x54, 0xc1},
},
expected: uint64(0x84c807fffd3854c1),
},
{
name: "big endian uint32",
conversion: "hextoint:BigEndian:uint32",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff},
},
expected: uint32(0x84c807ff),
},
{
name: "big endian uint16",
conversion: "hextoint:BigEndian:uint16",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8},
},
expected: uint16(0x84c8),
},
{
name: "big endian invalid",
conversion: "hextoint:BigEndian:invalid",
ent: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []uint8{}},
errmsg: "invalid bit value",
},
{
name: "little endian uint64",
conversion: "hextoint:LittleEndian:uint64",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff, 0xfd, 0x38, 0x54, 0xc1},
},
expected: uint64(0xc15438fdff07c884),
},
{
name: "little endian uint32",
conversion: "hextoint:LittleEndian:uint32",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8, 0x7, 0xff},
},
expected: uint32(0xff07c884),
},
{
name: "little endian uint16",
conversion: "hextoint:LittleEndian:uint16",
ent: gosnmp.SnmpPDU{
Type: gosnmp.OctetString,
Value: []byte{0x84, 0xc8},
},
expected: uint16(0xc884),
},
{
name: "little endian invalid",
conversion: "hextoint:LittleEndian:invalid",
ent: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte{}},
errmsg: "invalid bit value",
},
{
name: "invalid",
conversion: "hextoint:invalid:uint64",
ent: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte{}},
errmsg: "invalid Endian value",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := Field{Conversion: tt.conversion}

actual, err := f.Convert(tt.ent)

if tt.errmsg != "" {
require.ErrorContains(t, err, tt.errmsg)
} else {
require.NoError(t, err)
}

require.Equal(t, tt.expected, actual)
})
}
}
7 changes: 0 additions & 7 deletions internal/snmp/translator_gosmi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ func TestFieldConvertGosmi(t *testing.T) {
conv string
expected interface{}
}{
{[]byte("foo"), "", "foo"},
{"0.123", "float", float64(0.123)},
{[]byte("0.123"), "float", float64(0.123)},
{float32(0.123), "float", float64(float32(0.123))},
Expand Down Expand Up @@ -333,12 +332,6 @@ func TestFieldConvertGosmi(t *testing.T) {
{[]byte("abcd"), "ipaddr", "97.98.99.100"},
{"abcd", "ipaddr", "97.98.99.100"},
{[]byte("abcdefghijklmnop"), "ipaddr", "6162:6364:6566:6768:696a:6b6c:6d6e:6f70"},
{[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:BigEndian:uint64", uint64(2602423610063712)},
{[]byte{0x00, 0x09, 0x3E, 0xE3}, "hextoint:BigEndian:uint32", uint32(605923)},
{[]byte{0x00, 0x09}, "hextoint:BigEndian:uint16", uint16(9)},
{[]byte{0x00, 0x09, 0x3E, 0xE3, 0xF6, 0xD5, 0x3B, 0x60}, "hextoint:LittleEndian:uint64", uint64(6934371307618175232)},
{[]byte{0x00, 0x09, 0x3E, 0xE3}, "hextoint:LittleEndian:uint32", uint32(3812493568)},
{[]byte{0x00, 0x09}, "hextoint:LittleEndian:uint16", uint16(2304)},
{3, "enum", "testing"},
{3, "enum(1)", "testing(3)"},
}
Expand Down
Loading

0 comments on commit c1bbce3

Please sign in to comment.