summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/snmp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 08:15:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 08:15:35 +0000
commitf09848204fa5283d21ea43e262ee41aa578e1808 (patch)
treec62385d7adf209fa6a798635954d887f718fb3fb /src/go/plugin/go.d/modules/snmp
parentReleasing debian version 1.46.3-2. (diff)
downloadnetdata-f09848204fa5283d21ea43e262ee41aa578e1808.tar.xz
netdata-f09848204fa5283d21ea43e262ee41aa578e1808.zip
Merging upstream version 1.47.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/plugin/go.d/modules/snmp')
l---------src/go/plugin/go.d/modules/snmp/README.md1
-rw-r--r--src/go/plugin/go.d/modules/snmp/charts.go309
-rw-r--r--src/go/plugin/go.d/modules/snmp/collect.go395
-rw-r--r--src/go/plugin/go.d/modules/snmp/config.go52
-rw-r--r--src/go/plugin/go.d/modules/snmp/config_schema.json422
-rw-r--r--src/go/plugin/go.d/modules/snmp/init.go175
-rw-r--r--src/go/plugin/go.d/modules/snmp/integrations/snmp_devices.md496
-rw-r--r--src/go/plugin/go.d/modules/snmp/metadata.yaml496
-rw-r--r--src/go/plugin/go.d/modules/snmp/netif.go412
-rw-r--r--src/go/plugin/go.d/modules/snmp/snmp.go155
-rw-r--r--src/go/plugin/go.d/modules/snmp/snmp_test.go754
-rw-r--r--src/go/plugin/go.d/modules/snmp/testdata/config.json47
-rw-r--r--src/go/plugin/go.d/modules/snmp/testdata/config.yaml35
13 files changed, 3749 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/snmp/README.md b/src/go/plugin/go.d/modules/snmp/README.md
new file mode 120000
index 00000000..edf223bf
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/README.md
@@ -0,0 +1 @@
+integrations/snmp_devices.md \ No newline at end of file
diff --git a/src/go/plugin/go.d/modules/snmp/charts.go b/src/go/plugin/go.d/modules/snmp/charts.go
new file mode 100644
index 00000000..dd31f1cc
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/charts.go
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
+)
+
+const (
+ prioNetIfaceTraffic = module.Priority + iota
+ prioNetIfaceUnicast
+ prioNetIfaceMulticast
+ prioNetIfaceBroadcast
+ prioNetIfaceErrors
+ prioNetIfaceDiscards
+ prioNetIfaceAdminStatus
+ prioNetIfaceOperStatus
+ prioSysUptime
+)
+
+var netIfaceChartsTmpl = module.Charts{
+ netIfaceTrafficChartTmpl.Copy(),
+ netIfacePacketsChartTmpl.Copy(),
+ netIfaceMulticastChartTmpl.Copy(),
+ netIfaceBroadcastChartTmpl.Copy(),
+ netIfaceErrorsChartTmpl.Copy(),
+ netIfaceDiscardsChartTmpl.Copy(),
+ netIfaceAdminStatusChartTmpl.Copy(),
+ netIfaceOperStatusChartTmpl.Copy(),
+}
+
+var (
+ netIfaceTrafficChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_traffic",
+ Title: "SNMP device network interface traffic",
+ Units: "kilobits/s",
+ Fam: "traffic",
+ Ctx: "snmp.device_net_interface_traffic",
+ Priority: prioNetIfaceTraffic,
+ Type: module.Area,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_traffic_in", Name: "received", Algo: module.Incremental},
+ {ID: "net_iface_%s_traffic_out", Name: "sent", Mul: -1, Algo: module.Incremental},
+ },
+ }
+
+ netIfacePacketsChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_unicast",
+ Title: "SNMP device network interface unicast packets",
+ Units: "packets/s",
+ Fam: "packets",
+ Ctx: "snmp.device_net_interface_unicast",
+ Priority: prioNetIfaceUnicast,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_ucast_in", Name: "received", Algo: module.Incremental},
+ {ID: "net_iface_%s_ucast_out", Name: "sent", Mul: -1, Algo: module.Incremental},
+ },
+ }
+ netIfaceMulticastChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_multicast",
+ Title: "SNMP device network interface multicast packets",
+ Units: "packets/s",
+ Fam: "packets",
+ Ctx: "snmp.device_net_interface_multicast",
+ Priority: prioNetIfaceMulticast,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_mcast_in", Name: "received", Algo: module.Incremental},
+ {ID: "net_iface_%s_mcast_out", Name: "sent", Mul: -1, Algo: module.Incremental},
+ },
+ }
+ netIfaceBroadcastChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_broadcast",
+ Title: "SNMP device network interface broadcast packets",
+ Units: "packets/s",
+ Fam: "packets",
+ Ctx: "snmp.device_net_interface_broadcast",
+ Priority: prioNetIfaceBroadcast,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_bcast_in", Name: "received", Algo: module.Incremental},
+ {ID: "net_iface_%s_bcast_out", Name: "sent", Mul: -1, Algo: module.Incremental},
+ },
+ }
+
+ netIfaceErrorsChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_errors",
+ Title: "SNMP device network interface errors",
+ Units: "errors/s",
+ Fam: "errors",
+ Ctx: "snmp.device_net_interface_errors",
+ Priority: prioNetIfaceErrors,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_errors_in", Name: "inbound", Algo: module.Incremental},
+ {ID: "net_iface_%s_errors_out", Name: "outbound", Mul: -1, Algo: module.Incremental},
+ },
+ }
+
+ netIfaceDiscardsChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_discards",
+ Title: "SNMP device network interface discards",
+ Units: "discards/s",
+ Fam: "discards",
+ Ctx: "snmp.device_net_interface_discards",
+ Priority: prioNetIfaceDiscards,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_discards_in", Name: "inbound", Algo: module.Incremental},
+ {ID: "net_iface_%s_discards_out", Name: "outbound", Mul: -1, Algo: module.Incremental},
+ },
+ }
+
+ netIfaceAdminStatusChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_admin_status",
+ Title: "SNMP device network interface administrative status",
+ Units: "status",
+ Fam: "status",
+ Ctx: "snmp.device_net_interface_admin_status",
+ Priority: prioNetIfaceAdminStatus,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_admin_status_up", Name: "up"},
+ {ID: "net_iface_%s_admin_status_down", Name: "down"},
+ {ID: "net_iface_%s_admin_status_testing", Name: "testing"},
+ },
+ }
+ netIfaceOperStatusChartTmpl = module.Chart{
+ ID: "snmp_device_net_iface_%s_oper_status",
+ Title: "SNMP device network interface operational status",
+ Units: "status",
+ Fam: "status",
+ Ctx: "snmp.device_net_interface_oper_status",
+ Priority: prioNetIfaceOperStatus,
+ Dims: module.Dims{
+ {ID: "net_iface_%s_oper_status_up", Name: "up"},
+ {ID: "net_iface_%s_oper_status_down", Name: "down"},
+ {ID: "net_iface_%s_oper_status_testing", Name: "testing"},
+ {ID: "net_iface_%s_oper_status_unknown", Name: "unknown"},
+ {ID: "net_iface_%s_oper_status_dormant", Name: "dormant"},
+ {ID: "net_iface_%s_oper_status_notPresent", Name: "not_present"},
+ {ID: "net_iface_%s_oper_status_lowerLayerDown", Name: "lower_layer_down"},
+ },
+ }
+)
+
+var (
+ uptimeChart = module.Chart{
+ ID: "snmp_device_uptime",
+ Title: "SNMP device uptime",
+ Units: "seconds",
+ Fam: "uptime",
+ Ctx: "snmp.device_uptime",
+ Priority: prioSysUptime,
+ Dims: module.Dims{
+ {ID: "uptime", Name: "uptime"},
+ },
+ }
+)
+
+func (s *SNMP) addNetIfaceCharts(iface *netInterface) {
+ charts := netIfaceChartsTmpl.Copy()
+
+ for _, chart := range *charts {
+ chart.ID = fmt.Sprintf(chart.ID, cleanIfaceName(iface.ifName))
+ chart.Labels = []module.Label{
+ {Key: "sysName", Value: s.sysName},
+ {Key: "ifDescr", Value: iface.ifDescr},
+ {Key: "ifName", Value: iface.ifName},
+ {Key: "ifType", Value: ifTypeMapping[iface.ifType]},
+ }
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf(dim.ID, iface.ifName)
+ }
+ }
+
+ if err := s.Charts().Add(*charts...); err != nil {
+ s.Warning(err)
+ }
+}
+
+func (s *SNMP) removeNetIfaceCharts(iface *netInterface) {
+ px := fmt.Sprintf("snmp_device_net_iface_%s_", cleanIfaceName(iface.ifName))
+ for _, chart := range *s.Charts() {
+ if strings.HasPrefix(chart.ID, px) {
+ chart.MarkRemove()
+ chart.MarkNotCreated()
+ }
+ }
+}
+
+func (s *SNMP) addSysUptimeChart() {
+ chart := uptimeChart.Copy()
+ chart.Labels = []module.Label{
+ {Key: "sysName", Value: s.sysName},
+ }
+ if err := s.Charts().Add(chart); err != nil {
+ s.Warning(err)
+ }
+}
+
+func cleanIfaceName(name string) string {
+ r := strings.NewReplacer(".", "_", " ", "_")
+ return r.Replace(name)
+}
+
+func newUserInputCharts(configs []ChartConfig) (*module.Charts, error) {
+ charts := &module.Charts{}
+ for _, cfg := range configs {
+ if len(cfg.IndexRange) == 2 {
+ cs, err := newUserInputChartsFromIndexRange(cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err := charts.Add(*cs...); err != nil {
+ return nil, err
+ }
+ } else {
+ chart, err := newUserInputChart(cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err = charts.Add(chart); err != nil {
+ return nil, err
+ }
+ }
+ }
+ return charts, nil
+}
+
+func newUserInputChartsFromIndexRange(cfg ChartConfig) (*module.Charts, error) {
+ var addPrio int
+ charts := &module.Charts{}
+ for i := cfg.IndexRange[0]; i <= cfg.IndexRange[1]; i++ {
+ chart, err := newUserInputChartWithOIDIndex(i, cfg)
+ if err != nil {
+ return nil, err
+ }
+ chart.Priority += addPrio
+ addPrio += 1
+ if err = charts.Add(chart); err != nil {
+ return nil, err
+ }
+ }
+ return charts, nil
+}
+
+func newUserInputChartWithOIDIndex(oidIndex int, cfg ChartConfig) (*module.Chart, error) {
+ chart, err := newUserInputChart(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ chart.ID = fmt.Sprintf("%s_%d", chart.ID, oidIndex)
+ chart.Title = fmt.Sprintf("%s %d", chart.Title, oidIndex)
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf("%s.%d", dim.ID, oidIndex)
+ }
+
+ return chart, nil
+}
+
+func newUserInputChart(cfg ChartConfig) (*module.Chart, error) {
+ chart := &module.Chart{
+ ID: cfg.ID,
+ Title: cfg.Title,
+ Units: cfg.Units,
+ Fam: cfg.Family,
+ Ctx: fmt.Sprintf("snmp.%s", cfg.ID),
+ Type: module.ChartType(cfg.Type),
+ Priority: cfg.Priority,
+ }
+
+ if chart.Title == "" {
+ chart.Title = "Untitled chart"
+ }
+ if chart.Units == "" {
+ chart.Units = "num"
+ }
+ if chart.Priority < module.Priority {
+ chart.Priority += module.Priority
+ }
+
+ seen := make(map[string]struct{})
+ var a string
+ for _, cfg := range cfg.Dimensions {
+ if cfg.Algorithm != "" {
+ seen[cfg.Algorithm] = struct{}{}
+ a = cfg.Algorithm
+ }
+ dim := &module.Dim{
+ ID: strings.TrimPrefix(cfg.OID, "."),
+ Name: cfg.Name,
+ Algo: module.DimAlgo(cfg.Algorithm),
+ Mul: cfg.Multiplier,
+ Div: cfg.Divisor,
+ }
+ if err := chart.AddDim(dim); err != nil {
+ return nil, err
+ }
+ }
+ if len(seen) == 1 && a != "" && len(chart.Dims) > 1 {
+ for _, d := range chart.Dims {
+ if d.Algo == "" {
+ d.Algo = module.DimAlgo(a)
+ }
+ }
+ }
+
+ return chart, nil
+}
diff --git a/src/go/plugin/go.d/modules/snmp/collect.go b/src/go/plugin/go.d/modules/snmp/collect.go
new file mode 100644
index 00000000..24cc49db
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/collect.go
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "errors"
+ "fmt"
+ "log/slog"
+ "sort"
+ "strings"
+
+ "github.com/netdata/netdata/go/plugins/logger"
+
+ "github.com/gosnmp/gosnmp"
+)
+
+const (
+ oidSysUptime = "1.3.6.1.2.1.1.3.0"
+ oidSysName = "1.3.6.1.2.1.1.5.0"
+ rootOidIfMibIfTable = "1.3.6.1.2.1.2.2"
+ rootOidIfMibIfXTable = "1.3.6.1.2.1.31.1.1"
+)
+
+func (s *SNMP) collect() (map[string]int64, error) {
+ if s.sysName == "" {
+ sysName, err := s.getSysName()
+ if err != nil {
+ return nil, err
+ }
+ s.sysName = sysName
+ s.addSysUptimeChart()
+ }
+
+ mx := make(map[string]int64)
+
+ if err := s.collectSysUptime(mx); err != nil {
+ return nil, err
+ }
+
+ if s.collectIfMib {
+ if err := s.collectNetworkInterfaces(mx); err != nil {
+ return nil, err
+ }
+ }
+
+ if len(s.oids) > 0 {
+ if err := s.collectOIDs(mx); err != nil {
+ return nil, err
+ }
+ }
+
+ return mx, nil
+}
+
+func (s *SNMP) getSysName() (string, error) {
+ resp, err := s.snmpClient.Get([]string{oidSysName})
+ if err != nil {
+ return "", err
+ }
+ if len(resp.Variables) == 0 {
+ return "", errors.New("no system name")
+ }
+ return pduToString(resp.Variables[0])
+}
+
+func (s *SNMP) collectSysUptime(mx map[string]int64) error {
+ resp, err := s.snmpClient.Get([]string{oidSysUptime})
+ if err != nil {
+ return err
+ }
+ if len(resp.Variables) == 0 {
+ return errors.New("no system uptime")
+ }
+ v, err := pduToInt(resp.Variables[0])
+ if err != nil {
+ return err
+ }
+
+ mx["uptime"] = v / 100 // the time is in hundredths of a second
+
+ return nil
+}
+
+func (s *SNMP) collectNetworkInterfaces(mx map[string]int64) error {
+ if s.checkMaxReps {
+ ok, err := s.adjustMaxRepetitions()
+ if err != nil {
+ return err
+ }
+
+ s.checkMaxReps = false
+
+ if !ok {
+ s.collectIfMib = false
+
+ if len(s.oids) == 0 {
+ return errors.New("no IF-MIB data returned")
+ }
+
+ s.Warning("no IF-MIB data returned")
+ return nil
+ }
+ }
+
+ ifMibTable, err := s.walkAll(rootOidIfMibIfTable)
+ if err != nil {
+ return err
+ }
+
+ ifMibXTable, err := s.walkAll(rootOidIfMibIfXTable)
+ if err != nil {
+ return err
+ }
+
+ if len(ifMibTable) == 0 && len(ifMibXTable) == 0 {
+ s.Warning("no IF-MIB data returned")
+ s.collectIfMib = false
+ return nil
+ }
+
+ for _, i := range s.netInterfaces {
+ i.updated = false
+ }
+
+ pdus := make([]gosnmp.SnmpPDU, 0, len(ifMibTable)+len(ifMibXTable))
+ pdus = append(pdus, ifMibTable...)
+ pdus = append(pdus, ifMibXTable...)
+
+ for _, pdu := range pdus {
+ i := strings.LastIndexByte(pdu.Name, '.')
+ if i == -1 {
+ continue
+ }
+
+ idx := pdu.Name[i+1:]
+ oid := strings.TrimPrefix(pdu.Name[:i], ".")
+
+ iface, ok := s.netInterfaces[idx]
+ if !ok {
+ iface = &netInterface{idx: idx}
+ }
+
+ switch oid {
+ case oidIfIndex:
+ iface.ifIndex, err = pduToInt(pdu)
+ case oidIfDescr:
+ iface.ifDescr, err = pduToString(pdu)
+ case oidIfType:
+ iface.ifType, err = pduToInt(pdu)
+ case oidIfMtu:
+ iface.ifMtu, err = pduToInt(pdu)
+ case oidIfSpeed:
+ iface.ifSpeed, err = pduToInt(pdu)
+ case oidIfAdminStatus:
+ iface.ifAdminStatus, err = pduToInt(pdu)
+ case oidIfOperStatus:
+ iface.ifOperStatus, err = pduToInt(pdu)
+ case oidIfInOctets:
+ iface.ifInOctets, err = pduToInt(pdu)
+ case oidIfInUcastPkts:
+ iface.ifInUcastPkts, err = pduToInt(pdu)
+ case oidIfInNUcastPkts:
+ iface.ifInNUcastPkts, err = pduToInt(pdu)
+ case oidIfInDiscards:
+ iface.ifInDiscards, err = pduToInt(pdu)
+ case oidIfInErrors:
+ iface.ifInErrors, err = pduToInt(pdu)
+ case oidIfInUnknownProtos:
+ iface.ifInUnknownProtos, err = pduToInt(pdu)
+ case oidIfOutOctets:
+ iface.ifOutOctets, err = pduToInt(pdu)
+ case oidIfOutUcastPkts:
+ iface.ifOutUcastPkts, err = pduToInt(pdu)
+ case oidIfOutNUcastPkts:
+ iface.ifOutNUcastPkts, err = pduToInt(pdu)
+ case oidIfOutDiscards:
+ iface.ifOutDiscards, err = pduToInt(pdu)
+ case oidIfOutErrors:
+ iface.ifOutErrors, err = pduToInt(pdu)
+ case oidIfName:
+ iface.ifName, err = pduToString(pdu)
+ case oidIfInMulticastPkts:
+ iface.ifInMulticastPkts, err = pduToInt(pdu)
+ case oidIfInBroadcastPkts:
+ iface.ifInBroadcastPkts, err = pduToInt(pdu)
+ case oidIfOutMulticastPkts:
+ iface.ifOutMulticastPkts, err = pduToInt(pdu)
+ case oidIfOutBroadcastPkts:
+ iface.ifOutBroadcastPkts, err = pduToInt(pdu)
+ case oidIfHCInOctets:
+ iface.ifHCInOctets, err = pduToInt(pdu)
+ case oidIfHCInUcastPkts:
+ iface.ifHCInUcastPkts, err = pduToInt(pdu)
+ case oidIfHCInMulticastPkts:
+ iface.ifHCInMulticastPkts, err = pduToInt(pdu)
+ case oidIfHCInBroadcastPkts:
+ iface.ifHCInBroadcastPkts, err = pduToInt(pdu)
+ case oidIfHCOutOctets:
+ iface.ifHCOutOctets, err = pduToInt(pdu)
+ case oidIfHCOutUcastPkts:
+ iface.ifHCOutUcastPkts, err = pduToInt(pdu)
+ case oidIfHCOutMulticastPkts:
+ iface.ifHCOutMulticastPkts, err = pduToInt(pdu)
+ case oidIfHCOutBroadcastPkts:
+ iface.ifHCOutMulticastPkts, err = pduToInt(pdu)
+ case oidIfHighSpeed:
+ iface.ifHighSpeed, err = pduToInt(pdu)
+ case oidIfAlias:
+ iface.ifAlias, err = pduToString(pdu)
+ default:
+ continue
+ }
+
+ if err != nil {
+ return fmt.Errorf("OID '%s': %v", pdu.Name, err)
+ }
+
+ s.netInterfaces[idx] = iface
+ iface.updated = true
+ }
+
+ for _, iface := range s.netInterfaces {
+ if iface.ifName == "" {
+ continue
+ }
+
+ typeStr := ifTypeMapping[iface.ifType]
+ if s.netIfaceFilterByName.MatchString(iface.ifName) || s.netIfaceFilterByType.MatchString(typeStr) {
+ continue
+ }
+
+ if !iface.updated {
+ delete(s.netInterfaces, iface.idx)
+ if iface.hasCharts {
+ s.removeNetIfaceCharts(iface)
+ }
+ continue
+ }
+ if !iface.hasCharts {
+ iface.hasCharts = true
+ s.addNetIfaceCharts(iface)
+ }
+
+ px := fmt.Sprintf("net_iface_%s_", iface.ifName)
+ mx[px+"traffic_in"] = iface.ifHCInOctets * 8 / 1000 // kilobits
+ mx[px+"traffic_out"] = iface.ifHCOutOctets * 8 / 1000 // kilobits
+ mx[px+"ucast_in"] = iface.ifHCInUcastPkts
+ mx[px+"ucast_out"] = iface.ifHCOutUcastPkts
+ mx[px+"mcast_in"] = iface.ifHCInMulticastPkts
+ mx[px+"mcast_out"] = iface.ifHCOutMulticastPkts
+ mx[px+"bcast_in"] = iface.ifHCInBroadcastPkts
+ mx[px+"bcast_out"] = iface.ifHCOutBroadcastPkts
+ mx[px+"errors_in"] = iface.ifInErrors
+ mx[px+"errors_out"] = iface.ifOutErrors
+ mx[px+"discards_in"] = iface.ifInDiscards
+ mx[px+"discards_out"] = iface.ifOutDiscards
+
+ for _, v := range ifAdminStatusMapping {
+ mx[px+"admin_status_"+v] = 0
+ }
+ mx[px+"admin_status_"+ifAdminStatusMapping[iface.ifAdminStatus]] = 1
+
+ for _, v := range ifOperStatusMapping {
+ mx[px+"oper_status_"+v] = 0
+ }
+ mx[px+"oper_status_"+ifOperStatusMapping[iface.ifOperStatus]] = 1
+ }
+
+ if logger.Level.Enabled(slog.LevelDebug) {
+ ifaces := make([]*netInterface, 0, len(s.netInterfaces))
+ for _, nif := range s.netInterfaces {
+ ifaces = append(ifaces, nif)
+ }
+ sort.Slice(ifaces, func(i, j int) bool { return ifaces[i].ifIndex < ifaces[j].ifIndex })
+ for _, iface := range ifaces {
+ s.Debugf("found %s", iface)
+ }
+ }
+
+ return nil
+}
+
+func (s *SNMP) adjustMaxRepetitions() (bool, error) {
+ orig := s.Config.Options.MaxRepetitions
+ maxReps := s.Config.Options.MaxRepetitions
+
+ for {
+ v, err := s.walkAll(oidIfIndex)
+ if err != nil {
+ return false, err
+ }
+
+ if len(v) > 0 {
+ if orig != maxReps {
+ s.Infof("changed 'max_repetitions' %d => %d", orig, maxReps)
+ }
+ return true, nil
+ }
+
+ if maxReps > 5 {
+ maxReps = max(5, maxReps-5)
+ } else {
+ maxReps--
+ }
+
+ if maxReps <= 0 {
+ return false, nil
+ }
+
+ s.Debugf("no IF-MIB data returned, trying to decrese 'max_repetitions' to %d", maxReps)
+ s.snmpClient.SetMaxRepetitions(uint32(maxReps))
+ }
+}
+
+func (s *SNMP) walkAll(rootOid string) ([]gosnmp.SnmpPDU, error) {
+ if s.snmpClient.Version() == gosnmp.Version1 {
+ return s.snmpClient.WalkAll(rootOid)
+ }
+ return s.snmpClient.BulkWalkAll(rootOid)
+}
+
+func pduToString(pdu gosnmp.SnmpPDU) (string, error) {
+ switch pdu.Type {
+ case gosnmp.OctetString:
+ // TODO: this isn't reliable (e.g. physAddress we need hex.EncodeToString())
+ bs, ok := pdu.Value.([]byte)
+ if !ok {
+ return "", fmt.Errorf("OctetString is not a []byte but %T", pdu.Value)
+ }
+ return strings.ToValidUTF8(string(bs), "�"), nil
+ case gosnmp.Counter32, gosnmp.Counter64, gosnmp.Integer, gosnmp.Gauge32:
+ return gosnmp.ToBigInt(pdu.Value).String(), nil
+ default:
+ return "", fmt.Errorf("unussported type: '%v'", pdu.Type)
+ }
+}
+
+func pduToInt(pdu gosnmp.SnmpPDU) (int64, error) {
+ switch pdu.Type {
+ case gosnmp.Counter32, gosnmp.Counter64, gosnmp.Integer, gosnmp.Gauge32, gosnmp.TimeTicks:
+ return gosnmp.ToBigInt(pdu.Value).Int64(), nil
+ default:
+ return 0, fmt.Errorf("unussported type: '%v'", pdu.Type)
+ }
+}
+
+//func physAddressToString(pdu gosnmp.SnmpPDU) (string, error) {
+// address, ok := pdu.Value.([]uint8)
+// if !ok {
+// return "", errors.New("physAddress is not a []uint8")
+// }
+// parts := make([]string, 0, 6)
+// for _, v := range address {
+// parts = append(parts, fmt.Sprintf("%02X", v))
+// }
+// return strings.Join(parts, ":"), nil
+//}
+
+func (s *SNMP) collectOIDs(mx map[string]int64) error {
+ for i, end := 0, 0; i < len(s.oids); i += s.Options.MaxOIDs {
+ if end = i + s.Options.MaxOIDs; end > len(s.oids) {
+ end = len(s.oids)
+ }
+
+ oids := s.oids[i:end]
+ resp, err := s.snmpClient.Get(oids)
+ if err != nil {
+ s.Errorf("cannot get SNMP data: %v", err)
+ return err
+ }
+
+ for i, oid := range oids {
+ if i >= len(resp.Variables) {
+ continue
+ }
+
+ switch v := resp.Variables[i]; v.Type {
+ case gosnmp.Boolean,
+ gosnmp.Counter32,
+ gosnmp.Counter64,
+ gosnmp.Gauge32,
+ gosnmp.TimeTicks,
+ gosnmp.Uinteger32,
+ gosnmp.OpaqueFloat,
+ gosnmp.OpaqueDouble,
+ gosnmp.Integer:
+ mx[oid] = gosnmp.ToBigInt(v.Value).Int64()
+ default:
+ s.Debugf("skipping OID '%s' (unsupported type '%s')", oid, v.Type)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/src/go/plugin/go.d/modules/snmp/config.go b/src/go/plugin/go.d/modules/snmp/config.go
new file mode 100644
index 00000000..631c47d3
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/config.go
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+type (
+ Config struct {
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Hostname string `yaml:"hostname" json:"hostname"`
+ Community string `yaml:"community,omitempty" json:"community"`
+ User User `yaml:"user,omitempty" json:"user"`
+ Options Options `yaml:"options,omitempty" json:"options"`
+ ChartsInput []ChartConfig `yaml:"charts,omitempty" json:"charts"`
+ NetworkInterfaceFilter NetworkInterfaceFilter `yaml:"network_interface_filter,omitempty" json:"network_interface_filter"`
+ }
+ NetworkInterfaceFilter struct {
+ ByName string `yaml:"by_name,omitempty" json:"by_name"`
+ ByType string `yaml:"by_type,omitempty" json:"by_type"`
+ }
+ User struct {
+ Name string `yaml:"name,omitempty" json:"name"`
+ SecurityLevel string `yaml:"level,omitempty" json:"level"`
+ AuthProto string `yaml:"auth_proto,omitempty" json:"auth_proto"`
+ AuthKey string `yaml:"auth_key,omitempty" json:"auth_key"`
+ PrivProto string `yaml:"priv_proto,omitempty" json:"priv_proto"`
+ PrivKey string `yaml:"priv_key,omitempty" json:"priv_key"`
+ }
+ Options struct {
+ Port int `yaml:"port,omitempty" json:"port"`
+ Retries int `yaml:"retries,omitempty" json:"retries"`
+ Timeout int `yaml:"timeout,omitempty" json:"timeout"`
+ Version string `yaml:"version,omitempty" json:"version"`
+ MaxOIDs int `yaml:"max_request_size,omitempty" json:"max_request_size"`
+ MaxRepetitions int `yaml:"max_repetitions,omitempty" json:"max_repetitions"`
+ }
+ ChartConfig struct {
+ ID string `yaml:"id" json:"id"`
+ Title string `yaml:"title" json:"title"`
+ Units string `yaml:"units" json:"units"`
+ Family string `yaml:"family" json:"family"`
+ Type string `yaml:"type" json:"type"`
+ Priority int `yaml:"priority" json:"priority"`
+ IndexRange []int `yaml:"multiply_range,omitempty" json:"multiply_range"`
+ Dimensions []DimensionConfig `yaml:"dimensions" json:"dimensions"`
+ }
+ DimensionConfig struct {
+ OID string `yaml:"oid" json:"oid"`
+ Name string `yaml:"name" json:"name"`
+ Algorithm string `yaml:"algorithm" json:"algorithm"`
+ Multiplier int `yaml:"multiplier" json:"multiplier"`
+ Divisor int `yaml:"divisor" json:"divisor"`
+ }
+)
diff --git a/src/go/plugin/go.d/modules/snmp/config_schema.json b/src/go/plugin/go.d/modules/snmp/config_schema.json
new file mode 100644
index 00000000..8deb4f6c
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/config_schema.json
@@ -0,0 +1,422 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 10
+ },
+ "hostname": {
+ "title": "Hostname",
+ "description": "The hostname or IP address of the SNMP-enabled device.",
+ "type": "string"
+ },
+ "community": {
+ "title": "SNMPv1/2 community",
+ "description": "The SNMP community string for SNMPv1/v2c authentication.",
+ "type": "string",
+ "default": "public"
+ },
+ "network_interface_filter": {
+ "title": "Network interface filter",
+ "description": "Configuration for filtering specific network interfaces. If left empty, no interfaces will be filtered. You can filter interfaces by name or type using [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns).",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "by_name": {
+ "title": "By Name",
+ "description": "Specify the interface name or a pattern to match against the [ifName](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.31.1.1.1.1) label.",
+ "type": "string"
+ },
+ "by_type": {
+ "title": "By Type",
+ "description": "Specify the interface type or a pattern to match against the [ifType](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.2.2.1.3) label.",
+ "type": "string"
+ }
+ }
+ },
+ "options": {
+ "title": "Options",
+ "description": "Configuration options for SNMP monitoring.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "version": {
+ "title": "SNMP version",
+ "type": "string",
+ "enum": [
+ "1",
+ "2c",
+ "3"
+ ],
+ "default": "2c"
+ },
+ "port": {
+ "title": "Port",
+ "description": "The port number on which the SNMP service is running.",
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 161
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "The timeout duration in seconds for SNMP requests.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 5
+ },
+ "retries": {
+ "title": "Retries",
+ "description": "The number of retries to attempt for SNMP requests.",
+ "type": "integer",
+ "minimum": 0,
+ "default": 1
+ },
+ "max_repetitions": {
+ "title": "Max repetitions",
+ "description": "Controls how many SNMP variables to retrieve in a single GETBULK request.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 25
+ },
+ "max_request_size": {
+ "title": "Max OIDs",
+ "description": "The maximum number of OIDs allowed in a single GET request.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 60
+ }
+ },
+ "required": [
+ "version",
+ "port",
+ "retries",
+ "timeout",
+ "max_request_size"
+ ]
+ },
+ "user": {
+ "title": "SNMPv3 configuration",
+ "description": "Configuration options for SNMPv3 authentication and encryption.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "name": {
+ "title": "Username",
+ "description": "The username for SNMPv3 authentication.",
+ "type": "string"
+ },
+ "level": {
+ "title": "Security level",
+ "description": "Controls the security aspects of SNMPv3 communication, including authentication and encryption.",
+ "type": "string",
+ "enum": [
+ "none",
+ "authNoPriv",
+ "authPriv"
+ ],
+ "default": "authPriv"
+ },
+ "auth_proto": {
+ "title": "Authentication protocol",
+ "type": "string",
+ "enum": [
+ "none",
+ "md5",
+ "sha",
+ "sha224",
+ "sha256",
+ "sha384",
+ "sha512"
+ ],
+ "default": "sha512"
+ },
+ "auth_key": {
+ "title": "Authentication passphrase",
+ "type": "string"
+ },
+ "priv_proto": {
+ "title": "Privacy protocol",
+ "type": "string",
+ "enum": [
+ "none",
+ "des",
+ "aes",
+ "aes192",
+ "aes256",
+ "aes192c"
+ ],
+ "default": "aes192c"
+ },
+ "priv_key": {
+ "title": "Privacy passphrase",
+ "type": "string"
+ }
+ }
+ },
+ "charts": {
+ "title": "Charts configuration",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "items": {
+ "title": "Chart",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "id": {
+ "title": "ID",
+ "description": "Unique identifier for the chart.",
+ "type": "string"
+ },
+ "title": {
+ "title": "Title",
+ "description": "Title of the chart.",
+ "type": "string"
+ },
+ "units": {
+ "title": "Units",
+ "description": "Unit label for the vertical axis on charts.",
+ "type": "string"
+ },
+ "family": {
+ "title": "Family",
+ "description": "Subsection on the dashboard where the chart will be displayed.",
+ "type": "string"
+ },
+ "type": {
+ "title": "Type",
+ "type": "string",
+ "enum": [
+ "line",
+ "area",
+ "stacked"
+ ],
+ "default": "line"
+ },
+ "priority": {
+ "title": "Priority",
+ "description": "Rendering priority of the chart on the dashboard. Lower priority values will cause the chart to appear before those with higher priority values.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 90000
+ },
+ "multiply_range": {
+ "title": "OID index range",
+ "description": "Specifies the range of indexes used to create multiple charts. If set, a chart will be created for each index in the specified range. Each chart will have the index appended to the OID dimension.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "title": "Index",
+ "type": "integer",
+ "minimum": 0
+ },
+ "uniqueItems": true,
+ "maxItems": 2
+ },
+ "dimensions": {
+ "title": "Dimensions",
+ "description": "Configuration for dimensions of the chart.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "uniqueItems": true,
+ "minItems": 1,
+ "items": {
+ "title": "Dimension configuration",
+ "type": [
+ "object",
+ "null"
+ ],
+ "properties": {
+ "oid": {
+ "title": "OID",
+ "description": "SNMP OID.",
+ "type": "string"
+ },
+ "name": {
+ "title": "Dimension",
+ "description": "Name of the dimension.",
+ "type": "string"
+ },
+ "algorithm": {
+ "title": "Algorithm",
+ "description": "Algorithm of the dimension.",
+ "type": "string",
+ "enum": [
+ "absolute",
+ "incremental"
+ ],
+ "default": "absolute"
+ },
+ "multiplier": {
+ "title": "Multiplier",
+ "description": "Value to multiply the collected value.",
+ "type": "integer",
+ "not": {
+ "const": 0
+ },
+ "default": 1
+ },
+ "divisor": {
+ "title": "Divisor",
+ "description": "Value to divide the collected value.",
+ "type": "integer",
+ "not": {
+ "const": 0
+ },
+ "default": 1
+ }
+ },
+ "required": [
+ "oid",
+ "name",
+ "algorithm",
+ "multiplier",
+ "divisor"
+ ]
+ }
+ }
+ },
+ "required": [
+ "id",
+ "title",
+ "units",
+ "family",
+ "type",
+ "priority",
+ "dimensions"
+ ]
+ }
+ }
+ },
+ "required": [
+ "hostname",
+ "community",
+ "options"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "network_interface_filter": {
+ "ui:collapsible": true
+ },
+ "community": {
+ "ui:widget": "password"
+ },
+ "options": {
+ "version": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "max_repetitions": {
+ "ui:help": "A higher value retrieves more data in fewer round trips, potentially improving efficiency. This reduces network overhead compared to sending multiple individual requests. **Important**: Setting a value too high might cause the target device to return no data."
+ }
+ },
+ "user": {
+ "name": {
+ "ui:widget": "password"
+ },
+ "level": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "auth_proto": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "priv_proto": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ }
+ },
+ "charts": {
+ "items": {
+ "ui:collapsible": true,
+ "type": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ },
+ "multiply_range": {
+ "ui:listFlavour": "list"
+ },
+ "dimensions": {
+ "items": {
+ "ui:collapsible": true,
+ "algorithm": {
+ "ui:widget": "radio",
+ "ui:options": {
+ "inline": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "hostname",
+ "community"
+ ]
+ },
+ {
+ "title": "Options",
+ "fields": [
+ "network_interface_filter",
+ "options"
+ ]
+ },
+ {
+ "title": "SNMPv3",
+ "fields": [
+ "user"
+ ]
+ },
+ {
+ "title": "Charts",
+ "fields": [
+ "charts"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/go/plugin/go.d/modules/snmp/init.go b/src/go/plugin/go.d/modules/snmp/init.go
new file mode 100644
index 00000000..acde4b9b
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/init.go
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/matcher"
+
+ "github.com/gosnmp/gosnmp"
+)
+
+func (s *SNMP) validateConfig() error {
+ if s.Hostname == "" {
+ return errors.New("SNMP hostname is required")
+ }
+ return nil
+}
+
+func (s *SNMP) initSNMPClient() (gosnmp.Handler, error) {
+ client := s.newSnmpClient()
+
+ client.SetTarget(s.Hostname)
+ client.SetPort(uint16(s.Options.Port))
+ client.SetRetries(s.Options.Retries)
+ client.SetTimeout(time.Duration(s.Options.Timeout) * time.Second)
+ client.SetMaxOids(s.Options.MaxOIDs)
+ client.SetMaxRepetitions(uint32(s.Options.MaxRepetitions))
+
+ ver := parseSNMPVersion(s.Options.Version)
+ comm := s.Community
+
+ switch ver {
+ case gosnmp.Version1:
+ client.SetCommunity(comm)
+ client.SetVersion(gosnmp.Version1)
+ case gosnmp.Version2c:
+ client.SetCommunity(comm)
+ client.SetVersion(gosnmp.Version2c)
+ case gosnmp.Version3:
+ if s.User.Name == "" {
+ return nil, errors.New("username is required for SNMPv3")
+ }
+ client.SetVersion(gosnmp.Version3)
+ client.SetSecurityModel(gosnmp.UserSecurityModel)
+ client.SetMsgFlags(parseSNMPv3SecurityLevel(s.User.SecurityLevel))
+ client.SetSecurityParameters(&gosnmp.UsmSecurityParameters{
+ UserName: s.User.Name,
+ AuthenticationProtocol: parseSNMPv3AuthProtocol(s.User.AuthProto),
+ AuthenticationPassphrase: s.User.AuthKey,
+ PrivacyProtocol: parseSNMPv3PrivProtocol(s.User.PrivProto),
+ PrivacyPassphrase: s.User.PrivKey,
+ })
+ default:
+ return nil, fmt.Errorf("invalid SNMP version: %s", s.Options.Version)
+ }
+
+ s.Info(snmpClientConnInfo(client))
+
+ return client, nil
+}
+
+func (s *SNMP) initNetIfaceFilters() (matcher.Matcher, matcher.Matcher, error) {
+ byName, byType := matcher.FALSE(), matcher.FALSE()
+
+ if v := s.NetworkInterfaceFilter.ByName; v != "" {
+ m, err := matcher.NewSimplePatternsMatcher(v)
+ if err != nil {
+ return nil, nil, err
+ }
+ byName = m
+ }
+
+ if v := s.NetworkInterfaceFilter.ByType; v != "" {
+ m, err := matcher.NewSimplePatternsMatcher(v)
+ if err != nil {
+ return nil, nil, err
+ }
+ byType = m
+ }
+
+ return byName, byType, nil
+}
+
+func (s *SNMP) initOIDs() (oids []string) {
+ for _, c := range *s.charts {
+ for _, d := range c.Dims {
+ oids = append(oids, d.ID)
+ }
+ }
+ return oids
+}
+
+func parseSNMPVersion(version string) gosnmp.SnmpVersion {
+ switch version {
+ case "0", "1":
+ return gosnmp.Version1
+ case "2", "2c", "":
+ return gosnmp.Version2c
+ case "3":
+ return gosnmp.Version3
+ default:
+ return gosnmp.Version2c
+ }
+}
+
+func parseSNMPv3SecurityLevel(level string) gosnmp.SnmpV3MsgFlags {
+ switch level {
+ case "1", "none", "noAuthNoPriv", "":
+ return gosnmp.NoAuthNoPriv
+ case "2", "authNoPriv":
+ return gosnmp.AuthNoPriv
+ case "3", "authPriv":
+ return gosnmp.AuthPriv
+ default:
+ return gosnmp.NoAuthNoPriv
+ }
+}
+
+func parseSNMPv3AuthProtocol(protocol string) gosnmp.SnmpV3AuthProtocol {
+ switch protocol {
+ case "1", "none", "noAuth", "":
+ return gosnmp.NoAuth
+ case "2", "md5":
+ return gosnmp.MD5
+ case "3", "sha":
+ return gosnmp.SHA
+ case "4", "sha224":
+ return gosnmp.SHA224
+ case "5", "sha256":
+ return gosnmp.SHA256
+ case "6", "sha384":
+ return gosnmp.SHA384
+ case "7", "sha512":
+ return gosnmp.SHA512
+ default:
+ return gosnmp.NoAuth
+ }
+}
+
+func parseSNMPv3PrivProtocol(protocol string) gosnmp.SnmpV3PrivProtocol {
+ switch protocol {
+ case "1", "none", "noPriv", "":
+ return gosnmp.NoPriv
+ case "2", "des":
+ return gosnmp.DES
+ case "3", "aes":
+ return gosnmp.AES
+ case "4", "aes192":
+ return gosnmp.AES192
+ case "5", "aes256":
+ return gosnmp.AES256
+ case "6", "aes192c":
+ return gosnmp.AES192C
+ case "7", "aes256c":
+ return gosnmp.AES256C
+ default:
+ return gosnmp.NoPriv
+ }
+}
+
+func snmpClientConnInfo(c gosnmp.Handler) string {
+ var info strings.Builder
+ info.WriteString(fmt.Sprintf("hostname='%s',port='%d',snmp_version='%s'", c.Target(), c.Port(), c.Version()))
+ switch c.Version() {
+ case gosnmp.Version1, gosnmp.Version2c:
+ info.WriteString(fmt.Sprintf(",community='%s'", c.Community()))
+ case gosnmp.Version3:
+ info.WriteString(fmt.Sprintf(",security_level='%d,%s'", c.MsgFlags(), c.SecurityParameters().Description()))
+ }
+ return info.String()
+}
diff --git a/src/go/plugin/go.d/modules/snmp/integrations/snmp_devices.md b/src/go/plugin/go.d/modules/snmp/integrations/snmp_devices.md
new file mode 100644
index 00000000..a2431b00
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/integrations/snmp_devices.md
@@ -0,0 +1,496 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/snmp/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/snmp/metadata.yaml"
+sidebar_label: "SNMP devices"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/Generic Collecting Metrics"
+most_popular: True
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# SNMP devices
+
+
+<img src="https://netdata.cloud/img/snmp.png" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: snmp
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This SNMP collector discovers and gathers statistics for network interfaces on SNMP-enabled devices:
+
+- Traffic
+- Packets (unicast, multicast, broadcast)
+- Errors
+- Discards
+- Administrative and operational status
+
+Additionally, it collects overall device uptime.
+
+It is compatible with all SNMP versions (v1, v2c, and v3) and uses the [gosnmp](https://github.com/gosnmp/gosnmp) package.
+
+**For advanced users**:
+
+- You can manually specify custom OIDs (Object Identifiers) to retrieve specific data points beyond the default metrics.
+- However, defining custom charts with dimensions for these OIDs requires manual configuration.
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+**Device limitations**: Many SNMP switches and routers have limited processing power. They might not be able to report data as frequently as desired. You can monitor response times using go.d.plugin in debug mode to identify potential bottlenecks.
+
+**Concurrent access**: If multiple collectors or tools access the same SNMP device simultaneously, data points might be skipped. This is a limitation of the device itself, not this collector. To mitigate this, consider increasing the collection interval (update_every) to reduce the frequency of requests.
+
+
+
+## Metrics
+
+Metrics grouped by *scope*.
+
+The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
+
+The metrics that will be collected are defined in the configuration file.
+
+### Per snmp device
+
+These metrics refer to the SNMP device.
+
+Labels:
+
+| Label | Description |
+|:-----------|:----------------|
+| sysName | SNMP device's system name (OID: [1.3.6.1.2.1.1.5](https://oidref.com/1.3.6.1.2.1.1.5)). |
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| snmp.device_uptime | uptime | seconds |
+
+### Per network interface
+
+Network interfaces of the SNMP device being monitored. These metrics refer to each interface.
+
+Labels:
+
+| Label | Description |
+|:-----------|:----------------|
+| sysName | SNMP device's system name (OID: [1.3.6.1.2.1.1.5](https://oidref.com/1.3.6.1.2.1.1.5)). |
+| ifDescr | Network interface description (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.2.2.1.2)). |
+| ifName | Network interface name (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.31.1.1.1.1)). |
+| ifType | Network interface type (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.2.2.1.3)). |
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| snmp.device_net_interface_traffic | received, sent | kilobits/s |
+| snmp.device_net_interface_unicast | received, sent | packets/s |
+| snmp.device_net_interface_multicast | received, sent | packets/s |
+| snmp.device_net_interface_broadcast | received, sent | packets/s |
+| snmp.device_net_interface_errors | inbound, outbound | errors/s |
+| snmp.device_net_interface_discards | inbound, outbound | discards/s |
+| snmp.device_net_interface_admin_status | up, down, testing | status |
+| snmp.device_net_interface_oper_status | up, down, testing, unknown, dormant, not_present, lower_layer_down | status |
+
+
+
+## Alerts
+
+There are no alerts configured by default for this integration.
+
+
+## Setup
+
+### Prerequisites
+
+No action required.
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/snmp.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/snmp.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details open><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 10 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| hostname | Target ipv4 address. | | yes |
+| community | SNMPv1/2 community string. | public | no |
+| options.version | SNMP version. Available versions: 1, 2, 3. | 2 | no |
+| options.port | Target port. | 161 | no |
+| options.retries | Retries to attempt. | 1 | no |
+| options.timeout | SNMP request/response timeout. | 5 | no |
+| options.max_repetitions | Controls how many SNMP variables to retrieve in a single GETBULK request. | 25 | no |
+| options.max_request_size | Maximum number of OIDs allowed in a single GET request. | 60 | no |
+| network_interface_filter.by_name | Filter interfaces by their names using [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns). | | no |
+| network_interface_filter.by_type | Filter interfaces by their types using [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns). | | no |
+| user.name | SNMPv3 user name. | | no |
+| user.name | Security level of SNMPv3 messages. | | no |
+| user.auth_proto | Security level of SNMPv3 messages. | | no |
+| user.name | Authentication protocol for SNMPv3 messages. | | no |
+| user.auth_key | Authentication protocol pass phrase. | | no |
+| user.priv_proto | Privacy protocol for SNMPv3 messages. | | no |
+| user.priv_key | Privacy protocol pass phrase. | | no |
+| charts | List of charts. | [] | yes |
+| charts.id | Chart ID. Used to uniquely identify the chart. | | yes |
+| charts.title | Chart title. | Untitled chart | no |
+| charts.units | Chart units. | num | no |
+| charts.family | Chart family. | charts.id | no |
+| charts.type | Chart type (line, area, stacked). | line | no |
+| charts.priority | Chart priority. | 70000 | no |
+| charts.multiply_range | Used when you need to define many charts using incremental OIDs. | [] | no |
+| charts.dimensions | List of chart dimensions. | [] | yes |
+| charts.dimensions.oid | Collected metric OID. | | yes |
+| charts.dimensions.name | Dimension name. | | yes |
+| charts.dimensions.algorithm | Dimension algorithm (absolute, incremental). | absolute | no |
+| charts.dimensions.multiplier | Collected value multiplier, applied to convert it properly to units. | 1 | no |
+| charts.dimensions.divisor | Collected value divisor, applied to convert it properly to units. | 1 | no |
+
+##### user.auth_proto
+
+The security of an SNMPv3 message as per RFC 3414 (`user.level`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|------------------------------------------|
+| none | 1 | no message authentication or encryption |
+| authNoPriv | 2 | message authentication and no encryption |
+| authPriv | 3 | message authentication and encryption |
+
+
+##### user.name
+
+The digest algorithm for SNMPv3 messages that require authentication (`user.auth_proto`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|-------------------------------------------|
+| none | 1 | no message authentication |
+| md5 | 2 | MD5 message authentication (HMAC-MD5-96) |
+| sha | 3 | SHA message authentication (HMAC-SHA-96) |
+| sha224 | 4 | SHA message authentication (HMAC-SHA-224) |
+| sha256 | 5 | SHA message authentication (HMAC-SHA-256) |
+| sha384 | 6 | SHA message authentication (HMAC-SHA-384) |
+| sha512 | 7 | SHA message authentication (HMAC-SHA-512) |
+
+
+##### user.priv_proto
+
+The encryption algorithm for SNMPv3 messages that require privacy (`user.priv_proto`):
+
+| String value | Int value | Description |
+|:------------:|:---------:|-------------------------------------------------------------------------|
+| none | 1 | no message encryption |
+| des | 2 | ES encryption (CBC-DES) |
+| aes | 3 | 128-bit AES encryption (CFB-AES-128) |
+| aes192 | 4 | 192-bit AES encryption (CFB-AES-192) with "Blumenthal" key localization |
+| aes256 | 5 | 256-bit AES encryption (CFB-AES-256) with "Blumenthal" key localization |
+| aes192c | 6 | 192-bit AES encryption (CFB-AES-192) with "Reeder" key localization |
+| aes256c | 7 | 256-bit AES encryption (CFB-AES-256) with "Reeder" key localization |
+
+
+</details>
+
+#### Examples
+
+##### SNMPv1/2
+
+In this example:
+
+- the SNMP device is `192.0.2.1`.
+- the SNMP version is `2`.
+- the SNMP community is `public`.
+- we will update the values every 10 seconds.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+
+```
+</details>
+
+##### SNMPv3
+
+To use SNMPv3:
+
+- use `user` instead of `community`.
+- set `options.version` to 3.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ options:
+ version: 3
+ user:
+ name: username
+ level: authPriv
+ auth_proto: sha256
+ auth_key: auth_protocol_passphrase
+ priv_proto: aes256
+ priv_key: priv_protocol_passphrase
+
+```
+</details>
+
+##### Custom OIDs
+
+In this example:
+
+- the SNMP device is `192.0.2.1`.
+- the SNMP version is `2`.
+- the SNMP community is `public`.
+- we will update the values every 10 seconds.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - id: "bandwidth_port2"
+ title: "Switch Bandwidth for port 2"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.2"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.2"
+ multiplier: -8
+ divisor: 1000
+
+```
+</details>
+
+##### Custom OIDs with multiply range
+
+If you need to define many charts using incremental OIDs, you can use the `charts.multiply_range` option.
+
+This is like the SNMPv1/2 example, but the option will multiply the current chart from 1 to 24 inclusive, producing 24 charts in total for the 24 ports of the switch `192.0.2.1`.
+
+Each of the 24 new charts will have its id (1-24) appended at:
+
+- its chart unique `id`, i.e. `bandwidth_port_1` to `bandwidth_port_24`.
+- its title, i.e. `Switch Bandwidth for port 1` to `Switch Bandwidth for port 24`.
+- its `oid` (for all dimensions), i.e. dimension in will be `1.3.6.1.2.1.2.2.1.10.1` to `1.3.6.1.2.1.2.2.1.10.24`.
+- its `priority` will be incremented for each chart so that the charts will appear on the dashboard in this order.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port"
+ title: "Switch Bandwidth for port"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ multiply_range: [1, 24]
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16"
+ multiplier: -8
+ divisor: 1000
+
+```
+</details>
+
+##### Multiple devices with a common configuration
+
+YAML supports [anchors](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases).
+The `&` defines and names an anchor, and the `*` uses it. `<<: *anchor` means, inject the anchor, then extend. We can use anchors to share the common configuration for multiple devices.
+
+The following example:
+
+- adds an `anchor` to the first job.
+- injects (copies) the first job configuration to the second and updates `name` and `hostname` parameters.
+- injects (copies) the first job configuration to the third and updates `name` and `hostname` parameters.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - &anchor
+ name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - <<: *anchor
+ name: switch2
+ hostname: "192.0.2.2"
+ - <<: *anchor
+ name: switch3
+ hostname: "192.0.2.3"
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+**Important**: Debug mode is not supported for data collection jobs created via the UI using the Dyncfg feature.
+
+To troubleshoot issues with the `snmp` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m snmp
+ ```
+
+### Getting Logs
+
+If you're encountering problems with the `snmp` collector, follow these steps to retrieve logs and identify potential issues:
+
+- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
+- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem.
+
+#### System with systemd
+
+Use the following command to view logs generated since the last Netdata service restart:
+
+```bash
+journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep snmp
+```
+
+#### System without systemd
+
+Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
+
+```bash
+grep snmp /var/log/netdata/collector.log
+```
+
+**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
+
+#### Docker Container
+
+If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
+
+```bash
+docker logs netdata 2>&1 | grep snmp
+```
+
+
diff --git a/src/go/plugin/go.d/modules/snmp/metadata.yaml b/src/go/plugin/go.d/modules/snmp/metadata.yaml
new file mode 100644
index 00000000..0475a2f2
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/metadata.yaml
@@ -0,0 +1,496 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-snmp
+ plugin_name: go.d.plugin
+ module_name: snmp
+ monitored_instance:
+ name: SNMP devices
+ link: ""
+ icon_filename: snmp.png
+ categories:
+ - data-collection.generic-data-collection
+ keywords:
+ - snmp
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: true
+ overview:
+ data_collection:
+ metrics_description: |
+ This SNMP collector discovers and gathers statistics for network interfaces on SNMP-enabled devices:
+
+ - Traffic
+ - Packets (unicast, multicast, broadcast)
+ - Errors
+ - Discards
+ - Administrative and operational status
+
+ Additionally, it collects overall device uptime.
+
+ It is compatible with all SNMP versions (v1, v2c, and v3) and uses the [gosnmp](https://github.com/gosnmp/gosnmp) package.
+
+ **For advanced users**:
+
+ - You can manually specify custom OIDs (Object Identifiers) to retrieve specific data points beyond the default metrics.
+ - However, defining custom charts with dimensions for these OIDs requires manual configuration.
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: |
+ **Device limitations**: Many SNMP switches and routers have limited processing power. They might not be able to report data as frequently as desired. You can monitor response times using go.d.plugin in debug mode to identify potential bottlenecks.
+
+ **Concurrent access**: If multiple collectors or tools access the same SNMP device simultaneously, data points might be skipped. This is a limitation of the device itself, not this collector. To mitigate this, consider increasing the collection interval (update_every) to reduce the frequency of requests.
+ setup:
+ prerequisites:
+ list: []
+ configuration:
+ file:
+ name: go.d/snmp.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 10
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: hostname
+ description: Target ipv4 address.
+ default_value: ""
+ required: true
+ - name: community
+ description: SNMPv1/2 community string.
+ default_value: public
+ required: false
+ - name: options.version
+ description: "SNMP version. Available versions: 1, 2, 3."
+ default_value: 2
+ required: false
+ - name: options.port
+ description: Target port.
+ default_value: 161
+ required: false
+ - name: options.retries
+ description: Retries to attempt.
+ default_value: 1
+ required: false
+ - name: options.timeout
+ description: SNMP request/response timeout.
+ default_value: 5
+ required: false
+ - name: options.max_repetitions
+ description: Controls how many SNMP variables to retrieve in a single GETBULK request.
+ default_value: 25
+ required: false
+ - name: options.max_request_size
+ description: Maximum number of OIDs allowed in a single GET request.
+ default_value: 60
+ required: false
+ - name: network_interface_filter.by_name
+ description: "Filter interfaces by their names using [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns)."
+ default_value: ""
+ required: false
+ - name: network_interface_filter.by_type
+ description: "Filter interfaces by their types using [simple patterns](/src/libnetdata/simple_pattern/README.md#simple-patterns)."
+ default_value: ""
+ required: false
+ - name: user.name
+ description: SNMPv3 user name.
+ default_value: ""
+ required: false
+ - name: user.name
+ description: Security level of SNMPv3 messages.
+ default_value: ""
+ required: false
+ - name: user.auth_proto
+ description: Security level of SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The security of an SNMPv3 message as per RFC 3414 (`user.level`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|------------------------------------------|
+ | none | 1 | no message authentication or encryption |
+ | authNoPriv | 2 | message authentication and no encryption |
+ | authPriv | 3 | message authentication and encryption |
+ - name: user.name
+ description: Authentication protocol for SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The digest algorithm for SNMPv3 messages that require authentication (`user.auth_proto`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|-------------------------------------------|
+ | none | 1 | no message authentication |
+ | md5 | 2 | MD5 message authentication (HMAC-MD5-96) |
+ | sha | 3 | SHA message authentication (HMAC-SHA-96) |
+ | sha224 | 4 | SHA message authentication (HMAC-SHA-224) |
+ | sha256 | 5 | SHA message authentication (HMAC-SHA-256) |
+ | sha384 | 6 | SHA message authentication (HMAC-SHA-384) |
+ | sha512 | 7 | SHA message authentication (HMAC-SHA-512) |
+ - name: user.auth_key
+ description: Authentication protocol pass phrase.
+ default_value: ""
+ required: false
+ - name: user.priv_proto
+ description: Privacy protocol for SNMPv3 messages.
+ default_value: ""
+ required: false
+ detailed_description: |
+ The encryption algorithm for SNMPv3 messages that require privacy (`user.priv_proto`):
+
+ | String value | Int value | Description |
+ |:------------:|:---------:|-------------------------------------------------------------------------|
+ | none | 1 | no message encryption |
+ | des | 2 | ES encryption (CBC-DES) |
+ | aes | 3 | 128-bit AES encryption (CFB-AES-128) |
+ | aes192 | 4 | 192-bit AES encryption (CFB-AES-192) with "Blumenthal" key localization |
+ | aes256 | 5 | 256-bit AES encryption (CFB-AES-256) with "Blumenthal" key localization |
+ | aes192c | 6 | 192-bit AES encryption (CFB-AES-192) with "Reeder" key localization |
+ | aes256c | 7 | 256-bit AES encryption (CFB-AES-256) with "Reeder" key localization |
+ - name: user.priv_key
+ description: Privacy protocol pass phrase.
+ default_value: ""
+ required: false
+ - name: charts
+ description: List of charts.
+ default_value: "[]"
+ required: true
+ - name: charts.id
+ description: Chart ID. Used to uniquely identify the chart.
+ default_value: ""
+ required: true
+ - name: charts.title
+ description: Chart title.
+ default_value: "Untitled chart"
+ required: false
+ - name: charts.units
+ description: Chart units.
+ default_value: num
+ required: false
+ - name: charts.family
+ description: Chart family.
+ default_value: charts.id
+ required: false
+ - name: charts.type
+ description: Chart type (line, area, stacked).
+ default_value: line
+ required: false
+ - name: charts.priority
+ description: Chart priority.
+ default_value: 70000
+ required: false
+ - name: charts.multiply_range
+ description: Used when you need to define many charts using incremental OIDs.
+ default_value: "[]"
+ required: false
+ - name: charts.dimensions
+ description: List of chart dimensions.
+ default_value: "[]"
+ required: true
+ - name: charts.dimensions.oid
+ description: Collected metric OID.
+ default_value: ""
+ required: true
+ - name: charts.dimensions.name
+ description: Dimension name.
+ default_value: ""
+ required: true
+ - name: charts.dimensions.algorithm
+ description: Dimension algorithm (absolute, incremental).
+ default_value: absolute
+ required: false
+ - name: charts.dimensions.multiplier
+ description: Collected value multiplier, applied to convert it properly to units.
+ default_value: 1
+ required: false
+ - name: charts.dimensions.divisor
+ description: Collected value divisor, applied to convert it properly to units.
+ default_value: 1
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: SNMPv1/2
+ description: |
+ In this example:
+
+ - the SNMP device is `192.0.2.1`.
+ - the SNMP version is `2`.
+ - the SNMP community is `public`.
+ - we will update the values every 10 seconds.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+ - name: SNMPv3
+ description: |
+ To use SNMPv3:
+
+ - use `user` instead of `community`.
+ - set `options.version` to 3.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ options:
+ version: 3
+ user:
+ name: username
+ level: authPriv
+ auth_proto: sha256
+ auth_key: auth_protocol_passphrase
+ priv_proto: aes256
+ priv_key: priv_protocol_passphrase
+ - name: Custom OIDs
+ description: |
+ In this example:
+
+ - the SNMP device is `192.0.2.1`.
+ - the SNMP version is `2`.
+ - the SNMP community is `public`.
+ - we will update the values every 10 seconds.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: 192.0.2.1
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - id: "bandwidth_port2"
+ title: "Switch Bandwidth for port 2"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.2"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.2"
+ multiplier: -8
+ divisor: 1000
+ - name: Custom OIDs with multiply range
+ description: |
+ If you need to define many charts using incremental OIDs, you can use the `charts.multiply_range` option.
+
+ This is like the SNMPv1/2 example, but the option will multiply the current chart from 1 to 24 inclusive, producing 24 charts in total for the 24 ports of the switch `192.0.2.1`.
+
+ Each of the 24 new charts will have its id (1-24) appended at:
+
+ - its chart unique `id`, i.e. `bandwidth_port_1` to `bandwidth_port_24`.
+ - its title, i.e. `Switch Bandwidth for port 1` to `Switch Bandwidth for port 24`.
+ - its `oid` (for all dimensions), i.e. dimension in will be `1.3.6.1.2.1.2.2.1.10.1` to `1.3.6.1.2.1.2.2.1.10.24`.
+ - its `priority` will be incremented for each chart so that the charts will appear on the dashboard in this order.
+ config: |
+ jobs:
+ - name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port"
+ title: "Switch Bandwidth for port"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ multiply_range: [1, 24]
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16"
+ multiplier: -8
+ divisor: 1000
+ - name: Multiple devices with a common configuration
+ description: |
+ YAML supports [anchors](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases).
+ The `&` defines and names an anchor, and the `*` uses it. `<<: *anchor` means, inject the anchor, then extend. We can use anchors to share the common configuration for multiple devices.
+
+ The following example:
+
+ - adds an `anchor` to the first job.
+ - injects (copies) the first job configuration to the second and updates `name` and `hostname` parameters.
+ - injects (copies) the first job configuration to the third and updates `name` and `hostname` parameters.
+ config: |
+ jobs:
+ - &anchor
+ name: switch
+ update_every: 10
+ hostname: "192.0.2.1"
+ community: public
+ options:
+ version: 2
+ charts:
+ - id: "bandwidth_port1"
+ title: "Switch Bandwidth for port 1"
+ units: "kilobits/s"
+ type: "area"
+ family: "ports"
+ dimensions:
+ - name: "in"
+ oid: "1.3.6.1.2.1.2.2.1.10.1"
+ algorithm: "incremental"
+ multiplier: 8
+ divisor: 1000
+ - name: "out"
+ oid: "1.3.6.1.2.1.2.2.1.16.1"
+ multiplier: -8
+ divisor: 1000
+ - <<: *anchor
+ name: switch2
+ hostname: "192.0.2.2"
+ - <<: *anchor
+ name: switch3
+ hostname: "192.0.2.3"
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: The metrics that will be collected are defined in the configuration file.
+ availability: []
+ scopes:
+ - name: snmp device
+ description: These metrics refer to the SNMP device.
+ labels:
+ - name: sysName
+ description: "SNMP device's system name (OID: [1.3.6.1.2.1.1.5](https://oidref.com/1.3.6.1.2.1.1.5))."
+ metrics:
+ - name: snmp.device_uptime
+ description: SNMP device uptime
+ unit: seconds
+ chart_type: line
+ dimensions:
+ - name: uptime
+ - name: network interface
+ description: Network interfaces of the SNMP device being monitored. These metrics refer to each interface.
+ labels:
+ - name: sysName
+ description: "SNMP device's system name (OID: [1.3.6.1.2.1.1.5](https://oidref.com/1.3.6.1.2.1.1.5))."
+ - name: ifDescr
+ description: "Network interface description (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.2.2.1.2))."
+ - name: ifName
+ description: "Network interface name (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.31.1.1.1.1))."
+ - name: ifType
+ description: "Network interface type (OID: [1.3.6.1.2.1.2.2.1.2](https://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.2.2.1.3))."
+ metrics:
+ - name: snmp.device_net_interface_traffic
+ description: SNMP device network interface traffic
+ unit: kilobits/s
+ chart_type: area
+ dimensions:
+ - name: received
+ - name: sent
+ - name: snmp.device_net_interface_unicast
+ description: SNMP device network interface unicast packets
+ unit: packets/s
+ chart_type: line
+ dimensions:
+ - name: received
+ - name: sent
+ - name: snmp.device_net_interface_multicast
+ description: SNMP device network interface multicast packets
+ unit: packets/s
+ chart_type: line
+ dimensions:
+ - name: received
+ - name: sent
+ - name: snmp.device_net_interface_broadcast
+ description: SNMP device network interface broadcast packets
+ unit: packets/s
+ chart_type: line
+ dimensions:
+ - name: received
+ - name: sent
+ - name: snmp.device_net_interface_errors
+ description: SNMP device network interface errors
+ unit: errors/s
+ chart_type: line
+ dimensions:
+ - name: inbound
+ - name: outbound
+ - name: snmp.device_net_interface_discards
+ description: SNMP device network interface discards
+ unit: discards/s
+ chart_type: line
+ dimensions:
+ - name: inbound
+ - name: outbound
+ - name: snmp.device_net_interface_admin_status
+ description: SNMP device network interface administrative status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: up
+ - name: down
+ - name: testing
+ - name: snmp.device_net_interface_oper_status
+ description: SNMP device network interface operational status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: up
+ - name: down
+ - name: testing
+ - name: unknown
+ - name: dormant
+ - name: not_present
+ - name: lower_layer_down
diff --git a/src/go/plugin/go.d/modules/snmp/netif.go b/src/go/plugin/go.d/modules/snmp/netif.go
new file mode 100644
index 00000000..1345e5ee
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/netif.go
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "fmt"
+ "strings"
+)
+
+const (
+ oidIfIndex = "1.3.6.1.2.1.2.2.1.1"
+ oidIfDescr = "1.3.6.1.2.1.2.2.1.2"
+ oidIfType = "1.3.6.1.2.1.2.2.1.3"
+ oidIfMtu = "1.3.6.1.2.1.2.2.1.4"
+ oidIfSpeed = "1.3.6.1.2.1.2.2.1.5"
+ oidIfPhysAddress = "1.3.6.1.2.1.2.2.1.6"
+ oidIfAdminStatus = "1.3.6.1.2.1.2.2.1.7"
+ oidIfOperStatus = "1.3.6.1.2.1.2.2.1.8"
+ oidIfLastChange = "1.3.6.1.2.1.2.2.1.9"
+ oidIfInOctets = "1.3.6.1.2.1.2.2.1.10"
+ oidIfInUcastPkts = "1.3.6.1.2.1.2.2.1.11"
+ oidIfInNUcastPkts = "1.3.6.1.2.1.2.2.1.12"
+ oidIfInDiscards = "1.3.6.1.2.1.2.2.1.13"
+ oidIfInErrors = "1.3.6.1.2.1.2.2.1.14"
+ oidIfInUnknownProtos = "1.3.6.1.2.1.2.2.1.15"
+ oidIfOutOctets = "1.3.6.1.2.1.2.2.1.16"
+ oidIfOutUcastPkts = "1.3.6.1.2.1.2.2.1.17"
+ oidIfOutNUcastPkts = "1.3.6.1.2.1.2.2.1.18"
+ oidIfOutDiscards = "1.3.6.1.2.1.2.2.1.19"
+ oidIfOutErrors = "1.3.6.1.2.1.2.2.1.20"
+
+ oidIfName = "1.3.6.1.2.1.31.1.1.1.1"
+ oidIfInMulticastPkts = "1.3.6.1.2.1.31.1.1.1.2"
+ oidIfInBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.3"
+ oidIfOutMulticastPkts = "1.3.6.1.2.1.31.1.1.1.4"
+ oidIfOutBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.5"
+ oidIfHCInOctets = "1.3.6.1.2.1.31.1.1.1.6"
+ oidIfHCInUcastPkts = "1.3.6.1.2.1.31.1.1.1.7"
+ oidIfHCInMulticastPkts = "1.3.6.1.2.1.31.1.1.1.8"
+ oidIfHCInBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.9"
+ oidIfHCOutOctets = "1.3.6.1.2.1.31.1.1.1.10"
+ oidIfHCOutUcastPkts = "1.3.6.1.2.1.31.1.1.1.11"
+ oidIfHCOutMulticastPkts = "1.3.6.1.2.1.31.1.1.1.12"
+ oidIfHCOutBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.13"
+ oidIfHighSpeed = "1.3.6.1.2.1.31.1.1.1.15"
+ oidIfAlias = "1.3.6.1.2.1.31.1.1.1.18"
+)
+
+type netInterface struct {
+ updated bool
+ hasCharts bool
+ idx string
+
+ ifIndex int64
+ ifDescr string
+ ifType int64
+ ifMtu int64
+ ifSpeed int64
+ //ifPhysAddress string
+ ifAdminStatus int64
+ ifOperStatus int64
+ //ifLastChange string
+ ifInOctets int64
+ ifInUcastPkts int64
+ ifInNUcastPkts int64
+ ifInDiscards int64
+ ifInErrors int64
+ ifInUnknownProtos int64
+ ifOutOctets int64
+ ifOutUcastPkts int64
+ ifOutNUcastPkts int64
+ ifOutDiscards int64
+ ifOutErrors int64
+ ifName string
+ ifInMulticastPkts int64
+ ifInBroadcastPkts int64
+ ifOutMulticastPkts int64
+ ifOutBroadcastPkts int64
+ ifHCInOctets int64
+ ifHCInUcastPkts int64
+ ifHCInMulticastPkts int64
+ ifHCInBroadcastPkts int64
+ ifHCOutOctets int64
+ ifHCOutUcastPkts int64
+ ifHCOutMulticastPkts int64
+ ifHCOutBroadcastPkts int64
+ ifHighSpeed int64
+ ifAlias string
+}
+
+func (n *netInterface) String() string {
+ return fmt.Sprintf("iface index='%d',type='%s',name='%s',descr='%s',alias='%s'",
+ n.ifIndex, ifTypeMapping[n.ifType], n.ifName, n.ifDescr, strings.ReplaceAll(n.ifAlias, "\n", "\\n"))
+}
+
+var ifAdminStatusMapping = map[int64]string{
+ 1: "up",
+ 2: "down",
+ 3: "testing",
+}
+
+var ifOperStatusMapping = map[int64]string{
+ 1: "up",
+ 2: "down",
+ 3: "testing",
+ 4: "unknown",
+ 5: "dormant",
+ 6: "notPresent",
+ 7: "lowerLayerDown",
+}
+
+var ifTypeMapping = map[int64]string{
+ 1: "other",
+ 2: "regular1822",
+ 3: "hdh1822",
+ 4: "ddnX25",
+ 5: "rfc877x25",
+ 6: "ethernetCsmacd",
+ 7: "iso88023Csmacd",
+ 8: "iso88024TokenBus",
+ 9: "iso88025TokenRing",
+ 10: "iso88026Man",
+ 11: "starLan",
+ 12: "proteon10Mbit",
+ 13: "proteon80Mbit",
+ 14: "hyperchannel",
+ 15: "fddi",
+ 16: "lapb",
+ 17: "sdlc",
+ 18: "ds1",
+ 19: "e1",
+ 20: "basicISDN",
+ 21: "primaryISDN",
+ 22: "propPointToPointSerial",
+ 23: "ppp",
+ 24: "softwareLoopback",
+ 25: "eon",
+ 26: "ethernet3Mbit",
+ 27: "nsip",
+ 28: "slip",
+ 29: "ultra",
+ 30: "ds3",
+ 31: "sip",
+ 32: "frameRelay",
+ 33: "rs232",
+ 34: "para",
+ 35: "arcnet",
+ 36: "arcnetPlus",
+ 37: "atm",
+ 38: "miox25",
+ 39: "sonet",
+ 40: "x25ple",
+ 41: "iso88022llc",
+ 42: "localTalk",
+ 43: "smdsDxi",
+ 44: "frameRelayService",
+ 45: "v35",
+ 46: "hssi",
+ 47: "hippi",
+ 48: "modem",
+ 49: "aal5",
+ 50: "sonetPath",
+ 51: "sonetVT",
+ 52: "smdsIcip",
+ 53: "propVirtual",
+ 54: "propMultiplexor",
+ 55: "ieee80212",
+ 56: "fibreChannel",
+ 57: "hippiInterface",
+ 58: "frameRelayInterconnect",
+ 59: "aflane8023",
+ 60: "aflane8025",
+ 61: "cctEmul",
+ 62: "fastEther",
+ 63: "isdn",
+ 64: "v11",
+ 65: "v36",
+ 66: "g703at64k",
+ 67: "g703at2mb",
+ 68: "qllc",
+ 69: "fastEtherFX",
+ 70: "channel",
+ 71: "ieee80211",
+ 72: "ibm370parChan",
+ 73: "escon",
+ 74: "dlsw",
+ 75: "isdns",
+ 76: "isdnu",
+ 77: "lapd",
+ 78: "ipSwitch",
+ 79: "rsrb",
+ 80: "atmLogical",
+ 81: "ds0",
+ 82: "ds0Bundle",
+ 83: "bsc",
+ 84: "async",
+ 85: "cnr",
+ 86: "iso88025Dtr",
+ 87: "eplrs",
+ 88: "arap",
+ 89: "propCnls",
+ 90: "hostPad",
+ 91: "termPad",
+ 92: "frameRelayMPI",
+ 93: "x213",
+ 94: "adsl",
+ 95: "radsl",
+ 96: "sdsl",
+ 97: "vdsl",
+ 98: "iso88025CRFPInt",
+ 99: "myrinet",
+ 100: "voiceEM",
+ 101: "voiceFXO",
+ 102: "voiceFXS",
+ 103: "voiceEncap",
+ 104: "voiceOverIp",
+ 105: "atmDxi",
+ 106: "atmFuni",
+ 107: "atmIma",
+ 108: "pppMultilinkBundle",
+ 109: "ipOverCdlc",
+ 110: "ipOverClaw",
+ 111: "stackToStack",
+ 112: "virtualIpAddress",
+ 113: "mpc",
+ 114: "ipOverAtm",
+ 115: "iso88025Fiber",
+ 116: "tdlc",
+ 117: "gigabitEthernet",
+ 118: "hdlc",
+ 119: "lapf",
+ 120: "v37",
+ 121: "x25mlp",
+ 122: "x25huntGroup",
+ 123: "transpHdlc",
+ 124: "interleave",
+ 125: "fast",
+ 126: "ip",
+ 127: "docsCableMaclayer",
+ 128: "docsCableDownstream",
+ 129: "docsCableUpstream",
+ 130: "a12MppSwitch",
+ 131: "tunnel",
+ 132: "coffee",
+ 133: "ces",
+ 134: "atmSubInterface",
+ 135: "l2vlan",
+ 136: "l3ipvlan",
+ 137: "l3ipxvlan",
+ 138: "digitalPowerline",
+ 139: "mediaMailOverIp",
+ 140: "dtm",
+ 141: "dcn",
+ 142: "ipForward",
+ 143: "msdsl",
+ 144: "ieee1394",
+ 145: "if-gsn",
+ 146: "dvbRccMacLayer",
+ 147: "dvbRccDownstream",
+ 148: "dvbRccUpstream",
+ 149: "atmVirtual",
+ 150: "mplsTunnel",
+ 151: "srp",
+ 152: "voiceOverAtm",
+ 153: "voiceOverFrameRelay",
+ 154: "idsl",
+ 155: "compositeLink",
+ 156: "ss7SigLink",
+ 157: "propWirelessP2P",
+ 158: "frForward",
+ 159: "rfc1483",
+ 160: "usb",
+ 161: "ieee8023adLag",
+ 162: "bgppolicyaccounting",
+ 163: "frf16MfrBundle",
+ 164: "h323Gatekeeper",
+ 165: "h323Proxy",
+ 166: "mpls",
+ 167: "mfSigLink",
+ 168: "hdsl2",
+ 169: "shdsl",
+ 170: "ds1FDL",
+ 171: "pos",
+ 172: "dvbAsiIn",
+ 173: "dvbAsiOut",
+ 174: "plc",
+ 175: "nfas",
+ 176: "tr008",
+ 177: "gr303RDT",
+ 178: "gr303IDT",
+ 179: "isup",
+ 180: "propDocsWirelessMaclayer",
+ 181: "propDocsWirelessDownstream",
+ 182: "propDocsWirelessUpstream",
+ 183: "hiperlan2",
+ 184: "propBWAp2Mp",
+ 185: "sonetOverheadChannel",
+ 186: "digitalWrapperOverheadChannel",
+ 187: "aal2",
+ 188: "radioMAC",
+ 189: "atmRadio",
+ 190: "imt",
+ 191: "mvl",
+ 192: "reachDSL",
+ 193: "frDlciEndPt",
+ 194: "atmVciEndPt",
+ 195: "opticalChannel",
+ 196: "opticalTransport",
+ 197: "propAtm",
+ 198: "voiceOverCable",
+ 199: "infiniband",
+ 200: "teLink",
+ 201: "q2931",
+ 202: "virtualTg",
+ 203: "sipTg",
+ 204: "sipSig",
+ 205: "docsCableUpstreamChannel",
+ 206: "econet",
+ 207: "pon155",
+ 208: "pon622",
+ 209: "bridge",
+ 210: "linegroup",
+ 211: "voiceEMFGD",
+ 212: "voiceFGDEANA",
+ 213: "voiceDID",
+ 214: "mpegTransport",
+ 215: "sixToFour",
+ 216: "gtp",
+ 217: "pdnEtherLoop1",
+ 218: "pdnEtherLoop2",
+ 219: "opticalChannelGroup",
+ 220: "homepna",
+ 221: "gfp",
+ 222: "ciscoISLvlan",
+ 223: "actelisMetaLOOP",
+ 224: "fcipLink",
+ 225: "rpr",
+ 226: "qam",
+ 227: "lmp",
+ 228: "cblVectaStar",
+ 229: "docsCableMCmtsDownstream",
+ 230: "adsl2",
+ 231: "macSecControlledIF",
+ 232: "macSecUncontrolledIF",
+ 233: "aviciOpticalEther",
+ 234: "atmbond",
+ 235: "voiceFGDOS",
+ 236: "mocaVersion1",
+ 237: "ieee80216WMAN",
+ 238: "adsl2plus",
+ 239: "dvbRcsMacLayer",
+ 240: "dvbTdm",
+ 241: "dvbRcsTdma",
+ 242: "x86Laps",
+ 243: "wwanPP",
+ 244: "wwanPP2",
+ 245: "voiceEBS",
+ 246: "ifPwType",
+ 247: "ilan",
+ 248: "pip",
+ 249: "aluELP",
+ 250: "gpon",
+ 251: "vdsl2",
+ 252: "capwapDot11Profile",
+ 253: "capwapDot11Bss",
+ 254: "capwapWtpVirtualRadio",
+ 255: "bits",
+ 256: "docsCableUpstreamRfPort",
+ 257: "cableDownstreamRfPort",
+ 258: "vmwareVirtualNic",
+ 259: "ieee802154",
+ 260: "otnOdu",
+ 261: "otnOtu",
+ 262: "ifVfiType",
+ 263: "g9981",
+ 264: "g9982",
+ 265: "g9983",
+ 266: "aluEpon",
+ 267: "aluEponOnu",
+ 268: "aluEponPhysicalUni",
+ 269: "aluEponLogicalLink",
+ 270: "aluGponOnu",
+ 271: "aluGponPhysicalUni",
+ 272: "vmwareNicTeam",
+ 277: "docsOfdmDownstream",
+ 278: "docsOfdmaUpstream",
+ 279: "gfast",
+ 280: "sdci",
+ 281: "xboxWireless",
+ 282: "fastdsl",
+ 283: "docsCableScte55d1FwdOob",
+ 284: "docsCableScte55d1RetOob",
+ 285: "docsCableScte55d2DsOob",
+ 286: "docsCableScte55d2UsOob",
+ 287: "docsCableNdf",
+ 288: "docsCableNdr",
+ 289: "ptm",
+ 290: "ghn",
+ 291: "otnOtsi",
+ 292: "otnOtuc",
+ 293: "otnOduc",
+ 294: "otnOtsig",
+ 295: "microwaveCarrierTermination",
+ 296: "microwaveRadioLinkTerminal",
+ 297: "ieee8021axDrni",
+ 298: "ax25",
+ 299: "ieee19061nanocom",
+ 300: "cpri",
+ 301: "omni",
+ 302: "roe",
+ 303: "p2pOverLan",
+}
diff --git a/src/go/plugin/go.d/modules/snmp/snmp.go b/src/go/plugin/go.d/modules/snmp/snmp.go
new file mode 100644
index 00000000..253d9f50
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/snmp.go
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ _ "embed"
+ "errors"
+
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/matcher"
+
+ "github.com/gosnmp/gosnmp"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("snmp", module.Creator{
+ JobConfigSchema: configSchema,
+ Defaults: module.Defaults{
+ UpdateEvery: 10,
+ },
+ Create: func() module.Module { return New() },
+ Config: func() any { return &Config{} },
+ })
+}
+
+func New() *SNMP {
+ return &SNMP{
+ Config: Config{
+ Community: "public",
+ Options: Options{
+ Port: 161,
+ Retries: 1,
+ Timeout: 5,
+ Version: gosnmp.Version2c.String(),
+ MaxOIDs: 60,
+ MaxRepetitions: 25,
+ },
+ User: User{
+ SecurityLevel: "authPriv",
+ AuthProto: "sha512",
+ PrivProto: "aes192c",
+ },
+ },
+
+ newSnmpClient: gosnmp.NewHandler,
+
+ checkMaxReps: true,
+ collectIfMib: true,
+ netInterfaces: make(map[string]*netInterface),
+ }
+}
+
+type SNMP struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ newSnmpClient func() gosnmp.Handler
+ snmpClient gosnmp.Handler
+
+ netIfaceFilterByName matcher.Matcher
+ netIfaceFilterByType matcher.Matcher
+
+ checkMaxReps bool
+ collectIfMib bool
+ netInterfaces map[string]*netInterface
+ sysName string
+
+ oids []string
+}
+
+func (s *SNMP) Configuration() any {
+ return s.Config
+}
+
+func (s *SNMP) Init() error {
+ err := s.validateConfig()
+ if err != nil {
+ s.Errorf("config validation failed: %v", err)
+ return err
+ }
+
+ snmpClient, err := s.initSNMPClient()
+ if err != nil {
+ s.Errorf("failed to initialize SNMP client: %v", err)
+ return err
+ }
+
+ err = snmpClient.Connect()
+ if err != nil {
+ s.Errorf("SNMP client connection failed: %v", err)
+ return err
+ }
+ s.snmpClient = snmpClient
+
+ byName, byType, err := s.initNetIfaceFilters()
+ if err != nil {
+ s.Errorf("failed to initialize network interface filters: %v", err)
+ return err
+ }
+ s.netIfaceFilterByName = byName
+ s.netIfaceFilterByType = byType
+
+ charts, err := newUserInputCharts(s.ChartsInput)
+ if err != nil {
+ s.Errorf("failed to create user charts: %v", err)
+ return err
+ }
+ s.charts = charts
+
+ s.oids = s.initOIDs()
+
+ return nil
+}
+
+func (s *SNMP) Check() error {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ return err
+ }
+
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+
+ return nil
+}
+
+func (s *SNMP) Charts() *module.Charts {
+ return s.charts
+}
+
+func (s *SNMP) Collect() map[string]int64 {
+ mx, err := s.collect()
+ if err != nil {
+ s.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+
+ return mx
+}
+
+func (s *SNMP) Cleanup() {
+ if s.snmpClient != nil {
+ _ = s.snmpClient.Close()
+ }
+}
diff --git a/src/go/plugin/go.d/modules/snmp/snmp_test.go b/src/go/plugin/go.d/modules/snmp/snmp_test.go
new file mode 100644
index 00000000..1841235f
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/snmp_test.go
@@ -0,0 +1,754 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package snmp
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
+
+ "github.com/golang/mock/gomock"
+ "github.com/gosnmp/gosnmp"
+ snmpmock "github.com/gosnmp/gosnmp/mocks"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestSNMP_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &SNMP{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestSNMP_Init(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func() *SNMP
+ wantFail bool
+ }{
+ "fail with default config": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ return New()
+ },
+ },
+ "fail when using SNMPv3 but 'user.name' not set": {
+ wantFail: true,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ snmp.User.Name = ""
+ return snmp
+ },
+ },
+ "success when using SNMPv1 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV1Config()
+ return snmp
+ },
+ },
+ "success when using SNMPv2 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ return snmp
+ },
+ },
+ "success when using SNMPv3 with valid config": {
+ wantFail: false,
+ prepareSNMP: func() *SNMP {
+ snmp := New()
+ snmp.Config = prepareV3Config()
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ snmp := test.prepareSNMP()
+
+ if test.wantFail {
+ assert.Error(t, snmp.Init())
+ } else {
+ assert.NoError(t, snmp.Init())
+ }
+ })
+ }
+}
+
+func TestSNMP_Cleanup(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP
+ }{
+ "cleanup call if snmpClient initialized": {
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ snmp.newSnmpClient = func() gosnmp.Handler { return m }
+ setMockClientInitExpect(m)
+
+ require.NoError(t, snmp.Init())
+
+ m.EXPECT().Close().Times(1)
+
+ return snmp
+ },
+ },
+ "cleanup call does not panic if snmpClient not initialized": {
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ snmp.newSnmpClient = func() gosnmp.Handler { return m }
+ setMockClientInitExpect(m)
+
+ require.NoError(t, snmp.Init())
+
+ snmp.snmpClient = nil
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ snmp := test.prepareSNMP(t, mockSNMP)
+
+ assert.NotPanics(t, snmp.Cleanup)
+ })
+ }
+}
+
+func TestSNMP_Charts(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *SNMP
+ wantNumCharts int
+ doCollect bool
+ }{
+ "if-mib, no custom": {
+ doCollect: true,
+ wantNumCharts: len(netIfaceChartsTmpl)*4 + 1,
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ setMockClientSysExpect(m)
+ setMockClientIfMibExpect(m)
+
+ return snmp
+ },
+ },
+ "custom, no if-mib": {
+ wantNumCharts: 10,
+ prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 9)
+ snmp.collectIfMib = false
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ setMockClientInitExpect(mockSNMP)
+
+ snmp := test.prepareSNMP(t, mockSNMP)
+ snmp.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
+
+ require.NoError(t, snmp.Init())
+
+ if test.doCollect {
+ _ = snmp.Collect()
+ }
+
+ assert.Equal(t, test.wantNumCharts, len(*snmp.Charts()))
+ })
+ }
+}
+
+func TestSNMP_Check(t *testing.T) {
+ tests := map[string]struct {
+ wantFail bool
+ prepareSNMP func(m *snmpmock.MockHandler) *SNMP
+ }{
+ "success when collecting IF-MIB": {
+ wantFail: false,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+ setMockClientIfMibExpect(m)
+
+ return snmp
+ },
+ },
+ "success only custom OIDs supported type": {
+ wantFail: false,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
+ snmp.collectIfMib = false
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Counter32},
+ {Value: 20, Type: gosnmp.Counter64},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: 1, Type: gosnmp.Boolean},
+ {Value: 40, Type: gosnmp.Gauge32},
+ {Value: 50, Type: gosnmp.TimeTicks},
+ {Value: 60, Type: gosnmp.Uinteger32},
+ {Value: 70, Type: gosnmp.Integer},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ },
+ "fail when snmp client Get fails": {
+ wantFail: true,
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
+ snmp.collectIfMib = false
+
+ m.EXPECT().Get(gomock.Any()).Return(nil, errors.New("mock Get() error")).Times(1)
+
+ return snmp
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ setMockClientInitExpect(mockSNMP)
+ setMockClientSysExpect(mockSNMP)
+
+ snmp := test.prepareSNMP(mockSNMP)
+ snmp.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
+
+ require.NoError(t, snmp.Init())
+
+ if test.wantFail {
+ assert.Error(t, snmp.Check())
+ } else {
+ assert.NoError(t, snmp.Check())
+ }
+ })
+ }
+}
+
+func TestSNMP_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepareSNMP func(m *snmpmock.MockHandler) *SNMP
+ wantCollected map[string]int64
+ }{
+ "success only IF-MIB": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareV2Config()
+
+ setMockClientIfMibExpect(m)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "net_iface_ether1_admin_status_down": 0,
+ "net_iface_ether1_admin_status_testing": 0,
+ "net_iface_ether1_admin_status_up": 1,
+ "net_iface_ether1_bcast_in": 0,
+ "net_iface_ether1_bcast_out": 0,
+ "net_iface_ether1_discards_in": 0,
+ "net_iface_ether1_discards_out": 0,
+ "net_iface_ether1_errors_in": 0,
+ "net_iface_ether1_errors_out": 0,
+ "net_iface_ether1_mcast_in": 0,
+ "net_iface_ether1_mcast_out": 0,
+ "net_iface_ether1_oper_status_dormant": 0,
+ "net_iface_ether1_oper_status_down": 1,
+ "net_iface_ether1_oper_status_lowerLayerDown": 0,
+ "net_iface_ether1_oper_status_notPresent": 0,
+ "net_iface_ether1_oper_status_testing": 0,
+ "net_iface_ether1_oper_status_unknown": 0,
+ "net_iface_ether1_oper_status_up": 0,
+ "net_iface_ether1_traffic_in": 0,
+ "net_iface_ether1_traffic_out": 0,
+ "net_iface_ether1_ucast_in": 0,
+ "net_iface_ether1_ucast_out": 0,
+ "net_iface_ether2_admin_status_down": 0,
+ "net_iface_ether2_admin_status_testing": 0,
+ "net_iface_ether2_admin_status_up": 1,
+ "net_iface_ether2_bcast_in": 0,
+ "net_iface_ether2_bcast_out": 0,
+ "net_iface_ether2_discards_in": 0,
+ "net_iface_ether2_discards_out": 0,
+ "net_iface_ether2_errors_in": 0,
+ "net_iface_ether2_errors_out": 0,
+ "net_iface_ether2_mcast_in": 1891,
+ "net_iface_ether2_mcast_out": 7386,
+ "net_iface_ether2_oper_status_dormant": 0,
+ "net_iface_ether2_oper_status_down": 0,
+ "net_iface_ether2_oper_status_lowerLayerDown": 0,
+ "net_iface_ether2_oper_status_notPresent": 0,
+ "net_iface_ether2_oper_status_testing": 0,
+ "net_iface_ether2_oper_status_unknown": 0,
+ "net_iface_ether2_oper_status_up": 1,
+ "net_iface_ether2_traffic_in": 615057509,
+ "net_iface_ether2_traffic_out": 159677206,
+ "net_iface_ether2_ucast_in": 71080332,
+ "net_iface_ether2_ucast_out": 39509661,
+ "net_iface_sfp-sfpplus1_admin_status_down": 0,
+ "net_iface_sfp-sfpplus1_admin_status_testing": 0,
+ "net_iface_sfp-sfpplus1_admin_status_up": 1,
+ "net_iface_sfp-sfpplus1_bcast_in": 0,
+ "net_iface_sfp-sfpplus1_bcast_out": 0,
+ "net_iface_sfp-sfpplus1_discards_in": 0,
+ "net_iface_sfp-sfpplus1_discards_out": 0,
+ "net_iface_sfp-sfpplus1_errors_in": 0,
+ "net_iface_sfp-sfpplus1_errors_out": 0,
+ "net_iface_sfp-sfpplus1_mcast_in": 0,
+ "net_iface_sfp-sfpplus1_mcast_out": 0,
+ "net_iface_sfp-sfpplus1_oper_status_dormant": 0,
+ "net_iface_sfp-sfpplus1_oper_status_down": 0,
+ "net_iface_sfp-sfpplus1_oper_status_lowerLayerDown": 0,
+ "net_iface_sfp-sfpplus1_oper_status_notPresent": 1,
+ "net_iface_sfp-sfpplus1_oper_status_testing": 0,
+ "net_iface_sfp-sfpplus1_oper_status_unknown": 0,
+ "net_iface_sfp-sfpplus1_oper_status_up": 0,
+ "net_iface_sfp-sfpplus1_traffic_in": 0,
+ "net_iface_sfp-sfpplus1_traffic_out": 0,
+ "net_iface_sfp-sfpplus1_ucast_in": 0,
+ "net_iface_sfp-sfpplus1_ucast_out": 0,
+ "net_iface_sfp-sfpplus2_admin_status_down": 0,
+ "net_iface_sfp-sfpplus2_admin_status_testing": 0,
+ "net_iface_sfp-sfpplus2_admin_status_up": 1,
+ "net_iface_sfp-sfpplus2_bcast_in": 0,
+ "net_iface_sfp-sfpplus2_bcast_out": 0,
+ "net_iface_sfp-sfpplus2_discards_in": 0,
+ "net_iface_sfp-sfpplus2_discards_out": 0,
+ "net_iface_sfp-sfpplus2_errors_in": 0,
+ "net_iface_sfp-sfpplus2_errors_out": 0,
+ "net_iface_sfp-sfpplus2_mcast_in": 0,
+ "net_iface_sfp-sfpplus2_mcast_out": 0,
+ "net_iface_sfp-sfpplus2_oper_status_dormant": 0,
+ "net_iface_sfp-sfpplus2_oper_status_down": 0,
+ "net_iface_sfp-sfpplus2_oper_status_lowerLayerDown": 0,
+ "net_iface_sfp-sfpplus2_oper_status_notPresent": 1,
+ "net_iface_sfp-sfpplus2_oper_status_testing": 0,
+ "net_iface_sfp-sfpplus2_oper_status_unknown": 0,
+ "net_iface_sfp-sfpplus2_oper_status_up": 0,
+ "net_iface_sfp-sfpplus2_traffic_in": 0,
+ "net_iface_sfp-sfpplus2_traffic_out": 0,
+ "net_iface_sfp-sfpplus2_ucast_in": 0,
+ "net_iface_sfp-sfpplus2_ucast_out": 0,
+ "uptime": 60,
+ },
+ },
+ "success only custom OIDs supported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
+ snmp.collectIfMib = false
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Counter32},
+ {Value: 20, Type: gosnmp.Counter64},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: 1, Type: gosnmp.Boolean},
+ {Value: 40, Type: gosnmp.Gauge32},
+ {Value: 50, Type: gosnmp.TimeTicks},
+ {Value: 60, Type: gosnmp.Uinteger32},
+ {Value: 70, Type: gosnmp.Integer},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "1.3.6.1.2.1.2.2.1.10.0": 10,
+ "1.3.6.1.2.1.2.2.1.16.0": 20,
+ "1.3.6.1.2.1.2.2.1.10.1": 30,
+ "1.3.6.1.2.1.2.2.1.16.1": 1,
+ "1.3.6.1.2.1.2.2.1.10.2": 40,
+ "1.3.6.1.2.1.2.2.1.16.2": 50,
+ "1.3.6.1.2.1.2.2.1.10.3": 60,
+ "1.3.6.1.2.1.2.2.1.16.3": 70,
+ "uptime": 60,
+ },
+ },
+ "success only custom OIDs supported and unsupported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
+ snmp.collectIfMib = false
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: 10, Type: gosnmp.Counter32},
+ {Value: 20, Type: gosnmp.Counter64},
+ {Value: 30, Type: gosnmp.Gauge32},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "1.3.6.1.2.1.2.2.1.10.0": 10,
+ "1.3.6.1.2.1.2.2.1.16.0": 20,
+ "1.3.6.1.2.1.2.2.1.10.1": 30,
+ "uptime": 60,
+ },
+ },
+ "success only custom OIDs unsupported type": {
+ prepareSNMP: func(m *snmpmock.MockHandler) *SNMP {
+ snmp := New()
+ snmp.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
+ snmp.collectIfMib = false
+
+ m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchInstance},
+ {Value: nil, Type: gosnmp.NoSuchObject},
+ {Value: "192.0.2.0", Type: gosnmp.NsapAddress},
+ {Value: []uint8{118, 101, 116}, Type: gosnmp.OctetString},
+ {Value: ".1.3.6.1.2.1.4.32.1.5.2.1.4.10.19.0.0.16", Type: gosnmp.ObjectIdentifier},
+ },
+ }, nil).Times(1)
+
+ return snmp
+ },
+ wantCollected: map[string]int64{
+ "uptime": 60,
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ mockSNMP, cleanup := mockInit(t)
+ defer cleanup()
+
+ setMockClientInitExpect(mockSNMP)
+ setMockClientSysExpect(mockSNMP)
+
+ snmp := test.prepareSNMP(mockSNMP)
+ snmp.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
+
+ require.NoError(t, snmp.Init())
+
+ mx := snmp.Collect()
+
+ assert.Equal(t, test.wantCollected, mx)
+ })
+ }
+}
+
+func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) {
+ mockCtl := gomock.NewController(t)
+ cleanup := func() { mockCtl.Finish() }
+ mockSNMP := snmpmock.NewMockHandler(mockCtl)
+
+ return mockSNMP, cleanup
+}
+
+func prepareV3Config() Config {
+ cfg := prepareV2Config()
+ cfg.Options.Version = gosnmp.Version3.String()
+ cfg.User = User{
+ Name: "name",
+ SecurityLevel: "authPriv",
+ AuthProto: strings.ToLower(gosnmp.MD5.String()),
+ AuthKey: "auth_key",
+ PrivProto: strings.ToLower(gosnmp.AES.String()),
+ PrivKey: "priv_key",
+ }
+ return cfg
+}
+
+func prepareV2Config() Config {
+ cfg := prepareV1Config()
+ cfg.Options.Version = gosnmp.Version2c.String()
+ return cfg
+}
+
+func prepareV1Config() Config {
+ return Config{
+ UpdateEvery: 1,
+ Hostname: "192.0.2.1",
+ Community: "public",
+ Options: Options{
+ Port: 161,
+ Retries: 1,
+ Timeout: 5,
+ Version: gosnmp.Version1.String(),
+ MaxOIDs: 60,
+ MaxRepetitions: 25,
+ },
+ }
+}
+
+func prepareConfigWithUserCharts(cfg Config, start, end int) Config {
+ if start > end || start < 0 || end < 1 {
+ panic(fmt.Sprintf("invalid index range ('%d'-'%d')", start, end))
+ }
+ cfg.ChartsInput = []ChartConfig{
+ {
+ ID: "test_chart1",
+ Title: "This is Test Chart1",
+ Units: "kilobits/s",
+ Family: "family",
+ Type: module.Area.String(),
+ Priority: module.Priority,
+ Dimensions: []DimensionConfig{
+ {
+ OID: "1.3.6.1.2.1.2.2.1.10",
+ Name: "in",
+ Algorithm: module.Incremental.String(),
+ Multiplier: 8,
+ Divisor: 1000,
+ },
+ {
+ OID: "1.3.6.1.2.1.2.2.1.16",
+ Name: "out",
+ Algorithm: module.Incremental.String(),
+ Multiplier: 8,
+ Divisor: 1000,
+ },
+ },
+ },
+ }
+
+ for i := range cfg.ChartsInput {
+ cfg.ChartsInput[i].IndexRange = []int{start, end}
+ }
+
+ return cfg
+}
+
+func setMockClientInitExpect(m *snmpmock.MockHandler) {
+ m.EXPECT().Target().AnyTimes()
+ m.EXPECT().Port().AnyTimes()
+ m.EXPECT().Version().AnyTimes()
+ m.EXPECT().Community().AnyTimes()
+ m.EXPECT().SetTarget(gomock.Any()).AnyTimes()
+ m.EXPECT().SetPort(gomock.Any()).AnyTimes()
+ m.EXPECT().SetRetries(gomock.Any()).AnyTimes()
+ m.EXPECT().SetMaxRepetitions(gomock.Any()).AnyTimes()
+ m.EXPECT().SetMaxOids(gomock.Any()).AnyTimes()
+ m.EXPECT().SetLogger(gomock.Any()).AnyTimes()
+ m.EXPECT().SetTimeout(gomock.Any()).AnyTimes()
+ m.EXPECT().SetCommunity(gomock.Any()).AnyTimes()
+ m.EXPECT().SetVersion(gomock.Any()).AnyTimes()
+ m.EXPECT().SetSecurityModel(gomock.Any()).AnyTimes()
+ m.EXPECT().SetMsgFlags(gomock.Any()).AnyTimes()
+ m.EXPECT().SetSecurityParameters(gomock.Any()).AnyTimes()
+ m.EXPECT().Connect().Return(nil).AnyTimes()
+}
+
+func setMockClientSysExpect(m *snmpmock.MockHandler) {
+ m.EXPECT().Get([]string{oidSysName}).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: []uint8("mock-host"), Type: gosnmp.OctetString},
+ },
+ }, nil).MinTimes(1)
+
+ m.EXPECT().Get([]string{oidSysUptime}).Return(&gosnmp.SnmpPacket{
+ Variables: []gosnmp.SnmpPDU{
+ {Value: uint32(6048), Type: gosnmp.TimeTicks},
+ },
+ }, nil).MinTimes(1)
+}
+
+func setMockClientIfMibExpect(m *snmpmock.MockHandler) {
+ m.EXPECT().WalkAll(oidIfIndex).Return([]gosnmp.SnmpPDU{
+ {Name: oidIfIndex + ".1", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".2", Value: 2, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".17", Value: 17, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".18", Value: 18, Type: gosnmp.Integer},
+ }, nil).MinTimes(1)
+ m.EXPECT().WalkAll(rootOidIfMibIfTable).Return([]gosnmp.SnmpPDU{
+ {Name: oidIfIndex + ".1", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".2", Value: 2, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".17", Value: 17, Type: gosnmp.Integer},
+ {Name: oidIfIndex + ".18", Value: 18, Type: gosnmp.Integer},
+ {Name: oidIfDescr + ".1", Value: []uint8("ether1"), Type: gosnmp.OctetString},
+ {Name: oidIfDescr + ".2", Value: []uint8("ether2"), Type: gosnmp.OctetString},
+ {Name: oidIfDescr + ".17", Value: []uint8("sfp-sfpplus2"), Type: gosnmp.OctetString},
+ {Name: oidIfDescr + ".18", Value: []uint8("sfp-sfpplus1"), Type: gosnmp.OctetString},
+ {Name: oidIfType + ".1", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfType + ".2", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfType + ".17", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfType + ".18", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfMtu + ".1", Value: 1500, Type: gosnmp.Integer},
+ {Name: oidIfMtu + ".2", Value: 1500, Type: gosnmp.Integer},
+ {Name: oidIfMtu + ".17", Value: 1500, Type: gosnmp.Integer},
+ {Name: oidIfMtu + ".18", Value: 1500, Type: gosnmp.Integer},
+ {Name: oidIfSpeed + ".1", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfSpeed + ".2", Value: 1000000000, Type: gosnmp.Gauge32},
+ {Name: oidIfSpeed + ".17", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfSpeed + ".18", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfPhysAddress + ".1", Value: decodePhysAddr("18:fd:74:7e:c5:80"), Type: gosnmp.OctetString},
+ {Name: oidIfPhysAddress + ".2", Value: decodePhysAddr("18:fd:74:7e:c5:81"), Type: gosnmp.OctetString},
+ {Name: oidIfPhysAddress + ".17", Value: decodePhysAddr("18:fd:74:7e:c5:90"), Type: gosnmp.OctetString},
+ {Name: oidIfPhysAddress + ".18", Value: decodePhysAddr("18:fd:74:7e:c5:91"), Type: gosnmp.OctetString},
+ {Name: oidIfAdminStatus + ".1", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfAdminStatus + ".2", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfAdminStatus + ".17", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfAdminStatus + ".18", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfOperStatus + ".1", Value: 2, Type: gosnmp.Integer},
+ {Name: oidIfOperStatus + ".2", Value: 1, Type: gosnmp.Integer},
+ {Name: oidIfOperStatus + ".17", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfOperStatus + ".18", Value: 6, Type: gosnmp.Integer},
+ {Name: oidIfLastChange + ".1", Value: 0, Type: gosnmp.TimeTicks},
+ {Name: oidIfLastChange + ".2", Value: 3243, Type: gosnmp.TimeTicks},
+ {Name: oidIfLastChange + ".17", Value: 0, Type: gosnmp.TimeTicks},
+ {Name: oidIfLastChange + ".18", Value: 0, Type: gosnmp.TimeTicks},
+ {Name: oidIfInOctets + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInOctets + ".2", Value: 3827243723, Type: gosnmp.Counter32},
+ {Name: oidIfInOctets + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInOctets + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUcastPkts + ".2", Value: 71035992, Type: gosnmp.Counter32},
+ {Name: oidIfInUcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInNUcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInNUcastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInNUcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInNUcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInDiscards + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInDiscards + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInDiscards + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInDiscards + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInErrors + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInErrors + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInErrors + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInErrors + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUnknownProtos + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUnknownProtos + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUnknownProtos + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInUnknownProtos + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutOctets + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutOctets + ".2", Value: 2769838772, Type: gosnmp.Counter32},
+ {Name: oidIfOutOctets + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutOctets + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutUcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutUcastPkts + ".2", Value: 39482929, Type: gosnmp.Counter32},
+ {Name: oidIfOutUcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutUcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutNUcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutNUcastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutNUcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutNUcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutDiscards + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutDiscards + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutDiscards + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutDiscards + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutErrors + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutErrors + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutErrors + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutErrors + ".18", Value: 0, Type: gosnmp.Counter32},
+ }, nil).MinTimes(1)
+
+ m.EXPECT().WalkAll(rootOidIfMibIfXTable).Return([]gosnmp.SnmpPDU{
+ {Name: oidIfName + ".1", Value: []uint8("ether1"), Type: gosnmp.OctetString},
+ {Name: oidIfName + ".2", Value: []uint8("ether2"), Type: gosnmp.OctetString},
+ {Name: oidIfName + ".17", Value: []uint8("sfp-sfpplus2"), Type: gosnmp.OctetString},
+ {Name: oidIfName + ".18", Value: []uint8("sfp-sfpplus1"), Type: gosnmp.OctetString},
+ {Name: oidIfInMulticastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInMulticastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInMulticastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInMulticastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInBroadcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInBroadcastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInBroadcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfInBroadcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutMulticastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutMulticastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutMulticastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutMulticastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutBroadcastPkts + ".1", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutBroadcastPkts + ".2", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutBroadcastPkts + ".17", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfOutBroadcastPkts + ".18", Value: 0, Type: gosnmp.Counter32},
+ {Name: oidIfHCInOctets + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInOctets + ".2", Value: 76882188712, Type: gosnmp.Counter64},
+ {Name: oidIfHCInOctets + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInOctets + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInUcastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInUcastPkts + ".2", Value: 71080332, Type: gosnmp.Counter64},
+ {Name: oidIfHCInUcastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInUcastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInMulticastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInMulticastPkts + ".2", Value: 1891, Type: gosnmp.Counter64},
+ {Name: oidIfHCInMulticastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInMulticastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInBroadcastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInBroadcastPkts + ".2", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInBroadcastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCInBroadcastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutOctets + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutOctets + ".2", Value: 19959650810, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutOctets + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutOctets + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutUcastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutUcastPkts + ".2", Value: 39509661, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutUcastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutUcastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutMulticastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutMulticastPkts + ".2", Value: 28844, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutMulticastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutMulticastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutBroadcastPkts + ".1", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutBroadcastPkts + ".2", Value: 7386, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutBroadcastPkts + ".17", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHCOutBroadcastPkts + ".18", Value: 0, Type: gosnmp.Counter64},
+ {Name: oidIfHighSpeed + ".1", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfHighSpeed + ".2", Value: 1000, Type: gosnmp.Gauge32},
+ {Name: oidIfHighSpeed + ".17", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfHighSpeed + ".18", Value: 0, Type: gosnmp.Gauge32},
+ {Name: oidIfAlias + ".1", Value: []uint8(""), Type: gosnmp.OctetString},
+ {Name: oidIfAlias + ".2", Value: []uint8("UPLINK2 (2.1)"), Type: gosnmp.OctetString},
+ {Name: oidIfAlias + ".17", Value: []uint8(""), Type: gosnmp.OctetString},
+ {Name: oidIfAlias + ".18", Value: []uint8(""), Type: gosnmp.OctetString},
+ }, nil).MinTimes(1)
+}
+
+func decodePhysAddr(s string) []uint8 {
+ s = strings.ReplaceAll(s, ":", "")
+ v, _ := hex.DecodeString(s)
+ return v
+}
diff --git a/src/go/plugin/go.d/modules/snmp/testdata/config.json b/src/go/plugin/go.d/modules/snmp/testdata/config.json
new file mode 100644
index 00000000..b88ac1c2
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/testdata/config.json
@@ -0,0 +1,47 @@
+{
+ "update_every": 123,
+ "hostname": "ok",
+ "community": "ok",
+ "network_interface_filter": {
+ "by_name": "ok",
+ "by_type": "ok"
+ },
+ "user": {
+ "name": "ok",
+ "level": "ok",
+ "auth_proto": "ok",
+ "auth_key": "ok",
+ "priv_proto": "ok",
+ "priv_key": "ok"
+ },
+ "options": {
+ "port": 123,
+ "retries": 123,
+ "timeout": 123,
+ "version": "ok",
+ "max_request_size": 123,
+ "max_repetitions": 123
+ },
+ "charts": [
+ {
+ "id": "ok",
+ "title": "ok",
+ "units": "ok",
+ "family": "ok",
+ "type": "ok",
+ "priority": 123,
+ "multiply_range": [
+ 123
+ ],
+ "dimensions": [
+ {
+ "oid": "ok",
+ "name": "ok",
+ "algorithm": "ok",
+ "multiplier": 123,
+ "divisor": 123
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/go/plugin/go.d/modules/snmp/testdata/config.yaml b/src/go/plugin/go.d/modules/snmp/testdata/config.yaml
new file mode 100644
index 00000000..f4ddbf91
--- /dev/null
+++ b/src/go/plugin/go.d/modules/snmp/testdata/config.yaml
@@ -0,0 +1,35 @@
+update_every: 123
+hostname: "ok"
+community: "ok"
+network_interface_filter:
+ by_name: "ok"
+ by_type: "ok"
+user:
+ name: "ok"
+ level: "ok"
+ auth_proto: "ok"
+ auth_key: "ok"
+ priv_proto: "ok"
+ priv_key: "ok"
+options:
+ port: 123
+ retries: 123
+ timeout: 123
+ version: "ok"
+ max_request_size: 123
+ max_repetitions: 123
+charts:
+ - id: "ok"
+ title: "ok"
+ units: "ok"
+ family: "ok"
+ type: "ok"
+ priority: 123
+ multiply_range:
+ - 123
+ dimensions:
+ - oid: "ok"
+ name: "ok"
+ algorithm: "ok"
+ multiplier: 123
+ divisor: 123