@cryptotaxi247 / netdata-1 / commits / dae04b7a3

improve(go.d/snmp): automatically disable SNMP bulkwalk when not supported (#21139)

Ilya Mashchenko committed Oct 10, 2025 at 19:34 UTC dae04b7a3a3265e52f4283fae09c2baeee08de22
5 files changed +63 -33
src/go/plugin/go.d/collector/snmp/collect.go
+38 -13
@@ -104,10 +104,11 @@ func (c *Collector) ensureInitialized() error {
104
105 if c.ddSnmpColl == nil && len(c.snmpProfiles) > 0 {
106 c.ddSnmpColl = c.newDdSnmpColl(ddsnmpcollector.Config{
107 - SnmpClient: c.snmpClient,
108 - Profiles: c.snmpProfiles,
109 - Log: c.Logger,
110 - SysObjectID: si.SysObjectID,
107 + SnmpClient: c.snmpClient,
108 + Profiles: c.snmpProfiles,
109 + Log: c.Logger,
110 + SysObjectID: si.SysObjectID,
111 + DisableBulkWalk: c.disableBulkWalk,
112 })
113 }
114
@@ -214,6 +215,10 @@ func (c *Collector) initAndConnectSNMPClient() (gosnmp.Handler, error) {
215 return nil, fmt.Errorf("connect: %w", err)
216 }
217
218 + if snmpClient.Version() == gosnmp.Version1 {
219 + return snmpClient, nil
220 + }
221 +
222 if c.adjMaxRepetitions != 0 {
223 snmpClient.SetMaxRepetitions(c.adjMaxRepetitions)
224 } else {
@@ -222,7 +227,8 @@ func (c *Collector) initAndConnectSNMPClient() (gosnmp.Handler, error) {
227 return nil, fmt.Errorf("re-adjust max repetitions SNMP client: %w", err)
228 }
229 if !ok {
225 - c.Warningf("SNMP bulk walk disabled: table metrics collection unavailable (device may not support GETBULK or max-repetitions adjustment failed)")
230 + c.Warningf("SNMP bulk walk disabled (device may not support GETBULK or max-repetitions adjustment failed)")
231 + c.disableBulkWalk = true
232 }
233 c.adjMaxRepetitions = snmpClient.MaxRepetitions()
234 }
@@ -231,6 +237,15 @@ func (c *Collector) initAndConnectSNMPClient() (gosnmp.Handler, error) {
237 }
238
239 func (c *Collector) adjustMaxRepetitions(snmpClient gosnmp.Handler) (bool, error) {
240 + ok, err := c.detectBulkWalkSupport(snmpClient)
241 + if err != nil {
242 + c.Warningf("bulk support probe error: %v", err)
243 + return false, nil
244 + }
245 + if !ok {
246 + return false, nil
247 + }
248 +
249 orig := c.Config.Options.MaxRepetitions
250 maxReps := c.Config.Options.MaxRepetitions
251 attempts := 0
@@ -239,7 +254,7 @@ func (c *Collector) adjustMaxRepetitions(snmpClient gosnmp.Handler) (bool, error
254 for maxReps > 0 && attempts < maxAttempts {
255 attempts++
256
242 - v, err := walkAll(snmpClient, snmputils.RootOidMibSystem)
257 + v, err := snmpClient.BulkWalkAll(snmputils.RootOidMibSystem)
258 if err != nil {
259 return false, err
260 }
@@ -276,14 +291,24 @@ func (c *Collector) adjustMaxRepetitions(snmpClient gosnmp.Handler) (bool, error
291 return false, nil
292 }
293
279 -func walkAll(snmpClient gosnmp.Handler, rootOid string) ([]gosnmp.SnmpPDU, error) {
280 - if snmpClient.Version() == gosnmp.Version1 {
281 - return snmpClient.WalkAll(rootOid)
282 - }
283 - return snmpClient.BulkWalkAll(rootOid)
284 -}
285 -
294 func isPingUnrecoverableError(err error) bool {
295 var errno syscall.Errno
296 return errors.As(err, &errno) && (errors.Is(errno, syscall.EPERM) || errors.Is(errno, syscall.EACCES))
297 }
298 +
299 +func (c *Collector) detectBulkWalkSupport(snmpClient gosnmp.Handler) (bool, error) {
300 + if snmpClient.Version() == gosnmp.Version1 {
301 + return false, nil
302 + }
303 +
304 + // Use a very small max-reps for the probe to be gentle
305 + orig := snmpClient.MaxRepetitions()
306 + defer snmpClient.SetMaxRepetitions(orig)
307 + snmpClient.SetMaxRepetitions(5)
308 +
309 + oids, err := snmpClient.BulkWalkAll(snmputils.RootOidMibSystem)
310 + if err != nil {
311 + return false, err
312 + }
313 + return len(oids) > 0, nil
314 +}
src/go/plugin/go.d/collector/snmp/collector.go
+2
@@ -99,6 +99,8 @@ type (
99 snmpProfiles []*ddsnmp.Profile
100
101 adjMaxRepetitions uint32
102 +
103 + disableBulkWalk bool
104 }
105 ddCollector interface {
106 Collect() ([]*ddsnmp.ProfileMetrics, error)
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector.go
+6 -5
@@ -19,10 +19,11 @@ import (
19 )
20
21 type Config struct {
22 - SnmpClient gosnmp.Handler
23 - Profiles []*ddsnmp.Profile
24 - Log *logger.Logger
25 - SysObjectID string
22 + SnmpClient gosnmp.Handler
23 + Profiles []*ddsnmp.Profile
24 + Log *logger.Logger
25 + SysObjectID string
26 + DisableBulkWalk bool
27 }
28
29 func New(cfg Config) *Collector {
@@ -42,7 +43,7 @@ func New(cfg Config) *Collector {
43 coll.globalTagsCollector = newGlobalTagsCollector(cfg.SnmpClient, coll.missingOIDs, coll.log)
44 coll.deviceMetadataCollector = newDeviceMetadataCollector(cfg.SnmpClient, coll.missingOIDs, coll.log, cfg.SysObjectID)
45 coll.scalarCollector = newScalarCollector(cfg.SnmpClient, coll.missingOIDs, coll.log)
45 - coll.tableCollector = newTableCollector(cfg.SnmpClient, coll.missingOIDs, coll.tableCache, coll.log)
46 + coll.tableCollector = newTableCollector(cfg.SnmpClient, coll.missingOIDs, coll.tableCache, coll.log, cfg.DisableBulkWalk)
47 coll.vmetricsCollector = newVirtualMetricsCollector(coll.log)
48
49 return coll
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table.go
+16 -14
@@ -17,23 +17,25 @@ import (
17
18 // tableCollector handles collection of SNMP table metrics
19 type tableCollector struct {
20 - snmpClient gosnmp.Handler
21 - missingOIDs map[string]bool
22 - tableCache *tableCache
23 - log *logger.Logger
24 - valProc *valueProcessor
25 - rowProcessor *tableRowProcessor
20 + snmpClient gosnmp.Handler
21 + disableBulkWalk bool
22 + missingOIDs map[string]bool
23 + tableCache *tableCache
24 + log *logger.Logger
25 + valProc *valueProcessor
26 + rowProcessor *tableRowProcessor
27 }
28
29 // newTableCollector creates a new table collector
29 -func newTableCollector(snmpClient gosnmp.Handler, missingOIDs map[string]bool, tableCache *tableCache, log *logger.Logger) *tableCollector {
30 +func newTableCollector(snmpClient gosnmp.Handler, missingOIDs map[string]bool, tableCache *tableCache, log *logger.Logger, disableBulkWalk bool) *tableCollector {
31 return &tableCollector{
31 - snmpClient: snmpClient,
32 - missingOIDs: missingOIDs,
33 - tableCache: tableCache,
34 - log: log,
35 - valProc: newValueProcessor(),
36 - rowProcessor: newTableRowProcessor(log),
32 + snmpClient: snmpClient,
33 + disableBulkWalk: disableBulkWalk,
34 + missingOIDs: missingOIDs,
35 + tableCache: tableCache,
36 + log: log,
37 + valProc: newValueProcessor(),
38 + rowProcessor: newTableRowProcessor(log),
39 }
40 }
41
@@ -552,7 +554,7 @@ func (tc *tableCollector) snmpWalk(oid string) (map[string]gosnmp.SnmpPDU, error
554 var resp []gosnmp.SnmpPDU
555 var err error
556
555 - if tc.snmpClient.Version() == gosnmp.Version1 {
557 + if tc.snmpClient.Version() == gosnmp.Version1 || tc.disableBulkWalk {
558 resp, err = tc.snmpClient.WalkAll(oid)
559 } else {
560 resp, err = tc.snmpClient.BulkWalkAll(oid)
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table_test.go
+1 -1
@@ -3821,7 +3821,7 @@ func TestTableCollector_Collect(t *testing.T) {
3821
3822 missingOIDs := make(map[string]bool)
3823 tableCache := newTableCache(0, 0) // Cache disabled
3824 - collector := newTableCollector(mockHandler, missingOIDs, tableCache, logger.New())
3824 + collector := newTableCollector(mockHandler, missingOIDs, tableCache, logger.New(), false)
3825
3826 result, err := collector.Collect(tc.profile)
3827