From 7f454a1133f365593907147db0f2e7ba0174ed8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Jul 2024 08:48:10 +0800 Subject: [PATCH] Replace `template.extra_groups.per_subscription` with `target` --- docs/configuration/template.md | 29 +++++----- option/template.go | 48 +++++++++++++++- template/render_outbounds.go | 100 +++++++++++++-------------------- 3 files changed, 100 insertions(+), 77 deletions(-) diff --git a/docs/configuration/template.md b/docs/configuration/template.md index 2afe1d3..b283862 100644 --- a/docs/configuration/template.md +++ b/docs/configuration/template.md @@ -4,6 +4,7 @@ { "name": "", "extend": "", + // Global "log": {}, @@ -12,6 +13,7 @@ "disable_traffic_bypass": false, "disable_rule_set": false, "remote_resolve": false, + // DNS "dns": "", @@ -19,6 +21,7 @@ "enable_fakeip": false, "pre_dns_rules": [], "custom_dns_rules": [], + // Inbound "inbounds": [], @@ -27,14 +30,14 @@ "disable_system_proxy": false, "custom_tun": {}, "custom_mixed": {}, + // Outbound "extra_groups": [ { "tag": "", "type": "", - "exclude_outbounds": false, - "per_subscription": false, + "target": "", "tag_per_subscription": "", "filter": "", "exclude": "", @@ -42,13 +45,13 @@ "custom_urltest": {} } ], - "generate_global_urltest": false, "direct_tag": "", "default_tag": "", "urltest_tag": "", "custom_direct": {}, "custom_selector": {}, "custom_urltest": {}, + // Route "pre_rules": [], @@ -58,6 +61,7 @@ "custom_geosite": {}, "custom_rule_set": [], "post_rule_set": [], + // Experimental "disable_cache_file": false, @@ -66,6 +70,7 @@ "clash_mode_global": "", "clash_mode_direct": "", "custom_clash_api": {}, + // Debug "pprof_listen": "", @@ -190,17 +195,17 @@ Tag of the group outbound. Type of the group outbound. -#### extra_groups.exclude_outbounds +#### extra_groups.target -Only include subscriptions but not custom outbounds. - -#### extra_groups.per_subscription - -Generate a internal group for every subscription instead of a global group. +| Value | Description | +|----------------|------------------------------------------------------------| +| `default` | No additional behaviors. | +| `global` | Generate a group and add it to default selector. | +| `subscription` | Generate a internal group for every subscription selector. | #### extra_groups.tag_per_subscription -Tag for every new subscription internal group. +Tag for every new subscription internal group when `target` is `subscription`. `{{ .tag }} ({{ .subscription_name }})` is used by default. @@ -220,10 +225,6 @@ Custom [Selector](https://sing-box.sagernet.org/configuration/outbound/selector/ Custom [URLTest](https://sing-box.sagernet.org/configuration/outbound/urltest/) template. -#### generate_global_urltest - -Generate a global `URLTest` outbound with all global outbounds. - #### direct_tag Custom direct outbound tag. diff --git a/option/template.go b/option/template.go index 35cad37..e2534e9 100644 --- a/option/template.go +++ b/option/template.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/serenity/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" ) @@ -129,8 +130,7 @@ func (t Template) DisableIPv6() bool { type ExtraGroup struct { Tag string `json:"tag,omitempty"` - ExcludeOutbounds bool `json:"exclude_outbounds,omitempty"` - PerSubscription bool `json:"per_subscription,omitempty"` + Target ExtraGroupTarget `json:"target,omitempty"` TagPerSubscription string `json:"tag_per_subscription,omitempty"` Type string `json:"type,omitempty"` Filter option.Listable[string] `json:"filter,omitempty"` @@ -138,3 +138,47 @@ type ExtraGroup struct { CustomSelector *option.SelectorOutboundOptions `json:"custom_selector,omitempty"` CustomURLTest *option.URLTestOutboundOptions `json:"custom_urltest,omitempty"` } + +type ExtraGroupTarget uint8 + +const ( + ExtraGroupTargetDefault ExtraGroupTarget = iota + ExtraGroupTargetGlobal + ExtraGroupTargetSubscription +) + +func (t ExtraGroupTarget) String() string { + switch t { + case ExtraGroupTargetDefault: + return "default" + case ExtraGroupTargetGlobal: + return "global" + case ExtraGroupTargetSubscription: + return "subscription" + default: + return "unknown" + } +} + +func (t ExtraGroupTarget) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +func (t *ExtraGroupTarget) UnmarshalJSON(bytes []byte) error { + var stringValue string + err := json.Unmarshal(bytes, &stringValue) + if err != nil { + return err + } + switch stringValue { + case "default": + *t = ExtraGroupTargetDefault + case "global": + *t = ExtraGroupTargetGlobal + case "subscription": + *t = ExtraGroupTargetSubscription + default: + return E.New("unknown extra group target: ", stringValue) + } + return nil +} diff --git a/template/render_outbounds.go b/template/render_outbounds.go index b1b7637..c1c6014 100644 --- a/template/render_outbounds.go +++ b/template/render_outbounds.go @@ -6,15 +6,16 @@ import ( "text/template" M "github.com/sagernet/serenity/common/metadata" + "github.com/sagernet/serenity/option" "github.com/sagernet/serenity/subscription" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" + boxOption "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" ) -func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, outbounds [][]option.Outbound, subscriptions []*subscription.Subscription) error { +func (t *Template) renderOutbounds(metadata M.Metadata, options *boxOption.Options, outbounds [][]boxOption.Outbound, subscriptions []*subscription.Subscription) error { defaultTag := t.DefaultTag if defaultTag == "" { defaultTag = DefaultDefaultTag @@ -28,7 +29,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, if blockTag == "" { blockTag = DefaultBlockTag } - options.Outbounds = []option.Outbound{ + options.Outbounds = []boxOption.Outbound{ { Tag: directTag, Type: C.TypeDirect, @@ -52,26 +53,22 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, if urlTestTag == "" { urlTestTag = DefaultURLTestTag } - outboundToString := func(it option.Outbound) string { + outboundToString := func(it boxOption.Outbound) string { return it.Tag } - var ( - globalOutbounds []option.Outbound - globalOutboundTags []string - ) + var globalOutboundTags []string if len(outbounds) > 0 { for _, outbound := range outbounds { options.Outbounds = append(options.Outbounds, outbound...) } - globalOutbounds = common.Map(outbounds, func(it []option.Outbound) option.Outbound { - return it[0] + globalOutboundTags = common.Map(outbounds, func(it []boxOption.Outbound) string { + return it[0].Tag }) - globalOutboundTags = common.Map(globalOutbounds, outboundToString) } var ( - allGroups []option.Outbound - allGroupOutbounds []option.Outbound + allGroups []boxOption.Outbound + allGroupOutbounds []boxOption.Outbound groupTags []string ) @@ -79,11 +76,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, if len(it.Servers) == 0 { continue } - joinOutbounds := common.Map(it.Servers, func(it option.Outbound) string { + joinOutbounds := common.Map(it.Servers, func(it boxOption.Outbound) string { return it.Tag }) if it.GenerateSelector { - selectorOutbound := option.Outbound{ + selectorOutbound := boxOption.Outbound{ Type: C.TypeSelector, Tag: it.Name, SelectorOptions: common.PtrValueOrDefault(it.CustomSelector), @@ -101,7 +98,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, } else { urltestTag = it.Name + " - URLTest" } - urltestOutbound := option.Outbound{ + urltestOutbound := boxOption.Outbound{ Type: C.TypeURLTest, Tag: urltestTag, URLTestOptions: common.PtrValueOrDefault(t.CustomURLTest), @@ -116,10 +113,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, allGroupOutbounds = append(allGroupOutbounds, it.Servers...) } - globalOutbounds = append(globalOutbounds, allGroups...) - globalOutbounds = append(globalOutbounds, allGroupOutbounds...) - - allExtraGroups := make(map[string][]option.Outbound) + var ( + defaultGroups []boxOption.Outbound + globalGroups []boxOption.Outbound + subscriptionGroups = make(map[string][]boxOption.Outbound) + ) for _, extraGroup := range t.groups { myFilter := func(outboundTag string) bool { if len(extraGroup.filter) > 0 { @@ -138,19 +136,14 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, } return true } - if !extraGroup.PerSubscription { - var extraTags []string - if extraGroup.ExcludeOutbounds { - extraTags = common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string { - return common.Map(it.Servers, outboundToString) - }), myFilter) - } else { - extraTags = common.Filter(common.Map(globalOutbounds, outboundToString), myFilter) - } + if extraGroup.Target != option.ExtraGroupTargetSubscription { + extraTags := common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string { + return common.Map(it.Servers, outboundToString) + }), myFilter) if len(extraTags) == 0 { continue } - groupOutbound := option.Outbound{ + groupOutbound := boxOption.Outbound{ Tag: extraGroup.Tag, Type: extraGroup.Type, SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector), @@ -162,7 +155,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, case C.TypeURLTest: groupOutbound.URLTestOptions.Outbounds = append(groupOutbound.URLTestOptions.Outbounds, extraTags...) } - allExtraGroups[""] = append(allExtraGroups[""], groupOutbound) + if extraGroup.Target == option.ExtraGroupTargetDefault { + defaultGroups = append(defaultGroups, groupOutbound) + } else { + globalGroups = append(globalGroups, groupOutbound) + } } else { tmpl := template.New("tag") if extraGroup.TagPerSubscription != "" { @@ -174,26 +171,6 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, common.Must1(tmpl.Parse("{{ .tag }} ({{ .subscription_name }})")) } var outboundTags []string - if !extraGroup.ExcludeOutbounds { - outboundTags = common.Filter(common.FlatMap(outbounds, func(it []option.Outbound) []string { - return common.Map(it, outboundToString) - }), myFilter) - } - if len(outboundTags) > 0 { - groupOutbound := option.Outbound{ - Tag: extraGroup.Tag, - Type: extraGroup.Type, - SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector), - URLTestOptions: common.PtrValueOrDefault(extraGroup.CustomURLTest), - } - switch extraGroup.Type { - case C.TypeSelector: - groupOutbound.SelectorOptions.Outbounds = append(groupOutbound.SelectorOptions.Outbounds, outboundTags...) - case C.TypeURLTest: - groupOutbound.URLTestOptions.Outbounds = append(groupOutbound.URLTestOptions.Outbounds, outboundTags...) - } - allExtraGroups[""] = append(allExtraGroups[""], groupOutbound) - } for _, it := range subscriptions { subscriptionTags := common.Filter(common.Map(it.Servers, outboundToString), myFilter) if len(subscriptionTags) == 0 { @@ -213,7 +190,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, } tagPerSubscription = buffer.String() } - groupOutboundPerSubscription := option.Outbound{ + groupOutboundPerSubscription := boxOption.Outbound{ Tag: tagPerSubscription, Type: extraGroup.Type, SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector), @@ -225,20 +202,21 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, case C.TypeURLTest: groupOutboundPerSubscription.URLTestOptions.Outbounds = append(groupOutboundPerSubscription.URLTestOptions.Outbounds, subscriptionTags...) } - allExtraGroups[it.Name] = append(allExtraGroups[it.Name], groupOutboundPerSubscription) + subscriptionGroups[it.Name] = append(subscriptionGroups[it.Name], groupOutboundPerSubscription) } } } options.Outbounds = append(options.Outbounds, allGroups...) - - defaultExtraGroupOutbounds := allExtraGroups[""] - if len(defaultExtraGroupOutbounds) > 0 { - options.Outbounds = append(options.Outbounds, defaultExtraGroupOutbounds...) - options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, common.Map(defaultExtraGroupOutbounds, outboundToString)...) + if len(defaultGroups) > 0 { + options.Outbounds = append(options.Outbounds, defaultGroups...) + } + if len(globalGroups) > 0 { + options.Outbounds = append(options.Outbounds, globalGroups...) + options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, common.Map(globalGroups, outboundToString)...) } for _, it := range subscriptions { - extraGroupOutboundsForSubscription := allExtraGroups[it.Name] + extraGroupOutboundsForSubscription := subscriptionGroups[it.Name] if len(extraGroupOutboundsForSubscription) > 0 { options.Outbounds = append(options.Outbounds, extraGroupOutboundsForSubscription...) options.Outbounds = groupJoin(options.Outbounds, it.Name, true, common.Map(extraGroupOutboundsForSubscription, outboundToString)...) @@ -251,8 +229,8 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options, return nil } -func groupJoin(outbounds []option.Outbound, groupTag string, appendFront bool, groupOutbounds ...string) []option.Outbound { - groupIndex := common.Index(outbounds, func(it option.Outbound) bool { +func groupJoin(outbounds []boxOption.Outbound, groupTag string, appendFront bool, groupOutbounds ...string) []boxOption.Outbound { + groupIndex := common.Index(outbounds, func(it boxOption.Outbound) bool { return it.Tag == groupTag }) if groupIndex == -1 {