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.nsdp): Add plugin #16392

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
1 change: 0 additions & 1 deletion EXTERNAL_PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/LICENSE_OF_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,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)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,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.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
Expand Down
5 changes: 5 additions & 0 deletions plugins/inputs/all/nsdp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.nsdp

package all

import _ "github.com/influxdata/telegraf/plugins/inputs/nsdp" // register plugin
63 changes: 63 additions & 0 deletions plugins/inputs/nsdp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Netgear Switch Discovery Protocol Input Plugin

This input 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
```
119 changes: 119 additions & 0 deletions plugins/inputs/nsdp/nsdp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//go:generate ../../../tools/readme_config_includer/generator
package nsdp

import (
_ "embed"
"fmt"
"net"
"strconv"
"time"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/tdrn-org/go-nsdp"
)

//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 {
n.Timeout = config.Duration(2 * time.Second)
}
return nil
}

func (n *NSDP) Gather(acc telegraf.Accumulator) error {
if n.conn == nil {
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
}
responses, err := n.conn.SendReceiveMessage(n.newGatherRequest())
if err != nil {
// Close malfunctioning connection and re-connect on next Gather call
n.conn.Close()
n.conn = nil
return fmt.Errorf("failed to query address %s: %w", n.Address, err)
}
for device, response := range responses {
n.Log.Tracef("Processing device: %s", device)
n.gatherDevice(acc, device, response)
}
return nil
}

func (n *NSDP) newGatherRequest() *nsdp.Message {
request := nsdp.NewMessage(nsdp.ReadRequest)
request.AppendTLV(nsdp.EmptyDeviceModel())
request.AppendTLV(nsdp.EmptyDeviceName())
request.AppendTLV(nsdp.EmptyDeviceIP())
request.AppendTLV(nsdp.EmptyPortStatistic())
return request
}

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{}
})
}
69 changes: 69 additions & 0 deletions plugins/inputs/nsdp/nsdp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package nsdp

import (
"testing"
"time"

"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"
"github.com/tdrn-org/go-nsdp"
)

func TestConfig(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 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{}
plugin.Address = responder.Target()
plugin.DeviceLimit = 2
plugin.Log = testutil.Logger{Name: "nsdp"}

// Verify successful Init
require.NoError(t, plugin.Init())

// Verify successfull Gather
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
}
15 changes: 15 additions & 0 deletions plugins/inputs/nsdp/sample.conf
Original file line number Diff line number Diff line change
@@ -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"
15 changes: 15 additions & 0 deletions plugins/inputs/nsdp/testdata/conf/nsdp.conf
Original file line number Diff line number Diff line change
@@ -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"
16 changes: 16 additions & 0 deletions plugins/inputs/nsdp/testdata/metrics/nsdp_device_port.txt
Original file line number Diff line number Diff line change
@@ -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
Loading