[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)
This commit is contained in:
parent
0867bb8b8a
commit
6a78bca9fb
24 changed files with 637 additions and 98 deletions
56
common/collections/stack.go
Normal file
56
common/collections/stack.go
Normal file
|
@ -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++
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
R "github.com/Dreamacro/clash/rule"
|
||||||
|
RP "github.com/Dreamacro/clash/rule/provider"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -24,7 +26,6 @@ import (
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/dns"
|
"github.com/Dreamacro/clash/dns"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rule"
|
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -484,13 +485,13 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
|
|
||||||
// parse rule provider
|
// parse rule provider
|
||||||
for name, mapping := range cfg.RuleProvider {
|
for name, mapping := range cfg.RuleProvider {
|
||||||
rp, err := R.ParseRuleProvider(name, mapping)
|
rp, err := RP.ParseRuleProvider(name, mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleProviders[name] = &rp
|
ruleProviders[name] = &rp
|
||||||
R.SetRuleProvider(rp)
|
RP.SetRuleProvider(rp)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []C.Rule
|
var rules []C.Rule
|
||||||
|
@ -511,6 +512,10 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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); {
|
switch l := len(rule); {
|
||||||
case l == 2:
|
case l == 2:
|
||||||
target = rule[1]
|
target = rule[1]
|
||||||
|
@ -530,6 +535,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := proxies[target]; mode != T.Script && !ok {
|
if _, ok := proxies[target]; mode != T.Script && !ok {
|
||||||
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
|
||||||
|
|
|
@ -14,7 +14,12 @@ const (
|
||||||
Process
|
Process
|
||||||
Script
|
Script
|
||||||
RuleSet
|
RuleSet
|
||||||
|
Network
|
||||||
|
Combination
|
||||||
MATCH
|
MATCH
|
||||||
|
AND
|
||||||
|
OR
|
||||||
|
NOT
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleType int
|
type RuleType int
|
||||||
|
@ -47,6 +52,14 @@ func (rt RuleType) String() string {
|
||||||
return "Match"
|
return "Match"
|
||||||
case RuleSet:
|
case RuleSet:
|
||||||
return "RuleSet"
|
return "RuleSet"
|
||||||
|
case Network:
|
||||||
|
return "Network"
|
||||||
|
case AND:
|
||||||
|
return "AND"
|
||||||
|
case OR:
|
||||||
|
return "OR"
|
||||||
|
case NOT:
|
||||||
|
return "NOT"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -22,7 +22,7 @@ func HasNoResolve(params []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func findNetwork(params []string) C.NetWork {
|
func FindNetwork(params []string) C.NetWork {
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p == "tcp" {
|
if p == "tcp" {
|
||||||
return C.TCP
|
return C.TCP
|
||||||
|
@ -33,7 +33,7 @@ func findNetwork(params []string) C.NetWork {
|
||||||
return C.ALLNet
|
return C.ALLNet
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSourceIPs(params []string) []*net.IPNet {
|
func FindSourceIPs(params []string) []*net.IPNet {
|
||||||
var ips []*net.IPNet
|
var ips []*net.IPNet
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
if p == noResolve || len(p) < 7 {
|
if p == noResolve || len(p) < 7 {
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
53
rule/common/network_type.go
Normal file
53
rule/common/network_type.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
58
rule/logic/and.go
Normal file
58
rule/logic/and.go
Normal file
|
@ -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
|
||||||
|
}
|
185
rule/logic/common.go
Normal file
185
rule/logic/common.go
Normal file
|
@ -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
|
||||||
|
}
|
51
rule/logic/not.go
Normal file
51
rule/logic/not.go
Normal file
|
@ -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
|
||||||
|
}
|
58
rule/logic/or.go
Normal file
58
rule/logic/or.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
package rules
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
P "github.com/Dreamacro/clash/constant/provider"
|
RC "github.com/Dreamacro/clash/rule/common"
|
||||||
"time"
|
"github.com/Dreamacro/clash/rule/logic"
|
||||||
|
RP "github.com/Dreamacro/clash/rule/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
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{
|
ruleExtra := &C.RuleExtra{
|
||||||
Network: findNetwork(params),
|
Network: RC.FindNetwork(params),
|
||||||
SourceIPs: findSourceIPs(params),
|
SourceIPs: RC.FindSourceIPs(params),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tp {
|
switch tp {
|
||||||
case "DOMAIN":
|
case "DOMAIN":
|
||||||
parsed = NewDomain(payload, target, ruleExtra)
|
parsed = RC.NewDomain(payload, target, ruleExtra)
|
||||||
case "DOMAIN-SUFFIX":
|
case "DOMAIN-SUFFIX":
|
||||||
parsed = NewDomainSuffix(payload, target, ruleExtra)
|
parsed = RC.NewDomainSuffix(payload, target, ruleExtra)
|
||||||
case "DOMAIN-KEYWORD":
|
case "DOMAIN-KEYWORD":
|
||||||
parsed = NewDomainKeyword(payload, target, ruleExtra)
|
parsed = RC.NewDomainKeyword(payload, target, ruleExtra)
|
||||||
case "GEOSITE":
|
case "GEOSITE":
|
||||||
parsed, parseErr = NewGEOSITE(payload, target, ruleExtra)
|
parsed, parseErr = RC.NewGEOSITE(payload, target, ruleExtra)
|
||||||
case "GEOIP":
|
case "GEOIP":
|
||||||
noResolve := HasNoResolve(params)
|
noResolve := RC.HasNoResolve(params)
|
||||||
parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra)
|
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve, ruleExtra)
|
||||||
case "IP-CIDR", "IP-CIDR6":
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
noResolve := HasNoResolve(params)
|
noResolve := RC.HasNoResolve(params)
|
||||||
parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve))
|
parsed, parseErr = RC.NewIPCIDR(payload, target, ruleExtra, RC.WithIPCIDRNoResolve(noResolve))
|
||||||
case "SRC-IP-CIDR":
|
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":
|
case "SRC-PORT":
|
||||||
parsed, parseErr = NewPort(payload, target, true, ruleExtra)
|
parsed, parseErr = RC.NewPort(payload, target, true, ruleExtra)
|
||||||
case "DST-PORT":
|
case "DST-PORT":
|
||||||
parsed, parseErr = NewPort(payload, target, false, ruleExtra)
|
parsed, parseErr = RC.NewPort(payload, target, false, ruleExtra)
|
||||||
case "PROCESS-NAME":
|
case "PROCESS-NAME":
|
||||||
parsed, parseErr = NewProcess(payload, target, ruleExtra)
|
parsed, parseErr = RC.NewProcess(payload, target, ruleExtra)
|
||||||
case "MATCH":
|
case "MATCH":
|
||||||
parsed = NewMatch(target, ruleExtra)
|
parsed = RC.NewMatch(target, ruleExtra)
|
||||||
case "RULE-SET":
|
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:
|
default:
|
||||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed, parseErr
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
93
rule/provider/parse.go
Normal file
93
rule/provider/parse.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -205,7 +205,7 @@ func handleClassicalRules(rules []string) (interface{}, error) {
|
||||||
return nil, errors.New("error rule type")
|
return nil, errors.New("error rule type")
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := ParseRule(ruleType, rule, "", params)
|
r, err := parseRule(ruleType, rule, "", params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package rules
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -3,6 +3,7 @@ package tunnel
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
R "github.com/Dreamacro/clash/rule/common"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -15,7 +16,6 @@ import (
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
icontext "github.com/Dreamacro/clash/context"
|
icontext "github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rule"
|
|
||||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue