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