Add inline rule-set & Add reload for local rule-set

This commit is contained in:
世界
2024-06-26 00:43:51 +08:00
parent 6bebe2483b
commit 3a7acaa92a
14 changed files with 311 additions and 280 deletions

View File

@@ -20,8 +20,8 @@ import (
func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) {
switch options.Type {
case C.RuleSetTypeLocal:
return NewLocalRuleSet(ctx, router, options)
case C.RuleSetTypeInline, C.RuleSetTypeLocal, "":
return NewLocalRuleSet(ctx, router, logger, options)
case C.RuleSetTypeRemote:
return NewRemoteRuleSet(ctx, router, logger, options), nil
default:

View File

@@ -3,8 +3,10 @@ package route
import (
"context"
"os"
"path/filepath"
"strings"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
@@ -14,6 +16,7 @@ import (
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service/filemanager"
@@ -23,50 +26,55 @@ import (
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
type LocalRuleSet struct {
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
refs atomic.Int32
router adapter.Router
logger logger.Logger
tag string
rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata
fileFormat string
watcher *fswatch.Watcher
refs atomic.Int32
}
func NewLocalRuleSet(ctx context.Context, router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
var plainRuleSet option.PlainRuleSet
switch options.Format {
case C.RuleSetFormatSource, "":
content, err := os.ReadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
if err != nil {
return nil, err
}
compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return nil, err
}
plainRuleSet = compat.Upgrade()
case C.RuleSetFormatBinary:
setFile, err := os.Open(filemanager.BasePath(ctx, options.LocalOptions.Path))
if err != nil {
return nil, err
}
plainRuleSet, err = srs.Read(setFile, false)
if err != nil {
return nil, err
}
default:
return nil, E.New("unknown rule-set format: ", options.Format)
func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
ruleSet := &LocalRuleSet{
router: router,
logger: logger,
tag: options.Tag,
fileFormat: options.Format,
}
rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))
var err error
for i, ruleOptions := range plainRuleSet.Rules {
rules[i], err = NewHeadlessRule(router, ruleOptions)
if options.Type == C.RuleSetTypeInline {
if len(options.InlineOptions.Rules) == 0 {
return nil, E.New("empty inline rule-set")
}
err := ruleSet.reloadRules(options.InlineOptions.Rules)
if err != nil {
return nil, E.Cause(err, "parse rule_set.rules.[", i, "]")
return nil, err
}
} else {
err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
if err != nil {
return nil, err
}
}
var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
return &LocalRuleSet{tag: options.Tag, rules: rules, metadata: metadata}, nil
if options.Type == C.RuleSetTypeLocal {
var watcher *fswatch.Watcher
filePath, _ := filepath.Abs(options.LocalOptions.Path)
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: []string{filePath},
Callback: func(path string) {
uErr := ruleSet.reloadFile(path)
if uErr != nil {
logger.Error(E.Cause(uErr, "reload rule-set ", options.Tag))
}
},
})
if err != nil {
return nil, err
}
ruleSet.watcher = watcher
}
return ruleSet, nil
}
func (s *LocalRuleSet) Name() string {
@@ -78,6 +86,61 @@ func (s *LocalRuleSet) String() string {
}
func (s *LocalRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
if s.watcher != nil {
err := s.watcher.Start()
if err != nil {
s.logger.Error(E.Cause(err, "watch rule-set file"))
}
}
return nil
}
func (s *LocalRuleSet) reloadFile(path string) error {
var plainRuleSet option.PlainRuleSet
switch s.fileFormat {
case C.RuleSetFormatSource, "":
content, err := os.ReadFile(path)
if err != nil {
return err
}
compat, err := json.UnmarshalExtended[option.PlainRuleSetCompat](content)
if err != nil {
return err
}
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
setFile, err := os.Open(path)
if err != nil {
return err
}
plainRuleSet, err = srs.Read(setFile, false)
if err != nil {
return err
}
default:
return E.New("unknown rule-set format: ", s.fileFormat)
}
return s.reloadRules(plainRuleSet.Rules)
}
func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
rules := make([]adapter.HeadlessRule, len(headlessRules))
var err error
for i, ruleOptions := range headlessRules {
rules[i], err = NewHeadlessRule(s.router, ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}
}
var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
s.rules = rules
s.metadata = metadata
return nil
}
@@ -118,7 +181,7 @@ func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetU
func (s *LocalRuleSet) Close() error {
s.rules = nil
return nil
return common.Close(common.PtrOrNil(s.watcher))
}
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {

View File

@@ -168,7 +168,10 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
if err != nil {
return err
}
plainRuleSet = compat.Upgrade()
plainRuleSet, err = compat.Upgrade()
if err != nil {
return err
}
case C.RuleSetFormatBinary:
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
if err != nil {