package app import ( "fmt" "net/netip" "strings" ) func normalizeTransportIntent(in TransportPolicyIntent) (TransportPolicyIntent, string, netip.Prefix, error) { out := TransportPolicyIntent{ SelectorType: strings.ToLower(strings.TrimSpace(in.SelectorType)), SelectorValue: strings.TrimSpace(in.SelectorValue), ClientID: sanitizeID(in.ClientID), Priority: in.Priority, Mode: strings.ToLower(strings.TrimSpace(in.Mode)), } if out.ClientID == "" { return out, "", netip.Prefix{}, fmt.Errorf("missing client_id") } if out.Priority <= 0 { out.Priority = 100 } if out.Mode == "" { out.Mode = "strict" } if out.Mode != "strict" && out.Mode != "fallback" { return out, "", netip.Prefix{}, fmt.Errorf("mode must be strict|fallback") } switch out.SelectorType { case "domain": out.SelectorValue = strings.ToLower(out.SelectorValue) case "cidr": pfx, err := parseIntentCIDR(out.SelectorValue) if err != nil { return out, "", netip.Prefix{}, err } out.SelectorValue = pfx.String() return out, out.SelectorType + ":" + out.SelectorValue, pfx, nil case "app_key", "cgroup", "uid": // keep as-is default: return out, "", netip.Prefix{}, fmt.Errorf("selector_type must be domain|cidr|app_key|cgroup|uid") } if out.SelectorValue == "" { return out, "", netip.Prefix{}, fmt.Errorf("selector_value is empty") } return out, out.SelectorType + ":" + out.SelectorValue, netip.Prefix{}, nil } func parseIntentCIDR(raw string) (netip.Prefix, error) { v := strings.TrimSpace(raw) if v == "" { return netip.Prefix{}, fmt.Errorf("selector_value is empty") } if strings.Contains(v, "/") { pfx, err := netip.ParsePrefix(v) if err != nil || !pfx.Addr().Is4() { return netip.Prefix{}, fmt.Errorf("invalid cidr: %q", raw) } return pfx.Masked(), nil } ip, err := netip.ParseAddr(v) if err != nil || !ip.Is4() { return netip.Prefix{}, fmt.Errorf("invalid cidr: %q", raw) } return netip.PrefixFrom(ip, 32), nil } func prefixOverlap(a, b netip.Prefix) bool { if !a.IsValid() || !b.IsValid() { return false } return a.Contains(b.Addr()) || b.Contains(a.Addr()) }