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:
Your Name
2026-04-26 05:45:00 +08:00
parent 6d2323b5b6
commit e2f804ac52
3 changed files with 3834 additions and 113 deletions
+3607
View File
File diff suppressed because it is too large Load Diff
+82 -97
View File
@@ -16,23 +16,24 @@ type H3CParser struct {
// GetCommands 获取H3C设备命令列表 // GetCommands 获取H3C设备命令列表
func (p *H3CParser) GetCommands() []string { func (p *H3CParser) GetCommands() []string {
return []string{ return []string{
"screen-length disable", // 禁用分页(H3C/华为设备必需) "screen-length disable", // 禁用分页(H3C/华为设备必需)
"display version", "display version",
"display interface", "display interface",
"display interface brief", // 接口简要信息(包含VLAN和物理接口状态) "display interface brief", // 接口简要信息(包含VLAN和物理接口状态)
"display lldp neighbor-information verbose", // LLDP邻居详细信息(包含System name和Management address "display lldp neighbor-information", // 使用非verbose格式(v1.0.0验证可行
"display arp", // ARP表用于解析邻居IP
} }
} }
// Parse 解析H3C设备输出 // Parse 解析H3C设备输出
func (p *H3CParser) Parse(device *models.Device, outputs []string) error { func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 5 { if len(outputs) < 6 {
return fmt.Errorf("insufficient command outputs") return fmt.Errorf("insufficient command outputs")
} }
// outputs[0] 是 screen-length disable 的输出(通常为空) // outputs[0] 是 screen-length disable 的输出(通常为空)
p.parseVersion(device, outputs[1]) // outputs[1] 是 display version p.parseVersion(device, outputs[1]) // outputs[1] 是 display version
// outputs[2] 是 display interface // outputs[2] 是 display interface
fmt.Printf("[H3C DEBUG] display interface output length: %d\n", len(outputs[2])) fmt.Printf("[H3C DEBUG] display interface output length: %d\n", len(outputs[2]))
if len(outputs[2]) > 0 { if len(outputs[2]) > 0 {
@@ -45,17 +46,17 @@ func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
} else { } else {
fmt.Printf("[H3C DEBUG] display interface output is EMPTY!\n") fmt.Printf("[H3C DEBUG] display interface output is EMPTY!\n")
} }
if outputs[2] == "" { if outputs[2] == "" {
fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP) fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP)
} else { } else {
// outputs[3] 是 display interface brief // outputs[3] 是 display interface brief
device.Interfaces = p.parseInterfaces(outputs[2], outputs[3]) device.Interfaces = p.parseInterfaces(outputs[2], outputs[3])
if len(device.Interfaces) == 0 { if len(device.Interfaces) == 0 {
fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n", fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n",
device.IP, len(outputs[2])) device.IP, len(outputs[2]))
} }
// 收集所有接口的MAC地址(用于邻居匹配) // 收集所有接口的MAC地址(用于邻居匹配)
macSet := make(map[string]bool) macSet := make(map[string]bool)
for _, iface := range device.Interfaces { for _, iface := range device.Interfaces {
@@ -70,12 +71,24 @@ func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
} }
fmt.Printf(" Collected %d unique MAC addresses for device %s\n", len(macSet), device.IP) fmt.Printf(" Collected %d unique MAC addresses for device %s\n", len(macSet), device.IP)
} }
// outputs[4] 是 display lldp neighbor-information // outputs[4] 是 display lldp neighbor-information
// outputs[5] 是 display arp
// 解析LLDP邻居
device.Neighbors = p.parseNeighbors(outputs[4], nil) // 解析ARP表
fmt.Printf("[H3C ARP DEBUG] Raw ARP output length: %d bytes\n", len(outputs[5]))
if len(outputs[5]) > 0 && len(outputs[5]) <= 1000 {
fmt.Printf("[H3C ARP DEBUG] Raw ARP output:\n%s\n", outputs[5])
} else if len(outputs[5]) > 1000 {
fmt.Printf("[H3C ARP DEBUG] Raw ARP output (first 1000 bytes):\n%s\n", outputs[5][:1000])
}
arpTable := p.parseARPTable(outputs[5])
fmt.Printf("[H3C ARP DEBUG] Parsed ARP table: %d entries\n", len(arpTable))
// 解析LLDP邻居(传入ARP表)
device.Neighbors = p.parseNeighbors(outputs[4], arpTable)
fmt.Printf("Device %s: %d interfaces, %d neighbors\n", fmt.Printf("Device %s: %d interfaces, %d neighbors\n",
device.IP, len(device.Interfaces), len(device.Neighbors)) device.IP, len(device.Interfaces), len(device.Neighbors))
@@ -197,12 +210,12 @@ func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []model
func cidrToMask(cidr string) string { func cidrToMask(cidr string) string {
var mask int var mask int
fmt.Sscanf(cidr, "%d", &mask) fmt.Sscanf(cidr, "%d", &mask)
s := uint32(0) s := uint32(0)
for i := 0; i < mask; i++ { for i := 0; i < mask; i++ {
s |= (1 << (31 - uint(i))) s |= (1 << (31 - uint(i)))
} }
return fmt.Sprintf("%d.%d.%d.%d", return fmt.Sprintf("%d.%d.%d.%d",
(s>>24)&0xFF, (s>>24)&0xFF,
(s>>16)&0xFF, (s>>16)&0xFF,
@@ -217,10 +230,10 @@ func (p *H3CParser) parseARPTable(output string) map[string]string {
for _, line := range lines { for _, line := range lines {
// 跳过空行和标题行 // 跳过空行和标题行
if strings.TrimSpace(line) == "" || if strings.TrimSpace(line) == "" ||
strings.Contains(line, "Type:") || strings.Contains(line, "Type:") ||
strings.Contains(line, "------") || strings.Contains(line, "------") ||
strings.Contains(line, "IP address") { strings.Contains(line, "IP address") {
continue continue
} }
@@ -230,10 +243,14 @@ func (p *H3CParser) parseARPTable(output string) map[string]string {
if len(fields) >= 2 { if len(fields) >= 2 {
ip := fields[0] ip := fields[0]
mac := strings.ToLower(fields[1]) mac := strings.ToLower(fields[1])
// 标准化MAC地址(统一为aabb-ccdd-eeff格式)
mac = normalizeMACFormat(mac)
// 验证是有效的IP和MAC // 验证是有效的IP和MAC
if isValidIP(ip) && isValidMAC(mac) { if isValidIP(ip) && isValidMAC(mac) {
macToIP[mac] = ip macToIP[mac] = ip
fmt.Printf("[H3C ARP DEBUG] Added MAC->IP: %s -> %s\n", mac, ip)
} }
} }
} }
@@ -241,6 +258,24 @@ func (p *H3CParser) parseARPTable(output string) map[string]string {
return macToIP return macToIP
} }
// normalizeMACFormat 标准化MAC地址格式(统一为aabb-ccdd-eeff
func normalizeMACFormat(mac string) string {
// 去除所有分隔符
clean := ""
for _, c := range mac {
if c != '-' && c != ':' && c != '.' {
clean += string(c)
}
}
// 重新格式化为aabb-ccdd-eeff
if len(clean) == 12 {
return fmt.Sprintf("%s-%s-%s", clean[0:4], clean[4:8], clean[8:12])
}
return mac
}
func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface { func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface {
interfaces := make(map[string]models.Interface) interfaces := make(map[string]models.Interface)
lines := strings.Split(output, "\n") lines := strings.Split(output, "\n")
@@ -276,7 +311,7 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []
if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" { if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
neighbors = append(neighbors, *currentNeighbor) neighbors = append(neighbors, *currentNeighbor)
} }
matches := portRegex.FindStringSubmatch(line) matches := portRegex.FindStringSubmatch(line)
localInterface = matches[1] localInterface = matches[1]
currentNeighbor = &models.Neighbor{ currentNeighbor = &models.Neighbor{
@@ -287,24 +322,7 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []
} }
if currentNeighbor != nil { if currentNeighbor != nil {
// 提取 ChassisID (MAC地址) // 提取 ChassisID (MAC地址) - 非verbose格式
// Verbose格式: Chassis ID: 642f-c7e0-0333
if strings.Contains(line, "Chassis ID:") && !strings.Contains(line, "ChassisID/subtype") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
mac := strings.TrimSpace(strings.ToLower(parts[1]))
if isValidMAC(mac) {
currentNeighbor.RemoteMAC = mac
// 如果还没有RemoteDevice,先使用MAC作为占位符(后续可能被System name覆盖)
if currentNeighbor.RemoteDevice == "" {
currentNeighbor.RemoteDevice = mac
}
fmt.Printf(" [LLDP] Parsed neighbor MAC (verbose): %s\n", mac)
}
}
}
// 非verbose格式: ChassisID/subtype: a4bb-6de2-62cd/MAC address
if strings.Contains(line, "ChassisID/subtype") { if strings.Contains(line, "ChassisID/subtype") {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
@@ -312,61 +330,33 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []
// 格式: a4bb-6de2-62cd/MAC address // 格式: a4bb-6de2-62cd/MAC address
if macParts := strings.Split(value, "/"); len(macParts) > 0 { if macParts := strings.Split(value, "/"); len(macParts) > 0 {
mac := strings.TrimSpace(strings.ToLower(macParts[0])) mac := strings.TrimSpace(strings.ToLower(macParts[0]))
// 标准化MAC地址格式
mac = normalizeMACFormat(mac)
// 保存MAC地址
currentNeighbor.RemoteMAC = mac currentNeighbor.RemoteMAC = mac
if currentNeighbor.RemoteDevice == "" { fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line)
currentNeighbor.RemoteDevice = mac
// 使用MAC地址作为设备标识
currentNeighbor.RemoteDevice = mac
// 通过ARP表查找IP地址
if arpTable != nil {
if ip, found := arpTable[mac]; found {
currentNeighbor.RemoteIP = ip
fmt.Printf(" ✓ Found IP for MAC %s: %s\n", mac, ip)
} else {
fmt.Printf(" ✗ No IP found for MAC %s in ARP table\n", mac)
}
} }
fmt.Printf(" [LLDP] Parsed neighbor MAC: %s\n", mac)
}
}
}
// 提取 System name (verbose格式)
if strings.Contains(line, "System name:") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
systemName := strings.TrimSpace(parts[1])
if systemName != "" {
// System name 是最可靠的匹配方式,覆盖之前的MAC地址占位符
currentNeighbor.RemoteDevice = systemName
fmt.Printf(" [LLDP] Parsed neighbor System name: %s\n", systemName)
}
}
}
// 提取 Management address (verbose格式)
if strings.Contains(line, "Management address:") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
mgmtAddr := strings.TrimSpace(parts[1])
if isValidIP(mgmtAddr) {
// 如果还没有RemoteIP,使用Management address
if currentNeighbor.RemoteIP == "" {
currentNeighbor.RemoteIP = mgmtAddr
fmt.Printf(" [LLDP] Parsed neighbor Management address: %s\n", mgmtAddr)
}
}
}
}
// 提取 PortID (远程接口)
// Verbose格式: Port ID: GigabitEthernet1/0/48 或 Port ID: a4bb-6de2-62cd (MAC地址)
if strings.Contains(line, "Port ID:") && !strings.Contains(line, "PortID/subtype") && !strings.Contains(line, "Port ID type") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
portID := strings.TrimSpace(parts[1])
// 检查Port ID是否是MAC地址(格式: a4bb-6de2-62cd
if isValidMAC(portID) {
// Port ID是MAC地址,不赋值给RemoteInterface
fmt.Printf(" [LLDP] Port ID is MAC address (not interface): %s\n", portID)
} else { } else {
// Port ID是接口名 fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line)
currentNeighbor.RemoteInterface = portID
} }
} else {
fmt.Printf(" WARNING: ChassisID line has no colon: %s\n", line)
} }
} }
// 非verbose格式: PortID/subtype: GigabitEthernet0/0/1/Interface name // 提取 PortID (远程接口) - 非verbose格式
if strings.Contains(line, "PortID/subtype") { if strings.Contains(line, "PortID/subtype") {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
@@ -375,20 +365,15 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []
if portParts := strings.Split(value, "/"); len(portParts) >= 3 { if portParts := strings.Split(value, "/"); len(portParts) >= 3 {
// 取前3部分作为接口名: GigabitEthernet0/0/1 // 取前3部分作为接口名: GigabitEthernet0/0/1
currentNeighbor.RemoteInterface = strings.Join(portParts[:3], "/") currentNeighbor.RemoteInterface = strings.Join(portParts[:3], "/")
fmt.Printf(" Parsed neighbor interface: %s\n", currentNeighbor.RemoteInterface)
} }
} }
} }
} }
} }
// 添加最后一个邻居(修改:只要有RemoteDevice或RemoteInterface就添加) // 添加最后一个邻居
if currentNeighbor != nil && (currentNeighbor.RemoteDevice != "" || currentNeighbor.RemoteInterface != "") { if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
// 如果Port ID是MAC地址而不是接口名,将MAC赋值给RemoteInterface(如果为空)
if currentNeighbor.RemoteInterface == "" && currentNeighbor.RemoteMAC != "" {
// 检查Port ID是否是MAC地址(在解析过程中可能已经将MAC作为Port ID)
// 这种情况发生在Port ID type为MAC address时
fmt.Printf(" [LLDP] Neighbor has no interface name, using MAC as identifier: %s\n", currentNeighbor.RemoteMAC)
}
neighbors = append(neighbors, *currentNeighbor) neighbors = append(neighbors, *currentNeighbor)
} }
+145 -16
View File
@@ -2,8 +2,8 @@ package topology
import ( import (
"fmt" "fmt"
"strings"
"network-topology-discovery/pkg/models" "network-topology-discovery/pkg/models"
"strings"
) )
// Builder 拓扑构建器 // Builder 拓扑构建器
@@ -41,7 +41,7 @@ func (b *Builder) Build() models.TopologyGraph {
if hostname == "" { if hostname == "" {
hostname = device.IP hostname = device.IP
} }
node := models.TopologyNode{ node := models.TopologyNode{
ID: device.IP, ID: device.IP,
IP: device.IP, IP: device.IP,
@@ -53,9 +53,16 @@ func (b *Builder) Build() models.TopologyGraph {
graph.Nodes = append(graph.Nodes, 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) // 用于去重 edgeMap := make(map[string]bool) // 用于去重
// 打印所有设备的MAC地址,用于调试 // 打印所有设备的MAC地址,用于调试
fmt.Println("\n========== MAC Address Database ==========") fmt.Println("\n========== MAC Address Database ==========")
for _, d := range b.devices { for _, d := range b.devices {
@@ -72,21 +79,24 @@ func (b *Builder) Build() models.TopologyGraph {
} }
} }
fmt.Println("==========================================\n") fmt.Println("==========================================\n")
for _, device := range b.devices { for _, device := range b.devices {
fmt.Printf("Building edges for device %s: %d neighbors, %d MAC addresses\n", fmt.Printf("Building edges for device %s: %d neighbors, %d MAC addresses\n",
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 {
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)
// 尝试匹配邻居设备 // 尝试匹配邻居设备 - 多重策略
targetIP := neighbor.RemoteIP targetIP := neighbor.RemoteIP
matchMethod := "IP" matchMethod := "IP"
// 如果有IP尝试通过设备名匹配 // 策略1: 如果有IP直接使用
if targetIP == "" && neighbor.RemoteDevice != "" { if targetIP != "" {
fmt.Printf(" ✓ Using direct IP: %s\n", targetIP)
} else if neighbor.RemoteDevice != "" {
// 策略2: 尝试通过主机名匹配
fmt.Printf(" Trying hostname match: %s\n", neighbor.RemoteDevice) fmt.Printf(" Trying hostname match: %s\n", neighbor.RemoteDevice)
for _, d := range b.devices { for _, d := range b.devices {
if d.Hostname == neighbor.RemoteDevice { if d.Hostname == neighbor.RemoteDevice {
@@ -97,8 +107,8 @@ func (b *Builder) Build() models.TopologyGraph {
} }
} }
} }
// 如果还是没有,尝试通过MAC地址匹配(新增) // 策略3: 通过MAC地址匹配
if targetIP == "" && neighbor.RemoteMAC != "" { if targetIP == "" && neighbor.RemoteMAC != "" {
fmt.Printf(" Trying MAC match: %s\n", neighbor.RemoteMAC) fmt.Printf(" Trying MAC match: %s\n", neighbor.RemoteMAC)
for _, d := range b.devices { for _, d := range b.devices {
@@ -106,11 +116,11 @@ func (b *Builder) Build() models.TopologyGraph {
// 标准化MAC地址进行比较(去除分隔符,转小写) // 标准化MAC地址进行比较(去除分隔符,转小写)
normalizedNeighborMAC := normalizeMAC(neighbor.RemoteMAC) normalizedNeighborMAC := normalizeMAC(neighbor.RemoteMAC)
normalizedDeviceMAC := normalizeMAC(mac) normalizedDeviceMAC := normalizeMAC(mac)
if normalizedNeighborMAC == normalizedDeviceMAC { if normalizedNeighborMAC == normalizedDeviceMAC {
targetIP = d.IP targetIP = d.IP
matchMethod = "MAC" 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) mac, neighbor.RemoteMAC, d.IP)
break break
} }
@@ -121,11 +131,111 @@ func (b *Builder) Build() models.TopologyGraph {
} }
} }
// 策略4: 通过本地接口IP网段匹配(新增)
if targetIP == "" { 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 continue
} }
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)
@@ -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 return graph
} }
@@ -168,6 +287,16 @@ func normalizeMAC(mac string) string {
return strings.ToLower(result) 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 获取设备图标 // getDeviceIcon 获取设备图标
func getDeviceIcon(deviceType models.DeviceType) string { func getDeviceIcon(deviceType models.DeviceType) string {
switch deviceType { switch deviceType {