package device import ( "bufio" "fmt" "network-topology-discovery/pkg/models" "regexp" "strings" ) // H3CParser H3C设备解析器 type H3CParser struct { BaseParser } // GetCommands 获取H3C设备命令列表 func (p *H3CParser) GetCommands() []string { return []string{ "screen-length disable", // 禁用分页(H3C/华为设备必需) "display version", "display interface", "display interface brief", // 接口简要信息(包含VLAN和物理接口状态) "display lldp neighbor-information", // 使用非verbose格式(v1.0.0验证可行) "display arp", // ARP表用于解析邻居IP } } // Parse 解析H3C设备输出 func (p *H3CParser) Parse(device *models.Device, outputs []string) error { if len(outputs) < 6 { return fmt.Errorf("insufficient command outputs") } // outputs[0] 是 screen-length disable 的输出(通常为空) p.parseVersion(device, outputs[1]) // outputs[1] 是 display version // outputs[2] 是 display interface fmt.Printf("[H3C DEBUG] display interface output length: %d\n", len(outputs[2])) if len(outputs[2]) > 0 { if len(outputs[2]) <= 3000 { fmt.Printf("[H3C DEBUG] Complete interface output:\n%s\n", outputs[2]) } else { fmt.Printf("[H3C DEBUG] First 3000 chars:\n%s\n", outputs[2][:3000]) } } else { fmt.Printf("[H3C DEBUG] display interface output is EMPTY!\n") } // outputs[3] 是 display interface brief fmt.Printf("[H3C BRIEF DEBUG] display interface brief output length: %d\n", len(outputs[3])) if len(outputs[3]) > 0 { if len(outputs[3]) <= 3000 { fmt.Printf("[H3C BRIEF DEBUG] Complete brief output:\n%s\n", outputs[3]) } else { fmt.Printf("[H3C BRIEF DEBUG] First 3000 chars:\n%s\n", outputs[3][:3000]) } } else { fmt.Printf("[H3C BRIEF DEBUG] display interface brief output is EMPTY!\n") } if outputs[2] == "" { fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP) } else { // outputs[3] 是 display interface brief device.Interfaces = p.parseInterfaces(outputs[2], outputs[3]) if len(device.Interfaces) == 0 { fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n", device.IP, len(outputs[2])) } // 收集所有接口的MAC地址(用于邻居匹配) macSet := make(map[string]bool) for _, iface := range device.Interfaces { if iface.MAC != "" { macSet[iface.MAC] = true } else { fmt.Printf(" Interface %s has no MAC address\n", iface.Name) } } for mac := range macSet { device.MACAddresses = append(device.MACAddresses, mac) } fmt.Printf(" Collected %d unique MAC addresses for device %s\n", len(macSet), device.IP) } // outputs[4] 是 display lldp neighbor-information // outputs[5] 是 display arp // 解析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", device.IP, len(device.Interfaces), len(device.Neighbors)) return nil } func (p *H3CParser) parseVersion(device *models.Device, output string) { hostnameRegex := regexp.MustCompile(`<(\S+)>`) if matches := hostnameRegex.FindStringSubmatch(output); len(matches) > 1 { device.Hostname = matches[1] } if strings.Contains(output, "Comware") { lines := strings.Split(output, "\n") for _, line := range lines { if strings.Contains(line, "Comware") { device.OSVersion = strings.TrimSpace(line) break } } } uptimeRegex := regexp.MustCompile(`uptime is\s+(.+)`) if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 { device.Uptime = strings.TrimSpace(matches[1]) } } func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []models.Interface { var interfaces []models.Interface briefMap := p.parseInterfaceBrief(briefOutput) scanner := bufio.NewScanner(strings.NewReader(interfaceOutput)) var currentInterface *models.Interface var pendingInterfaceName string // 暂存接口名 for scanner.Scan() { line := scanner.Text() // H3C接口输出格式1: 接口名和状态在同一行 // GigabitEthernet1/0/0 current state: UP if nameRegex := regexp.MustCompile(`^(\S+)\s+current state:\s+(UP|DOWN)`); nameRegex.MatchString(line) { if currentInterface != nil { interfaces = append(interfaces, *currentInterface) } matches := nameRegex.FindStringSubmatch(line) currentInterface = &models.Interface{ Name: matches[1], Status: strings.ToLower(matches[2]), } if brief, ok := briefMap[currentInterface.Name]; ok { currentInterface.IP = brief.IP if currentInterface.Speed == "" { currentInterface.Speed = brief.Speed } if currentInterface.Duplex == "" { currentInterface.Duplex = brief.Duplex } if currentInterface.VLAN == "" { currentInterface.VLAN = brief.VLAN } } pendingInterfaceName = "" continue } // H3C接口输出格式2: 接口名单独一行,下一行是状态 // GigabitEthernet1/0/0 // Current state: DOWN if pendingInterfaceName != "" && regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).MatchString(line) { matches := regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).FindStringSubmatch(line) if currentInterface != nil { interfaces = append(interfaces, *currentInterface) } currentInterface = &models.Interface{ Name: pendingInterfaceName, Status: strings.ToLower(matches[1]), } if brief, ok := briefMap[currentInterface.Name]; ok { currentInterface.IP = brief.IP if currentInterface.Speed == "" { currentInterface.Speed = brief.Speed } if currentInterface.Duplex == "" { currentInterface.Duplex = brief.Duplex } if currentInterface.VLAN == "" { currentInterface.VLAN = brief.VLAN } } pendingInterfaceName = "" continue } // 匹配接口名(单独一行的情况) // 格式: GigabitEthernet1/0/0 if interfaceNameRegex := regexp.MustCompile(`^(GigabitEthernet|Ten-GigabitEthernet|FortyGigE|HundredGigE|Ethernet|Serial|LoopBack|Vlanif|NULL|Bridge-Aggregate|Route-Aggregate)\S*`); interfaceNameRegex.MatchString(line) { pendingInterfaceName = interfaceNameRegex.FindString(line) continue } // 解析接口属性 if currentInterface != nil { if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) { currentInterface.Description = descRegex.FindStringSubmatch(line)[1] } if macRegex := regexp.MustCompile(`hardware address:\s+(\S+)`); macRegex.MatchString(line) { currentInterface.MAC = macRegex.FindStringSubmatch(line)[1] } // 匹配IP地址格式: Internet address: 192.168.0.1/24 (primary) if ipRegex := regexp.MustCompile(`Internet address:\s+(\d+\.\d+\.\d+\.\d+)/(\d+)`); ipRegex.MatchString(line) { matches := ipRegex.FindStringSubmatch(line) currentInterface.IP = matches[1] // CIDR转换为子网掩码 currentInterface.Mask = cidrToMask(matches[2]) } // 匹配速度格式1: Speed : 1000Mbps 或 Speed : 10Gbps if speedRegex := regexp.MustCompile(`Speed\s*:\s*(\d+)\s*(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) { matches := speedRegex.FindStringSubmatch(line) currentInterface.Speed = matches[1] + matches[2] } // 匹配速度格式2: Speed: 1000, Loopback: not set (纯数字Mbps) if speedRegex2 := regexp.MustCompile(`Speed\s*:\s*(\d+)(?:,|$)`); speedRegex2.MatchString(line) && currentInterface.Speed == "" { matches := speedRegex2.FindStringSubmatch(line) currentInterface.Speed = matches[1] + "Mbps" } // 匹配双工: Duplex: Full 或 Duplex: Half if duplexRegex := regexp.MustCompile(`Duplex\s*:\s*(\S+)`); duplexRegex.MatchString(line) && currentInterface.Duplex == "" { currentInterface.Duplex = duplexRegex.FindStringSubmatch(line)[1] } // 匹配VLAN信息: Port link-type: Access / Trunk / Hybrid if linkTypeRegex := regexp.MustCompile(`Port link-type\s*:\s*(\S+)`); linkTypeRegex.MatchString(line) { linkType := linkTypeRegex.FindStringSubmatch(line)[1] currentInterface.VLAN = linkType } // 匹配Untagged VLAN: Untagged VLAN ID: 10 if untaggedRegex := regexp.MustCompile(`Untagged VLAN ID\s*:\s*(\S+)`); untaggedRegex.MatchString(line) { untagged := untaggedRegex.FindStringSubmatch(line)[1] if untagged != "none" && untagged != "--" { if currentInterface.VLAN != "" { currentInterface.VLAN = currentInterface.VLAN + " " + untagged } else { currentInterface.VLAN = untagged } } } // 匹配Tagged VLAN: Tagged VLAN ID: 100-200 if taggedRegex := regexp.MustCompile(`Tagged VLAN ID\s*:\s*(\S+)`); taggedRegex.MatchString(line) { tagged := taggedRegex.FindStringSubmatch(line)[1] if tagged != "none" && tagged != "--" { if currentInterface.VLAN != "" { currentInterface.VLAN = currentInterface.VLAN + " (T:" + tagged + ")" } else { currentInterface.VLAN = "T:" + tagged } } } } } if currentInterface != nil { interfaces = append(interfaces, *currentInterface) } return interfaces } // cidrToMask CIDR转换为子网掩码 func cidrToMask(cidr string) string { var mask int fmt.Sscanf(cidr, "%d", &mask) s := uint32(0) for i := 0; i < mask; i++ { s |= (1 << (31 - uint(i))) } return fmt.Sprintf("%d.%d.%d.%d", (s>>24)&0xFF, (s>>16)&0xFF, (s>>8)&0xFF, s&0xFF) } // parseARPTable 解析ARP表,建立MAC到IP的映射 func (p *H3CParser) parseARPTable(output string) map[string]string { macToIP := make(map[string]string) lines := strings.Split(output, "\n") for _, line := range lines { // 跳过空行和标题行 if strings.TrimSpace(line) == "" || strings.Contains(line, "Type:") || strings.Contains(line, "------") || strings.Contains(line, "IP address") { continue } // ARP表格式: IP address MAC address VLAN/VSI name Interface Aging Type // 例: 172.16.8.10 743a-2047-38e0 8 GE1/0/47 1163 D fields := strings.Fields(line) if len(fields) >= 2 { ip := fields[0] mac := strings.ToLower(fields[1]) // 标准化MAC地址(统一为aabb-ccdd-eeff格式) mac = normalizeMACFormat(mac) // 验证是有效的IP和MAC if isValidIP(ip) && isValidMAC(mac) { macToIP[mac] = ip fmt.Printf("[H3C ARP DEBUG] Added MAC->IP: %s -> %s\n", mac, ip) } } } 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 } // expandH3CInterfaceName 将H3C brief中的缩写接口名展开为全名 func expandH3CInterfaceName(shortName string) string { // GE1/0/10 -> GigabitEthernet1/0/10 if strings.HasPrefix(shortName, "GE") { return "GigabitEthernet" + shortName[2:] } // TGE1/0/52 -> Ten-GigabitEthernet1/0/52 if strings.HasPrefix(shortName, "TGE") { return "Ten-GigabitEthernet" + shortName[3:] } // FGE1/0/53 -> FortyGigE1/0/53 if strings.HasPrefix(shortName, "FGE") { return "FortyGigE" + shortName[3:] } // HGE1/0/1 -> HundredGigE1/0/1 if strings.HasPrefix(shortName, "HGE") { return "HundredGigE" + shortName[3:] } // XGE1/0/1 -> Ten-GigabitEthernet1/0/1 (有些型号用XGE) if strings.HasPrefix(shortName, "XGE") { return "Ten-GigabitEthernet" + shortName[3:] } // Vlan8 -> Vlan-interface8 if strings.HasPrefix(shortName, "Vlan") { return "Vlan-interface" + shortName[4:] } return shortName } func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface { interfaces := make(map[string]models.Interface) lines := strings.Split(output, "\n") // 状态机: 0=未开始, 1=route mode数据, 2=bridge mode数据 mode := 0 for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" || strings.HasPrefix(trimmed, "---") { continue } // 检测 route mode section if strings.Contains(trimmed, "route mode") { mode = 1 continue } // 检测 bridge mode section if strings.Contains(trimmed, "bridge mode") { mode = 2 continue } // 跳过表头行 fields := strings.Fields(trimmed) if len(fields) > 0 && (fields[0] == "Interface" || fields[0] == "interface") { continue } // Route mode: Interface Link Protocol Primary IP Description if mode == 1 && len(fields) >= 3 { shortName := fields[0] fullName := expandH3CInterfaceName(shortName) ip := "" if len(fields) >= 4 && fields[3] != "--" { ip = fields[3] } iface := models.Interface{ Name: fullName, Status: strings.ToLower(fields[1]), IP: ip, } interfaces[fullName] = iface } // Bridge mode: Interface Link Speed Duplex Type PVID Description if mode == 2 && len(fields) >= 5 { shortName := fields[0] fullName := expandH3CInterfaceName(shortName) speed := fields[2] if speed == "auto" { speed = "auto" } duplex := fields[3] switch duplex { case "A": duplex = "auto" case "F(a)", "F": duplex = "full" case "H": duplex = "half" } // VLAN: Type(A/T/H) + PVID vlan := "" if len(fields) >= 5 { linkType := fields[4] switch linkType { case "A": vlan = "Access" case "T": vlan = "Trunk" case "H": vlan = "Hybrid" } } if len(fields) >= 6 { vlan = vlan + " " + fields[5] } iface := models.Interface{ Name: fullName, Status: strings.ToLower(fields[1]), Speed: speed, Duplex: duplex, VLAN: strings.TrimSpace(vlan), } // 如果 route mode 已有此接口(通常不会有), 保留 IP 信息 if existing, ok := interfaces[fullName]; ok { if existing.IP != "" { iface.IP = existing.IP } } interfaces[fullName] = iface } } return interfaces } func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []models.Neighbor { var neighbors []models.Neighbor scanner := bufio.NewScanner(strings.NewReader(output)) var currentNeighbor *models.Neighbor var localInterface string for scanner.Scan() { line := scanner.Text() // 匹配本地接口行: LLDP neighbor-information of port 20[GigabitEthernet1/0/20]: if portRegex := regexp.MustCompile(`LLDP neighbor-information of port \d+\[([^\]]+)\]:`); portRegex.MatchString(line) { // 保存前一个邻居 if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" { neighbors = append(neighbors, *currentNeighbor) } matches := portRegex.FindStringSubmatch(line) localInterface = matches[1] currentNeighbor = &models.Neighbor{ LocalInterface: localInterface, Protocol: "LLDP", } continue } if currentNeighbor != nil { // 提取 ChassisID (MAC地址) - 非verbose格式 if strings.Contains(line, "ChassisID/subtype") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { value := strings.TrimSpace(parts[1]) // 格式: a4bb-6de2-62cd/MAC address if macParts := strings.Split(value, "/"); len(macParts) > 0 { mac := strings.TrimSpace(strings.ToLower(macParts[0])) // 标准化MAC地址格式 mac = normalizeMACFormat(mac) // 保存MAC地址 currentNeighbor.RemoteMAC = mac fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line) // 使用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) } } } else { fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line) } } else { fmt.Printf(" WARNING: ChassisID line has no colon: %s\n", line) } } // 提取 PortID (远程接口) - 非verbose格式 if strings.Contains(line, "PortID/subtype") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { value := strings.TrimSpace(parts[1]) // 格式: GigabitEthernet0/0/1/Interface name if portParts := strings.Split(value, "/"); len(portParts) >= 3 { // 取前3部分作为接口名: GigabitEthernet0/0/1 currentNeighbor.RemoteInterface = strings.Join(portParts[:3], "/") fmt.Printf(" Parsed neighbor interface: %s\n", currentNeighbor.RemoteInterface) } } } } } // 添加最后一个邻居 if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" { neighbors = append(neighbors, *currentNeighbor) } return neighbors } // isValidIP 简单验证IP地址 func isValidIP(ip string) bool { parts := strings.Split(ip, ".") if len(parts) != 4 { return false } for _, part := range parts { if len(part) == 0 || len(part) > 3 { return false } for _, c := range part { if c < '0' || c > '9' { return false } } } return true } // isValidMAC 简单验证MAC地址 func isValidMAC(mac string) bool { parts := strings.Split(mac, "-") if len(parts) != 3 && len(parts) != 6 { return false } for _, part := range parts { if len(part) != 2 { return false } for _, c := range part { if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { return false } } } return true }