feat(ui,snmp): IF-MIB интерфейсы в табл.1 связей, слияние с LLDP, сортировка по локальному порту

Made-with: Cursor
This commit is contained in:
Луценко Андрей Анатольевич
2026-04-10 16:04:16 +10:00
parent 2388b5f3ff
commit a7f80eac9a
7 changed files with 448 additions and 40 deletions
+88 -5
View File
@@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"runtime"
"sort"
"strconv"
"strings"
"sync"
@@ -101,13 +102,18 @@ func (r *Runner) run(job ScanJob) {
}
}
if isUp && r.cfg.SNMPEnabled {
snmpRes, lldpRes := probeSNMPAndLLDP(ip, r.cfg.SNMPCommunity, checkedAt, job.Options.PingTimeoutMS)
snmpRes, lldpRes, ifRes := probeSNMPAndLLDP(ip, r.cfg.SNMPCommunity, checkedAt, job.Options.PingTimeoutMS)
_ = r.store.SaveSNMPResult(job.ID, snmpRes)
for _, lr := range lldpRes {
if err := r.store.SaveLLDPResult(job.ID, lr); err != nil {
log.Printf("scan %s: save LLDP для %s: %v", job.ID, lr.IP, err)
}
}
for _, iface := range ifRes {
if err := r.store.SaveInterfaceResult(job.ID, iface); err != nil {
log.Printf("scan %s: save interface %s ifIndex=%d: %v", job.ID, iface.IP, iface.IfIndex, err)
}
}
}
mu.Lock()
@@ -147,7 +153,7 @@ func (r *Runner) run(job ScanJob) {
})
}
func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int) (SNMPResult, []LLDPResult) {
func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int) (SNMPResult, []LLDPResult, []InterfaceResult) {
if timeoutMS <= 0 {
timeoutMS = 700
}
@@ -164,7 +170,7 @@ func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int)
Retries: 1,
}
if err := client.Connect(); err != nil {
return SNMPResult{IP: ip, Success: false, Error: err.Error(), CheckedAt: checkedAt}, nil
return SNMPResult{IP: ip, Success: false, Error: err.Error(), CheckedAt: checkedAt}, nil, nil
}
defer client.Conn.Close()
@@ -175,7 +181,7 @@ func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int)
}
pkt, err := client.Get(oids)
if err != nil {
return SNMPResult{IP: ip, Success: false, Error: err.Error(), CheckedAt: checkedAt}, nil
return SNMPResult{IP: ip, Success: false, Error: err.Error(), CheckedAt: checkedAt}, nil, nil
}
out := SNMPResult{IP: ip, Success: true, CheckedAt: checkedAt}
@@ -200,7 +206,84 @@ func probeSNMPAndLLDP(ip, community string, checkedAt time.Time, timeoutMS int)
client.MaxRepetitions = 12
client.Retries = 2
return out, probeLLDP(client, ip, checkedAt)
lldpRes := probeLLDP(client, ip, checkedAt)
var ifList []InterfaceResult
if out.Success {
ifList = probeIfTable(client, ip, checkedAt)
}
return out, lldpRes, ifList
}
// probeIfTable собирает ifDescr, ifName (ifXTable), ifOperStatus по IF-MIB для списка портов в UI.
func probeIfTable(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []InterfaceResult {
descr := walkAsMap(client, ".1.3.6.1.2.1.2.2.1.2")
names := walkAsMap(client, ".1.3.6.1.2.1.31.1.1.1.1")
oper := walkAsMap(client, ".1.3.6.1.2.1.2.2.1.8")
idxs := mergeIFMIBKeys(descr, names, oper)
const maxIF = 2048
if len(idxs) > maxIF {
idxs = idxs[:maxIF]
}
out := make([]InterfaceResult, 0, len(idxs))
for _, ks := range idxs {
idx, err := strconv.Atoi(ks)
if err != nil || idx <= 0 {
continue
}
out = append(out, InterfaceResult{
IP: ip,
IfIndex: idx,
IfDescr: descr[ks],
IfName: names[ks],
IfOperStatus: mapIfOperStatus(oper[ks]),
CheckedAt: checkedAt,
})
}
return out
}
func mergeIFMIBKeys(maps ...map[string]string) []string {
seen := make(map[string]struct{})
for _, m := range maps {
for k := range m {
seen[k] = struct{}{}
}
}
out := make([]string, 0, len(seen))
for k := range seen {
out = append(out, k)
}
sort.Slice(out, func(i, j int) bool {
ai, e1 := strconv.Atoi(out[i])
bj, e2 := strconv.Atoi(out[j])
if e1 != nil || e2 != nil {
return out[i] < out[j]
}
return ai < bj
})
return out
}
func mapIfOperStatus(s string) string {
s = strings.TrimSpace(s)
switch s {
case "1":
return "up"
case "2":
return "down"
case "3":
return "testing"
case "4":
return "unknown"
case "5":
return "dormant"
case "6":
return "notPresent"
case "7":
return "lowerLayerDown"
default:
return s
}
}
// snmpBytesToDisplay строка для OCTET STRING SNMP: UTF-8 текст, иначе MAC (6 байт) или 0x+hex.