179 lines
5.0 KiB
Go
179 lines
5.0 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func ValidateCustomRouteRules(rules []CustomRouteRule, kernelType string, availableTags map[string]struct{}) error {
|
|
if len(rules) == 0 {
|
|
return nil
|
|
}
|
|
|
|
kernelType = strings.ToLower(strings.TrimSpace(kernelType))
|
|
matcherSupport := map[string]struct{}{}
|
|
actionSupport := map[string]struct{}{}
|
|
if kernelType != "" {
|
|
support, ok := RouteSupportMatrix()[kernelType]
|
|
if !ok {
|
|
return fmt.Errorf("unsupported kernel type %q", kernelType)
|
|
}
|
|
for _, matcher := range support.Matchers {
|
|
matcherSupport[strings.ToLower(matcher)] = struct{}{}
|
|
}
|
|
for _, action := range support.Actions {
|
|
actionSupport[strings.ToLower(action)] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for i, rule := range rules {
|
|
if rule.Disabled {
|
|
continue
|
|
}
|
|
if !hasRouteMatch(rule.Match) {
|
|
return fmt.Errorf("custom_route_rules[%d].match is required", i)
|
|
}
|
|
if err := ensureRouteMatcherSupported(i, kernelType, matcherSupport, rule.Match); err != nil {
|
|
return err
|
|
}
|
|
if err := validatePortSpecs(rule.Match.Ports, fmt.Sprintf("custom_route_rules[%d].match.ports", i)); err != nil {
|
|
return err
|
|
}
|
|
if err := validatePortSpecs(rule.Match.SourcePorts, fmt.Sprintf("custom_route_rules[%d].match.source_ports", i)); err != nil {
|
|
return err
|
|
}
|
|
if err := validateNetworks(rule.Match.Networks, fmt.Sprintf("custom_route_rules[%d].match.networks", i)); err != nil {
|
|
return err
|
|
}
|
|
|
|
actionType := strings.ToLower(strings.TrimSpace(rule.Action.Type))
|
|
if actionType == "" {
|
|
return fmt.Errorf("custom_route_rules[%d].action.type is required", i)
|
|
}
|
|
if kernelType != "" {
|
|
if _, ok := actionSupport[actionType]; !ok {
|
|
return fmt.Errorf("custom_route_rules[%d].action.type %q is not supported by kernel %q", i, rule.Action.Type, kernelType)
|
|
}
|
|
}
|
|
target := strings.TrimSpace(rule.Action.Target)
|
|
switch actionType {
|
|
case "route":
|
|
if target == "" {
|
|
return fmt.Errorf("custom_route_rules[%d].action.target is required when action.type is route", i)
|
|
}
|
|
if availableTags != nil {
|
|
if _, ok := availableTags[strings.ToLower(target)]; !ok {
|
|
return fmt.Errorf("custom_route_rules[%d].action.target references unknown outbound %q", i, rule.Action.Target)
|
|
}
|
|
}
|
|
case "direct", "block":
|
|
if target != "" {
|
|
return fmt.Errorf("custom_route_rules[%d].action.target is only allowed when action.type is route", i)
|
|
}
|
|
default:
|
|
return fmt.Errorf("custom_route_rules[%d].action.type %q is not supported", i, rule.Action.Type)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func hasRouteMatch(match RouteMatch) bool {
|
|
for _, values := range [][]string{
|
|
match.Domains,
|
|
match.DomainSuffixes,
|
|
match.IPCIDRs,
|
|
match.Ports,
|
|
match.Networks,
|
|
match.SourceCIDRs,
|
|
match.SourcePorts,
|
|
} {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ensureRouteMatcherSupported(index int, kernelType string, matcherSupport map[string]struct{}, match RouteMatch) error {
|
|
if kernelType == "" {
|
|
return nil
|
|
}
|
|
checks := map[string][]string{
|
|
"domains": match.Domains,
|
|
"domain_suffixes": match.DomainSuffixes,
|
|
"ip_cidrs": match.IPCIDRs,
|
|
"ports": match.Ports,
|
|
"networks": match.Networks,
|
|
"source_cidrs": match.SourceCIDRs,
|
|
"source_ports": match.SourcePorts,
|
|
}
|
|
for matcher, values := range checks {
|
|
if !hasAnyNonBlank(values) {
|
|
continue
|
|
}
|
|
if _, ok := matcherSupport[matcher]; !ok {
|
|
return fmt.Errorf("custom_route_rules[%d].match.%s is not supported by kernel %q", index, matcher, kernelType)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasAnyNonBlank(values []string) bool {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func validateNetworks(values []string, field string) error {
|
|
for _, value := range values {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if value != "tcp" && value != "udp" {
|
|
return fmt.Errorf("%s contains unsupported network %q", field, value)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validatePortSpecs(values []string, field string) error {
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(value, "-") || strings.Contains(value, ":") {
|
|
delimiter := "-"
|
|
if strings.Contains(value, ":") {
|
|
delimiter = ":"
|
|
}
|
|
parts := strings.Split(value, delimiter)
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("%s contains invalid port range %q", field, value)
|
|
}
|
|
start, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
|
if err != nil || start < 1 || start > 65535 {
|
|
return fmt.Errorf("%s contains invalid port range %q", field, value)
|
|
}
|
|
end, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
|
if err != nil || end < 1 || end > 65535 || end < start {
|
|
return fmt.Errorf("%s contains invalid port range %q", field, value)
|
|
}
|
|
continue
|
|
}
|
|
port, err := strconv.Atoi(value)
|
|
if err != nil || port < 1 || port > 65535 {
|
|
return fmt.Errorf("%s contains invalid port %q", field, value)
|
|
}
|
|
}
|
|
return nil
|
|
}
|