builder.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package topology
  2. import (
  3. "fmt"
  4. "network-topology-discovery/pkg/models"
  5. "strings"
  6. )
  7. // Builder 拓扑构建器
  8. type Builder struct {
  9. devices []models.Device
  10. }
  11. // NewBuilder 创建拓扑构建器
  12. func NewBuilder() *Builder {
  13. return &Builder{}
  14. }
  15. // AddDevice 添加设备(按IP去重)
  16. func (b *Builder) AddDevice(device models.Device) {
  17. for i, existing := range b.devices {
  18. if existing.IP == device.IP {
  19. b.devices[i] = device // 覆盖更新
  20. return
  21. }
  22. }
  23. b.devices = append(b.devices, device)
  24. }
  25. // AddDevices 批量添加设备
  26. func (b *Builder) AddDevices(devices []models.Device) {
  27. b.devices = append(b.devices, devices...)
  28. }
  29. // RemoveDevice 根据ID或IP移除设备
  30. func (b *Builder) RemoveDevice(id string) {
  31. for i, d := range b.devices {
  32. if d.ID == id || d.IP == id {
  33. b.devices = append(b.devices[:i], b.devices[i+1:]...)
  34. return
  35. }
  36. }
  37. }
  38. // Build 构建拓扑图
  39. func (b *Builder) Build() models.TopologyGraph {
  40. graph := models.TopologyGraph{
  41. Nodes: make([]models.TopologyNode, 0),
  42. Edges: make([]models.TopologyEdge, 0),
  43. }
  44. // 构建节点
  45. nodeMap := make(map[string]models.TopologyNode)
  46. for _, device := range b.devices {
  47. // 优先使用主机名,如果没有则使用IP作为显示名
  48. hostname := device.Hostname
  49. if hostname == "" {
  50. hostname = device.IP
  51. }
  52. node := models.TopologyNode{
  53. ID: device.IP,
  54. IP: device.IP,
  55. Hostname: hostname,
  56. Type: string(device.Type),
  57. Icon: getDeviceIcon(device.Type),
  58. }
  59. nodeMap[device.IP] = node
  60. graph.Nodes = append(graph.Nodes, node)
  61. }
  62. fmt.Printf("\n========== TOPOLOGY BUILD ===========\n")
  63. fmt.Printf("Total devices: %d\n", len(b.devices))
  64. fmt.Printf("Total nodes created: %d\n", len(graph.Nodes))
  65. for _, node := range graph.Nodes {
  66. fmt.Printf(" Node: %s (%s) - Type: %s\n", node.ID, node.Hostname, node.Type)
  67. }
  68. // 构建边(基于邻居信息)
  69. edgeMap := make(map[string]bool) // 用于去重
  70. // 打印所有设备的MAC地址,用于调试
  71. fmt.Println("\n========== MAC Address Database ==========")
  72. for _, d := range b.devices {
  73. if len(d.MACAddresses) > 0 {
  74. fmt.Printf("Device %s (%s): %d MAC addresses\n", d.Hostname, d.IP, len(d.MACAddresses))
  75. for i, mac := range d.MACAddresses {
  76. if i < 5 { // 只打印前5个
  77. fmt.Printf(" MAC[%d]: %s\n", i, mac)
  78. }
  79. }
  80. if len(d.MACAddresses) > 5 {
  81. fmt.Printf(" ... and %d more\n", len(d.MACAddresses)-5)
  82. }
  83. }
  84. }
  85. fmt.Println("==========================================\n")
  86. for _, device := range b.devices {
  87. fmt.Printf("Building edges for device %s: %d neighbors, %d MAC addresses\n",
  88. device.IP, len(device.Neighbors), len(device.MACAddresses))
  89. for _, neighbor := range device.Neighbors {
  90. // 过滤虚拟机邻居:识别MAC地址前缀,只保留真实网络设备
  91. if neighbor.RemoteMAC != "" {
  92. macPrefix := getMACPrefix(neighbor.RemoteMAC)
  93. if isVirtualMAC(macPrefix) {
  94. fmt.Printf(" ⊘ Skipping virtual machine neighbor on %s (MAC: %s, prefix: %s)\n",
  95. neighbor.LocalInterface, neighbor.RemoteMAC, macPrefix)
  96. continue
  97. }
  98. }
  99. fmt.Printf(" Processing neighbor on %s: RemoteMAC=%s, RemoteDevice=%s, RemoteIP=%s\n",
  100. neighbor.LocalInterface, neighbor.RemoteMAC, neighbor.RemoteDevice, neighbor.RemoteIP)
  101. // 尝试匹配邻居设备 - 多重策略
  102. targetIP := neighbor.RemoteIP
  103. matchMethod := "IP"
  104. // 策略1: 如果已有IP,直接使用
  105. if targetIP != "" {
  106. fmt.Printf(" ✓ Using direct IP: %s\n", targetIP)
  107. } else if neighbor.RemoteDevice != "" {
  108. // 策略2: 尝试通过主机名匹配
  109. fmt.Printf(" Trying hostname match: %s\n", neighbor.RemoteDevice)
  110. for _, d := range b.devices {
  111. if d.Hostname == neighbor.RemoteDevice {
  112. targetIP = d.IP
  113. matchMethod = "hostname"
  114. fmt.Printf(" ✓ Matched by hostname: %s -> %s\n", d.Hostname, d.IP)
  115. break
  116. }
  117. }
  118. }
  119. // 策略3: 通过MAC地址匹配
  120. if targetIP == "" && neighbor.RemoteMAC != "" {
  121. fmt.Printf(" Trying MAC match: %s\n", neighbor.RemoteMAC)
  122. for _, d := range b.devices {
  123. for _, mac := range d.MACAddresses {
  124. // 标准化MAC地址进行比较(去除分隔符,转小写)
  125. normalizedNeighborMAC := normalizeMAC(neighbor.RemoteMAC)
  126. normalizedDeviceMAC := normalizeMAC(mac)
  127. if normalizedNeighborMAC == normalizedDeviceMAC {
  128. targetIP = d.IP
  129. matchMethod = "MAC"
  130. fmt.Printf(" ✓ Matched by MAC: %s (device) == %s (neighbor) -> %s\n",
  131. mac, neighbor.RemoteMAC, d.IP)
  132. break
  133. }
  134. }
  135. if targetIP != "" {
  136. break
  137. }
  138. }
  139. }
  140. // 策略3b: MAC前缀匹配(使用12字符前缀 - 更精确)
  141. // 同品牌设备虽然4字符前缀相同,但12字符前缀通常不同
  142. if targetIP == "" && neighbor.RemoteMAC != "" {
  143. // 使用12字符前缀(例如:642f-c7e0-03)
  144. macPrefix := ""
  145. if len(neighbor.RemoteMAC) >= 12 {
  146. macPrefix = neighbor.RemoteMAC[:12]
  147. } else {
  148. macPrefix = neighbor.RemoteMAC
  149. }
  150. fmt.Printf(" Trying MAC prefix match (12 chars): %s (prefix: %s)\n", neighbor.RemoteMAC, macPrefix)
  151. var matchedDevices []string
  152. for _, d := range b.devices {
  153. if d.IP == device.IP {
  154. continue // 跳过自己
  155. }
  156. // 检查设备d的MAC地址是否有匹配的前缀
  157. matchingMACs := 0
  158. for _, mac := range d.MACAddresses {
  159. if len(mac) >= 12 && mac[:12] == macPrefix {
  160. matchingMACs++
  161. }
  162. }
  163. if matchingMACs >= 3 {
  164. matchedDevices = append(matchedDevices, d.IP)
  165. }
  166. }
  167. // 只有唯一匹配时才建立连接
  168. if len(matchedDevices) == 1 {
  169. targetIP = matchedDevices[0]
  170. matchMethod = fmt.Sprintf("MAC-prefix-12(%s)", macPrefix)
  171. fmt.Printf(" ✓ Matched by MAC prefix-12 (unique): %s -> %s\n", neighbor.RemoteMAC, targetIP)
  172. } else if len(matchedDevices) > 1 {
  173. fmt.Printf(" ✗ Ambiguous MAC prefix-12 match: %s matched %v\n", macPrefix, matchedDevices)
  174. } else {
  175. fmt.Printf(" ✗ No device matches MAC prefix-12 %s\n", macPrefix)
  176. }
  177. }
  178. // 策略3c: LLDP接口匹配(改进 - 单向但要求唯一性)
  179. // 如果设备A的LLDP信息显示它连接到接口Y
  180. // 而设备B有接口Y,则A连接到B
  181. // 但如果有多个设备都有接口Y,则不匹配(避免歧义)
  182. if targetIP == "" && neighbor.RemoteMAC != "" && neighbor.RemoteInterface != "" {
  183. fmt.Printf(" Trying LLDP interface match: looking for device with interface %s\n", neighbor.RemoteInterface)
  184. var matchedDevices []string
  185. for _, d := range b.devices {
  186. if d.IP == device.IP {
  187. continue // 跳过自己
  188. }
  189. // 检查设备d是否有这个接口
  190. for _, iface := range d.Interfaces {
  191. if iface.Name == neighbor.RemoteInterface {
  192. matchedDevices = append(matchedDevices, d.IP)
  193. break
  194. }
  195. }
  196. }
  197. // 只有唯一匹配时才建立连接
  198. if len(matchedDevices) == 1 {
  199. targetIP = matchedDevices[0]
  200. matchMethod = fmt.Sprintf("LLDP-interface(%s->%s)", neighbor.LocalInterface, neighbor.RemoteInterface)
  201. fmt.Printf(" ✓ Matched by LLDP interface (unique): %s %s -> %s %s\n",
  202. device.IP, neighbor.LocalInterface, targetIP, neighbor.RemoteInterface)
  203. } else if len(matchedDevices) > 1 {
  204. fmt.Printf(" ✗ Ambiguous: multiple devices have interface %s: %v\n",
  205. neighbor.RemoteInterface, matchedDevices)
  206. } else {
  207. fmt.Printf(" ✗ No device found with interface %s\n", neighbor.RemoteInterface)
  208. }
  209. }
  210. // 策略4: 通过本地接口IP网段匹配(新增)
  211. // 注意:此策略容易产生误匹配,仅在必要时使用
  212. if targetIP == "" {
  213. // 查找本地接口的IP地址
  214. localInterfaceIP := ""
  215. for _, iface := range device.Interfaces {
  216. if iface.Name == neighbor.LocalInterface && iface.IP != "" {
  217. localInterfaceIP = iface.IP
  218. break
  219. }
  220. }
  221. if localInterfaceIP != "" {
  222. fmt.Printf(" ⚠ Trying subnet match (risky): local interface %s has IP %s\n",
  223. neighbor.LocalInterface, localInterfaceIP)
  224. // 计算本地接口的网段
  225. localSubnet := getSubnet(localInterfaceIP)
  226. // 查找其他设备中是否有接口在同一网段
  227. for _, d := range b.devices {
  228. if d.IP == device.IP {
  229. continue // 跳过自己
  230. }
  231. // 检查设备的管理IP是否在同一网段
  232. if getSubnet(d.IP) == localSubnet {
  233. targetIP = d.IP
  234. matchMethod = "subnet(management IP)"
  235. fmt.Printf(" ⚠ Matched by subnet (risky): %s in %s\n", d.IP, localSubnet)
  236. break
  237. }
  238. // 检查设备的所有接口是否在同一网段
  239. for _, iface := range d.Interfaces {
  240. if iface.IP != "" && getSubnet(iface.IP) == localSubnet {
  241. targetIP = d.IP
  242. matchMethod = fmt.Sprintf("subnet(interface %s)", iface.Name)
  243. fmt.Printf(" ⚠ Matched by subnet (risky): %s (%s) in %s\n",
  244. d.IP, iface.Name, localSubnet)
  245. break
  246. }
  247. }
  248. if targetIP != "" {
  249. break
  250. }
  251. }
  252. } else {
  253. // 策略4b: 本地接口没有IP,尝试使用设备管理IP进行子网匹配(高风险策略 - 已禁用)
  254. // 此策略容易产生错误连接,暂时禁用
  255. fmt.Printf(" ✗ Skipping subnet match with management IP (disabled - too risky)\n")
  256. }
  257. }
  258. // 策略5: 通过接口描述匹配(新增)
  259. if targetIP == "" {
  260. // 查找本地接口的描述,看是否包含其他设备的IP或主机名
  261. for _, iface := range device.Interfaces {
  262. if iface.Name == neighbor.LocalInterface && iface.Description != "" {
  263. desc := strings.ToLower(iface.Description)
  264. fmt.Printf(" Trying description match: %s\n", iface.Description)
  265. for _, d := range b.devices {
  266. if d.IP == device.IP {
  267. continue
  268. }
  269. // 检查描述中是否包含目标设备的IP或主机名
  270. if strings.Contains(desc, strings.ToLower(d.IP)) ||
  271. (d.Hostname != "" && strings.Contains(desc, strings.ToLower(d.Hostname))) {
  272. targetIP = d.IP
  273. matchMethod = "description"
  274. fmt.Printf(" ✓ Matched by description: %s\n", iface.Description)
  275. break
  276. }
  277. }
  278. }
  279. if targetIP != "" {
  280. break
  281. }
  282. }
  283. }
  284. // 策略6: 通过邻居端口号反向匹配(已禁用 - 容易产生错误连接)
  285. // 暂时禁用,因为端口号匹配不够精确,可能产生环路
  286. if targetIP == "" {
  287. fmt.Printf(" ✗ Could not match neighbor on %s (MAC: %s)\n", neighbor.LocalInterface, neighbor.RemoteMAC)
  288. continue
  289. }
  290. fmt.Printf(" ✓ Creating edge: %s (%s) -> %s via %s, matched by %s\n",
  291. device.IP, neighbor.LocalInterface, targetIP, neighbor.RemoteInterface, matchMethod)
  292. // 避免重复边:同一对设备之间只保留一条边(不区分接口)
  293. // 如果这对设备已经有边了,跳过
  294. hasExistingEdge := false
  295. for _, existingEdge := range graph.Edges {
  296. if (existingEdge.Source == device.IP && existingEdge.Target == targetIP) ||
  297. (existingEdge.Source == targetIP && existingEdge.Target == device.IP) {
  298. fmt.Printf(" ⚠ Skipping: edge between %s and %s already exists\n", device.IP, targetIP)
  299. hasExistingEdge = true
  300. break
  301. }
  302. }
  303. if hasExistingEdge {
  304. continue
  305. }
  306. // 创建唯一的边ID(包含接口信息用于调试)
  307. edgeID := fmt.Sprintf("%s-%s-%s", device.IP, neighbor.LocalInterface, targetIP)
  308. edge := models.TopologyEdge{
  309. ID: edgeID,
  310. Source: device.IP,
  311. Target: targetIP,
  312. SourceInterface: neighbor.LocalInterface,
  313. TargetInterface: neighbor.RemoteInterface,
  314. Protocol: neighbor.Protocol,
  315. }
  316. graph.Edges = append(graph.Edges, edge)
  317. edgeMap[edgeID] = true
  318. }
  319. }
  320. fmt.Printf("\nTotal edges created: %d\n", len(graph.Edges))
  321. for _, edge := range graph.Edges {
  322. fmt.Printf(" Edge: %s (%s) -> %s (%s) [%s]\n",
  323. edge.Source, edge.SourceInterface,
  324. edge.Target, edge.TargetInterface,
  325. edge.Protocol)
  326. }
  327. fmt.Println("========================================\n")
  328. return graph
  329. }
  330. // normalizeMAC 标准化MAC地址(去除分隔符,转小写)
  331. func normalizeMAC(mac string) string {
  332. // 去除所有分隔符(-、:、.)
  333. result := ""
  334. for _, c := range mac {
  335. if c != '-' && c != ':' && c != '.' {
  336. result += string(c)
  337. }
  338. }
  339. // 转小写
  340. return strings.ToLower(result)
  341. }
  342. // getMACPrefix 获取MAC地址前缀(前4个字符)
  343. func getMACPrefix(mac string) string {
  344. normalized := normalizeMAC(mac)
  345. if len(normalized) >= 4 {
  346. return normalized[0:4]
  347. }
  348. return normalized
  349. }
  350. // isVirtualMAC 判断是否是虚拟机MAC地址前缀
  351. func isVirtualMAC(prefix string) bool {
  352. // 根据用户反馈,这些前缀都是虚拟机
  353. userVirtualPrefixes := []string{"9844", "9400", "cc05", "44a1"}
  354. for _, vp := range userVirtualPrefixes {
  355. if prefix == vp {
  356. return true
  357. }
  358. }
  359. // 常见虚拟机/虚拟网卡MAC前缀(VMware、Hyper-V、KVM等)
  360. virtualPrefixes := []string{
  361. "0005", "000c", "0015", "0050", // VMware
  362. "0017", "001d", "0025", "5254", // KVM/QEMU
  363. "0015", "0017", "00ff", // Xen
  364. }
  365. for _, vp := range virtualPrefixes {
  366. if prefix == vp {
  367. return true
  368. }
  369. }
  370. return false
  371. }
  372. // getSubnet 获取IP地址的C类网段(简化版,适用于/24网络)
  373. func getSubnet(ip string) string {
  374. parts := strings.Split(ip, ".")
  375. if len(parts) == 4 {
  376. // 返回前3段作为网段,例如: 172.16.8.0/24 -> "172.16.8"
  377. return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
  378. }
  379. return ip
  380. }
  381. // getDeviceIcon 获取设备图标
  382. func getDeviceIcon(deviceType models.DeviceType) string {
  383. switch deviceType {
  384. case models.DeviceTypeCisco:
  385. return "router"
  386. case models.DeviceTypeHuawei:
  387. return "router"
  388. case models.DeviceTypeH3C:
  389. return "switch"
  390. case models.DeviceTypeASA:
  391. return "firewall"
  392. case models.DeviceTypeLinux:
  393. return "server"
  394. case models.DeviceTypeWindows:
  395. return "server"
  396. default:
  397. return "device"
  398. }
  399. }
  400. // GetDevices 获取所有设备
  401. func (b *Builder) GetDevices() []models.Device {
  402. return b.devices
  403. }
  404. // Clear 清空拓扑
  405. func (b *Builder) Clear() {
  406. b.devices = make([]models.Device, 0)
  407. }