h3c.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  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. if len(outputs[2]) <= 3000 {
  35. fmt.Printf("[H3C DEBUG] Complete interface output:\n%s\n", outputs[2])
  36. } else {
  37. fmt.Printf("[H3C DEBUG] First 3000 chars:\n%s\n", outputs[2][:3000])
  38. }
  39. } else {
  40. fmt.Printf("[H3C DEBUG] display interface output is EMPTY!\n")
  41. }
  42. // outputs[3] 是 display interface brief
  43. fmt.Printf("[H3C BRIEF DEBUG] display interface brief output length: %d\n", len(outputs[3]))
  44. if len(outputs[3]) > 0 {
  45. if len(outputs[3]) <= 3000 {
  46. fmt.Printf("[H3C BRIEF DEBUG] Complete brief output:\n%s\n", outputs[3])
  47. } else {
  48. fmt.Printf("[H3C BRIEF DEBUG] First 3000 chars:\n%s\n", outputs[3][:3000])
  49. }
  50. } else {
  51. fmt.Printf("[H3C BRIEF DEBUG] display interface brief output is EMPTY!\n")
  52. }
  53. if outputs[2] == "" {
  54. fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP)
  55. } else {
  56. // outputs[3] 是 display interface brief
  57. device.Interfaces = p.parseInterfaces(outputs[2], outputs[3])
  58. if len(device.Interfaces) == 0 {
  59. fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n",
  60. device.IP, len(outputs[2]))
  61. }
  62. // 收集所有接口的MAC地址(用于邻居匹配)
  63. macSet := make(map[string]bool)
  64. for _, iface := range device.Interfaces {
  65. if iface.MAC != "" {
  66. macSet[iface.MAC] = true
  67. } else {
  68. fmt.Printf(" Interface %s has no MAC address\n", iface.Name)
  69. }
  70. }
  71. for mac := range macSet {
  72. device.MACAddresses = append(device.MACAddresses, mac)
  73. }
  74. fmt.Printf(" Collected %d unique MAC addresses for device %s\n", len(macSet), device.IP)
  75. }
  76. // outputs[4] 是 display lldp neighbor-information
  77. // outputs[5] 是 display arp
  78. // 解析ARP表
  79. fmt.Printf("[H3C ARP DEBUG] Raw ARP output length: %d bytes\n", len(outputs[5]))
  80. if len(outputs[5]) > 0 && len(outputs[5]) <= 1000 {
  81. fmt.Printf("[H3C ARP DEBUG] Raw ARP output:\n%s\n", outputs[5])
  82. } else if len(outputs[5]) > 1000 {
  83. fmt.Printf("[H3C ARP DEBUG] Raw ARP output (first 1000 bytes):\n%s\n", outputs[5][:1000])
  84. }
  85. arpTable := p.parseARPTable(outputs[5])
  86. fmt.Printf("[H3C ARP DEBUG] Parsed ARP table: %d entries\n", len(arpTable))
  87. // 解析LLDP邻居(传入ARP表)
  88. device.Neighbors = p.parseNeighbors(outputs[4], arpTable)
  89. fmt.Printf("Device %s: %d interfaces, %d neighbors\n",
  90. device.IP, len(device.Interfaces), len(device.Neighbors))
  91. return nil
  92. }
  93. func (p *H3CParser) parseVersion(device *models.Device, output string) {
  94. hostnameRegex := regexp.MustCompile(`<(\S+)>`)
  95. if matches := hostnameRegex.FindStringSubmatch(output); len(matches) > 1 {
  96. device.Hostname = matches[1]
  97. }
  98. if strings.Contains(output, "Comware") {
  99. lines := strings.Split(output, "\n")
  100. for _, line := range lines {
  101. if strings.Contains(line, "Comware") {
  102. device.OSVersion = strings.TrimSpace(line)
  103. break
  104. }
  105. }
  106. }
  107. uptimeRegex := regexp.MustCompile(`uptime is\s+(.+)`)
  108. if matches := uptimeRegex.FindStringSubmatch(output); len(matches) > 1 {
  109. device.Uptime = strings.TrimSpace(matches[1])
  110. }
  111. }
  112. func (p *H3CParser) parseInterfaces(interfaceOutput, briefOutput string) []models.Interface {
  113. var interfaces []models.Interface
  114. briefMap := p.parseInterfaceBrief(briefOutput)
  115. scanner := bufio.NewScanner(strings.NewReader(interfaceOutput))
  116. var currentInterface *models.Interface
  117. var pendingInterfaceName string // 暂存接口名
  118. for scanner.Scan() {
  119. line := scanner.Text()
  120. // H3C接口输出格式1: 接口名和状态在同一行
  121. // GigabitEthernet1/0/0 current state: UP
  122. if nameRegex := regexp.MustCompile(`^(\S+)\s+current state:\s+(UP|DOWN)`); nameRegex.MatchString(line) {
  123. if currentInterface != nil {
  124. interfaces = append(interfaces, *currentInterface)
  125. }
  126. matches := nameRegex.FindStringSubmatch(line)
  127. currentInterface = &models.Interface{
  128. Name: matches[1],
  129. Status: strings.ToLower(matches[2]),
  130. }
  131. if brief, ok := briefMap[currentInterface.Name]; ok {
  132. currentInterface.IP = brief.IP
  133. if currentInterface.Speed == "" {
  134. currentInterface.Speed = brief.Speed
  135. }
  136. if currentInterface.Duplex == "" {
  137. currentInterface.Duplex = brief.Duplex
  138. }
  139. if currentInterface.VLAN == "" {
  140. currentInterface.VLAN = brief.VLAN
  141. }
  142. }
  143. pendingInterfaceName = ""
  144. continue
  145. }
  146. // H3C接口输出格式2: 接口名单独一行,下一行是状态
  147. // GigabitEthernet1/0/0
  148. // Current state: DOWN
  149. if pendingInterfaceName != "" && regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).MatchString(line) {
  150. matches := regexp.MustCompile(`^Current state:\s+(UP|DOWN)`).FindStringSubmatch(line)
  151. if currentInterface != nil {
  152. interfaces = append(interfaces, *currentInterface)
  153. }
  154. currentInterface = &models.Interface{
  155. Name: pendingInterfaceName,
  156. Status: strings.ToLower(matches[1]),
  157. }
  158. if brief, ok := briefMap[currentInterface.Name]; ok {
  159. currentInterface.IP = brief.IP
  160. if currentInterface.Speed == "" {
  161. currentInterface.Speed = brief.Speed
  162. }
  163. if currentInterface.Duplex == "" {
  164. currentInterface.Duplex = brief.Duplex
  165. }
  166. if currentInterface.VLAN == "" {
  167. currentInterface.VLAN = brief.VLAN
  168. }
  169. }
  170. pendingInterfaceName = ""
  171. continue
  172. }
  173. // 匹配接口名(单独一行的情况)
  174. // 格式: GigabitEthernet1/0/0
  175. if interfaceNameRegex := regexp.MustCompile(`^(GigabitEthernet|Ten-GigabitEthernet|FortyGigE|HundredGigE|Ethernet|Serial|LoopBack|Vlanif|NULL|Bridge-Aggregate|Route-Aggregate)\S*`); interfaceNameRegex.MatchString(line) {
  176. pendingInterfaceName = interfaceNameRegex.FindString(line)
  177. continue
  178. }
  179. // 解析接口属性
  180. if currentInterface != nil {
  181. if descRegex := regexp.MustCompile(`Description:\s+(.+)`); descRegex.MatchString(line) {
  182. currentInterface.Description = descRegex.FindStringSubmatch(line)[1]
  183. }
  184. if macRegex := regexp.MustCompile(`hardware address:\s+(\S+)`); macRegex.MatchString(line) {
  185. currentInterface.MAC = macRegex.FindStringSubmatch(line)[1]
  186. }
  187. // 匹配IP地址格式: Internet address: 192.168.0.1/24 (primary)
  188. if ipRegex := regexp.MustCompile(`Internet address:\s+(\d+\.\d+\.\d+\.\d+)/(\d+)`); ipRegex.MatchString(line) {
  189. matches := ipRegex.FindStringSubmatch(line)
  190. currentInterface.IP = matches[1]
  191. // CIDR转换为子网掩码
  192. currentInterface.Mask = cidrToMask(matches[2])
  193. }
  194. // 匹配速度格式1: Speed : 1000Mbps 或 Speed : 10Gbps
  195. if speedRegex := regexp.MustCompile(`Speed\s*:\s*(\d+)\s*(Kbps|Mbps|Gbps)`); speedRegex.MatchString(line) {
  196. matches := speedRegex.FindStringSubmatch(line)
  197. currentInterface.Speed = matches[1] + matches[2]
  198. }
  199. // 匹配速度格式2: Speed: 1000, Loopback: not set (纯数字Mbps)
  200. if speedRegex2 := regexp.MustCompile(`Speed\s*:\s*(\d+)(?:,|$)`); speedRegex2.MatchString(line) && currentInterface.Speed == "" {
  201. matches := speedRegex2.FindStringSubmatch(line)
  202. currentInterface.Speed = matches[1] + "Mbps"
  203. }
  204. // 匹配双工: Duplex: Full 或 Duplex: Half
  205. if duplexRegex := regexp.MustCompile(`Duplex\s*:\s*(\S+)`); duplexRegex.MatchString(line) && currentInterface.Duplex == "" {
  206. currentInterface.Duplex = duplexRegex.FindStringSubmatch(line)[1]
  207. }
  208. // 匹配VLAN信息: Port link-type: Access / Trunk / Hybrid
  209. if linkTypeRegex := regexp.MustCompile(`Port link-type\s*:\s*(\S+)`); linkTypeRegex.MatchString(line) {
  210. linkType := linkTypeRegex.FindStringSubmatch(line)[1]
  211. currentInterface.VLAN = linkType
  212. }
  213. // 匹配Untagged VLAN: Untagged VLAN ID: 10
  214. if untaggedRegex := regexp.MustCompile(`Untagged VLAN ID\s*:\s*(\S+)`); untaggedRegex.MatchString(line) {
  215. untagged := untaggedRegex.FindStringSubmatch(line)[1]
  216. if untagged != "none" && untagged != "--" {
  217. if currentInterface.VLAN != "" {
  218. currentInterface.VLAN = currentInterface.VLAN + " " + untagged
  219. } else {
  220. currentInterface.VLAN = untagged
  221. }
  222. }
  223. }
  224. // 匹配Tagged VLAN: Tagged VLAN ID: 100-200
  225. if taggedRegex := regexp.MustCompile(`Tagged VLAN ID\s*:\s*(\S+)`); taggedRegex.MatchString(line) {
  226. tagged := taggedRegex.FindStringSubmatch(line)[1]
  227. if tagged != "none" && tagged != "--" {
  228. if currentInterface.VLAN != "" {
  229. currentInterface.VLAN = currentInterface.VLAN + " (T:" + tagged + ")"
  230. } else {
  231. currentInterface.VLAN = "T:" + tagged
  232. }
  233. }
  234. }
  235. }
  236. }
  237. if currentInterface != nil {
  238. interfaces = append(interfaces, *currentInterface)
  239. }
  240. return interfaces
  241. }
  242. // cidrToMask CIDR转换为子网掩码
  243. func cidrToMask(cidr string) string {
  244. var mask int
  245. fmt.Sscanf(cidr, "%d", &mask)
  246. s := uint32(0)
  247. for i := 0; i < mask; i++ {
  248. s |= (1 << (31 - uint(i)))
  249. }
  250. return fmt.Sprintf("%d.%d.%d.%d",
  251. (s>>24)&0xFF,
  252. (s>>16)&0xFF,
  253. (s>>8)&0xFF,
  254. s&0xFF)
  255. }
  256. // parseARPTable 解析ARP表,建立MAC到IP的映射
  257. func (p *H3CParser) parseARPTable(output string) map[string]string {
  258. macToIP := make(map[string]string)
  259. lines := strings.Split(output, "\n")
  260. for _, line := range lines {
  261. // 跳过空行和标题行
  262. if strings.TrimSpace(line) == "" ||
  263. strings.Contains(line, "Type:") ||
  264. strings.Contains(line, "------") ||
  265. strings.Contains(line, "IP address") {
  266. continue
  267. }
  268. // ARP表格式: IP address MAC address VLAN/VSI name Interface Aging Type
  269. // 例: 172.16.8.10 743a-2047-38e0 8 GE1/0/47 1163 D
  270. fields := strings.Fields(line)
  271. if len(fields) >= 2 {
  272. ip := fields[0]
  273. mac := strings.ToLower(fields[1])
  274. // 标准化MAC地址(统一为aabb-ccdd-eeff格式)
  275. mac = normalizeMACFormat(mac)
  276. // 验证是有效的IP和MAC
  277. if isValidIP(ip) && isValidMAC(mac) {
  278. macToIP[mac] = ip
  279. fmt.Printf("[H3C ARP DEBUG] Added MAC->IP: %s -> %s\n", mac, ip)
  280. }
  281. }
  282. }
  283. return macToIP
  284. }
  285. // normalizeMACFormat 标准化MAC地址格式(统一为aabb-ccdd-eeff)
  286. func normalizeMACFormat(mac string) string {
  287. // 去除所有分隔符
  288. clean := ""
  289. for _, c := range mac {
  290. if c != '-' && c != ':' && c != '.' {
  291. clean += string(c)
  292. }
  293. }
  294. // 重新格式化为aabb-ccdd-eeff
  295. if len(clean) == 12 {
  296. return fmt.Sprintf("%s-%s-%s", clean[0:4], clean[4:8], clean[8:12])
  297. }
  298. return mac
  299. }
  300. // expandH3CInterfaceName 将H3C brief中的缩写接口名展开为全名
  301. func expandH3CInterfaceName(shortName string) string {
  302. // GE1/0/10 -> GigabitEthernet1/0/10
  303. if strings.HasPrefix(shortName, "GE") {
  304. return "GigabitEthernet" + shortName[2:]
  305. }
  306. // TGE1/0/52 -> Ten-GigabitEthernet1/0/52
  307. if strings.HasPrefix(shortName, "TGE") {
  308. return "Ten-GigabitEthernet" + shortName[3:]
  309. }
  310. // FGE1/0/53 -> FortyGigE1/0/53
  311. if strings.HasPrefix(shortName, "FGE") {
  312. return "FortyGigE" + shortName[3:]
  313. }
  314. // HGE1/0/1 -> HundredGigE1/0/1
  315. if strings.HasPrefix(shortName, "HGE") {
  316. return "HundredGigE" + shortName[3:]
  317. }
  318. // XGE1/0/1 -> Ten-GigabitEthernet1/0/1 (有些型号用XGE)
  319. if strings.HasPrefix(shortName, "XGE") {
  320. return "Ten-GigabitEthernet" + shortName[3:]
  321. }
  322. // Vlan8 -> Vlan-interface8
  323. if strings.HasPrefix(shortName, "Vlan") {
  324. return "Vlan-interface" + shortName[4:]
  325. }
  326. return shortName
  327. }
  328. func (p *H3CParser) parseInterfaceBrief(output string) map[string]models.Interface {
  329. interfaces := make(map[string]models.Interface)
  330. lines := strings.Split(output, "\n")
  331. // 状态机: 0=未开始, 1=route mode数据, 2=bridge mode数据
  332. mode := 0
  333. for _, line := range lines {
  334. trimmed := strings.TrimSpace(line)
  335. if trimmed == "" || strings.HasPrefix(trimmed, "---") {
  336. continue
  337. }
  338. // 检测 route mode section
  339. if strings.Contains(trimmed, "route mode") {
  340. mode = 1
  341. continue
  342. }
  343. // 检测 bridge mode section
  344. if strings.Contains(trimmed, "bridge mode") {
  345. mode = 2
  346. continue
  347. }
  348. // 跳过表头行
  349. fields := strings.Fields(trimmed)
  350. if len(fields) > 0 && (fields[0] == "Interface" || fields[0] == "interface") {
  351. continue
  352. }
  353. // Route mode: Interface Link Protocol Primary IP Description
  354. if mode == 1 && len(fields) >= 3 {
  355. shortName := fields[0]
  356. fullName := expandH3CInterfaceName(shortName)
  357. ip := ""
  358. if len(fields) >= 4 && fields[3] != "--" {
  359. ip = fields[3]
  360. }
  361. iface := models.Interface{
  362. Name: fullName,
  363. Status: strings.ToLower(fields[1]),
  364. IP: ip,
  365. }
  366. interfaces[fullName] = iface
  367. }
  368. // Bridge mode: Interface Link Speed Duplex Type PVID Description
  369. if mode == 2 && len(fields) >= 5 {
  370. shortName := fields[0]
  371. fullName := expandH3CInterfaceName(shortName)
  372. speed := fields[2]
  373. if speed == "auto" {
  374. speed = "auto"
  375. }
  376. duplex := fields[3]
  377. switch duplex {
  378. case "A":
  379. duplex = "auto"
  380. case "F(a)", "F":
  381. duplex = "full"
  382. case "H":
  383. duplex = "half"
  384. }
  385. // VLAN: Type(A/T/H) + PVID
  386. vlan := ""
  387. if len(fields) >= 5 {
  388. linkType := fields[4]
  389. switch linkType {
  390. case "A":
  391. vlan = "Access"
  392. case "T":
  393. vlan = "Trunk"
  394. case "H":
  395. vlan = "Hybrid"
  396. }
  397. }
  398. if len(fields) >= 6 {
  399. vlan = vlan + " " + fields[5]
  400. }
  401. iface := models.Interface{
  402. Name: fullName,
  403. Status: strings.ToLower(fields[1]),
  404. Speed: speed,
  405. Duplex: duplex,
  406. VLAN: strings.TrimSpace(vlan),
  407. }
  408. // 如果 route mode 已有此接口(通常不会有), 保留 IP 信息
  409. if existing, ok := interfaces[fullName]; ok {
  410. if existing.IP != "" {
  411. iface.IP = existing.IP
  412. }
  413. }
  414. interfaces[fullName] = iface
  415. }
  416. }
  417. return interfaces
  418. }
  419. func (p *H3CParser) parseNeighbors(output string, arpTable map[string]string) []models.Neighbor {
  420. var neighbors []models.Neighbor
  421. scanner := bufio.NewScanner(strings.NewReader(output))
  422. var currentNeighbor *models.Neighbor
  423. var localInterface string
  424. for scanner.Scan() {
  425. line := scanner.Text()
  426. // 匹配本地接口行: LLDP neighbor-information of port 20[GigabitEthernet1/0/20]:
  427. if portRegex := regexp.MustCompile(`LLDP neighbor-information of port \d+\[([^\]]+)\]:`); portRegex.MatchString(line) {
  428. // 保存前一个邻居
  429. if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
  430. neighbors = append(neighbors, *currentNeighbor)
  431. }
  432. matches := portRegex.FindStringSubmatch(line)
  433. localInterface = matches[1]
  434. currentNeighbor = &models.Neighbor{
  435. LocalInterface: localInterface,
  436. Protocol: "LLDP",
  437. }
  438. continue
  439. }
  440. if currentNeighbor != nil {
  441. // 提取 ChassisID (MAC地址) - 非verbose格式
  442. if strings.Contains(line, "ChassisID/subtype") {
  443. parts := strings.SplitN(line, ":", 2)
  444. if len(parts) == 2 {
  445. value := strings.TrimSpace(parts[1])
  446. // 格式: a4bb-6de2-62cd/MAC address
  447. if macParts := strings.Split(value, "/"); len(macParts) > 0 {
  448. mac := strings.TrimSpace(strings.ToLower(macParts[0]))
  449. // 标准化MAC地址格式
  450. mac = normalizeMACFormat(mac)
  451. // 保存MAC地址
  452. currentNeighbor.RemoteMAC = mac
  453. fmt.Printf(" Parsed neighbor MAC: %s (from line: %s)\n", mac, line)
  454. // 使用MAC地址作为设备标识
  455. currentNeighbor.RemoteDevice = mac
  456. // 通过ARP表查找IP地址
  457. if arpTable != nil {
  458. if ip, found := arpTable[mac]; found {
  459. currentNeighbor.RemoteIP = ip
  460. fmt.Printf(" ✓ Found IP for MAC %s: %s\n", mac, ip)
  461. } else {
  462. fmt.Printf(" ✗ No IP found for MAC %s in ARP table\n", mac)
  463. }
  464. }
  465. } else {
  466. fmt.Printf(" WARNING: Could not parse MAC from ChassisID line: %s\n", line)
  467. }
  468. } else {
  469. fmt.Printf(" WARNING: ChassisID line has no colon: %s\n", line)
  470. }
  471. }
  472. // 提取 PortID (远程接口) - 非verbose格式
  473. if strings.Contains(line, "PortID/subtype") {
  474. parts := strings.SplitN(line, ":", 2)
  475. if len(parts) == 2 {
  476. value := strings.TrimSpace(parts[1])
  477. // 格式: GigabitEthernet0/0/1/Interface name
  478. if portParts := strings.Split(value, "/"); len(portParts) >= 3 {
  479. // 取前3部分作为接口名: GigabitEthernet0/0/1
  480. currentNeighbor.RemoteInterface = strings.Join(portParts[:3], "/")
  481. fmt.Printf(" Parsed neighbor interface: %s\n", currentNeighbor.RemoteInterface)
  482. }
  483. }
  484. }
  485. }
  486. }
  487. // 添加最后一个邻居
  488. if currentNeighbor != nil && currentNeighbor.RemoteInterface != "" {
  489. neighbors = append(neighbors, *currentNeighbor)
  490. }
  491. return neighbors
  492. }
  493. // isValidIP 简单验证IP地址
  494. func isValidIP(ip string) bool {
  495. parts := strings.Split(ip, ".")
  496. if len(parts) != 4 {
  497. return false
  498. }
  499. for _, part := range parts {
  500. if len(part) == 0 || len(part) > 3 {
  501. return false
  502. }
  503. for _, c := range part {
  504. if c < '0' || c > '9' {
  505. return false
  506. }
  507. }
  508. }
  509. return true
  510. }
  511. // isValidMAC 简单验证MAC地址
  512. func isValidMAC(mac string) bool {
  513. parts := strings.Split(mac, "-")
  514. if len(parts) != 3 && len(parts) != 6 {
  515. return false
  516. }
  517. for _, part := range parts {
  518. if len(part) != 2 {
  519. return false
  520. }
  521. for _, c := range part {
  522. if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
  523. return false
  524. }
  525. }
  526. }
  527. return true
  528. }