fix: compile structured custom_routes with match+action into sing-box format
The panel sends structured route rules via custom_routes (not custom_route_rules). Previously these were passed raw to sing-box, causing unmarshal errors when the action field is an object. Now detect and compile them through the same path as custom_route_rules.
This commit is contained in:
@@ -155,8 +155,15 @@ func buildRoutes(panelRoutes []model.RouteRule, customRules []model.CustomRouteR
|
|||||||
rules = append(rules, compileCustomRouteRule(rule)...)
|
rules = append(rules, compileCustomRouteRule(rule)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw custom routes remain the escape hatch, but no longer outrank structured rules.
|
// Raw custom routes: detect structured format (match+action) and compile them,
|
||||||
|
// otherwise pass through as raw sing-box rules.
|
||||||
for _, cr := range custom {
|
for _, cr := range custom {
|
||||||
|
if _, hasMatch := cr["match"]; hasMatch {
|
||||||
|
if parsed, ok := tryParseAsCustomRouteRule(cr); ok {
|
||||||
|
rules = append(rules, compileCustomRouteRule(parsed)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
rules = append(rules, M(cr))
|
rules = append(rules, M(cr))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +316,81 @@ func compileCustomRouteRule(rule model.CustomRouteRule) []M {
|
|||||||
return compiled
|
return compiled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// tryParseAsCustomRouteRule attempts to convert a raw map (from custom_routes)
|
||||||
|
// into a structured CustomRouteRule. Returns false if the map doesn't match
|
||||||
|
// the structured format.
|
||||||
|
func tryParseAsCustomRouteRule(cr map[string]any) (model.CustomRouteRule, bool) {
|
||||||
|
var rule model.CustomRouteRule
|
||||||
|
|
||||||
|
// Parse name
|
||||||
|
if name, ok := cr["name"].(string); ok {
|
||||||
|
rule.Name = name
|
||||||
|
}
|
||||||
|
if disabled, ok := cr["disabled"].(bool); ok {
|
||||||
|
rule.Disabled = disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse match
|
||||||
|
matchRaw, ok := cr["match"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return rule, false
|
||||||
|
}
|
||||||
|
rule.Match = parseRouteMatch(matchRaw)
|
||||||
|
|
||||||
|
// Parse action
|
||||||
|
switch action := cr["action"].(type) {
|
||||||
|
case string:
|
||||||
|
rule.Action = model.RouteAction{Type: action}
|
||||||
|
case map[string]any:
|
||||||
|
if t, ok := action["type"].(string); ok {
|
||||||
|
rule.Action.Type = t
|
||||||
|
}
|
||||||
|
if target, ok := action["target"].(string); ok {
|
||||||
|
rule.Action.Target = target
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return rule, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRouteMatch(m map[string]any) model.RouteMatch {
|
||||||
|
var match model.RouteMatch
|
||||||
|
if v, ok := m["domains"].([]any); ok {
|
||||||
|
match.Domains = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["domain_suffixes"].([]any); ok {
|
||||||
|
match.DomainSuffixes = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["ip_cidrs"].([]any); ok {
|
||||||
|
match.IPCIDRs = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["ports"].([]any); ok {
|
||||||
|
match.Ports = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["networks"].([]any); ok {
|
||||||
|
match.Networks = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["source_cidrs"].([]any); ok {
|
||||||
|
match.SourceCIDRs = toStringSlice(v)
|
||||||
|
}
|
||||||
|
if v, ok := m["source_ports"].([]any); ok {
|
||||||
|
match.SourcePorts = toStringSlice(v)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func toStringSlice(arr []any) []string {
|
||||||
|
result := make([]string, 0, len(arr))
|
||||||
|
for _, v := range arr {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
func singboxOutboundForAction(action model.RouteAction) string {
|
func singboxOutboundForAction(action model.RouteAction) string {
|
||||||
switch action.Type {
|
switch action.Type {
|
||||||
case "direct":
|
case "direct":
|
||||||
|
|||||||
Reference in New Issue
Block a user