From 1df5ddf67a00bb6f68721b4d093da9051d31eadb 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 14:56:43 +1000 Subject: [PATCH] =?UTF-8?q?fix(lldp):=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=BE=D1=80=D1=82=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=B0=20OID=20+?= =?UTF-8?q?=20lldpLocPortDesc=20=D0=B4=D0=BB=D1=8F=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86=D1=8B=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- internal/scans/runner.go | 67 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/internal/scans/runner.go b/internal/scans/runner.go index 8bc4964..4f359eb 100644 --- a/internal/scans/runner.go +++ b/internal/scans/runner.go @@ -259,6 +259,11 @@ func lldpRemRowsFromWalk(rows map[string]*lldpRemAggRow, pduName string, val any case "9": r.sysName = v } + // Индекс lldpRemEntry: { timeMark, localPortNum, remIndex } — часть прошивок не отдаёт колонку 2, но порт есть в суффиксе OID. + idx := parts[1:] + if len(idx) >= 3 && strings.TrimSpace(r.localPort) == "" { + r.localPort = idx[1] + } } // probeLLDPViaRemTableWalk один раз обходит всю lldpRemTable (стандарт IEEE 802.1AB). @@ -395,9 +400,13 @@ func probeLLDPViaColumnWalks(client *gosnmp.GoSNMP, ip string, checkedAt time.Ti out := make([]LLDPResult, 0, len(keys)) for k := range keys { + lp := strings.TrimSpace(ports[k]) + if lp == "" { + lp = lldpRemIndexLocalPortFromKey(k) + } item := LLDPResult{ IP: ip, - LocalPortNum: ports[k], + LocalPortNum: lp, RemoteChassisID: chassis[k], RemotePortID: remotePorts[k], RemoteSysName: sysNames[k], @@ -419,9 +428,65 @@ func probeLLDP(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []LLDPResu if len(out) == 0 { out = probeLLDPViaGetNextRemTable(client, ip, checkedAt) } + return enrichLLDPLocalPortLabels(client, out) +} + +// lldpRemIndexLocalPortFromKey суффикс ключа walk: timeMark.localPortNum.remIndex (IEEE LLDP remTable). +func lldpRemIndexLocalPortFromKey(key string) string { + parts := strings.Split(key, ".") + if len(parts) >= 3 { + return parts[1] + } + return "" +} + +// lldpLocPortDescOIDs — lldpLocPortDesc по номеру порта (RFC 4502 и встречающийся в поле вариант). +var lldpLocPortDescOIDs = []string{ + ".1.0.8802.1.1.2.1.2.1.7.1.3", + ".1.0.8802.1.1.2.1.3.7.1.3", +} + +func probeLLDPLocPortDescByPortNum(client *gosnmp.GoSNMP) map[string]string { + out := make(map[string]string) + for _, base := range lldpLocPortDescOIDs { + for k, v := range walkAsMap(client, base) { + v = strings.TrimSpace(v) + if v != "" { + out[k] = v + } + } + } return out } +func formatLocalPortLabel(portNum, ifDescr string) string { + portNum = strings.TrimSpace(portNum) + ifDescr = strings.TrimSpace(ifDescr) + if ifDescr != "" && portNum != "" && ifDescr != portNum { + return portNum + " — " + ifDescr + } + if ifDescr != "" { + return ifDescr + } + return portNum +} + +// enrichLLDPLocalPortLabels подставляет lldpLocPortDesc (как в CLI) к номеру локального порта. +func enrichLLDPLocalPortLabels(client *gosnmp.GoSNMP, items []LLDPResult) []LLDPResult { + if len(items) == 0 { + return items + } + descByPort := probeLLDPLocPortDescByPortNum(client) + if len(descByPort) == 0 { + return items + } + for i := range items { + num := strings.TrimSpace(items[i].LocalPortNum) + items[i].LocalPortNum = formatLocalPortLabel(num, descByPort[num]) + } + return items +} + func snmpOIDTrim(s string) string { return strings.TrimPrefix(strings.TrimSpace(s), ".") }