1
0
Files
Your Name 2a97f458a9 prod
2026-04-26 22:00:03 +08:00

602 خطوط
18 KiB
Go

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}