package validator import ( "fmt" "net" "strings" "nettool/internal/model" ) type Service struct{} func New() *Service { return &Service{} } func (s *Service) Validate(input model.InterfaceConfig) model.ValidateResponse { resp := model.ValidateResponse{Valid: false, Warnings: []string{}, Errors: []string{}} addresses := normalizedAddresses(input) routes := normalizedRoutes(input) if input.Interface == "" { resp.Errors = append(resp.Errors, "目标接口不能为空。") } for _, dns := range input.DNS { if dns == "" { continue } parsed := net.ParseIP(dns) if parsed == nil || parsed.To4() == nil { resp.Errors = append(resp.Errors, fmt.Sprintf("DNS 格式不正确:%s", dns)) } } if input.Dhcp4 { resp.Valid = len(resp.Errors) == 0 return resp } if len(addresses) == 0 { resp.Errors = append(resp.Errors, "至少需要填写一个 IP 地址。") } seenAddresses := make(map[string]struct{}) validNetworks := make([]*net.IPNet, 0, len(addresses)) for _, address := range addresses { ip := net.ParseIP(address.IP) if ip == nil || ip.To4() == nil { resp.Errors = append(resp.Errors, fmt.Sprintf("IP 地址格式不正确:%s", address.IP)) continue } if address.Prefix < 0 || address.Prefix > 32 { resp.Errors = append(resp.Errors, fmt.Sprintf("前缀长度不正确:%s/%d", address.IP, address.Prefix)) continue } if isNetworkOrBroadcastAddress(ip, address.Prefix) { resp.Errors = append(resp.Errors, fmt.Sprintf("接口 IP 不能填写网段地址或广播地址:%s/%d", address.IP, address.Prefix)) continue } key := fmt.Sprintf("%s/%d", ip.String(), address.Prefix) if _, ok := seenAddresses[key]; ok { resp.Errors = append(resp.Errors, fmt.Sprintf("IP 地址重复:%s", key)) continue } seenAddresses[key] = struct{}{} mask := net.CIDRMask(address.Prefix, 32) validNetworks = append(validNetworks, &net.IPNet{IP: ip.Mask(mask), Mask: mask}) } seenRoutes := make(map[string]struct{}) defaultRouteCount := 0 for _, route := range routes { to := strings.TrimSpace(route.To) via := strings.TrimSpace(route.Via) if to == "" { resp.Errors = append(resp.Errors, "路由目标不能为空。") continue } if via == "" { resp.Errors = append(resp.Errors, fmt.Sprintf("路由 %s 的下一跳不能为空。", to)) continue } if to == "default" { defaultRouteCount++ if defaultRouteCount > 1 { resp.Errors = append(resp.Errors, "默认网关只能配置一个。") } } else { ip, ipNet, err := net.ParseCIDR(to) if err != nil || ip == nil || ip.To4() == nil || ipNet == nil { resp.Errors = append(resp.Errors, fmt.Sprintf("路由目标格式不正确:%s", to)) } else { ipv4 := ip.To4() networkIP := ipv4.Mask(ipNet.Mask) prefix, _ := ipNet.Mask.Size() if !ipv4.Equal(networkIP) { resp.Errors = append(resp.Errors, fmt.Sprintf("路由目标必须填写目标网段地址(网络号):当前为 %s,应为 %s/%d", to, networkIP.String(), prefix)) } } } gateway := net.ParseIP(via) if gateway == nil || gateway.To4() == nil { resp.Errors = append(resp.Errors, fmt.Sprintf("路由下一跳格式不正确:%s", via)) continue } key := to + " via " + gateway.String() if _, ok := seenRoutes[key]; ok { resp.Errors = append(resp.Errors, fmt.Sprintf("路由重复:%s", key)) continue } seenRoutes[key] = struct{}{} if len(validNetworks) > 0 && !containsGateway(validNetworks, gateway) { resp.Errors = append(resp.Errors, fmt.Sprintf("路由下一跳与任一目标接口 IP 都不在同一子网:%s", via)) } } resp.Valid = len(resp.Errors) == 0 return resp } func normalizedAddresses(input model.InterfaceConfig) []model.InterfaceAddressConfig { if len(input.Addresses) > 0 { return input.Addresses } if strings.TrimSpace(input.IP) == "" { return nil } return []model.InterfaceAddressConfig{{IP: strings.TrimSpace(input.IP), Prefix: input.Prefix}} } func normalizedRoutes(input model.InterfaceConfig) []model.InterfaceRouteConfig { if len(input.Routes) > 0 { return input.Routes } if strings.TrimSpace(input.Gateway) == "" { return nil } return []model.InterfaceRouteConfig{{To: "default", Via: strings.TrimSpace(input.Gateway)}} } func containsGateway(networks []*net.IPNet, gateway net.IP) bool { for _, network := range networks { if network.Contains(gateway) { return true } } return false } func isNetworkOrBroadcastAddress(ip net.IP, prefix int) bool { if prefix > 30 { return false } ipv4 := ip.To4() if ipv4 == nil { return false } mask := net.CIDRMask(prefix, 32) network := ipv4.Mask(mask) broadcast := make(net.IP, len(network)) for i := range network { broadcast[i] = network[i] | ^mask[i] } return ipv4.Equal(network) || ipv4.Equal(broadcast) }