feat(inputs.nsdp): Add plugin (#16392)

This commit is contained in:
Holger 2025-03-05 16:32:56 +01:00 committed by GitHub
parent 2c292b4d66
commit b122159245
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 333 additions and 1 deletions

View File

@ -31,7 +31,6 @@ Pull requests welcome.
- [fritzbox](https://github.com/hdecarne-github/fritzbox-telegraf-plugin) - Gather statistics from [FRITZ!Box](https://avm.de/produkte/fritzbox/) router and repeater
- [linux-psi-telegraf-plugin](https://github.com/gridscale/linux-psi-telegraf-plugin) - Gather pressure stall information ([PSI](https://facebookmicrosites.github.io/psi/)) from the Linux Kernel
- [huebridge](https://github.com/hdecarne-github/huebridge-telegraf-plugin) - Gather smart home statistics from [Hue Bridge](https://www.philips-hue.com/) devices
- [nsdp](https://github.com/hdecarne-github/nsdp-telegraf-plugin) - Gather switch network statistics via [Netgear Switch Discovery Protocol](https://en.wikipedia.org/wiki/Netgear_Switch_Discovery_Protocol)
- [hwinfo](https://github.com/zachstence/hwinfo-telegraf-plugin) - Gather Windows system hardware information from [HWiNFO](https://www.hwinfo.com/)
- [libvirt](https://gitlab.com/warrenio/tools/telegraf-input-libvirt) - Gather libvirt domain stats, based on a historical Telegraf implementation [libvirt](https://libvirt.org/)
- [bacnet](https://github.com/JurajMarcin/telegraf-bacnet) - Gather statistics from BACnet devices, with support for device discovery and Change of Value subscriptions

View File

@ -368,6 +368,7 @@ following works:
- github.com/stoewer/go-strcase [MIT License](https://github.com/stoewer/go-strcase/blob/master/LICENSE)
- github.com/stretchr/objx [MIT License](https://github.com/stretchr/objx/blob/master/LICENSE)
- github.com/stretchr/testify [MIT License](https://github.com/stretchr/testify/blob/master/LICENSE)
- github.com/tdrn-org/go-nsdp [MIT License](https://github.com/tdrn-org/go-nsdp/blob/main/LICENSE)
- github.com/testcontainers/testcontainers-go [MIT License](https://github.com/testcontainers/testcontainers-go/blob/main/LICENSE)
- github.com/thomasklein94/packer-plugin-libvirt [Mozilla Public License 2.0](https://github.com/thomasklein94/packer-plugin-libvirt/blob/main/LICENSE)
- github.com/tidwall/gjson [MIT License](https://github.com/tidwall/gjson/blob/master/LICENSE)

1
go.mod
View File

@ -476,6 +476,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tdrn-org/go-nsdp v0.5.0
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/tinylru v1.2.1 // indirect

2
go.sum
View File

@ -2281,6 +2281,8 @@ github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7 h1:Jtcrb09q0AVWe3
github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62 h1:Oj2e7Sae4XrOsk3ij21QjjEgAcVSeo9nkp0dI//cD2o=
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62/go.mod h1:qUzPVlSj2UgxJkVbH0ZwuuiR46U8RBMDT5KLY78Ifpw=
github.com/tdrn-org/go-nsdp v0.5.0 h1:bOs8qABaP/BSQlWeziZx9gjGkC2ld9UQek9p5w6PvdY=
github.com/tdrn-org/go-nsdp v0.5.0/go.mod h1:zp7CxiCPcyXHo+s6tn+wrNBr1qQe1G/hOh/FybM5xiM=
github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=

View File

@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.nsdp
package all
import _ "github.com/influxdata/telegraf/plugins/inputs/nsdp" // register plugin

View File

@ -0,0 +1,63 @@
# Netgear Switch Discovery Protocol Input Plugin
This plugin gathers metrics from devices via
[Netgear Switch Discovery Protocol (NSDP)][nsdp]
for all available switches and ports.
⭐ Telegraf v1.34.0
🏷️ network
💻 all
[nsdp]: https://en.wikipedia.org/wiki/Netgear_Switch_Discovery_Protocol
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Gather Netgear Switch Discovery Protocol status
[[inputs.nsdp]]
## The target address to use for status gathering. Either Broadcast (default)
## or the address of a single well-known device.
# address = "255.255.255.255:63322"
## The maximum number of device responses to wait for. 0 means no limit.
## NSDP works asynchronously. Without a limit (0) the plugin always waits
## the amount given in timeout for possible responses. By setting this
## option to the known number of devices, the plugin completes
## processing as soon as the last device has answered.
# device_limit = 0
## The maximum duration to wait for device responses.
# timeout = "2s"
```
## Metrics
- `nsdp_device_port`
- tags
- `device` - The device identifier (MAC/HW address)
- `device_ip` - The device's IP address
- `device_name` - The device's name
- `device_model` - The device's model
- `device_port` - The port id the fields are referring to
- fields
- `bytes_sent` (uint) - Number of bytes sent via this port
- `bytes_recv` (uint) - Number of bytes received via this port
- `packets_total` (uint) - Total number of packets processed on this port
- `broadcasts_total` (uint) - Total number of broadcasts processed on this port
- `multicasts_total` (uint) - Total number of multicasts processed on this port
- `errors_total` (uint) - Total number of errors encountered on this port
## Example Output
```text
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=1 broadcasts_total=0u,bytes_recv=3879427866u,bytes_sent=506548796u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014578000
```

134
plugins/inputs/nsdp/nsdp.go Normal file
View File

@ -0,0 +1,134 @@
//go:generate ../../../tools/readme_config_includer/generator
package nsdp
import (
_ "embed"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/tdrn-org/go-nsdp"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type NSDP struct {
Address string `toml:"address"`
DeviceLimit uint `toml:"device_limit"`
Timeout config.Duration `toml:"timeout"`
Log telegraf.Logger `toml:"-"`
conn *nsdp.Conn
}
func (*NSDP) SampleConfig() string {
return sampleConfig
}
func (n *NSDP) Init() error {
if n.Address == "" {
n.Address = nsdp.IPv4BroadcastTarget
}
if n.Timeout <= 0 {
return errors.New("timeout must be greater than zero")
}
return nil
}
func (n *NSDP) Start(telegraf.Accumulator) error {
conn, err := nsdp.NewConn(n.Address, n.Log.Level().Includes(telegraf.Trace))
if err != nil {
return fmt.Errorf("failed to create connection to address %s: %s", n.Address, err)
}
conn.ReceiveDeviceLimit = n.DeviceLimit
conn.ReceiveTimeout = time.Duration(n.Timeout)
n.conn = conn
return nil
}
func (n *NSDP) Stop() {
if n.conn == nil {
return
}
n.conn.Close()
n.conn = nil
}
func (n *NSDP) Gather(acc telegraf.Accumulator) error {
if n.conn == nil {
if err := n.Start(nil); err != nil {
return err
}
}
// Send request to query devices including infos (model, name, IP) and status (port statistics)
request := nsdp.NewMessage(nsdp.ReadRequest)
request.AppendTLV(nsdp.EmptyDeviceModel())
request.AppendTLV(nsdp.EmptyDeviceName())
request.AppendTLV(nsdp.EmptyDeviceIP())
request.AppendTLV(nsdp.EmptyPortStatistic())
responses, err := n.conn.SendReceiveMessage(request)
if err != nil {
// Close malfunctioning connection and re-connect on next Gather call
n.Stop()
return fmt.Errorf("failed to query address %s: %w", n.Address, err)
}
// Create metrics for each responding device
for device, response := range responses {
n.Log.Tracef("Processing device: %s", device)
n.gatherDevice(acc, device, response)
}
return nil
}
func (n *NSDP) gatherDevice(acc telegraf.Accumulator, device string, response *nsdp.Message) {
var deviceModel string
var deviceName string
var deviceIP net.IP
portStats := make(map[uint8]*nsdp.PortStatistic, 0)
for _, tlv := range response.Body {
switch tlv.Type() {
case nsdp.TypeDeviceModel:
deviceModel = tlv.(*nsdp.DeviceModel).Model
case nsdp.TypeDeviceName:
deviceName = tlv.(*nsdp.DeviceName).Name
case nsdp.TypeDeviceIP:
deviceIP = tlv.(*nsdp.DeviceIP).IP
case nsdp.TypePortStatistic:
portStat := tlv.(*nsdp.PortStatistic)
portStats[portStat.Port] = portStat
}
}
for port, stat := range portStats {
tags := map[string]string{
"device": device,
"device_ip": deviceIP.String(),
"device_name": deviceName,
"device_model": deviceModel,
"device_port": strconv.FormatUint(uint64(port), 10),
}
fields := map[string]interface{}{
"bytes_sent": stat.Sent,
"bytes_recv": stat.Received,
"packets_total": stat.Packets,
"broadcasts_total": stat.Broadcasts,
"multicasts_total": stat.Multicasts,
"errors_total": stat.Errors,
}
acc.AddCounter("nsdp_device_port", fields, tags)
}
}
func init() {
inputs.Add("nsdp", func() telegraf.Input {
return &NSDP{Timeout: config.Duration(2 * time.Second)}
})
}

View File

@ -0,0 +1,81 @@
package nsdp
import (
"testing"
"time"
"github.com/tdrn-org/go-nsdp"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/parsers/influx"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
func TestLoadConfig(t *testing.T) {
// Verify plugin can be loaded from config
conf := config.NewConfig()
require.NoError(t, conf.LoadConfig("testdata/conf/nsdp.conf"))
require.Len(t, conf.Inputs, 1)
plugin, ok := conf.Inputs[0].Input.(*NSDP)
require.True(t, ok)
// Verify successful Init
require.NoError(t, plugin.Init())
// Verify everything is setup according to config file
require.Equal(t, "127.0.0.1:63322", plugin.Address)
require.Equal(t, uint(1), plugin.DeviceLimit)
require.Equal(t, config.Duration(5*time.Second), plugin.Timeout)
}
func TestInvalidTimeoutConfig(t *testing.T) {
plugin := &NSDP{
Timeout: config.Duration(0 * time.Second),
}
// Verify failing Init
require.EqualError(t, plugin.Init(), "invalid Timeout value 0, must be greater 0")
}
func TestGather(t *testing.T) {
// Setup and start test responder
responder, err := nsdp.NewTestResponder("localhost:0")
require.NoError(t, err)
defer responder.Stop()
responder.AddResponses(
"0102000000000000bcd07432b8dc123456789abc000037b94e534450000000000001000847533130384576330003000773776974636832000600040a010004100000310100000000e73b5f1a000000001e31523c0000000000000000000000000000000000000000000000000000000000000000100000310200000000152d5eae0000000052ea11ea0000000000000000000000000000000000000000000000000000000000000000100000310300000000068561aa00000000bcc8cb35000000000000000000000000000000000000000000000000000000000000000010000031040000000002d5fe00000000002b37dad900000000000000000000000000000000000000000000000000000000000000001000003105000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000310600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000031070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000003108000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0000",
"0102000000000000bcd07432b8dccba987654321000037b94e534450000000000001000847533130384576330003000773776974636831000600040a0100031000003101000000059a9d833200000000303e8eb5000000000000000000000000000000000000000000000000000000000000000010000031020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000003103000000000d9a35e4000000026523c66600000000000000000000000000000000000000000000000000000000000000001000003104000000000041c7530000000002cd94ba000000000000000000000000000000000000000000000000000000000000000010000031050000000021b9ca41000000031a9bff610000000000000000000000000000000000000000000000000000000000000000100000310600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000031070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000003108000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff0000")
require.NoError(t, responder.Start())
// Setup the plugin to target the test responder
plugin := &NSDP{
Address: responder.Target(),
DeviceLimit: 2,
Timeout: config.Duration(2 * time.Second),
Log: testutil.Logger{Name: "nsdp"},
}
// Verify successful Init
require.NoError(t, plugin.Init())
// Verify successfull Gather
var acc testutil.Accumulator
require.NoError(t, acc.GatherError(plugin.Gather))
// Verify collected metrics are as expected
expectedMetrics := loadExpectedMetrics(t, "testdata/metrics/nsdp_device_port.txt", telegraf.Counter)
testutil.RequireMetricsEqual(t, expectedMetrics, acc.GetTelegrafMetrics(), testutil.IgnoreTime(), testutil.SortMetrics())
}
func loadExpectedMetrics(t *testing.T, file string, vt telegraf.ValueType) []telegraf.Metric {
parser := &influx.Parser{}
require.NoError(t, parser.Init())
expectedMetrics, err := testutil.ParseMetricsFromFile(file, parser)
require.NoError(t, err)
for index := range expectedMetrics {
expectedMetrics[index].SetType(vt)
}
return expectedMetrics
}

View File

@ -0,0 +1,15 @@
# Gather Netgear Switch Discovery Protocol status
[[inputs.nsdp]]
## The target address to use for status gathering. Either Broadcast (default)
## or the address of a single well-known device.
# address = "255.255.255.255:63322"
## The maximum number of device responses to wait for. 0 means no limit.
## NSDP works asynchronously. Without a limit (0) the plugin always waits
## the amount given in timeout for possible responses. By setting this
## option to the known number of devices, the plugin completes
## processing as soon as the last device has answered.
# device_limit = 0
## The maximum duration to wait for device responses.
# timeout = "2s"

View File

@ -0,0 +1,15 @@
# Gather Netgear Switch Discovery Protocol status
[[inputs.nsdp]]
## The target address to use for status gathering. Either Broadcast (default)
## or the address of a single well-known device.
address = "127.0.0.1:63322"
## The maximum number of device responses to wait for. 0 means no limit.
## NSDP works asynchronously. Without a limit (0) the plugin always waits
## the amount given in timeout for possible responses. By setting this
## option to the known number of devices, the plugin completes
## processing as soon as the last device has answered.
device_limit = 1
## The maximum duration to wait for device responses.
timeout = "5s"

View File

@ -0,0 +1,16 @@
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=1 broadcasts_total=0u,bytes_recv=3879427866u,bytes_sent=506548796u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014578000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=2 broadcasts_total=0u,bytes_recv=355294894u,bytes_sent=1391071722u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014606000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=3 broadcasts_total=0u,bytes_recv=109404586u,bytes_sent=3167275829u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014615000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=4 broadcasts_total=0u,bytes_recv=47578624u,bytes_sent=725080793u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014624000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=5 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014624000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=6 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014624000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=7 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014624000
nsdp_device_port,device=12:34:56:78:9a:bc,device_ip=10.1.0.4,device_model=GS108Ev3,device_name=switch2,device_port=8 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014624000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=1 broadcasts_total=0u,bytes_recv=24068850482u,bytes_sent=809406133u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014647000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=2 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014676000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=3 broadcasts_total=0u,bytes_recv=228210148u,bytes_sent=10286777958u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014657000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=4 broadcasts_total=0u,bytes_recv=4310867u,bytes_sent=47027386u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014668000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=5 broadcasts_total=0u,bytes_recv=565824065u,bytes_sent=13331332961u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014676000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=6 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014676000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=7 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014676000
nsdp_device_port,device=cb:a9:87:65:43:21,device_ip=10.1.0.3,device_model=GS108Ev3,device_name=switch1,device_port=8 broadcasts_total=0u,bytes_recv=0u,bytes_sent=0u,errors_total=0u,multicasts_total=0u,packets_total=0u 1737152505014676000