This commit is contained in:
Your Name
2026-04-26 22:00:03 +08:00
parent bcad5b8e27
commit 2a97f458a9
11 changed files with 888 additions and 53 deletions
+197 -15
View File
@@ -37,16 +37,27 @@ func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
// 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])
if len(outputs[2]) <= 3000 {
fmt.Printf("[H3C DEBUG] Complete interface output:\n%s\n", outputs[2])
} else {
fmt.Printf("[H3C DEBUG] First 200 chars: %q\n", outputs[2][:200])
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 {
@@ -111,9 +122,9 @@ func (p *H3CParser) parseVersion(device *models.Device, output string) {
}
}
uptimeRegex := regexp.MustCompile(`uptime is\s+(\d+\s+\S+)`)
uptimeRegex := regexp.MustCompile(`uptime is\s+(.+)`)
if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 {
device.Uptime = matches[1]
device.Uptime = strings.TrimSpace(matches[1])
}
}
@@ -142,6 +153,15 @@ func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []model
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
@@ -162,6 +182,15 @@ func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []model
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
@@ -192,9 +221,48 @@ func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []model
currentInterface.Mask = cidrToMask(matches[2])
}
if speedRegex := regexp.MustCompile(`(\d+)\s+(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) {
// 匹配速度格式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]
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
}
}
}
}
}
@@ -276,19 +344,133 @@ func normalizeMACFormat(mac string) string {
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 {
fields := strings.Fields(line)
if len(fields) >= 4 {
iface := models.Interface{
Name: fields[0],
IP: fields[1],
Status: strings.ToLower(fields[3]),
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]
}
interfaces[iface.Name] = iface
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
}
}
+119 -10
View File
@@ -18,7 +18,7 @@ func (p *HuaweiParser) GetCommands() []string {
return []string{
"display version",
"display interface",
"display ip interface brief",
"display interface brief",
"display lldp neighbor",
}
}
@@ -84,6 +84,15 @@ func (p *HuaweiParser) parseInterfaces(interfaceOutput, briefOutput string) []mo
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
}
}
}
@@ -105,11 +114,41 @@ func (p *HuaweiParser) parseInterfaces(interfaceOutput, briefOutput string) []mo
currentInterface.Mask = matches[2]
}
// 带宽
if speedRegex := regexp.MustCompile(`(\d+)\s+(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) {
// 带宽格式1: Speed : 1000Mbps
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: BW 1000000 Kbit
if speedRegex2 := regexp.MustCompile(`BW\s+(\d+)\s+(Kbit|Mbit|Gbit)`); speedRegex2.MatchString(line) && currentInterface.Speed == "" {
matches := speedRegex2.FindStringSubmatch(line)
currentInterface.Speed = matches[1] + " " + matches[2]
}
// VLAN信息: Port link-type
if linkTypeRegex := regexp.MustCompile(`Port link-type\s*:\s*(\S+)`); linkTypeRegex.MatchString(line) {
currentInterface.VLAN = linkTypeRegex.FindStringSubmatch(line)[1]
}
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
}
}
}
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
}
}
}
}
}
@@ -124,15 +163,85 @@ func (p *HuaweiParser) parseInterfaceBrief(output string) map[string]models.Inte
interfaces := make(map[string]models.Interface)
lines := strings.Split(output, "\n")
mode := 0 // 0=未开始, 1=route mode, 2=bridge mode
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]),
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "---") {
continue
}
if strings.Contains(trimmed, "route mode") {
mode = 1
continue
}
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 {
fullName := expandH3CInterfaceName(fields[0])
ip := ""
if len(fields) >= 4 && fields[3] != "--" {
ip = fields[3]
}
interfaces[iface.Name] = iface
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 {
fullName := expandH3CInterfaceName(fields[0])
speed := fields[2]
duplex := fields[3]
switch duplex {
case "A":
duplex = "auto"
case "F(a)", "F":
duplex = "full"
case "H":
duplex = "half"
}
vlan := ""
if len(fields) >= 5 {
switch fields[4] {
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),
}
if existing, ok := interfaces[fullName]; ok {
if existing.IP != "" {
iface.IP = existing.IP
}
}
interfaces[fullName] = iface
}
}