Initial commit: 网络拓扑发现系统

- 支持Cisco、华为、H3C、ASA、Linux、Windows设备
- SSH远程采集设备信息
- 自动发现网络拓扑(LLDP/CDP)
- Web可视化界面
- 支持旧版SSH加密算法兼容
This commit is contained in:
Your Name
2026-04-25 22:35:51 +08:00
commit d0927cbad5
24 changed files with 3513 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
package device
import (
"bufio"
"fmt"
"network-topology-discovery/pkg/models"
"regexp"
"strings"
)
// ASAParser ASA防火墙解析器
type ASAParser struct {
BaseParser
}
// GetCommands 获取ASA设备命令列表
func (p *ASAParser) GetCommands() []string {
return []string{
"show version",
"show interface",
"show ip",
"show inventory",
}
}
// Parse 解析ASA设备输出
func (p *ASAParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 4 {
return fmt.Errorf("insufficient command outputs")
}
p.parseVersion(device, outputs[0])
device.Interfaces = p.parseInterfaces(outputs[1])
return nil
}
func (p *ASAParser) parseVersion(device *models.Device, output string) {
hostnameRegex := regexp.MustCompile(`^(\S+)\s*>`)
lines := strings.Split(output, "\n")
for _, line := range lines {
if matches := hostnameRegex.FindStringSubmatch(line); len(matches) > 1 {
device.Hostname = matches[1]
break
}
}
if strings.Contains(output, "ASA Version") {
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "ASA Version") {
device.OSVersion = strings.TrimSpace(line)
break
}
}
}
uptimeRegex := regexp.MustCompile(`up\s+\d+\s+hours?\s+\d+\s+mins?`)
if matches := uptimeRegex.FindString(output); matches != "" {
device.Uptime = matches
}
}
func (p *ASAParser) parseInterfaces(output string) []models.Interface {
var interfaces []models.Interface
scanner := bufio.NewScanner(strings.NewReader(output))
var currentInterface *models.Interface
for scanner.Scan() {
line := scanner.Text()
if nameRegex := regexp.MustCompile(`^Interface\s+(\S+)\s+"(\S+)"`); nameRegex.MatchString(line) {
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
matches := nameRegex.FindStringSubmatch(line)
currentInterface = &models.Interface{
Name: matches[1],
Description: matches[2],
}
}
if currentInterface != nil {
if statusRegex := regexp.MustCompile(`\S+\s+is\s+(up|down)`); statusRegex.MatchString(line) {
matches := statusRegex.FindStringSubmatch(line)
currentInterface.Status = matches[1]
}
if macRegex := regexp.MustCompile(`Hardware is\s+\S+,\s+address is\s+(\S+)`); macRegex.MatchString(line) {
matches := macRegex.FindStringSubmatch(line)
currentInterface.MAC = matches[1]
}
if ipRegex := regexp.MustCompile(`IP address\s+(\d+\.\d+\.\d+\.\d+),\s+subnet mask\s+(\d+\.\d+\.\d+\.\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentInterface.IP = matches[1]
currentInterface.Mask = matches[2]
}
if speedRegex := regexp.MustCompile(`(\d+)\s+(Mbps|Gbps)`); speedRegex.MatchString(line) {
matches := speedRegex.FindStringSubmatch(line)
currentInterface.Speed = matches[1] + " " + matches[2]
}
}
}
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
return interfaces
}
+284
View File
@@ -0,0 +1,284 @@
package device
import (
"bufio"
"fmt"
"net"
"network-topology-discovery/pkg/models"
"regexp"
"strings"
)
// CiscoParser Cisco设备解析器
type CiscoParser struct {
BaseParser
}
// GetCommands 获取Cisco设备命令列表
func (p *CiscoParser) GetCommands() []string {
return []string{
"show version",
"show interface",
"show ip interface brief",
"show cdp neighbors detail",
"show lldp neighbors detail",
}
}
// Parse 解析Cisco设备输出
func (p *CiscoParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 5 {
return fmt.Errorf("insufficient command outputs")
}
// 解析设备基本信息
p.parseVersion(device, outputs[0])
// 解析接口信息
interfaces := p.parseInterfaces(outputs[1], outputs[2])
device.Interfaces = interfaces
// 解析邻居信息
neighbors := p.parseNeighbors(outputs[3], outputs[4])
device.Neighbors = neighbors
return nil
}
// parseVersion 解析版本信息
func (p *CiscoParser) parseVersion(device *models.Device, output string) {
// 提取主机名
hostnameRegex := regexp.MustCompile(`^(\S+)\s+#`)
lines := strings.Split(output, "\n")
for _, line := range lines {
if matches := hostnameRegex.FindStringSubmatch(line); len(matches) > 1 {
device.Hostname = matches[1]
break
}
}
// 提取系统版本
versionRegex := regexp.MustCompile(`Cisco IOS Software,?\s+(?:\S+\s+)?(?:\S+\s+)?Version\s+(\S+)`)
if matches := versionRegex.FindStringSubmatch(output); len(matches) > 1 {
device.OSVersion = matches[1]
}
// 提取运行时间
uptimeRegex := regexp.MustCompile(`uptime is\s+(.+)`)
if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 {
device.Uptime = strings.TrimSpace(matches[1])
}
}
// parseInterfaces 解析接口信息
func (p *CiscoParser) parseInterfaces(interfaceOutput, briefOutput string) []models.Interface {
var interfaces []models.Interface
// 从brief输出解析接口状态
briefMap := p.parseInterfaceBrief(briefOutput)
// 从详细输出解析接口详情
scanner := bufio.NewScanner(strings.NewReader(interfaceOutput))
var currentInterface *models.Interface
for scanner.Scan() {
line := scanner.Text()
// 匹配接口名称
if nameRegex := regexp.MustCompile(`^(\S+)\s+is\s+(up|down|administratively down)`); nameRegex.MatchString(line) {
// 保存前一个接口
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
matches := nameRegex.FindStringSubmatch(line)
currentInterface = &models.Interface{
Name: matches[1],
Status: matches[2],
}
// 填充brief信息
if brief, ok := briefMap[currentInterface.Name]; ok {
currentInterface.IP = brief.IP
currentInterface.Status = brief.Status
}
}
if currentInterface != nil {
// 提取描述
if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) {
matches := descRegex.FindStringSubmatch(line)
currentInterface.Description = matches[1]
}
// 提取MAC地址
if macRegex := regexp.MustCompile(`Hardware is\s+\S+,\s+address is\s+(\S+)`); macRegex.MatchString(line) {
matches := macRegex.FindStringSubmatch(line)
currentInterface.MAC = matches[1]
}
// 提取IP地址
if ipRegex := regexp.MustCompile(`Internet address is\s+(\d+\.\d+\.\d+\.\d+/\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
ipParts := strings.Split(matches[1], "/")
if len(ipParts) == 2 {
currentInterface.IP = ipParts[0]
currentInterface.Mask = ipParts[1]
}
}
// 提取带宽
if speedRegex := regexp.MustCompile(`(\d+)\s+(Kbit|Mbit|Gbit)`); speedRegex.MatchString(line) {
matches := speedRegex.FindStringSubmatch(line)
currentInterface.Speed = matches[1] + " " + matches[2]
}
}
}
// 保存最后一个接口
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
return interfaces
}
// parseInterfaceBrief 解析接口简要信息
func (p *CiscoParser) parseInterfaceBrief(output string) map[string]models.Interface {
interfaces := make(map[string]models.Interface)
lines := strings.Split(output, "\n")
for _, line := range lines {
// 匹配: Interface IP-Address OK? Method Status Protocol
fields := strings.Fields(line)
if len(fields) >= 4 {
iface := models.Interface{
Name: fields[0],
}
// 检查是否是IP地址
if net.ParseIP(fields[1]) != nil {
iface.IP = fields[1]
}
// 状态
if len(fields) >= 3 {
iface.Status = strings.ToLower(fields[2])
}
interfaces[iface.Name] = iface
}
}
return interfaces
}
// parseNeighbors 解析邻居信息
func (p *CiscoParser) parseNeighbors(cdpOutput, lldpOutput string) []models.Neighbor {
var neighbors []models.Neighbor
// 解析CDP邻居
cdpNeighbors := p.parseCDPNeighbors(cdpOutput)
neighbors = append(neighbors, cdpNeighbors...)
// 解析LLDP邻居
lldpNeighbors := p.parseLLDPNeighbors(lldpOutput)
neighbors = append(neighbors, lldpNeighbors...)
return neighbors
}
// parseCDPNeighbors 解析CDP邻居
func (p *CiscoParser) parseCDPNeighbors(output string) []models.Neighbor {
var neighbors []models.Neighbor
scanner := bufio.NewScanner(strings.NewReader(output))
var currentNeighbor *models.Neighbor
for scanner.Scan() {
line := scanner.Text()
// 设备ID
if deviceRegex := regexp.MustCompile(`Device ID:\s+(\S+)`); deviceRegex.MatchString(line) {
if currentNeighbor != nil {
neighbors = append(neighbors, *currentNeighbor)
}
matches := deviceRegex.FindStringSubmatch(line)
currentNeighbor = &models.Neighbor{
RemoteDevice: matches[1],
Protocol: "CDP",
}
}
if currentNeighbor != nil {
// 本地接口
if localRegex := regexp.MustCompile(`Interface:\s+(\S+),\s+Port ID \(outgoing port\):\s+(\S+)`); localRegex.MatchString(line) {
matches := localRegex.FindStringSubmatch(line)
currentNeighbor.LocalInterface = matches[1]
currentNeighbor.RemoteInterface = matches[2]
}
// IP地址
if ipRegex := regexp.MustCompile(`IP address:\s+(\d+\.\d+\.\d+\.\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentNeighbor.RemoteIP = matches[1]
}
}
}
if currentNeighbor != nil {
neighbors = append(neighbors, *currentNeighbor)
}
return neighbors
}
// parseLLDPNeighbors 解析LLDP邻居
func (p *CiscoParser) parseLLDPNeighbors(output string) []models.Neighbor {
var neighbors []models.Neighbor
scanner := bufio.NewScanner(strings.NewReader(output))
var currentNeighbor *models.Neighbor
for scanner.Scan() {
line := scanner.Text()
// 邻居设备
if deviceRegex := regexp.MustCompile(`Chassis id:\s+(\S+)`); deviceRegex.MatchString(line) {
if currentNeighbor != nil && currentNeighbor.RemoteDevice != "" {
neighbors = append(neighbors, *currentNeighbor)
}
matches := deviceRegex.FindStringSubmatch(line)
currentNeighbor = &models.Neighbor{
RemoteDevice: matches[1],
Protocol: "LLDP",
}
}
if currentNeighbor != nil {
// 本地接口
if localRegex := regexp.MustCompile(`Local Int:\s+(\S+)`); localRegex.MatchString(line) {
matches := localRegex.FindStringSubmatch(line)
currentNeighbor.LocalInterface = matches[1]
}
// 远程接口
if remoteRegex := regexp.MustCompile(`Port ID:\s+(\S+)`); remoteRegex.MatchString(line) {
matches := remoteRegex.FindStringSubmatch(line)
currentNeighbor.RemoteInterface = matches[1]
}
// IP地址
if ipRegex := regexp.MustCompile(`Management address:\s+(\d+\.\d+\.\d+\.\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentNeighbor.RemoteIP = matches[1]
}
}
}
if currentNeighbor != nil && currentNeighbor.RemoteDevice != "" {
neighbors = append(neighbors, *currentNeighbor)
}
return neighbors
}
+158
View File
@@ -0,0 +1,158 @@
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{
"display version",
"display interface",
"display ip interface brief",
"display lldp neighbor-list",
}
}
// Parse 解析H3C设备输出
func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 4 {
return fmt.Errorf("insufficient command outputs")
}
p.parseVersion(device, outputs[0])
device.Interfaces = p.parseInterfaces(outputs[1], outputs[2])
device.Neighbors = p.parseNeighbors(outputs[3])
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
for scanner.Scan() {
line := scanner.Text()
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 != nil {
if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) {
currentInterface.Description = descRegex.FindStringSubmatch(line)[1]
}
if macRegex := regexp.MustCompile(`Hardware address is\s+(\S+)`); macRegex.MatchString(line) {
currentInterface.MAC = macRegex.FindStringSubmatch(line)[1]
}
if ipRegex := regexp.MustCompile(`IP Address:\s+(\d+\.\d+\.\d+\.\d+)\s+Subnet Mask:\s+(\d+\.\d+\.\d+\.\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentInterface.IP = matches[1]
currentInterface.Mask = 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
}
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) []models.Neighbor {
var neighbors []models.Neighbor
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "Local Interface") || strings.Contains(line, "-----") {
continue
}
fields := strings.Fields(line)
if len(fields) >= 5 {
neighbor := models.Neighbor{
LocalInterface: fields[0],
RemoteDevice: fields[2],
RemoteInterface: fields[4],
Protocol: "LLDP",
}
neighbors = append(neighbors, neighbor)
}
}
return neighbors
}
+169
View File
@@ -0,0 +1,169 @@
package device
import (
"bufio"
"fmt"
"network-topology-discovery/pkg/models"
"regexp"
"strings"
)
// HuaweiParser 华为设备解析器
type HuaweiParser struct {
BaseParser
}
// GetCommands 获取华为设备命令列表
func (p *HuaweiParser) GetCommands() []string {
return []string{
"display version",
"display interface",
"display ip interface brief",
"display lldp neighbor",
}
}
// Parse 解析华为设备输出
func (p *HuaweiParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 4 {
return fmt.Errorf("insufficient command outputs")
}
p.parseVersion(device, outputs[0])
device.Interfaces = p.parseInterfaces(outputs[1], outputs[2])
device.Neighbors = p.parseNeighbors(outputs[3])
return nil
}
func (p *HuaweiParser) 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, "VRP") {
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "VRP") {
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 *HuaweiParser) parseInterfaces(interfaceOutput, briefOutput string) []models.Interface {
var interfaces []models.Interface
briefMap := p.parseInterfaceBrief(briefOutput)
scanner := bufio.NewScanner(strings.NewReader(interfaceOutput))
var currentInterface *models.Interface
for scanner.Scan() {
line := scanner.Text()
// 匹配接口名称
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 != nil {
// 描述
if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) {
currentInterface.Description = descRegex.FindStringSubmatch(line)[1]
}
// MAC地址
if macRegex := regexp.MustCompile(`Hardware address is\s+(\S+)`); macRegex.MatchString(line) {
currentInterface.MAC = macRegex.FindStringSubmatch(line)[1]
}
// IP地址
if ipRegex := regexp.MustCompile(`Internet Address is\s+(\d+\.\d+\.\d+\.\d+),\s+Subnet mask is\s+(\d+\.\d+\.\d+\.\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentInterface.IP = matches[1]
currentInterface.Mask = 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
}
func (p *HuaweiParser) 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 *HuaweiParser) parseNeighbors(output string) []models.Neighbor {
var neighbors []models.Neighbor
scanner := bufio.NewScanner(strings.NewReader(output))
var currentNeighbor *models.Neighbor
for scanner.Scan() {
line := scanner.Text()
// 跳过标题行
if strings.Contains(line, "Local Interface") || strings.Contains(line, "-----") {
continue
}
fields := strings.Fields(line)
if len(fields) >= 5 {
currentNeighbor = &models.Neighbor{
LocalInterface: fields[0],
RemoteDevice: fields[2],
RemoteInterface: fields[4],
Protocol: "LLDP",
}
neighbors = append(neighbors, *currentNeighbor)
}
}
return neighbors
}
+107
View File
@@ -0,0 +1,107 @@
package device
import (
"fmt"
"network-topology-discovery/pkg/models"
"regexp"
"strings"
)
// LinuxParser Linux服务器解析器
type LinuxParser struct {
BaseParser
}
// GetCommands 获取Linux命令列表
func (p *LinuxParser) GetCommands() []string {
return []string{
"hostname",
"uname -a",
"ip addr show",
"ip link show",
"uptime",
}
}
// Parse 解析Linux输出
func (p *LinuxParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 5 {
return fmt.Errorf("insufficient command outputs")
}
device.Hostname = strings.TrimSpace(outputs[0])
device.OSVersion = p.parseOSVersion(outputs[1])
device.Uptime = strings.TrimSpace(outputs[4])
device.Interfaces = p.parseInterfaces(outputs[2], outputs[3])
return nil
}
func (p *LinuxParser) parseOSVersion(output string) string {
// 从uname提取内核版本
parts := strings.Fields(output)
if len(parts) >= 3 {
return fmt.Sprintf("Linux %s", parts[2])
}
return output
}
func (p *LinuxParser) parseInterfaces(addrOutput, linkOutput string) []models.Interface {
var interfaces []models.Interface
// 解析ip addr show输出
interfaceMap := make(map[string]*models.Interface)
var currentInterface *models.Interface
lines := strings.Split(addrOutput, "\n")
for _, line := range lines {
// 匹配接口: 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
if nameRegex := regexp.MustCompile(`^\d+:\s+(\S+):\s+<([^>]*)>`); nameRegex.MatchString(line) {
matches := nameRegex.FindStringSubmatch(line)
name := strings.TrimSuffix(matches[1], ":")
flags := matches[2]
currentInterface = &models.Interface{
Name: name,
Status: "down",
}
if strings.Contains(flags, "UP") {
currentInterface.Status = "up"
}
// 提取MTU
if mtuRegex := regexp.MustCompile(`mtu\s+(\d+)`); mtuRegex.MatchString(line) {
matches := mtuRegex.FindStringSubmatch(line)
currentInterface.MTU = 0
fmt.Sscanf(matches[1], "%d", &currentInterface.MTU)
}
interfaceMap[name] = currentInterface
interfaces = append(interfaces, *currentInterface)
}
if currentInterface != nil {
// 提取MAC地址
if macRegex := regexp.MustCompile(`link/ether\s+(\S+)`); macRegex.MatchString(line) {
matches := macRegex.FindStringSubmatch(line)
currentInterface.MAC = matches[1]
}
// 提取IP地址
if ipRegex := regexp.MustCompile(`inet\s+(\d+\.\d+\.\d+\.\d+)/(\d+)`); ipRegex.MatchString(line) {
matches := ipRegex.FindStringSubmatch(line)
currentInterface.IP = matches[1]
currentInterface.Mask = matches[2]
}
}
}
// 更新接口列表
finalInterfaces := make([]models.Interface, 0, len(interfaceMap))
for _, iface := range interfaceMap {
finalInterfaces = append(finalInterfaces, *iface)
}
return finalInterfaces
}
+94
View File
@@ -0,0 +1,94 @@
package device
import (
"network-topology-discovery/pkg/models"
sshclient "network-topology-discovery/internal/ssh"
)
// Parser 设备解析器接口
type Parser interface {
// GetCommands 获取需要执行的命令列表
GetCommands() []string
// Parse 解析命令输出,填充设备信息
Parse(device *models.Device, outputs []string) error
// GetType 获取设备类型
GetType() models.DeviceType
}
// BaseParser 基础解析器
type BaseParser struct {
DeviceType models.DeviceType
}
// GetType 获取设备类型
func (b *BaseParser) GetType() models.DeviceType {
return b.DeviceType
}
// DiscoverDevice 发现并采集设备信息
func DiscoverDevice(ip string, deviceType models.DeviceType, username, password string) (*models.Device, error) {
device := &models.Device{
IP: ip,
Type: deviceType,
}
// 创建SSH客户端 - 默认启用不安全加密算法以兼容老旧设备
client := sshclient.NewClient(sshclient.Config{
Host: ip,
Username: username,
Password: password,
InsecureCiphers: true, // 启用旧版加密算法支持
})
// 连接
if err := client.Connect(); err != nil {
device.ScanStatus = "failed"
device.ErrorMessage = err.Error()
return device, err
}
defer client.Close()
// 获取对应的解析器
var parser Parser
switch deviceType {
case models.DeviceTypeCisco:
parser = &CiscoParser{}
case models.DeviceTypeHuawei:
parser = &HuaweiParser{}
case models.DeviceTypeH3C:
parser = &H3CParser{}
case models.DeviceTypeASA:
parser = &ASAParser{}
case models.DeviceTypeLinux:
parser = &LinuxParser{}
case models.DeviceTypeWindows:
parser = &WindowsParser{}
default:
device.ScanStatus = "failed"
device.ErrorMessage = "unsupported device type"
return device, nil
}
// 获取命令列表
commands := parser.GetCommands()
// 执行命令
outputs, err := client.ExecuteCommands(commands)
if err != nil {
device.ScanStatus = "failed"
device.ErrorMessage = err.Error()
return device, err
}
// 解析输出
if err := parser.Parse(device, outputs); err != nil {
device.ScanStatus = "failed"
device.ErrorMessage = err.Error()
return device, err
}
device.ScanStatus = "success"
return device, nil
}
+200
View File
@@ -0,0 +1,200 @@
package device
import (
"fmt"
"network-topology-discovery/pkg/models"
"strings"
)
// WindowsParser Windows Server解析器
type WindowsParser struct {
BaseParser
}
// GetCommands 获取Windows命令列表
func (p *WindowsParser) GetCommands() []string {
return []string{
"hostname",
"systeminfo | findstr /B /C:\"OS Name\" /C:\"OS Version\"",
"Get-NetAdapter | Select-Object Name, InterfaceDescription, Status, MacAddress, LinkSpeed | Format-List",
"Get-NetIPAddress | Where-Object AddressFamily -eq IPv4 | Select-Object InterfaceAlias, IPAddress, PrefixLength | Format-List",
"systeminfo | findstr /B /C:\"System Boot Time\"",
}
}
// Parse 解析Windows输出
func (p *WindowsParser) Parse(device *models.Device, outputs []string) error {
if len(outputs) < 5 {
return fmt.Errorf("insufficient command outputs")
}
device.Hostname = strings.TrimSpace(outputs[0])
device.OSVersion = p.parseOSVersion(outputs[1])
device.Uptime = p.parseUptime(outputs[4])
device.Interfaces = p.parseInterfaces(outputs[2], outputs[3])
return nil
}
func (p *WindowsParser) parseOSVersion(output string) string {
lines := strings.Split(output, "\n")
var osInfo []string
for _, line := range lines {
if strings.Contains(line, "OS Name") || strings.Contains(line, "OS Version") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
osInfo = append(osInfo, strings.TrimSpace(parts[1]))
}
}
}
return strings.Join(osInfo, ", ")
}
func (p *WindowsParser) parseUptime(output string) string {
if strings.Contains(output, "System Boot Time") {
parts := strings.SplitN(output, ":", 2)
if len(parts) == 2 {
return strings.TrimSpace(parts[1])
}
}
return output
}
func (p *WindowsParser) parseInterfaces(adapterOutput, ipOutput string) []models.Interface {
var interfaces []models.Interface
// 解析网卡信息
adapters := p.parseNetAdapters(adapterOutput)
// 解析IP信息
ipMap := p.parseIPAddresses(ipOutput)
// 合并信息
for _, adapter := range adapters {
iface := adapter
if ipInfo, ok := ipMap[adapter.Name]; ok {
iface.IP = ipInfo.IP
iface.Mask = ipInfo.Mask
}
interfaces = append(interfaces, iface)
}
return interfaces
}
func (p *WindowsParser) parseNetAdapters(output string) []models.Interface {
var interfaces []models.Interface
var currentInterface *models.Interface
lines := strings.Split(output, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if strings.Contains(line, "Name") && strings.Contains(line, ":") {
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentInterface = &models.Interface{
Name: strings.TrimSpace(parts[1]),
}
}
}
if currentInterface != nil {
if strings.Contains(line, "InterfaceDescription") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentInterface.Description = strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "Status") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
status := strings.TrimSpace(parts[1])
if status == "Up" {
currentInterface.Status = "up"
} else {
currentInterface.Status = "down"
}
}
}
if strings.Contains(line, "MacAddress") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentInterface.MAC = strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "LinkSpeed") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentInterface.Speed = strings.TrimSpace(parts[1])
}
}
}
}
if currentInterface != nil {
interfaces = append(interfaces, *currentInterface)
}
return interfaces
}
type ipInfo struct {
IP string
Mask string
}
func (p *WindowsParser) parseIPAddresses(output string) map[string]ipInfo {
ipMap := make(map[string]ipInfo)
var currentAlias string
lines := strings.Split(output, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if strings.Contains(line, "InterfaceAlias") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentAlias = strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "IPAddress") && currentAlias != "" {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
if _, ok := ipMap[currentAlias]; !ok {
ipMap[currentAlias] = ipInfo{}
}
info := ipMap[currentAlias]
info.IP = strings.TrimSpace(parts[1])
ipMap[currentAlias] = info
}
}
if strings.Contains(line, "PrefixLength") && currentAlias != "" {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
if _, ok := ipMap[currentAlias]; !ok {
ipMap[currentAlias] = ipInfo{}
}
info := ipMap[currentAlias]
info.Mask = strings.TrimSpace(parts[1])
ipMap[currentAlias] = info
}
}
}
return ipMap
}