feat(mac-locations): VLAN-aware port MAC counts, access/trunk thresholds, likely_attachment
Made-with: Cursor
This commit is contained in:
+83
-2
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -60,6 +61,8 @@ type MacLocationRow struct {
|
||||
MAC string `json:"mac"`
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
ClientName string `json:"client_name,omitempty"`
|
||||
MacsOnPort int `json:"macs_on_port"`
|
||||
PortKind string `json:"port_kind"` // access | ambiguous | strong_transit
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
|
||||
@@ -434,6 +437,35 @@ func (h *Handler) getScanMacLocations(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
limit = n
|
||||
}
|
||||
accessMax := 8
|
||||
trunkMin := 32
|
||||
if v := strings.TrimSpace(r.URL.Query().Get("access_max_macs")); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 1 || n > 4096 {
|
||||
writeError(w, http.StatusBadRequest, "access_max_macs must be between 1 and 4096")
|
||||
return
|
||||
}
|
||||
accessMax = n
|
||||
}
|
||||
if v := strings.TrimSpace(r.URL.Query().Get("trunk_min_macs")); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 2 || n > 65536 {
|
||||
writeError(w, http.StatusBadRequest, "trunk_min_macs must be between 2 and 65536")
|
||||
return
|
||||
}
|
||||
trunkMin = n
|
||||
}
|
||||
if accessMax >= trunkMin {
|
||||
writeError(w, http.StatusBadRequest, "access_max_macs must be less than trunk_min_macs")
|
||||
return
|
||||
}
|
||||
|
||||
counts, err := h.store.CountDistinctMACsPerSwitchPort(id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
snmpItems := h.store.ListSNMPResults(id, 200000)
|
||||
ipToName := make(map[string]string, len(snmpItems))
|
||||
for _, s := range snmpItems {
|
||||
@@ -451,6 +483,11 @@ func (h *Handler) getScanMacLocations(w http.ResponseWriter, r *http.Request) {
|
||||
items := h.store.ListPortDevicesByMac(id, norm, limit)
|
||||
out := make([]MacLocationRow, 0, len(items))
|
||||
for _, r := range items {
|
||||
key := scans.PortFDBLocationKey(r.IP, r.IfIndex, r.BridgePort, r.Vlan)
|
||||
nOn := counts[key]
|
||||
if nOn < 1 {
|
||||
nOn = 1
|
||||
}
|
||||
row := MacLocationRow{
|
||||
SwitchIP: r.IP,
|
||||
IfIndex: r.IfIndex,
|
||||
@@ -458,6 +495,8 @@ func (h *Handler) getScanMacLocations(w http.ResponseWriter, r *http.Request) {
|
||||
Vlan: r.Vlan,
|
||||
MAC: r.MAC,
|
||||
ClientIP: strings.TrimSpace(r.LearnedIP),
|
||||
MacsOnPort: nOn,
|
||||
PortKind: scans.ClassifyPortKind(nOn, accessMax, trunkMin),
|
||||
CheckedAt: r.CheckedAt,
|
||||
}
|
||||
if nm, ok := ipToName[r.IP]; ok {
|
||||
@@ -470,9 +509,51 @@ func (h *Handler) getScanMacLocations(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
out = append(out, row)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
ri, rj := scans.PortKindRank(out[i].PortKind), scans.PortKindRank(out[j].PortKind)
|
||||
if ri != rj {
|
||||
return ri < rj
|
||||
}
|
||||
if out[i].MacsOnPort != out[j].MacsOnPort {
|
||||
return out[i].MacsOnPort < out[j].MacsOnPort
|
||||
}
|
||||
if out[i].SwitchIP != out[j].SwitchIP {
|
||||
return out[i].SwitchIP < out[j].SwitchIP
|
||||
}
|
||||
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
|
||||
}
|
||||
return out[i].ClientIP < out[j].ClientIP
|
||||
})
|
||||
|
||||
likely := make([]MacLocationRow, 0)
|
||||
for _, row := range out {
|
||||
if row.PortKind == "access" {
|
||||
likely = append(likely, row)
|
||||
}
|
||||
}
|
||||
sort.Slice(likely, func(i, j int) bool {
|
||||
if likely[i].MacsOnPort != likely[j].MacsOnPort {
|
||||
return likely[i].MacsOnPort < likely[j].MacsOnPort
|
||||
}
|
||||
if likely[i].SwitchIP != likely[j].SwitchIP {
|
||||
return likely[i].SwitchIP < likely[j].SwitchIP
|
||||
}
|
||||
if likely[i].IfIndex != likely[j].IfIndex {
|
||||
return likely[i].IfIndex < likely[j].IfIndex
|
||||
}
|
||||
return likely[i].Vlan < likely[j].Vlan
|
||||
})
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"mac": norm,
|
||||
"items": out,
|
||||
"mac": norm,
|
||||
"access_max_macs": accessMax,
|
||||
"trunk_min_macs": trunkMin,
|
||||
"likely_attachment": likely,
|
||||
"items": out,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user