From 6d2323b5b6a6ce940bbe023e890d59d1784fc36a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 26 Apr 2026 03:56:04 +0800 Subject: [PATCH] feat: enhance H3C LLDP parser to support verbose format with System name and Management address - Add support for LLDP verbose output format (display lldp neighbor-information verbose) - Parse System name field for accurate device hostname matching - Parse Management address field for IP-based neighbor identification - Handle edge cases: endpoint devices without System name (fallback to MAC) - Handle Port ID as MAC address (not interface name) for endpoint devices - Add detailed debug logging for LLDP neighbor parsing - Implement three-level fallback strategy: System name > Management IP > MAC address This fixes the topology auto-linking issue where only 1 link was created despite having neighbor data. --- internal/device/h3c.go | 89 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/internal/device/h3c.go b/internal/device/h3c.go index 9c4bff4..fe592a1 100644 --- a/internal/device/h3c.go +++ b/internal/device/h3c.go @@ -20,7 +20,7 @@ func (p *H3CParser) GetCommands() []string { "display version", "display interface", "display interface brief", // 接口简要信息(包含VLAN和物理接口状态) - "display lldp neighbor-information", + "display lldp neighbor-information verbose", // LLDP邻居详细信息(包含System name和Management address) } } @@ -288,6 +288,23 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) [] if currentNeighbor != nil { // 提取 ChassisID (MAC地址) + // 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") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { @@ -295,27 +312,61 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) [] // 格式: a4bb-6de2-62cd/MAC address if macParts := strings.Split(value, "/"); len(macParts) > 0 { mac := strings.TrimSpace(strings.ToLower(macParts[0])) - // 保存MAC地址 currentNeighbor.RemoteMAC = mac - fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line) - - // 通过ARP表查找IP(如果有) - if ip, ok := arpTable[mac]; ok { - currentNeighbor.RemoteIP = ip - currentNeighbor.RemoteDevice = ip - } else { - // 如果ARP表中没有,使用MAC地址作为标识(但RemoteIP仍为空) + if currentNeighbor.RemoteDevice == "" { currentNeighbor.RemoteDevice = mac } - } else { - fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line) + 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) + } } - } else { - fmt.Printf(" WARNING: ChassisID line has no colon: %s\n", line) } } // 提取 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 { + // Port ID是接口名 + currentNeighbor.RemoteInterface = portID + } + } + } + + // 非verbose格式: PortID/subtype: GigabitEthernet0/0/1/Interface name if strings.Contains(line, "PortID/subtype") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { @@ -330,8 +381,14 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) [] } } - // 添加最后一个邻居 - if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" { + // 添加最后一个邻居(修改:只要有RemoteDevice或RemoteInterface就添加) + if currentNeighbor != nil && (currentNeighbor.RemoteDevice != "" || 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) }