1
0
Dosyalar
Your Name e2f804ac52 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)
2026-04-26 05:45:00 +08:00

420 satır
12 KiB
Go
Ham Kalıcı Bağlantı Suçlama Geçmiş

Bu dosya muğlak Evrensel Kodlu karakter içeriyor
Bu dosya, başka karakterlerle karıştırılabilecek evrensel kodlu karakter içeriyor. Eğer bunu kasıtlı olarak yaptıysanız bu uyarıyı yok sayabilirsiniz. Gizli karakterleri göstermek için Kaçış Karakterli düğmesine tıklayın.
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 {
// 如果输出小于1000字节,完整输出以便调试
if len(outputs[2]) <= 1000 {
fmt.Printf("[H3C DEBUG] Complete output:\n%s\n", outputs[2])
} else {
fmt.Printf("[H3C DEBUG] First 200 chars: %q\n", outputs[2][:200])
}
} else {
fmt.Printf("[H3C DEBUG] display interface 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+(\d+\s+\S+)`)
if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 {
device.Uptime = 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
}
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
}
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])
}
if speedRegex := regexp.MustCompile(`(\d+)\s+(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) {
matches := speedRegex.FindStringSubmatch(line)
currentInterface.Speed = matches[1] + " " + matches[2]
}
}
}
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
}
func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface {
interfaces := make(map[string]models.Interface)
lines := strings.Split(output, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) >= 4 {
iface := models.Interface{
Name: fields[0],
IP: fields[1],
Status: strings.ToLower(fields[3]),
}
interfaces[iface.Name] = 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
}