101 lines
3.1 KiB
Go
101 lines
3.1 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cedar2025/xboard-node/internal/config"
|
|
)
|
|
|
|
func ValidateNodeSpec(n *NodeSpec, kcfg config.KernelConfig) error {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
|
|
effectiveKernelType := strings.TrimSpace(kcfg.Type)
|
|
if effectiveKernelType == "" {
|
|
effectiveKernelType = strings.TrimSpace(n.KernelType)
|
|
}
|
|
kernelType, err := normalizeKernelType(effectiveKernelType)
|
|
if err != nil {
|
|
return fmt.Errorf("normalize kernel type: %w", err)
|
|
}
|
|
|
|
additionalOutboundSources, err := collectAdditionalOutboundTagSources(kcfg.CustomConfig, kcfg.CustomOutbound)
|
|
if err != nil {
|
|
return fmt.Errorf("collect additional outbound tags: %w", err)
|
|
}
|
|
if err := validateOutboundTagCollisions(n.CustomOutbounds, additionalOutboundSources); err != nil {
|
|
return fmt.Errorf("validate outbound tags: %w", err)
|
|
}
|
|
additionalTags := additionalTagNames(additionalOutboundSources)
|
|
availableTags := buildAvailableOutboundTags(n.CustomOutbounds, additionalTags)
|
|
if err := ValidateCustomOutboundsForKernel(n.CustomOutbounds, kernelType, additionalTags); err != nil {
|
|
return fmt.Errorf("validate custom outbounds: %w", err)
|
|
}
|
|
if err := ValidateCustomRouteRules(n.CustomRouteRules, kernelType, availableTags); err != nil {
|
|
return fmt.Errorf("validate custom route rules: %w", err)
|
|
}
|
|
if err := validateTransportKernel(n.Network, kernelType); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// singboxUnsupportedTransports lists transport types that sing-box does not support.
|
|
var singboxUnsupportedTransports = map[string]bool{
|
|
"xhttp": true,
|
|
"splithttp": true,
|
|
}
|
|
|
|
func validateTransportKernel(network, kernelType string) error {
|
|
net := strings.ToLower(strings.TrimSpace(network))
|
|
if kernelType == "singbox" && singboxUnsupportedTransports[net] {
|
|
return fmt.Errorf("transport %q is not supported by sing-box kernel; use xray kernel instead", net)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResolveKernelForTransport returns the kernel type required by the given
|
|
// transport. If the configured kernel cannot handle the transport, it returns
|
|
// the kernel that can. Otherwise it returns configuredKernel unchanged.
|
|
// This is used in machine mode to auto-switch kernel per node.
|
|
func ResolveKernelForTransport(network, configuredKernel string) string {
|
|
net := strings.ToLower(strings.TrimSpace(network))
|
|
if configuredKernel == "singbox" && singboxUnsupportedTransports[net] {
|
|
return "xray"
|
|
}
|
|
return configuredKernel
|
|
}
|
|
|
|
func normalizeKernelType(value string) (string, error) {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "singbox", "sing-box":
|
|
return "singbox", nil
|
|
case "xray":
|
|
return "xray", nil
|
|
default:
|
|
return "", fmt.Errorf("unsupported kernel type %q", value)
|
|
}
|
|
}
|
|
|
|
func buildAvailableOutboundTags(structured []OutboundConfig, rawTags []string) map[string]struct{} {
|
|
available := map[string]struct{}{
|
|
"direct": {},
|
|
"block": {},
|
|
}
|
|
for _, outbound := range structured {
|
|
tag := strings.ToLower(strings.TrimSpace(outbound.Tag))
|
|
if tag != "" {
|
|
available[tag] = struct{}{}
|
|
}
|
|
}
|
|
for _, tag := range rawTags {
|
|
tag = strings.ToLower(strings.TrimSpace(tag))
|
|
if tag != "" {
|
|
available[tag] = struct{}{}
|
|
}
|
|
}
|
|
return available
|
|
}
|