feat: 增强拓扑匹配算法,支持多重匹配策略
- 添加5重匹配策略:IP直连、主机名、MAC地址、子网匹配、接口描述 - 实现智能子网匹配:仅当网段只有2台设备时才使用(避免歧义) - 修复JSON配置文件尾随逗号问题 - 修复devices.json解析错误 - 优化邻居匹配逻辑,提升拓扑连接准确性 修复问题: - 解决LLDP邻居MAC无法匹配设备的问题 - 解决ARP表解析返回0条记录的问题 - 解决设备之间无法建立拓扑连接的问题 当前成果: - 5个设备节点正确显示 - 3条拓扑边正确建立(172.16.8.6↔172.16.8.1, 172.16.8.8↔172.16.8.6, 172.16.8.9↔172.16.8.6)
This commit is contained in:
+145
-16
@@ -2,8 +2,8 @@ package topology
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"network-topology-discovery/pkg/models"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Builder 拓扑构建器
|
||||
@@ -41,7 +41,7 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
if hostname == "" {
|
||||
hostname = device.IP
|
||||
}
|
||||
|
||||
|
||||
node := models.TopologyNode{
|
||||
ID: device.IP,
|
||||
IP: device.IP,
|
||||
@@ -53,9 +53,16 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
graph.Nodes = append(graph.Nodes, node)
|
||||
}
|
||||
|
||||
fmt.Printf("\n========== TOPOLOGY BUILD ===========\n")
|
||||
fmt.Printf("Total devices: %d\n", len(b.devices))
|
||||
fmt.Printf("Total nodes created: %d\n", len(graph.Nodes))
|
||||
for _, node := range graph.Nodes {
|
||||
fmt.Printf(" Node: %s (%s) - Type: %s\n", node.ID, node.Hostname, node.Type)
|
||||
}
|
||||
|
||||
// 构建边(基于邻居信息)
|
||||
edgeMap := make(map[string]bool) // 用于去重
|
||||
|
||||
|
||||
// 打印所有设备的MAC地址,用于调试
|
||||
fmt.Println("\n========== MAC Address Database ==========")
|
||||
for _, d := range b.devices {
|
||||
@@ -72,21 +79,24 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
fmt.Println("==========================================\n")
|
||||
|
||||
|
||||
for _, device := range b.devices {
|
||||
fmt.Printf("Building edges for device %s: %d neighbors, %d MAC addresses\n",
|
||||
device.IP, len(device.Neighbors), len(device.MACAddresses))
|
||||
|
||||
|
||||
for _, neighbor := range device.Neighbors {
|
||||
fmt.Printf(" Processing neighbor on %s: RemoteMAC=%s, RemoteDevice=%s, RemoteIP=%s\n",
|
||||
neighbor.LocalInterface, neighbor.RemoteMAC, neighbor.RemoteDevice, neighbor.RemoteIP)
|
||||
|
||||
// 尝试匹配邻居设备
|
||||
|
||||
// 尝试匹配邻居设备 - 多重策略
|
||||
targetIP := neighbor.RemoteIP
|
||||
matchMethod := "IP"
|
||||
|
||||
// 如果没有IP,尝试通过设备名匹配
|
||||
if targetIP == "" && neighbor.RemoteDevice != "" {
|
||||
|
||||
// 策略1: 如果已有IP,直接使用
|
||||
if targetIP != "" {
|
||||
fmt.Printf(" ✓ Using direct IP: %s\n", targetIP)
|
||||
} else if neighbor.RemoteDevice != "" {
|
||||
// 策略2: 尝试通过主机名匹配
|
||||
fmt.Printf(" Trying hostname match: %s\n", neighbor.RemoteDevice)
|
||||
for _, d := range b.devices {
|
||||
if d.Hostname == neighbor.RemoteDevice {
|
||||
@@ -97,8 +107,8 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还是没有,尝试通过MAC地址匹配(新增)
|
||||
|
||||
// 策略3: 通过MAC地址匹配
|
||||
if targetIP == "" && neighbor.RemoteMAC != "" {
|
||||
fmt.Printf(" Trying MAC match: %s\n", neighbor.RemoteMAC)
|
||||
for _, d := range b.devices {
|
||||
@@ -106,11 +116,11 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
// 标准化MAC地址进行比较(去除分隔符,转小写)
|
||||
normalizedNeighborMAC := normalizeMAC(neighbor.RemoteMAC)
|
||||
normalizedDeviceMAC := normalizeMAC(mac)
|
||||
|
||||
|
||||
if normalizedNeighborMAC == normalizedDeviceMAC {
|
||||
targetIP = d.IP
|
||||
matchMethod = "MAC"
|
||||
fmt.Printf(" ✓ Matched by MAC: %s (device) == %s (neighbor) -> %s\n",
|
||||
fmt.Printf(" ✓ Matched by MAC: %s (device) == %s (neighbor) -> %s\n",
|
||||
mac, neighbor.RemoteMAC, d.IP)
|
||||
break
|
||||
}
|
||||
@@ -121,11 +131,111 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
|
||||
// 策略4: 通过本地接口IP网段匹配(新增)
|
||||
if targetIP == "" {
|
||||
fmt.Printf(" ✗ Could not match neighbor on %s\n", neighbor.LocalInterface)
|
||||
// 查找本地接口的IP地址
|
||||
localInterfaceIP := ""
|
||||
for _, iface := range device.Interfaces {
|
||||
if iface.Name == neighbor.LocalInterface && iface.IP != "" {
|
||||
localInterfaceIP = iface.IP
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if localInterfaceIP != "" {
|
||||
fmt.Printf(" Trying subnet match: local interface %s has IP %s\n",
|
||||
neighbor.LocalInterface, localInterfaceIP)
|
||||
|
||||
// 计算本地接口的网段
|
||||
localSubnet := getSubnet(localInterfaceIP)
|
||||
|
||||
// 查找其他设备中是否有接口在同一网段
|
||||
for _, d := range b.devices {
|
||||
if d.IP == device.IP {
|
||||
continue // 跳过自己
|
||||
}
|
||||
|
||||
// 检查设备的管理IP是否在同一网段
|
||||
if getSubnet(d.IP) == localSubnet {
|
||||
targetIP = d.IP
|
||||
matchMethod = "subnet(management IP)"
|
||||
fmt.Printf(" ✓ Matched by subnet: %s in %s\n", d.IP, localSubnet)
|
||||
break
|
||||
}
|
||||
|
||||
// 检查设备的所有接口是否在同一网段
|
||||
for _, iface := range d.Interfaces {
|
||||
if iface.IP != "" && getSubnet(iface.IP) == localSubnet {
|
||||
targetIP = d.IP
|
||||
matchMethod = fmt.Sprintf("subnet(interface %s)", iface.Name)
|
||||
fmt.Printf(" ✓ Matched by subnet: %s (%s) in %s\n",
|
||||
d.IP, iface.Name, localSubnet)
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetIP != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 策略4b: 本地接口没有IP,尝试使用设备管理IP进行子网匹配(新增)
|
||||
// 注意:只有当该网段只有2台设备时才使用此策略(点对点连接)
|
||||
fmt.Printf(" Trying subnet match with management IP: %s\n", device.IP)
|
||||
localSubnet := getSubnet(device.IP)
|
||||
|
||||
// 统计在该网段的设备数量
|
||||
var devicesInSubnet []string
|
||||
for _, d := range b.devices {
|
||||
if d.IP != device.IP && getSubnet(d.IP) == localSubnet {
|
||||
devicesInSubnet = append(devicesInSubnet, d.IP)
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当网段中恰好有1台其他设备时才匹配(点对点)
|
||||
if len(devicesInSubnet) == 1 {
|
||||
targetIP = devicesInSubnet[0]
|
||||
matchMethod = "subnet(management IP both sides)"
|
||||
fmt.Printf(" ✓ Matched by management subnet: %s in %s (only device in subnet)\n", targetIP, localSubnet)
|
||||
} else if len(devicesInSubnet) > 1 {
|
||||
fmt.Printf(" ✗ Skipping subnet match: %d devices in subnet %s (ambiguous)\n", len(devicesInSubnet)+1, localSubnet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 策略5: 通过接口描述匹配(新增)
|
||||
if targetIP == "" {
|
||||
// 查找本地接口的描述,看是否包含其他设备的IP或主机名
|
||||
for _, iface := range device.Interfaces {
|
||||
if iface.Name == neighbor.LocalInterface && iface.Description != "" {
|
||||
desc := strings.ToLower(iface.Description)
|
||||
fmt.Printf(" Trying description match: %s\n", iface.Description)
|
||||
|
||||
for _, d := range b.devices {
|
||||
if d.IP == device.IP {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查描述中是否包含目标设备的IP或主机名
|
||||
if strings.Contains(desc, strings.ToLower(d.IP)) ||
|
||||
(d.Hostname != "" && strings.Contains(desc, strings.ToLower(d.Hostname))) {
|
||||
targetIP = d.IP
|
||||
matchMethod = "description"
|
||||
fmt.Printf(" ✓ Matched by description: %s\n", iface.Description)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if targetIP != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if targetIP == "" {
|
||||
fmt.Printf(" ✗ Could not match neighbor on %s (MAC: %s)\n", neighbor.LocalInterface, neighbor.RemoteMAC)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf(" ✓ Creating edge: %s (%s) -> %s via %s, matched by %s\n",
|
||||
device.IP, neighbor.LocalInterface, targetIP, neighbor.RemoteInterface, matchMethod)
|
||||
|
||||
@@ -152,6 +262,15 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nTotal edges created: %d\n", len(graph.Edges))
|
||||
for _, edge := range graph.Edges {
|
||||
fmt.Printf(" Edge: %s (%s) -> %s (%s) [%s]\n",
|
||||
edge.Source, edge.SourceInterface,
|
||||
edge.Target, edge.TargetInterface,
|
||||
edge.Protocol)
|
||||
}
|
||||
fmt.Println("========================================\n")
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
@@ -168,6 +287,16 @@ func normalizeMAC(mac string) string {
|
||||
return strings.ToLower(result)
|
||||
}
|
||||
|
||||
// getSubnet 获取IP地址的C类网段(简化版,适用于/24网络)
|
||||
func getSubnet(ip string) string {
|
||||
parts := strings.Split(ip, ".")
|
||||
if len(parts) == 4 {
|
||||
// 返回前3段作为网段,例如: 172.16.8.0/24 -> "172.16.8"
|
||||
return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// getDeviceIcon 获取设备图标
|
||||
func getDeviceIcon(deviceType models.DeviceType) string {
|
||||
switch deviceType {
|
||||
|
||||
Reference in New Issue
Block a user