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.
Цей коміт міститься в:
Your Name
2026-04-26 03:56:04 +08:00
джерело 24263a7439
коміт 6d2323b5b6
+73 -16
Переглянути файл
@@ -20,7 +20,7 @@ func (p *H3CParser) GetCommands() []string {
"display version", "display version",
"display interface", "display interface",
"display interface brief", // 接口简要信息(包含VLAN和物理接口状态) "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 { if currentNeighbor != nil {
// 提取 ChassisID (MAC地址) // 提取 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") { if strings.Contains(line, "ChassisID/subtype") {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
@@ -295,27 +312,61 @@ 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地址
currentNeighbor.RemoteMAC = mac currentNeighbor.RemoteMAC = mac
fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line) if currentNeighbor.RemoteDevice == "" {
// 通过ARP表查找IP(如果有)
if ip, ok := arpTable[mac]; ok {
currentNeighbor.RemoteIP = ip
currentNeighbor.RemoteDevice = ip
} else {
// 如果ARP表中没有,使用MAC地址作为标识(但RemoteIP仍为空)
currentNeighbor.RemoteDevice = mac currentNeighbor.RemoteDevice = mac
} }
} else { fmt.Printf(" [LLDP] Parsed neighbor MAC: %s\n", mac)
fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line) }
}
}
// 提取 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 (远程接口) // 提取 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") { if strings.Contains(line, "PortID/subtype") {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
@@ -330,8 +381,14 @@ func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []
} }
} }
// 添加最后一个邻居 // 添加最后一个邻居(修改:只要有RemoteDevice或RemoteInterface就添加)
if currentNeighbor != nil && currentNeighbor.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) neighbors = append(neighbors, *currentNeighbor)
} }