validator.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package validator
  2. import (
  3. "fmt"
  4. "net"
  5. "strings"
  6. "networktool/internal/model"
  7. )
  8. type Service struct{}
  9. func New() *Service { return &Service{} }
  10. func (s *Service) Validate(input model.InterfaceConfig) model.ValidateResponse {
  11. resp := model.ValidateResponse{Valid: false, Warnings: []string{}, Errors: []string{}}
  12. addresses := normalizedAddresses(input)
  13. routes := normalizedRoutes(input)
  14. if input.Interface == "" {
  15. resp.Errors = append(resp.Errors, "目标接口不能为空。")
  16. }
  17. if input.Dhcp4 {
  18. resp.Valid = len(resp.Errors) == 0
  19. return resp
  20. }
  21. if len(addresses) == 0 {
  22. resp.Errors = append(resp.Errors, "至少需要填写一个 IP 地址。")
  23. }
  24. seenAddresses := make(map[string]struct{})
  25. validNetworks := make([]*net.IPNet, 0, len(addresses))
  26. for _, address := range addresses {
  27. ip := net.ParseIP(address.IP)
  28. if ip == nil || ip.To4() == nil {
  29. resp.Errors = append(resp.Errors, fmt.Sprintf("IP 地址格式不正确:%s", address.IP))
  30. continue
  31. }
  32. if address.Prefix < 0 || address.Prefix > 32 {
  33. resp.Errors = append(resp.Errors, fmt.Sprintf("前缀长度不正确:%s/%d", address.IP, address.Prefix))
  34. continue
  35. }
  36. key := fmt.Sprintf("%s/%d", ip.String(), address.Prefix)
  37. if _, ok := seenAddresses[key]; ok {
  38. resp.Errors = append(resp.Errors, fmt.Sprintf("IP 地址重复:%s", key))
  39. continue
  40. }
  41. seenAddresses[key] = struct{}{}
  42. mask := net.CIDRMask(address.Prefix, 32)
  43. validNetworks = append(validNetworks, &net.IPNet{IP: ip.Mask(mask), Mask: mask})
  44. ipv4 := ip.To4()
  45. if ipv4[0] == 169 && ipv4[1] == 254 {
  46. resp.Warnings = append(resp.Warnings, "目标接口使用的是链路本地地址,通常仅适合同链路通信。")
  47. }
  48. }
  49. seenRoutes := make(map[string]struct{})
  50. for _, route := range routes {
  51. to := strings.TrimSpace(route.To)
  52. via := strings.TrimSpace(route.Via)
  53. if to == "" {
  54. resp.Errors = append(resp.Errors, "路由目标不能为空。")
  55. continue
  56. }
  57. if via == "" {
  58. resp.Errors = append(resp.Errors, fmt.Sprintf("路由 %s 的下一跳不能为空。", to))
  59. continue
  60. }
  61. if to != "default" {
  62. ip, ipNet, err := net.ParseCIDR(to)
  63. if err != nil || ip == nil || ip.To4() == nil || ipNet == nil {
  64. resp.Errors = append(resp.Errors, fmt.Sprintf("路由目标格式不正确:%s", to))
  65. }
  66. }
  67. gateway := net.ParseIP(via)
  68. if gateway == nil || gateway.To4() == nil {
  69. resp.Errors = append(resp.Errors, fmt.Sprintf("路由下一跳格式不正确:%s", via))
  70. continue
  71. }
  72. key := to + " via " + gateway.String()
  73. if _, ok := seenRoutes[key]; ok {
  74. resp.Errors = append(resp.Errors, fmt.Sprintf("路由重复:%s", key))
  75. continue
  76. }
  77. seenRoutes[key] = struct{}{}
  78. if len(validNetworks) > 0 && !containsGateway(validNetworks, gateway) {
  79. resp.Errors = append(resp.Errors, fmt.Sprintf("路由下一跳与任一目标接口 IP 都不在同一子网:%s", via))
  80. }
  81. }
  82. for _, dns := range input.DNS {
  83. if dns == "" {
  84. continue
  85. }
  86. parsed := net.ParseIP(dns)
  87. if parsed == nil || parsed.To4() == nil {
  88. resp.Errors = append(resp.Errors, fmt.Sprintf("DNS 格式不正确:%s", dns))
  89. }
  90. }
  91. resp.Valid = len(resp.Errors) == 0
  92. return resp
  93. }
  94. func normalizedAddresses(input model.InterfaceConfig) []model.InterfaceAddressConfig {
  95. if len(input.Addresses) > 0 {
  96. return input.Addresses
  97. }
  98. if strings.TrimSpace(input.IP) == "" {
  99. return nil
  100. }
  101. return []model.InterfaceAddressConfig{{IP: strings.TrimSpace(input.IP), Prefix: input.Prefix}}
  102. }
  103. func normalizedRoutes(input model.InterfaceConfig) []model.InterfaceRouteConfig {
  104. if len(input.Routes) > 0 {
  105. return input.Routes
  106. }
  107. if strings.TrimSpace(input.Gateway) == "" {
  108. return nil
  109. }
  110. return []model.InterfaceRouteConfig{{To: "default", Via: strings.TrimSpace(input.Gateway)}}
  111. }
  112. func containsGateway(networks []*net.IPNet, gateway net.IP) bool {
  113. for _, network := range networks {
  114. if network.Contains(gateway) {
  115. return true
  116. }
  117. }
  118. return false
  119. }