feat: 优化拓扑匹配策略,修复环路问题
- 禁用端口号匹配策略(策略6),避免产生错误连接和环路 - 保留精确匹配策略:MAC精确匹配、MAC前缀匹配、子网匹配 - 优化虚拟机邻居过滤(过滤9844、9400等虚拟机MAC前缀) - 实现设备间去重逻辑(同一对设备只保留一条边) 最终拓扑结构: - 5个节点,4条边 - 防火墙(172.16.8.1) 核心交换机(172.16.8.6) - 核心交换机(172.16.8.6) 接入交换机(172.16.8.8/9/10) - 星型拓扑,无环路
这个提交包含在:
+97
-6
@@ -85,6 +85,16 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
device.IP, len(device.Neighbors), len(device.MACAddresses))
|
||||
|
||||
for _, neighbor := range device.Neighbors {
|
||||
// 过滤虚拟机邻居:识别MAC地址前缀,只保留真实网络设备
|
||||
if neighbor.RemoteMAC != "" {
|
||||
macPrefix := getMACPrefix(neighbor.RemoteMAC)
|
||||
if isVirtualMAC(macPrefix) {
|
||||
fmt.Printf(" ⊘ Skipping virtual machine neighbor on %s (MAC: %s, prefix: %s)\n",
|
||||
neighbor.LocalInterface, neighbor.RemoteMAC, macPrefix)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" Processing neighbor on %s: RemoteMAC=%s, RemoteDevice=%s, RemoteIP=%s\n",
|
||||
neighbor.LocalInterface, neighbor.RemoteMAC, neighbor.RemoteDevice, neighbor.RemoteIP)
|
||||
|
||||
@@ -131,6 +141,39 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
|
||||
// 策略3b: 通过MAC前缀匹配(新增)
|
||||
// 当精确MAC匹配失败时,尝试通过MAC前缀匹配(适用于同一设备的多个接口)
|
||||
if targetIP == "" && neighbor.RemoteMAC != "" {
|
||||
neighborMACPrefix := getMACPrefix(neighbor.RemoteMAC)
|
||||
fmt.Printf(" Trying MAC prefix match: %s (prefix: %s)\n", neighbor.RemoteMAC, neighborMACPrefix)
|
||||
|
||||
for _, d := range b.devices {
|
||||
if d.IP == device.IP {
|
||||
continue // 跳过自己
|
||||
}
|
||||
|
||||
// 检查该设备的MAC地址是否有相同前缀
|
||||
matchingMACs := 0
|
||||
for _, mac := range d.MACAddresses {
|
||||
if getMACPrefix(mac) == neighborMACPrefix {
|
||||
matchingMACs++
|
||||
}
|
||||
}
|
||||
|
||||
// 如果该设备有多个MAC地址使用相同前缀,说明是同一台设备
|
||||
if matchingMACs >= 3 { // 至少3个MAC使用相同前缀
|
||||
// 进一步验证:检查是否在同一网段
|
||||
if getSubnet(d.IP) == getSubnet(device.IP) {
|
||||
targetIP = d.IP
|
||||
matchMethod = fmt.Sprintf("MAC-prefix(%s)", neighborMACPrefix)
|
||||
fmt.Printf(" ✓ Matched by MAC prefix: %s (device has %d MACs with prefix %s, same subnet) -> %s\n",
|
||||
neighbor.RemoteMAC, matchingMACs, neighborMACPrefix, d.IP)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 策略4: 通过本地接口IP网段匹配(新增)
|
||||
if targetIP == "" {
|
||||
// 查找本地接口的IP地址
|
||||
@@ -231,6 +274,9 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
}
|
||||
}
|
||||
|
||||
// 策略6: 通过邻居端口号反向匹配(已禁用 - 容易产生错误连接)
|
||||
// 暂时禁用,因为端口号匹配不够精确,可能产生环路
|
||||
|
||||
if targetIP == "" {
|
||||
fmt.Printf(" ✗ Could not match neighbor on %s (MAC: %s)\n", neighbor.LocalInterface, neighbor.RemoteMAC)
|
||||
continue
|
||||
@@ -239,15 +285,24 @@ func (b *Builder) Build() models.TopologyGraph {
|
||||
fmt.Printf(" ✓ Creating edge: %s (%s) -> %s via %s, matched by %s\n",
|
||||
device.IP, neighbor.LocalInterface, targetIP, neighbor.RemoteInterface, matchMethod)
|
||||
|
||||
// 创建唯一的边ID
|
||||
edgeID := fmt.Sprintf("%s-%s-%s", device.IP, neighbor.LocalInterface, targetIP)
|
||||
reverseEdgeID := fmt.Sprintf("%s-%s-%s", targetIP, neighbor.RemoteInterface, device.IP)
|
||||
|
||||
// 避免重复边
|
||||
if edgeMap[edgeID] || edgeMap[reverseEdgeID] {
|
||||
// 避免重复边:同一对设备之间只保留一条边(不区分接口)
|
||||
// 如果这对设备已经有边了,跳过
|
||||
hasExistingEdge := false
|
||||
for _, existingEdge := range graph.Edges {
|
||||
if (existingEdge.Source == device.IP && existingEdge.Target == targetIP) ||
|
||||
(existingEdge.Source == targetIP && existingEdge.Target == device.IP) {
|
||||
fmt.Printf(" ⚠ Skipping: edge between %s and %s already exists\n", device.IP, targetIP)
|
||||
hasExistingEdge = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasExistingEdge {
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建唯一的边ID(包含接口信息用于调试)
|
||||
edgeID := fmt.Sprintf("%s-%s-%s", device.IP, neighbor.LocalInterface, targetIP)
|
||||
|
||||
edge := models.TopologyEdge{
|
||||
ID: edgeID,
|
||||
Source: device.IP,
|
||||
@@ -287,6 +342,42 @@ func normalizeMAC(mac string) string {
|
||||
return strings.ToLower(result)
|
||||
}
|
||||
|
||||
// getMACPrefix 获取MAC地址前缀(前4个字符)
|
||||
func getMACPrefix(mac string) string {
|
||||
normalized := normalizeMAC(mac)
|
||||
if len(normalized) >= 4 {
|
||||
return normalized[0:4]
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// isVirtualMAC 判断是否是虚拟机MAC地址前缀
|
||||
func isVirtualMAC(prefix string) bool {
|
||||
// 根据用户反馈,这些前缀都是虚拟机
|
||||
userVirtualPrefixes := []string{"9844", "9400", "cc05", "44a1"}
|
||||
|
||||
for _, vp := range userVirtualPrefixes {
|
||||
if prefix == vp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 常见虚拟机/虚拟网卡MAC前缀(VMware、Hyper-V、KVM等)
|
||||
virtualPrefixes := []string{
|
||||
"0005", "000c", "0015", "0050", // VMware
|
||||
"0017", "001d", "0025", "5254", // KVM/QEMU
|
||||
"0015", "0017", "00ff", // Xen
|
||||
}
|
||||
|
||||
for _, vp := range virtualPrefixes {
|
||||
if prefix == vp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getSubnet 获取IP地址的C类网段(简化版,适用于/24网络)
|
||||
func getSubnet(ip string) string {
|
||||
parts := strings.Split(ip, ".")
|
||||
|
||||
在新工单中引用
屏蔽一个用户