From 6a78bca9fb3210e125c43af4518b43c7234d6358 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Sat, 22 Jan 2022 22:10:45 +0800 Subject: [PATCH] [Feature] 1.Add Network rule, match network type(TCP/UDP) 2.Add logic rules(NOT,OR,AND) -AND,((DOMAIN,baidu.com),(NETWORK,UDP)),REJECT (cherry picked from commit d7092e2e37f2c48282c878edea1b2ebc2912b09a) --- common/collections/stack.go | 56 +++++++++ config/config.go | 44 ++++--- constant/rule.go | 13 ++ rule/{ => common}/base.go | 6 +- rule/{ => common}/domain.go | 2 +- rule/{ => common}/domain_keyword.go | 2 +- rule/{ => common}/domain_suffix.go | 2 +- rule/{ => common}/final.go | 2 +- rule/{ => common}/geoip.go | 2 +- rule/{ => common}/geosite.go | 2 +- rule/{ => common}/ipcidr.go | 2 +- rule/common/network_type.go | 53 ++++++++ rule/{ => common}/port.go | 2 +- rule/{ => common}/process.go | 2 +- rule/logic/and.go | 58 +++++++++ rule/logic/common.go | 185 ++++++++++++++++++++++++++++ rule/logic/not.go | 51 ++++++++ rule/logic/or.go | 58 +++++++++ rule/parser.go | 90 +++++--------- rule/{ => provider}/fetcher.go | 2 +- rule/provider/parse.go | 93 ++++++++++++++ rule/{ => provider}/provider.go | 4 +- rule/{ => provider}/rule_set.go | 2 +- tunnel/tunnel.go | 2 +- 24 files changed, 637 insertions(+), 98 deletions(-) create mode 100644 common/collections/stack.go rename rule/{ => common}/base.go (85%) rename rule/{ => common}/domain.go (98%) rename rule/{ => common}/domain_keyword.go (98%) rename rule/{ => common}/domain_suffix.go (98%) rename rule/{ => common}/final.go (97%) rename rule/{ => common}/geoip.go (98%) rename rule/{ => common}/geosite.go (98%) rename rule/{ => common}/ipcidr.go (98%) create mode 100644 rule/common/network_type.go rename rule/{ => common}/port.go (99%) rename rule/{ => common}/process.go (99%) create mode 100644 rule/logic/and.go create mode 100644 rule/logic/common.go create mode 100644 rule/logic/not.go create mode 100644 rule/logic/or.go rename rule/{ => provider}/fetcher.go (99%) create mode 100644 rule/provider/parse.go rename rule/{ => provider}/provider.go (98%) rename rule/{ => provider}/rule_set.go (98%) diff --git a/common/collections/stack.go b/common/collections/stack.go new file mode 100644 index 00000000..74673f9a --- /dev/null +++ b/common/collections/stack.go @@ -0,0 +1,56 @@ +package collections + +import "sync" + +type ( + stack struct { + top *node + length int + lock *sync.RWMutex + } + + node struct { + value interface{} + prev *node + } +) + +// NewStack Create a new stack +func NewStack() *stack { + return &stack{nil, 0, &sync.RWMutex{}} +} + +// Len Return the number of items in the stack +func (this *stack) Len() int { + return this.length +} + +// Peek View the top item on the stack +func (this *stack) Peek() interface{} { + if this.length == 0 { + return nil + } + return this.top.value +} + +// Pop the top item of the stack and return it +func (this *stack) Pop() interface{} { + this.lock.Lock() + defer this.lock.Unlock() + if this.length == 0 { + return nil + } + n := this.top + this.top = n.prev + this.length-- + return n.value +} + +// Push a value onto the top of the stack +func (this *stack) Push(value interface{}) { + this.lock.Lock() + defer this.lock.Unlock() + n := &node{value, this.top} + this.top = n + this.length++ +} diff --git a/config/config.go b/config/config.go index d4437865..a302a63a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,8 @@ import ( "container/list" "errors" "fmt" + R "github.com/Dreamacro/clash/rule" + RP "github.com/Dreamacro/clash/rule/provider" "net" "net/url" "os" @@ -24,7 +26,6 @@ import ( providerTypes "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rule" T "github.com/Dreamacro/clash/tunnel" "gopkg.in/yaml.v2" @@ -484,13 +485,13 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin // parse rule provider for name, mapping := range cfg.RuleProvider { - rp, err := R.ParseRuleProvider(name, mapping) + rp, err := RP.ParseRuleProvider(name, mapping) if err != nil { return nil, nil, err } ruleProviders[name] = &rp - R.SetRuleProvider(rp) + RP.SetRuleProvider(rp) } var rules []C.Rule @@ -511,24 +512,29 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin continue } - switch l := len(rule); { - case l == 2: - target = rule[1] - case l == 3: - if ruleName == "MATCH" { - payload = "" + if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" { + payload = strings.Join(rule[1:len(rule)-1], ",") + target = rule[len(rule)-1] + } else { + switch l := len(rule); { + case l == 2: target = rule[1] - params = rule[2:] - break + case l == 3: + if ruleName == "MATCH" { + payload = "" + target = rule[1] + params = rule[2:] + break + } + payload = rule[1] + target = rule[2] + case l >= 4: + payload = rule[1] + target = rule[2] + params = rule[3:] + default: + return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } - payload = rule[1] - target = rule[2] - case l >= 4: - payload = rule[1] - target = rule[2] - params = rule[3:] - default: - return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } if _, ok := proxies[target]; mode != T.Script && !ok { diff --git a/constant/rule.go b/constant/rule.go index df7a5d19..2efc2010 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -14,7 +14,12 @@ const ( Process Script RuleSet + Network + Combination MATCH + AND + OR + NOT ) type RuleType int @@ -47,6 +52,14 @@ func (rt RuleType) String() string { return "Match" case RuleSet: return "RuleSet" + case Network: + return "Network" + case AND: + return "AND" + case OR: + return "OR" + case NOT: + return "NOT" default: return "Unknown" } diff --git a/rule/base.go b/rule/common/base.go similarity index 85% rename from rule/base.go rename to rule/common/base.go index 96d8bfe5..3e27ac24 100644 --- a/rule/base.go +++ b/rule/common/base.go @@ -1,4 +1,4 @@ -package rules +package common import ( "errors" @@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool { return false } -func findNetwork(params []string) C.NetWork { +func FindNetwork(params []string) C.NetWork { for _, p := range params { if p == "tcp" { return C.TCP @@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork { return C.ALLNet } -func findSourceIPs(params []string) []*net.IPNet { +func FindSourceIPs(params []string) []*net.IPNet { var ips []*net.IPNet for _, p := range params { if p == noResolve || len(p) < 7 { diff --git a/rule/domain.go b/rule/common/domain.go similarity index 98% rename from rule/domain.go rename to rule/common/domain.go index 42281b5d..31aa0f5b 100644 --- a/rule/domain.go +++ b/rule/common/domain.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/domain_keyword.go b/rule/common/domain_keyword.go similarity index 98% rename from rule/domain_keyword.go rename to rule/common/domain_keyword.go index 933e2524..90819b9d 100644 --- a/rule/domain_keyword.go +++ b/rule/common/domain_keyword.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/domain_suffix.go b/rule/common/domain_suffix.go similarity index 98% rename from rule/domain_suffix.go rename to rule/common/domain_suffix.go index 0ea4cea6..d577e721 100644 --- a/rule/domain_suffix.go +++ b/rule/common/domain_suffix.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/final.go b/rule/common/final.go similarity index 97% rename from rule/final.go rename to rule/common/final.go index 532824b9..1cdd20f1 100644 --- a/rule/final.go +++ b/rule/common/final.go @@ -1,4 +1,4 @@ -package rules +package common import ( C "github.com/Dreamacro/clash/constant" diff --git a/rule/geoip.go b/rule/common/geoip.go similarity index 98% rename from rule/geoip.go rename to rule/common/geoip.go index 1783b7b7..1485a335 100644 --- a/rule/geoip.go +++ b/rule/common/geoip.go @@ -1,4 +1,4 @@ -package rules +package common import ( "strings" diff --git a/rule/geosite.go b/rule/common/geosite.go similarity index 98% rename from rule/geosite.go rename to rule/common/geosite.go index 875320bd..fcc04c17 100644 --- a/rule/geosite.go +++ b/rule/common/geosite.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/ipcidr.go b/rule/common/ipcidr.go similarity index 98% rename from rule/ipcidr.go rename to rule/common/ipcidr.go index 8910110f..7f7f437d 100644 --- a/rule/ipcidr.go +++ b/rule/common/ipcidr.go @@ -1,4 +1,4 @@ -package rules +package common import ( "net" diff --git a/rule/common/network_type.go b/rule/common/network_type.go new file mode 100644 index 00000000..965f9575 --- /dev/null +++ b/rule/common/network_type.go @@ -0,0 +1,53 @@ +package common + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + "strings" +) + +type NetworkType struct { + network C.NetWork + adapter string +} + +func NewNetworkType(network, adapter string) (*NetworkType, error) { + ntType := new(NetworkType) + ntType.adapter = adapter + switch strings.ToUpper(network) { + case "TCP": + ntType.network = C.TCP + break + case "UDP": + ntType.network = C.UDP + break + default: + return nil, fmt.Errorf("unsupported network type, only TCP/UDP") + } + + return ntType, nil +} + +func (n *NetworkType) RuleType() C.RuleType { + return C.Network +} + +func (n *NetworkType) Match(metadata *C.Metadata) bool { + return n.network == metadata.NetWork +} + +func (n *NetworkType) Adapter() string { + return n.adapter +} + +func (n *NetworkType) Payload() string { + return n.network.String() +} + +func (n *NetworkType) ShouldResolveIP() bool { + return false +} + +func (n *NetworkType) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/port.go b/rule/common/port.go similarity index 99% rename from rule/port.go rename to rule/common/port.go index e978e28d..6990af50 100644 --- a/rule/port.go +++ b/rule/common/port.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/process.go b/rule/common/process.go similarity index 99% rename from rule/process.go rename to rule/common/process.go index aa2a9129..47731443 100644 --- a/rule/process.go +++ b/rule/common/process.go @@ -1,4 +1,4 @@ -package rules +package common import ( "fmt" diff --git a/rule/logic/and.go b/rule/logic/and.go new file mode 100644 index 00000000..f75de048 --- /dev/null +++ b/rule/logic/and.go @@ -0,0 +1,58 @@ +package logic + +import C "github.com/Dreamacro/clash/constant" + +type AND struct { + rules []C.Rule + payload string + adapter string + needIP bool +} + +func NewAND(payload string, adapter string) (*AND, error) { + and := &AND{payload: payload, adapter: adapter} + rules, err := parseRule(payload) + if err != nil { + return nil, err + } + + and.rules = rules + for _, rule := range rules { + if rule.ShouldResolveIP() { + and.needIP = true + break + } + } + + return and, nil +} + +func (A *AND) RuleType() C.RuleType { + return C.AND +} + +func (A *AND) Match(metadata *C.Metadata) bool { + for _, rule := range A.rules { + if !rule.Match(metadata) { + return false + } + } + + return true +} + +func (A *AND) Adapter() string { + return A.adapter +} + +func (A *AND) Payload() string { + return A.payload +} + +func (A *AND) ShouldResolveIP() bool { + return A.needIP +} + +func (A *AND) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/logic/common.go b/rule/logic/common.go new file mode 100644 index 00000000..4c1aa44c --- /dev/null +++ b/rule/logic/common.go @@ -0,0 +1,185 @@ +package logic + +import ( + "fmt" + "github.com/Dreamacro/clash/common/collections" + C "github.com/Dreamacro/clash/constant" + RC "github.com/Dreamacro/clash/rule/common" + "github.com/Dreamacro/clash/rule/provider" + "regexp" + "strings" +) + +func parseRule(payload string) ([]C.Rule, error) { + regex, err := regexp.Compile("\\(.*\\)") + if err != nil { + return nil, err + } + + if regex.MatchString(payload) { + subRanges, err := format(payload) + if err != nil { + return nil, err + } + rules := make([]C.Rule, 0, len(subRanges)) + + if len(subRanges) == 1 { + subPayload := payload[subRanges[0].start+1 : subRanges[0].end-1] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + } else { + preStart := subRanges[0].start + preEnd := subRanges[0].end + for _, sr := range subRanges[1:] { + if containRange(sr, preStart, preEnd) && sr.start-preStart > 1 { + str := "" + if preStart+1 <= sr.start-1 { + str = strings.TrimSpace(payload[preStart+1 : sr.start-1]) + } + + if str == "AND" || str == "OR" || str == "NOT" { + subPayload := payload[preStart+1 : preEnd] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + preStart = sr.start + preEnd = sr.end + } + + continue + } + + preStart = sr.start + preEnd = sr.end + + subPayload := payload[sr.start+1 : sr.end] + rule, err := payloadToRule(subPayload) + if err != nil { + return nil, err + } + + rules = append(rules, rule) + } + } + + return rules, nil + } + + return nil, fmt.Errorf("payload format error") +} + +func containRange(r Range, preStart, preEnd int) bool { + return preStart < r.start && preEnd > r.end +} + +func payloadToRule(subPayload string) (C.Rule, error) { + splitStr := strings.SplitN(subPayload, ",", 2) + tp := splitStr[0] + payload := splitStr[1] + if tp == "NOT" || tp == "OR" || tp == "AND" { + return parseSubRule(tp, payload, nil) + } + + param := strings.Split(payload, ",") + return parseSubRule(tp, param[0], param[1:]) +} + +func splitSubRule(subRuleStr string) (string, string, []string, error) { + typeAndRule := strings.Split(subRuleStr, ",") + if len(typeAndRule) < 2 { + return "", "", nil, fmt.Errorf("format error:[%s]", typeAndRule) + } + + return strings.TrimSpace(typeAndRule[0]), strings.TrimSpace(typeAndRule[1]), typeAndRule[2:], nil +} + +func parseSubRule(tp, payload string, params []string) (C.Rule, error) { + var ( + parseErr error + parsed C.Rule + ) + + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, "", nil) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, "", nil) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, "", nil) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, "", nil) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, "", noResolve, nil) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, "", nil, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, "", true, nil) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, "", false, nil) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, "", nil) + case "RULE-SET": + parsed, parseErr = provider.NewRuleSet(payload, "", nil) + case "NOT": + parsed, parseErr = NewNOT(payload, "") + case "AND": + parsed, parseErr = NewAND(payload, "") + case "OR": + parsed, parseErr = NewOR(payload, "") + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, "") + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return parsed, parseErr +} + +type Range struct { + start int + end int + index int +} + +func format(payload string) ([]Range, error) { + stack := collections.NewStack() + num := 0 + subRanges := make([]Range, 0) + for i, c := range payload { + if c == '(' { + sr := Range{ + start: i, + index: num, + } + + num++ + stack.Push(sr) + } else if c == ')' { + sr := stack.Pop().(Range) + sr.end = i + subRanges = append(subRanges, sr) + } + } + + if stack.Len() != 0 { + return nil, fmt.Errorf("format error is missing )") + } + + sortResult := make([]Range, len(subRanges)) + for _, sr := range subRanges { + sortResult[sr.index] = sr + } + + return sortResult, nil +} diff --git a/rule/logic/not.go b/rule/logic/not.go new file mode 100644 index 00000000..94ca0e3c --- /dev/null +++ b/rule/logic/not.go @@ -0,0 +1,51 @@ +package logic + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" +) + +type NOT struct { + rule C.Rule + payload string + adapter string +} + +func NewNOT(payload string, adapter string) (*NOT, error) { + not := &NOT{payload: payload, adapter: adapter} + rule, err := parseRule(payload) + if err != nil { + return nil, err + } + + if len(rule) < 1 { + return nil, fmt.Errorf("the parsed rule is empty") + } + + not.rule = rule[0] + return not, nil +} + +func (not *NOT) RuleType() C.RuleType { + return C.NOT +} + +func (not *NOT) Match(metadata *C.Metadata) bool { + return !not.rule.Match(metadata) +} + +func (not *NOT) Adapter() string { + return not.adapter +} + +func (not *NOT) Payload() string { + return not.payload +} + +func (not *NOT) ShouldResolveIP() bool { + return not.rule.ShouldResolveIP() +} + +func (not *NOT) RuleExtra() *C.RuleExtra { + return nil +} diff --git a/rule/logic/or.go b/rule/logic/or.go new file mode 100644 index 00000000..fb38a20f --- /dev/null +++ b/rule/logic/or.go @@ -0,0 +1,58 @@ +package logic + +import C "github.com/Dreamacro/clash/constant" + +type OR struct { + rules []C.Rule + payload string + adapter string + needIP bool +} + +func (or *OR) RuleType() C.RuleType { + return C.OR +} + +func (or *OR) Match(metadata *C.Metadata) bool { + for _, rule := range or.rules { + if rule.Match(metadata) { + return true + } + } + + return false +} + +func (or *OR) Adapter() string { + return or.adapter +} + +func (or *OR) Payload() string { + return or.payload +} + +func (or *OR) ShouldResolveIP() bool { + return or.needIP +} + +func (or *OR) RuleExtra() *C.RuleExtra { + return nil +} + +func NewOR(payload string, adapter string) (*OR, error) { + or := &OR{payload: payload, adapter: adapter} + rules, err := parseRule(payload) + if err != nil { + return nil, err + } + + or.rules = rules + for _, rule := range rules { + if rule.ShouldResolveIP() { + or.needIP = true + break + } + } + + return or, nil +} diff --git a/rule/parser.go b/rule/parser.go index 79c67143..700c51fd 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -1,12 +1,11 @@ -package rules +package rule import ( "fmt" - "github.com/Dreamacro/clash/adapter/provider" - "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" - P "github.com/Dreamacro/clash/constant/provider" - "time" + RC "github.com/Dreamacro/clash/rule/common" + "github.com/Dreamacro/clash/rule/logic" + RP "github.com/Dreamacro/clash/rule/provider" ) func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { @@ -16,81 +15,48 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { ) ruleExtra := &C.RuleExtra{ - Network: findNetwork(params), - SourceIPs: findSourceIPs(params), + Network: RC.FindNetwork(params), + SourceIPs: RC.FindSourceIPs(params), } switch tp { case "DOMAIN": - parsed = NewDomain(payload, target, ruleExtra) + parsed = RC.NewDomain(payload, target, ruleExtra) case "DOMAIN-SUFFIX": - parsed = NewDomainSuffix(payload, target, ruleExtra) + parsed = RC.NewDomainSuffix(payload, target, ruleExtra) case "DOMAIN-KEYWORD": - parsed = NewDomainKeyword(payload, target, ruleExtra) + parsed = RC.NewDomainKeyword(payload, target, ruleExtra) case "GEOSITE": - parsed, parseErr = NewGEOSITE(payload, target, ruleExtra) + parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra) case "GEOIP": - noResolve := HasNoResolve(params) - parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra) + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra) case "IP-CIDR", "IP-CIDR6": - noResolve := HasNoResolve(params) - parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve)) + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": - parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) case "SRC-PORT": - parsed, parseErr = NewPort(payload, target, true, ruleExtra) + parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra) case "DST-PORT": - parsed, parseErr = NewPort(payload, target, false, ruleExtra) + parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra) case "PROCESS-NAME": - parsed, parseErr = NewProcess(payload, target, ruleExtra) + parsed, parseErr = RC.NewProcess(payload, target, ruleExtra) case "MATCH": - parsed = NewMatch(target, ruleExtra) + parsed = RC.NewMatch(target, ruleExtra) case "RULE-SET": - parsed, parseErr = NewRuleSet(payload, target, ruleExtra) + parsed, parseErr = RP.NewRuleSet(payload, target, ruleExtra) + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, target) + case "AND": + parsed, parseErr = logic.NewAND(payload, target) + case "OR": + parsed, parseErr = logic.NewOR(payload, target) + case "NOT": + parsed, parseErr = logic.NewNOT(payload, target) default: parseErr = fmt.Errorf("unsupported rule type %s", tp) } return parsed, parseErr } - -type ruleProviderSchema struct { - Type string `provider:"type"` - Behavior string `provider:"behavior"` - Path string `provider:"path"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` -} - -func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) { - schema := &ruleProviderSchema{} - decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) - if err := decoder.Decode(mapping, schema); err != nil { - return nil, err - } - var behavior P.RuleType - - switch schema.Behavior { - case "domain": - behavior = P.Domain - case "ipcidr": - behavior = P.IPCIDR - case "classical": - behavior = P.Classical - default: - return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) - } - - path := C.Path.Resolve(schema.Path) - var vehicle P.Vehicle - switch schema.Type { - case "file": - vehicle = provider.NewFileVehicle(path) - case "http": - vehicle = provider.NewHTTPVehicle(schema.URL, path) - default: - return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) - } - - return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil -} diff --git a/rule/fetcher.go b/rule/provider/fetcher.go similarity index 99% rename from rule/fetcher.go rename to rule/provider/fetcher.go index 89f45ab1..c8204dae 100644 --- a/rule/fetcher.go +++ b/rule/provider/fetcher.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "bytes" diff --git a/rule/provider/parse.go b/rule/provider/parse.go new file mode 100644 index 00000000..070dd8e0 --- /dev/null +++ b/rule/provider/parse.go @@ -0,0 +1,93 @@ +package provider + +import ( + "fmt" + "github.com/Dreamacro/clash/adapter/provider" + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" + P "github.com/Dreamacro/clash/constant/provider" + RC "github.com/Dreamacro/clash/rule/common" + "time" +) + +type ruleProviderSchema struct { + Type string `provider:"type"` + Behavior string `provider:"behavior"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` +} + +func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) { + schema := &ruleProviderSchema{} + decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) + if err := decoder.Decode(mapping, schema); err != nil { + return nil, err + } + var behavior P.RuleType + + switch schema.Behavior { + case "domain": + behavior = P.Domain + case "ipcidr": + behavior = P.IPCIDR + case "classical": + behavior = P.Classical + default: + return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior) + } + + path := C.Path.Resolve(schema.Path) + var vehicle P.Vehicle + switch schema.Type { + case "file": + vehicle = provider.NewFileVehicle(path) + case "http": + vehicle = provider.NewHTTPVehicle(schema.URL, path) + default: + return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) + } + + return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil +} + +func parseRule(tp, payload, target string, params []string) (C.Rule, error) { + var ( + parseErr error + parsed C.Rule + ) + + ruleExtra := &C.RuleExtra{ + Network: RC.FindNetwork(params), + SourceIPs: RC.FindSourceIPs(params), + } + + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, target, ruleExtra) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, target, ruleExtra) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, target, ruleExtra) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, target, ruleExtra) + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return parsed, parseErr +} diff --git a/rule/provider.go b/rule/provider/provider.go similarity index 98% rename from rule/provider.go rename to rule/provider/provider.go index 561dd12a..f37e6289 100644 --- a/rule/provider.go +++ b/rule/provider/provider.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "encoding/json" @@ -205,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) { return nil, errors.New("error rule type") } - r, err := ParseRule(ruleType, rule, "", params) + r, err := parseRule(ruleType, rule, "", params) if err != nil { return nil, err } diff --git a/rule/rule_set.go b/rule/provider/rule_set.go similarity index 98% rename from rule/rule_set.go rename to rule/provider/rule_set.go index 03e7b2a3..cd06c821 100644 --- a/rule/rule_set.go +++ b/rule/provider/rule_set.go @@ -1,4 +1,4 @@ -package rules +package provider import ( "fmt" diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 05354f2d..c321e9e7 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -3,6 +3,7 @@ package tunnel import ( "context" "fmt" + R "github.com/Dreamacro/clash/rule/common" "net" "runtime" "sync" @@ -15,7 +16,6 @@ import ( "github.com/Dreamacro/clash/constant/provider" icontext "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rule" "github.com/Dreamacro/clash/tunnel/statistic" )