Add tempalte.extended, template.<rule_set>.<type=github/owner/repo/branch/rule_set>, template.block_tag

This commit is contained in:
世界 2024-05-11 22:43:29 +08:00
parent 404de85ab1
commit 9ae4f644f1
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
9 changed files with 178 additions and 23 deletions

6
constant/rule_set.go Normal file
View File

@ -0,0 +1,6 @@
package constant
const (
RuleSetTypeDefault = "default"
RuleSetTypeGitHub = "github"
)

View File

@ -57,7 +57,7 @@
"custom_geoip": {}, "custom_geoip": {},
"custom_geosite": {}, "custom_geosite": {},
"custom_rule_set": [], "custom_rule_set": [],
"post_custom_rule_set": [], "post_rule_set": [],
// Experimental // Experimental
@ -253,7 +253,7 @@ List of [RuleSet](https://sing-box.sagernet.org/configuration/rule-set/).
Default rule sets will not be generated if not empty. Default rule sets will not be generated if not empty.
#### post_custom_rule_set #### post_rule_set
List of [RuleSet](https://sing-box.sagernet.org/configuration/rule-set/). List of [RuleSet](https://sing-box.sagernet.org/configuration/rule-set/).

View File

@ -2,13 +2,17 @@ package option
import ( import (
"github.com/sagernet/serenity/common/semver" "github.com/sagernet/serenity/common/semver"
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/badjson" "github.com/sagernet/sing/common/json/badjson"
) )
type Template struct { type Template struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Extend string `json:"extend,omitempty"`
// Global // Global
@ -37,6 +41,7 @@ type Template struct {
ExtraGroups []ExtraGroup `json:"extra_groups,omitempty"` ExtraGroups []ExtraGroup `json:"extra_groups,omitempty"`
GenerateGlobalURLTest bool `json:"generate_global_urltest,omitempty"` GenerateGlobalURLTest bool `json:"generate_global_urltest,omitempty"`
DirectTag string `json:"direct_tag,omitempty"` DirectTag string `json:"direct_tag,omitempty"`
BlockTag string `json:"block_tag,omitempty"`
DefaultTag string `json:"default_tag,omitempty"` DefaultTag string `json:"default_tag,omitempty"`
URLTestTag string `json:"urltest_tag,omitempty"` URLTestTag string `json:"urltest_tag,omitempty"`
CustomDirect *option.DirectOutboundOptions `json:"custom_direct,omitempty"` CustomDirect *option.DirectOutboundOptions `json:"custom_direct,omitempty"`
@ -51,8 +56,8 @@ type Template struct {
EnableJSDelivr bool `json:"enable_jsdelivr,omitempty"` EnableJSDelivr bool `json:"enable_jsdelivr,omitempty"`
CustomGeoIP *option.GeoIPOptions `json:"custom_geoip,omitempty"` CustomGeoIP *option.GeoIPOptions `json:"custom_geoip,omitempty"`
CustomGeosite *option.GeositeOptions `json:"custom_geosite,omitempty"` CustomGeosite *option.GeositeOptions `json:"custom_geosite,omitempty"`
CustomRuleSet []option.RuleSet `json:"custom_rule_set,omitempty"` CustomRuleSet []RuleSet `json:"custom_rule_set,omitempty"`
PostCustomRuleSet []option.RuleSet `json:"post_custom_rule_set,omitempty"` PostRuleSet []RuleSet `json:"post_rule_set,omitempty"`
// Experimental // Experimental
DisableCacheFile bool `json:"disable_cache_file,omitempty"` DisableCacheFile bool `json:"disable_cache_file,omitempty"`
@ -70,6 +75,53 @@ type Template struct {
MemoryLimit option.MemoryBytes `json:"memory_limit,omitempty"` MemoryLimit option.MemoryBytes `json:"memory_limit,omitempty"`
} }
type _RuleSet struct {
Type string `json:"type,omitempty"`
DefaultOptions option.RuleSet `json:"-"`
GitHubOptions GitHubRuleSetOptions `json:"-"`
}
type RuleSet _RuleSet
func (r *RuleSet) RawOptions() (any, error) {
switch r.Type {
case C.RuleSetTypeDefault, "":
r.Type = ""
return &r.DefaultOptions, nil
case C.RuleSetTypeGitHub:
return &r.GitHubOptions, nil
default:
return nil, E.New("unknown rule set type", r.Type)
}
}
func (r *RuleSet) MarshalJSON() ([]byte, error) {
rawOptions, err := r.RawOptions()
if err != nil {
return nil, err
}
return option.MarshallObjects((*_RuleSet)(r), rawOptions)
}
func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_RuleSet)(r))
if err != nil {
return err
}
rawOptions, err := r.RawOptions()
if err != nil {
return err
}
return option.UnmarshallExcluded(bytes, (*_RuleSet)(r), rawOptions)
}
type GitHubRuleSetOptions struct {
Owner string `json:"owner,omitempty"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
RuleSet option.Listable[string] `json:"rule_set,omitempty"`
}
func (t Template) DisableIPv6() bool { func (t Template) DisableIPv6() bool {
return t.DomainStrategy == option.DomainStrategy(dns.DomainStrategyUseIPv4) return t.DomainStrategy == option.DomainStrategy(dns.DomainStrategyUseIPv4)
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/sagernet/serenity/option" "github.com/sagernet/serenity/option"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
) )
@ -16,12 +17,49 @@ type Manager struct {
templates []*Template templates []*Template
} }
func extendTemplate(rawTemplates []option.Template, root, current option.Template) (option.Template, error) {
if current.Extend == "" {
return current, nil
} else if root.Name == current.Extend {
return option.Template{}, E.New("initialize template[", current.Name, "]: circular extend detected: ", current.Extend)
}
var next option.Template
for _, it := range rawTemplates {
if it.Name == current.Extend {
next = it
break
}
}
if next.Name == "" {
return option.Template{}, E.New("initialize template[", current.Name, "]: extended template not found: ", current.Extend)
}
if next.Extend != "" {
newNext, err := extendTemplate(rawTemplates, root, next)
if err != nil {
return option.Template{}, E.Cause(err, next.Extend)
}
next = newNext
}
newTemplate, err := badjson.Merge(current, next)
if err != nil {
return option.Template{}, E.Cause(err, "initialize template[", current.Name, "]: merge extended template: ", current.Extend)
}
return newTemplate, nil
}
func NewManager(ctx context.Context, logger logger.Logger, rawTemplates []option.Template) (*Manager, error) { func NewManager(ctx context.Context, logger logger.Logger, rawTemplates []option.Template) (*Manager, error) {
var templates []*Template var templates []*Template
for templateIndex, template := range rawTemplates { for templateIndex, template := range rawTemplates {
if template.Name == "" { if template.Name == "" {
return nil, E.New("initialize template[", templateIndex, "]: missing name") return nil, E.New("initialize template[", templateIndex, "]: missing name")
} }
if template.Extend != "" {
newTemplate, err := extendTemplate(rawTemplates, template, template)
if err != nil {
return nil, err
}
template = newTemplate
}
var groups []*ExtraGroup var groups []*ExtraGroup
for groupIndex, group := range template.ExtraGroups { for groupIndex, group := range template.ExtraGroups {
if group.Tag == "" { if group.Tag == "" {

View File

@ -37,6 +37,10 @@ func (t *Template) renderDNS(metadata M.Metadata, options *option.Options) error
if dnsLocal == "" { if dnsLocal == "" {
dnsLocal = DefaultDNSLocal dnsLocal = DefaultDNSLocal
} }
directTag := t.DirectTag
if directTag == "" {
directTag = DefaultDirectTag
}
defaultDNSOptions := option.DNSServerOptions{ defaultDNSOptions := option.DNSServerOptions{
Tag: DNSDefaultTag, Tag: DNSDefaultTag,
Address: dnsDefault, Address: dnsDefault,
@ -58,7 +62,7 @@ func (t *Template) renderDNS(metadata M.Metadata, options *option.Options) error
localDNSOptions = option.DNSServerOptions{ localDNSOptions = option.DNSServerOptions{
Tag: DNSLocalTag, Tag: DNSLocalTag,
Address: dnsLocal, Address: dnsLocal,
Detour: DefaultDirectTag, Detour: directTag,
} }
if dnsLocalUrl, err := url.Parse(dnsLocal); err == nil && BM.IsDomainName(dnsLocalUrl.Hostname()) { if dnsLocalUrl, err := url.Parse(dnsLocal); err == nil && BM.IsDomainName(dnsLocalUrl.Hostname()) {
localDNSOptions.AddressResolver = DNSLocalSetupTag localDNSOptions.AddressResolver = DNSLocalSetupTag

View File

@ -3,11 +3,13 @@ package template
import ( import (
M "github.com/sagernet/serenity/common/metadata" M "github.com/sagernet/serenity/common/metadata"
"github.com/sagernet/serenity/common/semver" "github.com/sagernet/serenity/common/semver"
"github.com/sagernet/serenity/constant"
"github.com/sagernet/serenity/option"
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"
) )
func (t *Template) renderGeoResources(metadata M.Metadata, options *option.Options) { func (t *Template) renderGeoResources(metadata M.Metadata, options *boxOption.Options) {
if t.DisableRuleSet || (metadata.Version != nil && metadata.Version.LessThan(semver.ParseVersion("1.8.0-alpha.10"))) { if t.DisableRuleSet || (metadata.Version != nil && metadata.Version.LessThan(semver.ParseVersion("1.8.0-alpha.10"))) {
var ( var (
geoipDownloadURL string geoipDownloadURL string
@ -27,13 +29,13 @@ func (t *Template) renderGeoResources(metadata M.Metadata, options *option.Optio
geositeDownloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-cn.db" geositeDownloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite-cn.db"
} }
if t.CustomGeoIP == nil { if t.CustomGeoIP == nil {
options.Route.GeoIP = &option.GeoIPOptions{ options.Route.GeoIP = &boxOption.GeoIPOptions{
DownloadURL: geoipDownloadURL, DownloadURL: geoipDownloadURL,
DownloadDetour: downloadDetour, DownloadDetour: downloadDetour,
} }
} }
if t.CustomGeosite == nil { if t.CustomGeosite == nil {
options.Route.Geosite = &option.GeositeOptions{ options.Route.Geosite = &boxOption.GeositeOptions{
DownloadURL: geositeDownloadURL, DownloadURL: geositeDownloadURL,
DownloadDetour: downloadDetour, DownloadDetour: downloadDetour,
} }
@ -57,12 +59,12 @@ func (t *Template) renderGeoResources(metadata M.Metadata, options *option.Optio
downloadURL = "https://raw.githubusercontent.com/" downloadURL = "https://raw.githubusercontent.com/"
branchSplit = "/" branchSplit = "/"
} }
options.Route.RuleSet = []option.RuleSet{ options.Route.RuleSet = []boxOption.RuleSet{
{ {
Type: C.RuleSetTypeRemote, Type: C.RuleSetTypeRemote,
Tag: "geoip-cn", Tag: "geoip-cn",
Format: C.RuleSetFormatBinary, Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{ RemoteOptions: boxOption.RemoteRuleSet{
URL: downloadURL + "SagerNet/sing-geoip" + branchSplit + "rule-set/geoip-cn.srs", URL: downloadURL + "SagerNet/sing-geoip" + branchSplit + "rule-set/geoip-cn.srs",
DownloadDetour: downloadDetour, DownloadDetour: downloadDetour,
}, },
@ -71,7 +73,7 @@ func (t *Template) renderGeoResources(metadata M.Metadata, options *option.Optio
Type: C.RuleSetTypeRemote, Type: C.RuleSetTypeRemote,
Tag: "geosite-geolocation-cn", Tag: "geosite-geolocation-cn",
Format: C.RuleSetFormatBinary, Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{ RemoteOptions: boxOption.RemoteRuleSet{
URL: downloadURL + "SagerNet/sing-geosite" + branchSplit + "rule-set/geosite-geolocation-cn.srs", URL: downloadURL + "SagerNet/sing-geosite" + branchSplit + "rule-set/geosite-geolocation-cn.srs",
DownloadDetour: downloadDetour, DownloadDetour: downloadDetour,
}, },
@ -80,13 +82,58 @@ func (t *Template) renderGeoResources(metadata M.Metadata, options *option.Optio
Type: C.RuleSetTypeRemote, Type: C.RuleSetTypeRemote,
Tag: "geosite-geolocation-!cn", Tag: "geosite-geolocation-!cn",
Format: C.RuleSetFormatBinary, Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{ RemoteOptions: boxOption.RemoteRuleSet{
URL: downloadURL + "SagerNet/sing-geosite" + branchSplit + "rule-set/geosite-geolocation-!cn.srs", URL: downloadURL + "SagerNet/sing-geosite" + branchSplit + "rule-set/geosite-geolocation-!cn.srs",
DownloadDetour: downloadDetour, DownloadDetour: downloadDetour,
}, },
}, },
} }
} }
options.Route.RuleSet = append(options.Route.RuleSet, t.PostCustomRuleSet...) options.Route.RuleSet = append(options.Route.RuleSet, t.renderRuleSet(t.PostRuleSet)...)
} }
} }
func (t *Template) renderRuleSet(ruleSets []option.RuleSet) []boxOption.RuleSet {
var result []boxOption.RuleSet
for _, ruleSet := range ruleSets {
switch ruleSet.Type {
case constant.RuleSetTypeDefault, "":
result = append(result, ruleSet.DefaultOptions)
case constant.RuleSetTypeGitHub:
var (
downloadURL string
downloadDetour string
branchSplit string
)
if t.EnableJSDelivr {
downloadURL = "https://testingcf.jsdelivr.net/gh/"
if t.DirectTag != "" {
downloadDetour = t.DirectTag
} else {
downloadDetour = DefaultDirectTag
}
branchSplit = "@"
} else {
downloadURL = "https://raw.githubusercontent.com/"
branchSplit = "/"
}
for _, code := range ruleSet.GitHubOptions.RuleSet {
result = append(result, boxOption.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: code,
Format: C.RuleSetFormatBinary,
RemoteOptions: boxOption.RemoteRuleSet{
URL: downloadURL +
ruleSet.GitHubOptions.Owner + "/" +
ruleSet.GitHubOptions.Repo + "/" +
branchSplit +
ruleSet.GitHubOptions.Branch + "/" +
code + ".srs",
DownloadDetour: downloadDetour,
},
})
}
}
}
return result
}

View File

@ -20,6 +20,10 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
if directTag == "" { if directTag == "" {
directTag = DefaultDirectTag directTag = DefaultDirectTag
} }
blockTag := t.BlockTag
if blockTag == "" {
blockTag = DefaultBlockTag
}
options.Outbounds = []option.Outbound{ options.Outbounds = []option.Outbound{
{ {
Tag: directTag, Tag: directTag,
@ -27,7 +31,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
DirectOptions: common.PtrValueOrDefault(t.CustomDirect), DirectOptions: common.PtrValueOrDefault(t.CustomDirect),
}, },
{ {
Tag: BlockTag, Tag: blockTag,
Type: C.TypeBlock, Type: C.TypeBlock,
}, },
{ {
@ -85,7 +89,7 @@ func (t *Template) renderOutbounds(metadata M.Metadata, options *option.Options,
selectorOutbound := option.Outbound{ selectorOutbound := option.Outbound{
Type: C.TypeSelector, Type: C.TypeSelector,
Tag: it.Name, Tag: it.Name,
SelectorOptions: common.PtrValueOrDefault(t.CustomSelector), SelectorOptions: common.PtrValueOrDefault(it.CustomSelector),
} }
selectorOutbound.SelectorOptions.Outbounds = append(selectorOutbound.SelectorOptions.Outbounds, joinOutbounds...) selectorOutbound.SelectorOptions.Outbounds = append(selectorOutbound.SelectorOptions.Outbounds, joinOutbounds...)
allGroups = append(allGroups, selectorOutbound) allGroups = append(allGroups, selectorOutbound)
@ -171,9 +175,9 @@ func groupJoin(outbounds []option.Outbound, groupTag string, groupOutbounds ...s
groupOutbound := outbounds[groupIndex] groupOutbound := outbounds[groupIndex]
switch groupOutbound.Type { switch groupOutbound.Type {
case C.TypeSelector: case C.TypeSelector:
groupOutbound.SelectorOptions.Outbounds = append(groupOutbound.SelectorOptions.Outbounds, groupOutbounds...) groupOutbound.SelectorOptions.Outbounds = common.Dup(append(groupOutbound.SelectorOptions.Outbounds, groupOutbounds...))
case C.TypeURLTest: case C.TypeURLTest:
groupOutbound.URLTestOptions.Outbounds = append(groupOutbound.URLTestOptions.Outbounds, groupOutbounds...) groupOutbound.URLTestOptions.Outbounds = common.Dup(append(groupOutbound.URLTestOptions.Outbounds, groupOutbounds...))
} }
outbounds[groupIndex] = groupOutbound outbounds[groupIndex] = groupOutbound
return outbounds return outbounds

View File

@ -13,7 +13,7 @@ func (t *Template) renderRoute(metadata M.Metadata, options *option.Options) err
options.Route = &option.RouteOptions{ options.Route = &option.RouteOptions{
GeoIP: t.CustomGeoIP, GeoIP: t.CustomGeoIP,
Geosite: t.CustomGeosite, Geosite: t.CustomGeosite,
RuleSet: t.CustomRuleSet, RuleSet: t.renderRuleSet(t.CustomRuleSet),
} }
} }
if !t.DisableTrafficBypass { if !t.DisableTrafficBypass {
@ -45,6 +45,10 @@ func (t *Template) renderRoute(metadata M.Metadata, options *option.Options) err
}, },
} }
if !t.DisableTrafficBypass && !t.DisableDefaultRules { if !t.DisableTrafficBypass && !t.DisableDefaultRules {
blockTag := t.BlockTag
if blockTag == "" {
blockTag = DefaultBlockTag
}
options.Route.Rules = append(options.Route.Rules, option.Rule{ options.Route.Rules = append(options.Route.Rules, option.Rule{
Type: C.RuleTypeLogical, Type: C.RuleTypeLogical,
LogicalOptions: option.LogicalRule{ LogicalOptions: option.LogicalRule{
@ -64,7 +68,7 @@ func (t *Template) renderRoute(metadata M.Metadata, options *option.Options) err
}, },
}, },
}, },
Outbound: BlockTag, Outbound: blockTag,
}, },
}) })
} }

View File

@ -19,9 +19,9 @@ const (
DNSFakeIPTag = "remote" DNSFakeIPTag = "remote"
DefaultDNS = "tls://8.8.8.8" DefaultDNS = "tls://8.8.8.8"
DefaultDNSLocal = "https://223.5.5.5/dns-query" DefaultDNSLocal = "https://223.5.5.5/dns-query"
DefaultDefaultTag = "Default" DefaultDefaultTag = "default"
DefaultDirectTag = "direct" DefaultDirectTag = "direct"
BlockTag = "block" DefaultBlockTag = "block"
DNSTag = "dns" DNSTag = "dns"
DefaultURLTestTag = "URLTest" DefaultURLTestTag = "URLTest"
) )