feat: VLAN in FDB, persist vlan, enrich port-devices with hostname, UI columns
Made-with: Cursor
This commit is contained in:
@@ -542,8 +542,8 @@ func (s *PostgresStore) PurgeAllScanData() error {
|
||||
|
||||
func (s *PostgresStore) SavePortDeviceResult(scanID string, result PortDeviceResult) error {
|
||||
query := `
|
||||
insert into scan_port_devices (scan_id, ip, if_index, bridge_port, mac, learned_ip, checked_at)
|
||||
values ($1, $2, $3, $4, $5, $6, $7)`
|
||||
insert into scan_port_devices (scan_id, ip, if_index, bridge_port, vlan, mac, learned_ip, checked_at)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8)`
|
||||
var learnedIP any
|
||||
if v := strings.TrimSpace(result.LearnedIP); v != "" {
|
||||
learnedIP = v
|
||||
@@ -557,6 +557,7 @@ values ($1, $2, $3, $4, $5, $6, $7)`
|
||||
result.IP,
|
||||
result.IfIndex,
|
||||
result.BridgePort,
|
||||
result.Vlan,
|
||||
result.MAC,
|
||||
learnedIP,
|
||||
result.CheckedAt,
|
||||
@@ -569,12 +570,12 @@ func (s *PostgresStore) ListPortDeviceResults(scanID string, filterIP string, fi
|
||||
limit = 20000
|
||||
}
|
||||
query := `
|
||||
select ip::text, if_index, bridge_port, coalesce(mac, ''), coalesce(learned_ip::text, ''), checked_at
|
||||
select ip::text, if_index, bridge_port, vlan, coalesce(mac, ''), coalesce(learned_ip::text, ''), checked_at
|
||||
from scan_port_devices
|
||||
where scan_id = $1
|
||||
and ($2 = '' or ip = $2::inet)
|
||||
and ($3 = 0 or if_index = $3 or bridge_port = $3)
|
||||
order by if_index asc, mac asc, learned_ip asc
|
||||
order by if_index asc, vlan asc, mac asc, learned_ip asc
|
||||
limit $4`
|
||||
rows, err := s.db.QueryContext(context.Background(), query, scanID, filterIP, filterIfIndex, limit)
|
||||
if err != nil {
|
||||
@@ -585,7 +586,7 @@ limit $4`
|
||||
out := make([]PortDeviceResult, 0, 256)
|
||||
for rows.Next() {
|
||||
var r PortDeviceResult
|
||||
if err = rows.Scan(&r.IP, &r.IfIndex, &r.BridgePort, &r.MAC, &r.LearnedIP, &r.CheckedAt); err != nil {
|
||||
if err = rows.Scan(&r.IP, &r.IfIndex, &r.BridgePort, &r.Vlan, &r.MAC, &r.LearnedIP, &r.CheckedAt); err != nil {
|
||||
return nil
|
||||
}
|
||||
out = append(out, r)
|
||||
|
||||
@@ -266,7 +266,8 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
seen := make(map[string]struct{})
|
||||
out := make([]PortDeviceResult, 0, len(combinedFdb))
|
||||
for macTail, bridgePortRaw := range combinedFdb {
|
||||
mac := normalizeMACKey(dottedDecimalToMACTail(macTail))
|
||||
vlan, macSix := parseFdbKeyVlanAndMacTail(macTail)
|
||||
mac := normalizeMACKey(dottedDecimalToMACTail(macSix))
|
||||
if mac == "" {
|
||||
continue
|
||||
}
|
||||
@@ -285,7 +286,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
|
||||
ipsSet := macToIPs[mac]
|
||||
if len(ipsSet) == 0 {
|
||||
key := fmt.Sprintf("%d|%s|", ifIndex, mac)
|
||||
key := fmt.Sprintf("%d|%d|%s|", vlan, ifIndex, mac)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -294,6 +295,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
IP: ip,
|
||||
IfIndex: ifIndex,
|
||||
BridgePort: bridgePort,
|
||||
Vlan: vlan,
|
||||
MAC: mac,
|
||||
LearnedIP: "",
|
||||
CheckedAt: checkedAt,
|
||||
@@ -301,7 +303,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
continue
|
||||
}
|
||||
for learnedIP := range ipsSet {
|
||||
key := fmt.Sprintf("%d|%s|%s", ifIndex, mac, learnedIP)
|
||||
key := fmt.Sprintf("%d|%d|%s|%s", vlan, ifIndex, mac, learnedIP)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -310,6 +312,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
IP: ip,
|
||||
IfIndex: ifIndex,
|
||||
BridgePort: bridgePort,
|
||||
Vlan: vlan,
|
||||
MAC: mac,
|
||||
LearnedIP: learnedIP,
|
||||
CheckedAt: checkedAt,
|
||||
@@ -321,6 +324,9 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
if out[i].IfIndex != out[j].IfIndex {
|
||||
return out[i].IfIndex < out[j].IfIndex
|
||||
}
|
||||
if out[i].Vlan != out[j].Vlan {
|
||||
return out[i].Vlan < out[j].Vlan
|
||||
}
|
||||
if out[i].MAC != out[j].MAC {
|
||||
return out[i].MAC < out[j].MAC
|
||||
}
|
||||
@@ -333,6 +339,19 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
||||
return out
|
||||
}
|
||||
|
||||
// parseFdbKeyVlanAndMacTail — суффикс OID FDB: dot1q — {vlan}.{6 октетов MAC}; dot1d — только 6 октетов (vlan=0).
|
||||
func parseFdbKeyVlanAndMacTail(macTail string) (vlan int, macSixTail string) {
|
||||
p := strings.Split(strings.TrimSpace(macTail), ".")
|
||||
if len(p) >= 7 {
|
||||
vlan, _ = strconv.Atoi(p[0])
|
||||
return vlan, strings.Join(p[1:7], ".")
|
||||
}
|
||||
if len(p) >= 6 {
|
||||
return 0, strings.Join(p[len(p)-6:], ".")
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// snmpNumericString — значение SNMP как строка (int/float/[]byte) для Atoi.
|
||||
func snmpNumericString(v any) string {
|
||||
switch x := v.(type) {
|
||||
|
||||
@@ -122,8 +122,10 @@ type PortDeviceResult struct {
|
||||
IP string `json:"ip"`
|
||||
IfIndex int `json:"if_index"`
|
||||
BridgePort int `json:"bridge_port,omitempty"`
|
||||
Vlan int `json:"vlan"`
|
||||
MAC string `json:"mac"`
|
||||
LearnedIP string `json:"learned_ip"`
|
||||
Hostname string `json:"hostname,omitempty"` // подставляется при ответе API по sysName скана, не хранится в БД
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
|
||||
@@ -419,6 +421,9 @@ func (s *MemoryStore) ListPortDeviceResults(scanID string, filterIP string, filt
|
||||
if filtered[i].IfIndex != filtered[j].IfIndex {
|
||||
return filtered[i].IfIndex < filtered[j].IfIndex
|
||||
}
|
||||
if filtered[i].Vlan != filtered[j].Vlan {
|
||||
return filtered[i].Vlan < filtered[j].Vlan
|
||||
}
|
||||
if filtered[i].MAC != filtered[j].MAC {
|
||||
return filtered[i].MAC < filtered[j].MAC
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user