1
0
Ficheiros
network-topology-discovery/internal/topology/builder.go
T
Your Name e2f804ac52 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)
2026-04-26 05:45:00 +08:00

329 linhas
9.7 KiB
Go

Este ficheiro contém caracteres Unicode ambíguos
Este ficheiro contém caracteres Unicode que podem ser confundidos com outros caracteres. Se acha que é intencional, pode ignorar este aviso com segurança. Use o botão Revelar para os mostrar.
package topology
import (
"fmt"
"network-topology-discovery/pkg/models"
"strings"
)
// Builder 拓扑构建器
type Builder struct {
devices []models.Device
}
// NewBuilder 创建拓扑构建器
func NewBuilder() *Builder {
return &Builder{}
}
// AddDevice 添加设备
func (b *Builder) AddDevice(device models.Device) {
b.devices = append(b.devices, device)
}
// AddDevices 批量添加设备
func (b *Builder) AddDevices(devices []models.Device) {
b.devices = append(b.devices, devices...)
}
// Build 构建拓扑图
func (b *Builder) Build() models.TopologyGraph {
graph := models.TopologyGraph{
Nodes: make([]models.TopologyNode, 0),
Edges: make([]models.TopologyEdge, 0),
}
// 构建节点
nodeMap := make(map[string]models.TopologyNode)
for _, device := range b.devices {
// 优先使用主机名,如果没有则使用IP作为显示名
hostname := device.Hostname
if hostname == "" {
hostname = device.IP
}
node := models.TopologyNode{
ID: device.IP,
IP: device.IP,
Hostname: hostname,
Type: string(device.Type),
Icon: getDeviceIcon(device.Type),
}
nodeMap[device.IP] = node
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 {
if len(d.MACAddresses) > 0 {
fmt.Printf("Device %s (%s): %d MAC addresses\n", d.Hostname, d.IP, len(d.MACAddresses))
for i, mac := range d.MACAddresses {
if i < 5 { // 只打印前5个
fmt.Printf(" MAC[%d]: %s\n", i, mac)
}
}
if len(d.MACAddresses) > 5 {
fmt.Printf(" ... and %d more\n", len(d.MACAddresses)-5)
}
}
}
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"
// 策略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 {
targetIP = d.IP
matchMethod = "hostname"
fmt.Printf(" ✓ Matched by hostname: %s -> %s\n", d.Hostname, d.IP)
break
}
}
}
// 策略3: 通过MAC地址匹配
if targetIP == "" && neighbor.RemoteMAC != "" {
fmt.Printf(" Trying MAC match: %s\n", neighbor.RemoteMAC)
for _, d := range b.devices {
for _, mac := range d.MACAddresses {
// 标准化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",
mac, neighbor.RemoteMAC, d.IP)
break
}
}
if targetIP != "" {
break
}
}
}
// 策略4: 通过本地接口IP网段匹配(新增)
if targetIP == "" {
// 查找本地接口的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)
// 创建唯一的边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] {
continue
}
edge := models.TopologyEdge{
ID: edgeID,
Source: device.IP,
Target: targetIP,
SourceInterface: neighbor.LocalInterface,
TargetInterface: neighbor.RemoteInterface,
Protocol: neighbor.Protocol,
}
graph.Edges = append(graph.Edges, edge)
edgeMap[edgeID] = true
}
}
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
}
// normalizeMAC 标准化MAC地址(去除分隔符,转小写)
func normalizeMAC(mac string) string {
// 去除所有分隔符(-、:、.
result := ""
for _, c := range mac {
if c != '-' && c != ':' && c != '.' {
result += string(c)
}
}
// 转小写
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 {
case models.DeviceTypeCisco:
return "router"
case models.DeviceTypeHuawei:
return "router"
case models.DeviceTypeH3C:
return "switch"
case models.DeviceTypeASA:
return "firewall"
case models.DeviceTypeLinux:
return "server"
case models.DeviceTypeWindows:
return "server"
default:
return "device"
}
}
// GetDevices 获取所有设备
func (b *Builder) GetDevices() []models.Device {
return b.devices
}
// Clear 清空拓扑
func (b *Builder) Clear() {
b.devices = make([]models.Device, 0)
}