Replace template.extra_groups.per_subscription with target

This commit is contained in:
世界 2024-07-22 08:48:10 +08:00
parent 53227cd88a
commit 7f454a1133
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
3 changed files with 100 additions and 77 deletions

View File

@ -4,6 +4,7 @@
{ {
"name": "", "name": "",
"extend": "", "extend": "",
// Global // Global
"log": {}, "log": {},
@ -12,6 +13,7 @@
"disable_traffic_bypass": false, "disable_traffic_bypass": false,
"disable_rule_set": false, "disable_rule_set": false,
"remote_resolve": false, "remote_resolve": false,
// DNS // DNS
"dns": "", "dns": "",
@ -19,6 +21,7 @@
"enable_fakeip": false, "enable_fakeip": false,
"pre_dns_rules": [], "pre_dns_rules": [],
"custom_dns_rules": [], "custom_dns_rules": [],
// Inbound // Inbound
"inbounds": [], "inbounds": [],
@ -27,14 +30,14 @@
"disable_system_proxy": false, "disable_system_proxy": false,
"custom_tun": {}, "custom_tun": {},
"custom_mixed": {}, "custom_mixed": {},
// Outbound // Outbound
"extra_groups": [ "extra_groups": [
{ {
"tag": "", "tag": "",
"type": "", "type": "",
"exclude_outbounds": false, "target": "",
"per_subscription": false,
"tag_per_subscription": "", "tag_per_subscription": "",
"filter": "", "filter": "",
"exclude": "", "exclude": "",
@ -42,13 +45,13 @@
"custom_urltest": {} "custom_urltest": {}
} }
], ],
"generate_global_urltest": false,
"direct_tag": "", "direct_tag": "",
"default_tag": "", "default_tag": "",
"urltest_tag": "", "urltest_tag": "",
"custom_direct": {}, "custom_direct": {},
"custom_selector": {}, "custom_selector": {},
"custom_urltest": {}, "custom_urltest": {},
// Route // Route
"pre_rules": [], "pre_rules": [],
@ -58,6 +61,7 @@
"custom_geosite": {}, "custom_geosite": {},
"custom_rule_set": [], "custom_rule_set": [],
"post_rule_set": [], "post_rule_set": [],
// Experimental // Experimental
"disable_cache_file": false, "disable_cache_file": false,
@ -66,6 +70,7 @@
"clash_mode_global": "", "clash_mode_global": "",
"clash_mode_direct": "", "clash_mode_direct": "",
"custom_clash_api": {}, "custom_clash_api": {},
// Debug // Debug
"pprof_listen": "", "pprof_listen": "",
@ -190,17 +195,17 @@ Tag of the group outbound.
Type of the group outbound. Type of the group outbound.
#### extra_groups.exclude_outbounds #### extra_groups.target
Only include subscriptions but not custom outbounds. | Value | Description |
|----------------|------------------------------------------------------------|
#### extra_groups.per_subscription | `default` | No additional behaviors. |
| `global` | Generate a group and add it to default selector. |
Generate a internal group for every subscription instead of a global group. | `subscription` | Generate a internal group for every subscription selector. |
#### extra_groups.tag_per_subscription #### 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. `{{ .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. 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 #### direct_tag
Custom direct outbound tag. Custom direct outbound tag.

View File

@ -4,6 +4,7 @@ import (
C "github.com/sagernet/serenity/constant" C "github.com/sagernet/serenity/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
) )
@ -129,8 +130,7 @@ func (t Template) DisableIPv6() bool {
type ExtraGroup struct { type ExtraGroup struct {
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
ExcludeOutbounds bool `json:"exclude_outbounds,omitempty"` Target ExtraGroupTarget `json:"target,omitempty"`
PerSubscription bool `json:"per_subscription,omitempty"`
TagPerSubscription string `json:"tag_per_subscription,omitempty"` TagPerSubscription string `json:"tag_per_subscription,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Filter option.Listable[string] `json:"filter,omitempty"` Filter option.Listable[string] `json:"filter,omitempty"`
@ -138,3 +138,47 @@ type ExtraGroup struct {
CustomSelector *option.SelectorOutboundOptions `json:"custom_selector,omitempty"` CustomSelector *option.SelectorOutboundOptions `json:"custom_selector,omitempty"`
CustomURLTest *option.URLTestOutboundOptions `json:"custom_urltest,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
}

View File

@ -6,15 +6,16 @@ import (
"text/template" "text/template"
M "github.com/sagernet/serenity/common/metadata" M "github.com/sagernet/serenity/common/metadata"
"github.com/sagernet/serenity/option"
"github.com/sagernet/serenity/subscription" "github.com/sagernet/serenity/subscription"
C "github.com/sagernet/sing-box/constant" 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" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" 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 defaultTag := t.DefaultTag
if defaultTag == "" { if defaultTag == "" {
defaultTag = DefaultDefaultTag defaultTag = DefaultDefaultTag
@ -28,7 +29,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
if blockTag == "" { if blockTag == "" {
blockTag = DefaultBlockTag blockTag = DefaultBlockTag
} }
options.Outbounds = []option.Outbound{ options.Outbounds = []boxOption.Outbound{
{ {
Tag: directTag, Tag: directTag,
Type: C.TypeDirect, Type: C.TypeDirect,
@ -52,26 +53,22 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
if urlTestTag == "" { if urlTestTag == "" {
urlTestTag = DefaultURLTestTag urlTestTag = DefaultURLTestTag
} }
outboundToString := func(it option.Outbound) string { outboundToString := func(it boxOption.Outbound) string {
return it.Tag return it.Tag
} }
var ( var globalOutboundTags []string
globalOutbounds []option.Outbound
globalOutboundTags []string
)
if len(outbounds) > 0 { if len(outbounds) > 0 {
for _, outbound := range outbounds { for _, outbound := range outbounds {
options.Outbounds = append(options.Outbounds, outbound...) options.Outbounds = append(options.Outbounds, outbound...)
} }
globalOutbounds = common.Map(outbounds, func(it []option.Outbound) option.Outbound { globalOutboundTags = common.Map(outbounds, func(it []boxOption.Outbound) string {
return it[0] return it[0].Tag
}) })
globalOutboundTags = common.Map(globalOutbounds, outboundToString)
} }
var ( var (
allGroups []option.Outbound allGroups []boxOption.Outbound
allGroupOutbounds []option.Outbound allGroupOutbounds []boxOption.Outbound
groupTags []string groupTags []string
) )
@ -79,11 +76,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
if len(it.Servers) == 0 { if len(it.Servers) == 0 {
continue continue
} }
joinOutbounds := common.Map(it.Servers, func(it option.Outbound) string { joinOutbounds := common.Map(it.Servers, func(it boxOption.Outbound) string {
return it.Tag return it.Tag
}) })
if it.GenerateSelector { if it.GenerateSelector {
selectorOutbound := option.Outbound{ selectorOutbound := boxOption.Outbound{
Type: C.TypeSelector, Type: C.TypeSelector,
Tag: it.Name, Tag: it.Name,
SelectorOptions: common.PtrValueOrDefault(it.CustomSelector), SelectorOptions: common.PtrValueOrDefault(it.CustomSelector),
@ -101,7 +98,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
} else { } else {
urltestTag = it.Name + " - URLTest" urltestTag = it.Name + " - URLTest"
} }
urltestOutbound := option.Outbound{ urltestOutbound := boxOption.Outbound{
Type: C.TypeURLTest, Type: C.TypeURLTest,
Tag: urltestTag, Tag: urltestTag,
URLTestOptions: common.PtrValueOrDefault(t.CustomURLTest), URLTestOptions: common.PtrValueOrDefault(t.CustomURLTest),
@ -116,10 +113,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
allGroupOutbounds = append(allGroupOutbounds, it.Servers...) allGroupOutbounds = append(allGroupOutbounds, it.Servers...)
} }
globalOutbounds = append(globalOutbounds, allGroups...) var (
globalOutbounds = append(globalOutbounds, allGroupOutbounds...) defaultGroups []boxOption.Outbound
globalGroups []boxOption.Outbound
allExtraGroups := make(map[string][]option.Outbound) subscriptionGroups = make(map[string][]boxOption.Outbound)
)
for _, extraGroup := range t.groups { for _, extraGroup := range t.groups {
myFilter := func(outboundTag string) bool { myFilter := func(outboundTag string) bool {
if len(extraGroup.filter) > 0 { if len(extraGroup.filter) > 0 {
@ -138,19 +136,14 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
} }
return true return true
} }
if !extraGroup.PerSubscription { if extraGroup.Target != option.ExtraGroupTargetSubscription {
var extraTags []string extraTags := common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string {
if extraGroup.ExcludeOutbounds { return common.Map(it.Servers, outboundToString)
extraTags = common.Filter(common.FlatMap(subscriptions, func(it *subscription.Subscription) []string { }), myFilter)
return common.Map(it.Servers, outboundToString)
}), myFilter)
} else {
extraTags = common.Filter(common.Map(globalOutbounds, outboundToString), myFilter)
}
if len(extraTags) == 0 { if len(extraTags) == 0 {
continue continue
} }
groupOutbound := option.Outbound{ groupOutbound := boxOption.Outbound{
Tag: extraGroup.Tag, Tag: extraGroup.Tag,
Type: extraGroup.Type, Type: extraGroup.Type,
SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector), SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector),
@ -162,7 +155,11 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
case C.TypeURLTest: case C.TypeURLTest:
groupOutbound.URLTestOptions.Outbounds = append(groupOutbound.URLTestOptions.Outbounds, extraTags...) 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 { } else {
tmpl := template.New("tag") tmpl := template.New("tag")
if extraGroup.TagPerSubscription != "" { if extraGroup.TagPerSubscription != "" {
@ -174,26 +171,6 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
common.Must1(tmpl.Parse("{{ .tag }} ({{ .subscription_name }})")) common.Must1(tmpl.Parse("{{ .tag }} ({{ .subscription_name }})"))
} }
var outboundTags []string 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 { for _, it := range subscriptions {
subscriptionTags := common.Filter(common.Map(it.Servers, outboundToString), myFilter) subscriptionTags := common.Filter(common.Map(it.Servers, outboundToString), myFilter)
if len(subscriptionTags) == 0 { if len(subscriptionTags) == 0 {
@ -213,7 +190,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
} }
tagPerSubscription = buffer.String() tagPerSubscription = buffer.String()
} }
groupOutboundPerSubscription := option.Outbound{ groupOutboundPerSubscription := boxOption.Outbound{
Tag: tagPerSubscription, Tag: tagPerSubscription,
Type: extraGroup.Type, Type: extraGroup.Type,
SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector), SelectorOptions: common.PtrValueOrDefault(extraGroup.CustomSelector),
@ -225,20 +202,21 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
case C.TypeURLTest: case C.TypeURLTest:
groupOutboundPerSubscription.URLTestOptions.Outbounds = append(groupOutboundPerSubscription.URLTestOptions.Outbounds, subscriptionTags...) 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...) options.Outbounds = append(options.Outbounds, allGroups...)
if len(defaultGroups) > 0 {
defaultExtraGroupOutbounds := allExtraGroups[""] options.Outbounds = append(options.Outbounds, defaultGroups...)
if len(defaultExtraGroupOutbounds) > 0 { }
options.Outbounds = append(options.Outbounds, defaultExtraGroupOutbounds...) if len(globalGroups) > 0 {
options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, common.Map(defaultExtraGroupOutbounds, outboundToString)...) options.Outbounds = append(options.Outbounds, globalGroups...)
options.Outbounds = groupJoin(options.Outbounds, defaultTag, false, common.Map(globalGroups, outboundToString)...)
} }
for _, it := range subscriptions { for _, it := range subscriptions {
extraGroupOutboundsForSubscription := allExtraGroups[it.Name] extraGroupOutboundsForSubscription := subscriptionGroups[it.Name]
if len(extraGroupOutboundsForSubscription) > 0 { if len(extraGroupOutboundsForSubscription) > 0 {
options.Outbounds = append(options.Outbounds, extraGroupOutboundsForSubscription...) options.Outbounds = append(options.Outbounds, extraGroupOutboundsForSubscription...)
options.Outbounds = groupJoin(options.Outbounds, it.Name, true, common.Map(extraGroupOutboundsForSubscription, outboundToString)...) 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 return nil
} }
func groupJoin(outbounds []option.Outbound, groupTag string, appendFront bool, groupOutbounds ...string) []option.Outbound { func groupJoin(outbounds []boxOption.Outbound, groupTag string, appendFront bool, groupOutbounds ...string) []boxOption.Outbound {
groupIndex := common.Index(outbounds, func(it option.Outbound) bool { groupIndex := common.Index(outbounds, func(it boxOption.Outbound) bool {
return it.Tag == groupTag return it.Tag == groupTag
}) })
if groupIndex == -1 { if groupIndex == -1 {