SNMP LLDP: longer timeout, lower MaxRepetitions, Walk fallback after BulkWalk errors (e.g. Ubiquiti EdgeSwitch).

Made-with: Cursor
This commit is contained in:
Луценко Андрей Анатольевич
2026-04-10 13:22:59 +10:00
parent 6d75de1e2e
commit 16762c14ac
2 changed files with 44 additions and 4 deletions
+4
View File
@@ -94,6 +94,10 @@ SNMP-проверка по умолчанию включена (`SNMP_ENABLED=tr
В веб-UI после **«Создать scan и ждать»** опрашивается статус, показывается расшифровка в строке состояния, по завершении обновляется таблица **найденных устройств** (ping + SNMP) и строится граф по кнопке **«Загрузить»** (или автоматически после ожидания).
### LLDP пустой на коммутаторе (например Ubiquiti EdgeSwitch)
Опрос LLDP идёт по стандартному **LLDP-MIB** (`1.0.8802.1.1.2.1.4.1.1.*`) после успешного SNMP **Get** системной группы. Раньше при **GET-BULK** часть прошивок отвечала слишком долго или с ошибкой — обход обрывался, в БД не попадало ни строки («No LLDP data» в UI). Сейчас для фазы LLDP увеличен таймаут (не меньше **3 c** на запрос), снижен размер «пачки» **MaxRepetitions**, добавлен **повтор при ошибке через SNMP Walk (GetNext)**. В логах сервера ищите строки `lldp snmp BulkWalkAll` и `fallback Walk`. Проверьте с хоста nettopo: community (**`SNMP_COMMUNITY` / `public`**), доступ UDP **161** с этого сервера и что на свиче включён **SNMP v2c** (не только LLDP в L2, но и ответ агента SNMP).
## PostgreSQL без Docker
### Вариант A: macOS (Homebrew)
+40 -4
View File
@@ -184,6 +184,16 @@ func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int)
out.SysObjectID = snmpValueToString(vb.Value)
}
}
// LLDP — несколько BulkWalk подряд; часть прошивок (в т.ч. Ubiquiti EdgeSwitch) отваливается по таймауту
// или некорректно обрабатывает GET-BULK с большим MaxRepetitions — увеличиваем время и снижаем пакет,
// при ошибке BulkWalk в walkAsMap делаем запасной SNMP Walk (GetNext).
if min := time.Duration(3000) * time.Millisecond; client.Timeout < min {
client.Timeout = min
}
client.MaxRepetitions = 12
client.Retries = 2
return out, probeLLDP(client, ip, checkedAt)
}
@@ -239,16 +249,42 @@ func probeLLDP(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []LLDPResu
return out
}
func snmpOIDTrim(s string) string {
return strings.TrimPrefix(strings.TrimSpace(s), ".")
}
// walkAsMap выполняет поддерево SNMP по base oid. Сначала GET-BULK (быстро); при ошибке — Walk (GetNext),
// что совместимо с большим числом встраиваемых коммутаторов.
func walkAsMap(client *gosnmp.GoSNMP, oid string) map[string]string {
out := map[string]string{}
base := snmpOIDTrim(oid)
prefix := base + "."
out := make(map[string]string)
record := func(pduName string, val any) {
n := snmpOIDTrim(pduName)
if !strings.HasPrefix(n, prefix) {
return
}
key := strings.TrimPrefix(n, prefix)
if key == "" {
return
}
out[key] = snmpValueToString(val)
}
pdus, err := client.BulkWalkAll(oid)
if err != nil {
log.Printf("lldp snmp BulkWalkAll target=%s oid=%s: %v (fallback Walk)", client.Target, base, err)
if werr := client.Walk(oid, func(pdu gosnmp.SnmpPDU) error {
record(pdu.Name, pdu.Value)
return nil
}); werr != nil {
log.Printf("lldp snmp Walk target=%s oid=%s: %v", client.Target, base, werr)
}
return out
}
prefix := oid + "."
for _, p := range pdus {
key := strings.TrimPrefix(p.Name, prefix)
out[key] = snmpValueToString(p.Value)
record(p.Name, p.Value)
}
return out
}