From 16762c14ac75907c81583e7fb8b1e3658dfb20b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D1=83=D1=86=D0=B5=D0=BD=D0=BA=D0=BE=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B4=D1=80=D0=B5=D0=B9=20=D0=90=D0=BD=D0=B0=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Fri, 10 Apr 2026 13:22:59 +1000 Subject: [PATCH] SNMP LLDP: longer timeout, lower MaxRepetitions, Walk fallback after BulkWalk errors (e.g. Ubiquiti EdgeSwitch). Made-with: Cursor --- README.md | 4 ++++ internal/scans/runner.go | 44 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 489f0a9..3ec7c04 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/internal/scans/runner.go b/internal/scans/runner.go index 819e7f5..d17d122 100644 --- a/internal/scans/runner.go +++ b/internal/scans/runner.go @@ -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 }