Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inputs.snmp): Convert octet string with invalid data to hex #15439

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) (v interface{}, err 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
}
Hipska marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -180,7 +186,29 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (v interface{}, err 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 @@ -234,7 +262,7 @@ func (f *Field) Convert(ent gosnmp.SnmpPDU) (v interface{}, err 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
Loading