Initial commit: 网络拓扑发现系统
- 支持Cisco、华为、H3C、ASA、Linux、Windows设备 - SSH远程采集设备信息 - 自动发现网络拓扑(LLDP/CDP) - Web可视化界面 - 支持旧版SSH加密算法兼容
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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", ¤tInterface.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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user