fix(inputs.snmp_trap): Copy GoSNMP global defaults to prevent side-effects (#13542)

This commit is contained in:
mark-chandler 2023-07-06 00:10:19 +10:00 committed by GitHub
parent 77b153e50f
commit ae163536e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 402 additions and 4 deletions

2
go.mod
View File

@ -94,7 +94,7 @@ require (
github.com/gophercloud/gophercloud v1.2.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/gosnmp/gosnmp v1.35.0
github.com/gosnmp/gosnmp v1.35.1-0.20230602062452-f30602b8dad6
github.com/grid-x/modbus v0.0.0-20211113184042-7f2251c342c9
github.com/gwos/tcg/sdk v0.0.0-20220621192633-df0eac0a1a4c
github.com/harlow/kinesis-consumer v0.3.6-0.20211204214318-c2b9f79d7ab6

4
go.sum
View File

@ -761,8 +761,8 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosnmp/gosnmp v1.35.0 h1:EuWWNPxTCdAUx2/NbQcSa3WdNxjzpy4Phv57b4MWpJM=
github.com/gosnmp/gosnmp v1.35.0/go.mod h1:2AvKZ3n9aEl5TJEo/fFmf/FGO4Nj4cVeEc5yuk88CYc=
github.com/gosnmp/gosnmp v1.35.1-0.20230602062452-f30602b8dad6 h1:pzIZ9ij5bf6vL8aSAoFoksiT7pZXyzBOhDdRlZUT89Q=
github.com/gosnmp/gosnmp v1.35.1-0.20230602062452-f30602b8dad6/go.mod h1:V8wQurBU216WENrmFCZVFV4bVcMWIb9ZmPVI5PoH80A=
github.com/grid-x/modbus v0.0.0-20211113184042-7f2251c342c9 h1:Q7e9kXS3sRbTjsNDKazbcbDSGAKjFdk096M3qYbwNpE=
github.com/grid-x/modbus v0.0.0-20211113184042-7f2251c342c9/go.mod h1:qVX2WhsI5xyAoM6I/MV1bXSKBPdLAjp7pCvieO/S0AY=
github.com/grid-x/serial v0.0.0-20191104121038-e24bc9bf6f08/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk=

View File

@ -26,6 +26,18 @@ type translator interface {
lookup(oid string) (snmp.MibEntry, error)
}
type wrapLog struct {
telegraf.Logger
}
func (l wrapLog) Printf(format string, args ...interface{}) {
l.Debugf(format, args...)
}
func (l wrapLog) Print(args ...interface{}) {
l.Debug(args...)
}
type SnmpTrap struct {
ServiceAddress string `toml:"service_address"`
Timeout config.Duration `toml:"timeout" deprecated:"1.20.0;unused option"`
@ -104,7 +116,12 @@ func (s *SnmpTrap) Start(acc telegraf.Accumulator) error {
s.acc = acc
s.listener = gosnmp.NewTrapListener()
s.listener.OnNewTrap = makeTrapHandler(s)
s.listener.Params = gosnmp.Default
// gosnmp.Default is a pointer, using this more than once
// has side effects
defaults := *gosnmp.Default
s.listener.Params = &defaults
s.listener.Params.Logger = gosnmp.NewLogger(wrapLog{s.Log})
switch s.Version {
case "3":

View File

@ -1319,3 +1319,384 @@ func TestReceiveTrap(t *testing.T) {
})
}
}
func TestReceiveTrapMultipleConfig(t *testing.T) {
now := uint32(123123123)
fakeTime := time.Unix(456456456, 456)
// If the first pdu isn't type TimeTicks, gosnmp.SendTrap() will
// prepend one with time.Now()
var tests = []struct {
name string
// send
version gosnmp.SnmpVersion
trap gosnmp.SnmpTrap // include pdus
// V3 auth and priv parameters
secName string // v3 username
secLevel string // v3 security level
authProto string // Auth protocol: "", MD5 or SHA
authPass string // Auth passphrase
privProto string // Priv protocol: "", DES or AES
privPass string // Priv passphrase
// V3 sender context
contextName string
engineID string
// receive
entries []entry
metrics []telegraf.Metric
}{
//ordinary v3 coldStart SHA trap auth and AES priv
{
name: "v3 coldStart authSHAPrivAES",
version: gosnmp.Version3,
secName: "authSHAPrivAES",
secLevel: "authPriv",
authProto: "SHA",
authPass: "passpass",
privProto: "AES",
privPass: "passpass",
trap: gosnmp.SnmpTrap{
Variables: []gosnmp.SnmpPDU{
{
Name: ".1.3.6.1.2.1.1.3.0",
Type: gosnmp.TimeTicks,
Value: now,
},
{
Name: ".1.3.6.1.6.3.1.1.4.1.0", // SNMPv2-MIB::snmpTrapOID.0
Type: gosnmp.ObjectIdentifier,
Value: ".1.3.6.1.6.3.1.1.5.1", // coldStart
},
},
},
entries: []entry{
{
oid: ".1.3.6.1.6.3.1.1.4.1.0",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "snmpTrapOID.0",
},
},
{
oid: ".1.3.6.1.6.3.1.1.5.1",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "coldStart",
},
},
{
oid: ".1.3.6.1.2.1.1.3.0",
e: snmp.MibEntry{
MibName: "UNUSED_MIB_NAME",
OidText: "sysUpTimeInstance",
},
},
},
metrics: []telegraf.Metric{
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
),
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
),
},
},
//ordinary v3 coldStart SHA trap auth and AES256 priv
{
name: "v3 coldStart authSHAPrivAES256",
version: gosnmp.Version3,
secName: "authSHAPrivAES256",
secLevel: "authPriv",
authProto: "SHA",
authPass: "passpass",
privProto: "AES256",
privPass: "passpass",
trap: gosnmp.SnmpTrap{
Variables: []gosnmp.SnmpPDU{
{
Name: ".1.3.6.1.2.1.1.3.0",
Type: gosnmp.TimeTicks,
Value: now,
},
{
Name: ".1.3.6.1.6.3.1.1.4.1.0", // SNMPv2-MIB::snmpTrapOID.0
Type: gosnmp.ObjectIdentifier,
Value: ".1.3.6.1.6.3.1.1.5.1", // coldStart
},
},
},
entries: []entry{
{
oid: ".1.3.6.1.6.3.1.1.4.1.0",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "snmpTrapOID.0",
},
},
{
oid: ".1.3.6.1.6.3.1.1.5.1",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "coldStart",
},
},
{
oid: ".1.3.6.1.2.1.1.3.0",
e: snmp.MibEntry{
MibName: "UNUSED_MIB_NAME",
OidText: "sysUpTimeInstance",
},
},
},
metrics: []telegraf.Metric{
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
),
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
)},
},
//ordinary v3 coldStart SHA trap auth and AES256C priv
{
name: "v3 coldStart authSHAPrivAES256C",
version: gosnmp.Version3,
secName: "authSHAPrivAES256C",
secLevel: "authPriv",
authProto: "SHA",
authPass: "passpass",
privProto: "AES256C",
privPass: "passpass",
trap: gosnmp.SnmpTrap{
Variables: []gosnmp.SnmpPDU{
{
Name: ".1.3.6.1.2.1.1.3.0",
Type: gosnmp.TimeTicks,
Value: now,
},
{
Name: ".1.3.6.1.6.3.1.1.4.1.0", // SNMPv2-MIB::snmpTrapOID.0
Type: gosnmp.ObjectIdentifier,
Value: ".1.3.6.1.6.3.1.1.5.1", // coldStart
},
},
},
entries: []entry{
{
oid: ".1.3.6.1.6.3.1.1.4.1.0",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "snmpTrapOID.0",
},
},
{
oid: ".1.3.6.1.6.3.1.1.5.1",
e: snmp.MibEntry{
MibName: "SNMPv2-MIB",
OidText: "coldStart",
},
},
{
oid: ".1.3.6.1.2.1.1.3.0",
e: snmp.MibEntry{
MibName: "UNUSED_MIB_NAME",
OidText: "sysUpTimeInstance",
},
},
},
metrics: []telegraf.Metric{
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
),
testutil.MustMetric(
"snmp_trap", // name
map[string]string{ // tags
"oid": ".1.3.6.1.6.3.1.1.5.1",
"name": "coldStart",
"mib": "SNMPv2-MIB",
"version": "3",
"source": "127.0.0.1",
},
map[string]interface{}{ // fields
"sysUpTimeInstance": now,
},
fakeTime,
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// We would prefer to specify port 0 and let the network
// stack choose an unused port for us but TrapListener
// doesn't have a way to return the autoselected port.
// Instead, we'll use an unusual port and hope it's
// unused.
const port1 = 12399
const port2 = 12400
// Hook into the trap handler so the test knows when the
// trap has been received
received1 := make(chan int)
wrap1 := func(f gosnmp.TrapHandlerFunc) gosnmp.TrapHandlerFunc {
return func(p *gosnmp.SnmpPacket, a *net.UDPAddr) {
f(p, a)
received1 <- 0
}
}
// Set up the service input plugin
s1 := &SnmpTrap{
ServiceAddress: "udp://:" + strconv.Itoa(port1),
makeHandlerWrapper: wrap1,
timeFunc: func() time.Time {
return fakeTime
},
//if cold start be answer otherwise err
Log: testutil.Logger{},
Version: tt.version.String(),
SecName: config.NewSecret([]byte(tt.secName + "1")),
SecLevel: tt.secLevel,
AuthProtocol: tt.authProto,
AuthPassword: config.NewSecret([]byte(tt.authPass + "1")),
PrivProtocol: tt.privProto,
PrivPassword: config.NewSecret([]byte(tt.privPass + "1")),
Translator: "netsnmp",
}
// Hook into the trap handler so the test knows when the
// trap has been received
received2 := make(chan int)
wrap2 := func(f gosnmp.TrapHandlerFunc) gosnmp.TrapHandlerFunc {
return func(p *gosnmp.SnmpPacket, a *net.UDPAddr) {
f(p, a)
received2 <- 0
}
}
// Set up a second service input plugin
s2 := &SnmpTrap{
ServiceAddress: "udp://:" + strconv.Itoa(port2),
makeHandlerWrapper: wrap2,
timeFunc: func() time.Time {
return fakeTime
},
//if cold start be answer otherwise err
Log: testutil.Logger{},
Version: tt.version.String(),
SecName: config.NewSecret([]byte(tt.secName + "2")),
SecLevel: tt.secLevel,
AuthProtocol: tt.authProto,
AuthPassword: config.NewSecret([]byte(tt.authPass + "2")),
PrivProtocol: tt.privProto,
PrivPassword: config.NewSecret([]byte(tt.privPass + "2")),
Translator: "netsnmp",
}
require.NoError(t, s1.Init())
require.NoError(t, s2.Init())
//inject test translator
s1.transl = newTestTranslator(tt.entries)
s2.transl = newTestTranslator(tt.entries)
var acc testutil.Accumulator
require.NoError(t, s1.Start(&acc))
require.NoError(t, s2.Start(&acc))
defer s1.Stop()
defer s2.Stop()
var goSNMP1 gosnmp.GoSNMP
var goSNMP2 gosnmp.GoSNMP
if tt.version == gosnmp.Version3 {
msgFlags := newMsgFlagsV3(tt.secLevel)
sp1 := newUsmSecurityParametersForV3(tt.authProto, tt.privProto, tt.secName+"1", tt.privPass+"1", tt.authPass+"1")
sp2 := newUsmSecurityParametersForV3(tt.authProto, tt.privProto, tt.secName+"2", tt.privPass+"2", tt.authPass+"2")
goSNMP1 = newGoSNMPV3(port1, tt.contextName, tt.engineID, msgFlags, sp1)
goSNMP2 = newGoSNMPV3(port2, tt.contextName, tt.engineID, msgFlags, sp2)
} else {
goSNMP1 = newGoSNMP(tt.version, port1)
goSNMP2 = newGoSNMP(tt.version, port2)
}
// Send the trap to both receivers
sendTrap(t, goSNMP1, tt.trap)
sendTrap(t, goSNMP2, tt.trap)
// Wait for trap to be received on receiver1
select {
case <-received1:
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for trap to be received")
}
// Wait for trap to be received on receiver2
select {
case <-received2:
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for trap to be received")
}
// Verify plugin output
testutil.RequireMetricsEqual(t,
tt.metrics, acc.GetTelegrafMetrics(),
testutil.SortMetrics())
})
}
}