Add derived links endpoint from LLDP and SNMP data.
Build link views from LLDP neighbors and resolve targets by matching remote system names to SNMP sysName entries. Made-with: Cursor
This commit is contained in:
@@ -15,6 +15,16 @@ type Handler struct {
|
||||
run *scans.Runner
|
||||
}
|
||||
|
||||
type LinkView struct {
|
||||
SourceIP string `json:"source_ip"`
|
||||
SourcePort string `json:"source_port"`
|
||||
TargetIP string `json:"target_ip,omitempty"`
|
||||
TargetSysName string `json:"target_sys_name,omitempty"`
|
||||
TargetPort string `json:"target_port,omitempty"`
|
||||
RemoteChassis string `json:"remote_chassis_id,omitempty"`
|
||||
ResolvedBy string `json:"resolved_by"`
|
||||
}
|
||||
|
||||
func NewHandler(store scans.Store, run *scans.Runner) *Handler {
|
||||
return &Handler{store: store, run: run}
|
||||
}
|
||||
@@ -29,6 +39,7 @@ func (h *Handler) Routes() http.Handler {
|
||||
mux.HandleFunc("GET /api/scans/{id}/ports", h.getScanPorts)
|
||||
mux.HandleFunc("GET /api/scans/{id}/snmp", h.getScanSNMP)
|
||||
mux.HandleFunc("GET /api/scans/{id}/lldp", h.getScanLLDP)
|
||||
mux.HandleFunc("GET /api/scans/{id}/links", h.getScanLinks)
|
||||
return withJSON(mux)
|
||||
}
|
||||
|
||||
@@ -202,6 +213,69 @@ func (h *Handler) getScanLLDP(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) getScanLinks(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
if id == "" {
|
||||
writeError(w, http.StatusBadRequest, "scan id is required")
|
||||
return
|
||||
}
|
||||
if _, ok := h.store.GetScan(id); !ok {
|
||||
writeError(w, http.StatusNotFound, "scan not found")
|
||||
return
|
||||
}
|
||||
|
||||
limit := 2000
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
n, err := strconv.Atoi(raw)
|
||||
if err != nil || n <= 0 || n > 20000 {
|
||||
writeError(w, http.StatusBadRequest, "limit must be between 1 and 20000")
|
||||
return
|
||||
}
|
||||
limit = n
|
||||
}
|
||||
|
||||
snmpItems := h.store.ListSNMPResults(id, 10000)
|
||||
lldpItems := h.store.ListLLDPResults(id, limit)
|
||||
|
||||
sysNameToIP := make(map[string]string, len(snmpItems))
|
||||
for _, s := range snmpItems {
|
||||
name := strings.TrimSpace(strings.ToLower(s.SysName))
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := sysNameToIP[name]; !exists {
|
||||
sysNameToIP[name] = s.IP
|
||||
}
|
||||
}
|
||||
|
||||
links := make([]LinkView, 0, len(lldpItems))
|
||||
for _, l := range lldpItems {
|
||||
targetNameNorm := strings.TrimSpace(strings.ToLower(l.RemoteSysName))
|
||||
targetIP := ""
|
||||
resolvedBy := "unresolved"
|
||||
if targetNameNorm != "" {
|
||||
if ip, ok := sysNameToIP[targetNameNorm]; ok {
|
||||
targetIP = ip
|
||||
resolvedBy = "remote_sys_name"
|
||||
}
|
||||
}
|
||||
|
||||
links = append(links, LinkView{
|
||||
SourceIP: l.IP,
|
||||
SourcePort: l.LocalPortNum,
|
||||
TargetIP: targetIP,
|
||||
TargetSysName: l.RemoteSysName,
|
||||
TargetPort: l.RemotePortID,
|
||||
RemoteChassis: l.RemoteChassisID,
|
||||
ResolvedBy: resolvedBy,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"items": links,
|
||||
})
|
||||
}
|
||||
|
||||
func withJSON(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
Reference in New Issue
Block a user