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) - 星型拓扑,无环路
Este cometimento está contido em:
@@ -85,6 +85,16 @@ func (b *Builder) Build() models.TopologyGraph {
|
|||||||
device.IP, len(device.Neighbors), len(device.MACAddresses))
|
device.IP, len(device.Neighbors), len(device.MACAddresses))
|
||||||
|
|
||||||
for _, neighbor := range device.Neighbors {
|
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",
|
fmt.Printf(" Processing neighbor on %s: RemoteMAC=%s, RemoteDevice=%s, RemoteIP=%s\n",
|
||||||
neighbor.LocalInterface, neighbor.RemoteMAC, neighbor.RemoteDevice, neighbor.RemoteIP)
|
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网段匹配(新增)
|
// 策略4: 通过本地接口IP网段匹配(新增)
|
||||||
if targetIP == "" {
|
if targetIP == "" {
|
||||||
// 查找本地接口的IP地址
|
// 查找本地接口的IP地址
|
||||||
@@ -231,6 +274,9 @@ func (b *Builder) Build() models.TopologyGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 策略6: 通过邻居端口号反向匹配(已禁用 - 容易产生错误连接)
|
||||||
|
// 暂时禁用,因为端口号匹配不够精确,可能产生环路
|
||||||
|
|
||||||
if targetIP == "" {
|
if targetIP == "" {
|
||||||
fmt.Printf(" ✗ Could not match neighbor on %s (MAC: %s)\n", neighbor.LocalInterface, neighbor.RemoteMAC)
|
fmt.Printf(" ✗ Could not match neighbor on %s (MAC: %s)\n", neighbor.LocalInterface, neighbor.RemoteMAC)
|
||||||
continue
|
continue
|
||||||
@@ -239,15 +285,24 @@ func (b *Builder) Build() models.TopologyGraph {
|
|||||||
fmt.Printf(" ✓ Creating edge: %s (%s) -> %s via %s, matched by %s\n",
|
fmt.Printf(" ✓ Creating edge: %s (%s) -> %s via %s, matched by %s\n",
|
||||||
device.IP, neighbor.LocalInterface, targetIP, neighbor.RemoteInterface, matchMethod)
|
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)
|
hasExistingEdge := false
|
||||||
|
for _, existingEdge := range graph.Edges {
|
||||||
// 避免重复边
|
if (existingEdge.Source == device.IP && existingEdge.Target == targetIP) ||
|
||||||
if edgeMap[edgeID] || edgeMap[reverseEdgeID] {
|
(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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建唯一的边ID(包含接口信息用于调试)
|
||||||
|
edgeID := fmt.Sprintf("%s-%s-%s", device.IP, neighbor.LocalInterface, targetIP)
|
||||||
|
|
||||||
edge := models.TopologyEdge{
|
edge := models.TopologyEdge{
|
||||||
ID: edgeID,
|
ID: edgeID,
|
||||||
Source: device.IP,
|
Source: device.IP,
|
||||||
@@ -287,6 +342,42 @@ func normalizeMAC(mac string) string {
|
|||||||
return strings.ToLower(result)
|
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网络)
|
// getSubnet 获取IP地址的C类网段(简化版,适用于/24网络)
|
||||||
func getSubnet(ip string) string {
|
func getSubnet(ip string) string {
|
||||||
parts := strings.Split(ip, ".")
|
parts := strings.Split(ip, ".")
|
||||||
|
|||||||
Criar uma nova questão referindo esta
Bloquear um utilizador