h3c.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. package device
  2. import (
  3. "bufio"
  4. "fmt"
  5. "network-topology-discovery/pkg/models"
  6. "regexp"
  7. "strings"
  8. )
  9. // H3CParser H3C设备解析器
  10. type H3CParser struct {
  11. BaseParser
  12. }
  13. // GetCommands 获取H3C设备命令列表
  14. func (p *H3CParser) GetCommands() []string {
  15. return []string{
  16. "screen-length disable", // 禁用分页(H3C/华为设备必需)
  17. "display version",
  18. "display interface",
  19. "display interface brief", // 接口简要信息(包含VLAN和物理接口状态)
  20. "display lldp neighbor-information", // 使用非verbose格式(v1.0.0验证可行)
  21. "display arp", // ARP表用于解析邻居IP
  22. }
  23. }
  24. // Parse 解析H3C设备输出
  25. func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
  26. if len(outputs) < 6 {
  27. return fmt.Errorf("insufficient command outputs")
  28. }
  29. // outputs[0] 是 screen-length disable 的输出(通常为空)
  30. p.parseVersion(device, outputs[1]) // outputs[1] 是 display version
  31. // outputs[2] 是 display interface
  32. fmt.Printf("[H3C DEBUG] display interface output length: %d\n", len(outputs[2]))
  33. if len(outputs[2]) > 0 {
  34. // 如果输出小于1000字节,完整输出以便调试
  35. if len(outputs[2]) <= 1000 {
  36. fmt.Printf("[H3C DEBUG] Complete output:\n%s\n", outputs[2])
  37. } else {
  38. fmt.Printf("[H3C DEBUG] First 200 chars: %q\n", outputs[2][:200])
  39. }
  40. } else {
  41. fmt.Printf("[H3C DEBUG] display interface output is EMPTY!\n")
  42. }
  43. if outputs[2] == "" {
  44. fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP)
  45. } else {
  46. // outputs[3] 是 display interface brief
  47. device.Interfaces = p.parseInterfaces(outputs[2], outputs[3])
  48. if len(device.Interfaces) == 0 {
  49. fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n",
  50. device.IP, len(outputs[2]))
  51. }
  52. // 收集所有接口的MAC地址(用于邻居匹配)
  53. macSet := make(map[string]bool)
  54. for _, iface := range device.Interfaces {
  55. if iface.MAC != "" {
  56. macSet[iface.MAC] = true
  57. } else {
  58. fmt.Printf(" Interface %s has no MAC address\n", iface.Name)
  59. }
  60. }
  61. for mac := range macSet {
  62. device.MACAddresses = append(device.MACAddresses, mac)
  63. }
  64. fmt.Printf(" Collected %d unique MAC addresses for device %s\n", len(macSet), device.IP)
  65. }
  66. // outputs[4] 是 display lldp neighbor-information
  67. // outputs[5] 是 display arp
  68. // 解析ARP表
  69. fmt.Printf("[H3C ARP DEBUG] Raw ARP output length: %d bytes\n", len(outputs[5]))
  70. if len(outputs[5]) > 0 && len(outputs[5]) <= 1000 {
  71. fmt.Printf("[H3C ARP DEBUG] Raw ARP output:\n%s\n", outputs[5])
  72. } else if len(outputs[5]) > 1000 {
  73. fmt.Printf("[H3C ARP DEBUG] Raw ARP output (first 1000 bytes):\n%s\n", outputs[5][:1000])
  74. }
  75. arpTable := p.parseARPTable(outputs[5])
  76. fmt.Printf("[H3C ARP DEBUG] Parsed ARP table: %d entries\n", len(arpTable))
  77. // 解析LLDP邻居(传入ARP表)
  78. device.Neighbors = p.parseNeighbors(outputs[4], arpTable)
  79. fmt.Printf("Device %s: %d interfaces, %d neighbors\n",
  80. device.IP, len(device.Interfaces), len(device.Neighbors))
  81. return nil
  82. }
  83. func (p *H3CParser) parseVersion(device *models.Device, output string) {
  84. hostnameRegex := regexp.MustCompile(`<(\S+)>`)
  85. if matches := hostnameRegex.FindStringSubmatch(output); len(matches) > 1 {
  86. device.Hostname = matches[1]
  87. }
  88. if strings.Contains(output, "Comware") {
  89. lines := strings.Split(output, "\n")
  90. for _, line := range lines {
  91. if strings.Contains(line, "Comware") {
  92. device.OSVersion = strings.TrimSpace(line)
  93. break
  94. }
  95. }
  96. }
  97. uptimeRegex := regexp.MustCompile(`uptime is\s+(\d+\s+\S+)`)
  98. if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 {
  99. device.Uptime = matches[1]
  100. }
  101. }
  102. func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []models.Interface {
  103. var interfaces []models.Interface
  104. briefMap := p.parseInterfaceBrief(briefOutput)
  105. scanner := bufio.NewScanner(strings.NewReader(interfaceOutput))
  106. var currentInterface *models.Interface
  107. var pendingInterfaceName string // 暂存接口名
  108. for scanner.Scan() {
  109. line := scanner.Text()
  110. // H3C接口输出格式1: 接口名和状态在同一行
  111. // GigabitEthernet1/0/0 current state: UP
  112. if nameRegex := regexp.MustCompile(`^(\S+)\s+current state:\s+(UP|DOWN)`); nameRegex.MatchString(line) {
  113. if currentInterface != nil {
  114. interfaces = append(interfaces, *currentInterface)
  115. }
  116. matches := nameRegex.FindStringSubmatch(line)
  117. currentInterface = &models.Interface{
  118. Name: matches[1],
  119. Status: strings.ToLower(matches[2]),
  120. }
  121. if brief, ok := briefMap[currentInterface.Name]; ok {
  122. currentInterface.IP = brief.IP
  123. }
  124. pendingInterfaceName = ""
  125. continue
  126. }
  127. // H3C接口输出格式2: 接口名单独一行,下一行是状态
  128. // GigabitEthernet1/0/0
  129. // Current state: DOWN
  130. if pendingInterfaceName != "" && regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).MatchString(line) {
  131. matches := regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).FindStringSubmatch(line)
  132. if currentInterface != nil {
  133. interfaces = append(interfaces, *currentInterface)
  134. }
  135. currentInterface = &models.Interface{
  136. Name: pendingInterfaceName,
  137. Status: strings.ToLower(matches[1]),
  138. }
  139. if brief, ok := briefMap[currentInterface.Name]; ok {
  140. currentInterface.IP = brief.IP
  141. }
  142. pendingInterfaceName = ""
  143. continue
  144. }
  145. // 匹配接口名(单独一行的情况)
  146. // 格式: GigabitEthernet1/0/0
  147. if interfaceNameRegex := regexp.MustCompile(`^(GigabitEthernet|Ten-GigabitEthernet|FortyGigE|HundredGigE|Ethernet|Serial|LoopBack|Vlanif|NULL|Bridge-Aggregate|Route-Aggregate)\S*`); interfaceNameRegex.MatchString(line) {
  148. pendingInterfaceName = interfaceNameRegex.FindString(line)
  149. continue
  150. }
  151. // 解析接口属性
  152. if currentInterface != nil {
  153. if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) {
  154. currentInterface.Description = descRegex.FindStringSubmatch(line)[1]
  155. }
  156. if macRegex := regexp.MustCompile(`hardware address:\s+(\S+)`); macRegex.MatchString(line) {
  157. currentInterface.MAC = macRegex.FindStringSubmatch(line)[1]
  158. }
  159. // 匹配IP地址格式: Internet address: 192.168.0.1/24 (primary)
  160. if ipRegex := regexp.MustCompile(`Internet address:\s+(\d+\.\d+\.\d+\.\d+)/(\d+)`); ipRegex.MatchString(line) {
  161. matches := ipRegex.FindStringSubmatch(line)
  162. currentInterface.IP = matches[1]
  163. // CIDR转换为子网掩码
  164. currentInterface.Mask = cidrToMask(matches[2])
  165. }
  166. if speedRegex := regexp.MustCompile(`(\d+)\s+(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) {
  167. matches := speedRegex.FindStringSubmatch(line)
  168. currentInterface.Speed = matches[1] + " " + matches[2]
  169. }
  170. }
  171. }
  172. if currentInterface != nil {
  173. interfaces = append(interfaces, *currentInterface)
  174. }
  175. return interfaces
  176. }
  177. // cidrToMask CIDR转换为子网掩码
  178. func cidrToMask(cidr string) string {
  179. var mask int
  180. fmt.Sscanf(cidr, "%d", &mask)
  181. s := uint32(0)
  182. for i := 0; i < mask; i++ {
  183. s |= (1 << (31 - uint(i)))
  184. }
  185. return fmt.Sprintf("%d.%d.%d.%d",
  186. (s>>24)&0xFF,
  187. (s>>16)&0xFF,
  188. (s>>8)&0xFF,
  189. s&0xFF)
  190. }
  191. // parseARPTable 解析ARP表,建立MAC到IP的映射
  192. func (p *H3CParser) parseARPTable(output string) map[string]string {
  193. macToIP := make(map[string]string)
  194. lines := strings.Split(output, "\n")
  195. for _, line := range lines {
  196. // 跳过空行和标题行
  197. if strings.TrimSpace(line) == "" ||
  198. strings.Contains(line, "Type:") ||
  199. strings.Contains(line, "------") ||
  200. strings.Contains(line, "IP address") {
  201. continue
  202. }
  203. // ARP表格式: IP address MAC address VLAN/VSI name Interface Aging Type
  204. // 例: 172.16.8.10 743a-2047-38e0 8 GE1/0/47 1163 D
  205. fields := strings.Fields(line)
  206. if len(fields) >= 2 {
  207. ip := fields[0]
  208. mac := strings.ToLower(fields[1])
  209. // 标准化MAC地址(统一为aabb-ccdd-eeff格式)
  210. mac = normalizeMACFormat(mac)
  211. // 验证是有效的IP和MAC
  212. if isValidIP(ip) && isValidMAC(mac) {
  213. macToIP[mac] = ip
  214. fmt.Printf("[H3C ARP DEBUG] Added MAC->IP: %s -> %s\n", mac, ip)
  215. }
  216. }
  217. }
  218. return macToIP
  219. }
  220. // normalizeMACFormat 标准化MAC地址格式(统一为aabb-ccdd-eeff)
  221. func normalizeMACFormat(mac string) string {
  222. // 去除所有分隔符
  223. clean := ""
  224. for _, c := range mac {
  225. if c != '-' && c != ':' && c != '.' {
  226. clean += string(c)
  227. }
  228. }
  229. // 重新格式化为aabb-ccdd-eeff
  230. if len(clean) == 12 {
  231. return fmt.Sprintf("%s-%s-%s", clean[0:4], clean[4:8], clean[8:12])
  232. }
  233. return mac
  234. }
  235. func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface {
  236. interfaces := make(map[string]models.Interface)
  237. lines := strings.Split(output, "\n")
  238. for _, line := range lines {
  239. fields := strings.Fields(line)
  240. if len(fields) >= 4 {
  241. iface := models.Interface{
  242. Name: fields[0],
  243. IP: fields[1],
  244. Status: strings.ToLower(fields[3]),
  245. }
  246. interfaces[iface.Name] = iface
  247. }
  248. }
  249. return interfaces
  250. }
  251. func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []models.Neighbor {
  252. var neighbors []models.Neighbor
  253. scanner := bufio.NewScanner(strings.NewReader(output))
  254. var currentNeighbor *models.Neighbor
  255. var localInterface string
  256. for scanner.Scan() {
  257. line := scanner.Text()
  258. // 匹配本地接口行: LLDP neighbor-information of port 20[GigabitEthernet1/0/20]:
  259. if portRegex := regexp.MustCompile(`LLDP neighbor-information of port \d+\[([^\]]+)\]:`); portRegex.MatchString(line) {
  260. // 保存前一个邻居
  261. if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
  262. neighbors = append(neighbors, *currentNeighbor)
  263. }
  264. matches := portRegex.FindStringSubmatch(line)
  265. localInterface = matches[1]
  266. currentNeighbor = &models.Neighbor{
  267. LocalInterface: localInterface,
  268. Protocol: "LLDP",
  269. }
  270. continue
  271. }
  272. if currentNeighbor != nil {
  273. // 提取 ChassisID (MAC地址) - 非verbose格式
  274. if strings.Contains(line, "ChassisID/subtype") {
  275. parts := strings.SplitN(line, ":", 2)
  276. if len(parts) == 2 {
  277. value := strings.TrimSpace(parts[1])
  278. // 格式: a4bb-6de2-62cd/MAC address
  279. if macParts := strings.Split(value, "/"); len(macParts) > 0 {
  280. mac := strings.TrimSpace(strings.ToLower(macParts[0]))
  281. // 标准化MAC地址格式
  282. mac = normalizeMACFormat(mac)
  283. // 保存MAC地址
  284. currentNeighbor.RemoteMAC = mac
  285. fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line)
  286. // 使用MAC地址作为设备标识
  287. currentNeighbor.RemoteDevice = mac
  288. // 通过ARP表查找IP地址
  289. if arpTable != nil {
  290. if ip, found := arpTable[mac]; found {
  291. currentNeighbor.RemoteIP = ip
  292. fmt.Printf(" ✓ Found IP for MAC %s: %s\n", mac, ip)
  293. } else {
  294. fmt.Printf(" ✗ No IP found for MAC %s in ARP table\n", mac)
  295. }
  296. }
  297. } else {
  298. fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line)
  299. }
  300. } else {
  301. fmt.Printf(" WARNING: ChassisID line has no colon: %s\n", line)
  302. }
  303. }
  304. // 提取 PortID (远程接口) - 非verbose格式
  305. if strings.Contains(line, "PortID/subtype") {
  306. parts := strings.SplitN(line, ":", 2)
  307. if len(parts) == 2 {
  308. value := strings.TrimSpace(parts[1])
  309. // 格式: GigabitEthernet0/0/1/Interface name
  310. if portParts := strings.Split(value, "/"); len(portParts) >= 3 {
  311. // 取前3部分作为接口名: GigabitEthernet0/0/1
  312. currentNeighbor.RemoteInterface = strings.Join(portParts[:3], "/")
  313. fmt.Printf(" Parsed neighbor interface: %s\n", currentNeighbor.RemoteInterface)
  314. }
  315. }
  316. }
  317. }
  318. }
  319. // 添加最后一个邻居
  320. if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
  321. neighbors = append(neighbors, *currentNeighbor)
  322. }
  323. return neighbors
  324. }
  325. // isValidIP 简单验证IP地址
  326. func isValidIP(ip string) bool {
  327. parts := strings.Split(ip, ".")
  328. if len(parts) != 4 {
  329. return false
  330. }
  331. for _, part := range parts {
  332. if len(part) == 0 || len(part) > 3 {
  333. return false
  334. }
  335. for _, c := range part {
  336. if c < '0' || c > '9' {
  337. return false
  338. }
  339. }
  340. }
  341. return true
  342. }
  343. // isValidMAC 简单验证MAC地址
  344. func isValidMAC(mac string) bool {
  345. parts := strings.Split(mac, "-")
  346. if len(parts) != 3 && len(parts) != 6 {
  347. return false
  348. }
  349. for _, part := range parts {
  350. if len(part) != 2 {
  351. return false
  352. }
  353. for _, c := range part {
  354. if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
  355. return false
  356. }
  357. }
  358. }
  359. return true
  360. }