Feature: add no-resolve for ip rules (#375)

This commit is contained in:
Fndroid 2019-10-28 00:02:23 +08:00 committed by Dreamacro
parent 207371aeae
commit 82a8c03953
12 changed files with 137 additions and 50 deletions

View File

@ -279,9 +279,10 @@ Rule:
- DOMAIN-KEYWORD,google,auto - DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,auto - DOMAIN,google.com,auto
- DOMAIN-SUFFIX,ad.com,REJECT - DOMAIN-SUFFIX,ad.com,REJECT
- IP-CIDR,127.0.0.0/8,DIRECT
# rename SOURCE-IP-CIDR and would remove after prerelease # rename SOURCE-IP-CIDR and would remove after prerelease
- SRC-IP-CIDR,192.168.1.201/32,DIRECT - SRC-IP-CIDR,192.168.1.201/32,DIRECT
# optional param "no-resolve" for IP rules (GEOIP IP-CIDR)
- IP-CIDR,127.0.0.0/8,DIRECT
- GEOIP,CN,DIRECT - GEOIP,CN,DIRECT
- DST-PORT,80,DIRECT - DST-PORT,80,DIRECT
- SRC-PORT,7777,DIRECT - SRC-PORT,7777,DIRECT

View File

@ -427,14 +427,19 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
var ( var (
payload string payload string
target string target string
params = []string{}
) )
switch len(rule) { switch l := len(rule); {
case 2: case l == 2:
target = rule[1] target = rule[1]
case 3: case l == 3:
payload = rule[1] payload = rule[1]
target = rule[2] target = rule[2]
case l >= 4:
payload = rule[1]
target = rule[2]
params = rule[3:]
default: default:
return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line)
} }
@ -444,7 +449,12 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
} }
rule = trimArr(rule) rule = trimArr(rule)
var parsed C.Rule params = trimArr(params)
var (
parseErr error
parsed C.Rule
)
switch rule[0] { switch rule[0] {
case "DOMAIN": case "DOMAIN":
parsed = R.NewDomain(payload, target) parsed = R.NewDomain(payload, target)
@ -453,26 +463,20 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
case "DOMAIN-KEYWORD": case "DOMAIN-KEYWORD":
parsed = R.NewDomainKeyword(payload, target) parsed = R.NewDomainKeyword(payload, target)
case "GEOIP": case "GEOIP":
parsed = R.NewGEOIP(payload, target) noResolve := R.HasNoResolve(params)
parsed = R.NewGEOIP(payload, target, noResolve)
case "IP-CIDR", "IP-CIDR6": case "IP-CIDR", "IP-CIDR6":
if rule := R.NewIPCIDR(payload, target, false); rule != nil { noResolve := R.HasNoResolve(params)
parsed = rule parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve))
}
// deprecated when bump to 1.0 // deprecated when bump to 1.0
case "SOURCE-IP-CIDR": case "SOURCE-IP-CIDR":
fallthrough fallthrough
case "SRC-IP-CIDR": case "SRC-IP-CIDR":
if rule := R.NewIPCIDR(payload, target, true); rule != nil { parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true))
parsed = rule
}
case "SRC-PORT": case "SRC-PORT":
if rule := R.NewPort(payload, target, true); rule != nil { parsed, parseErr = R.NewPort(payload, target, true)
parsed = rule
}
case "DST-PORT": case "DST-PORT":
if rule := R.NewPort(payload, target, false); rule != nil { parsed, parseErr = R.NewPort(payload, target, false)
parsed = rule
}
case "MATCH": case "MATCH":
fallthrough fallthrough
// deprecated when bump to 1.0 // deprecated when bump to 1.0
@ -480,8 +484,8 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
parsed = R.NewMatch(target) parsed = R.NewMatch(target)
} }
if parsed == nil { if parseErr != nil {
return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line) return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error())
} }
rules = append(rules, parsed) rules = append(rules, parsed)

View File

@ -42,7 +42,8 @@ func (rt RuleType) String() string {
type Rule interface { type Rule interface {
RuleType() RuleType RuleType() RuleType
IsMatch(metadata *Metadata) bool Match(metadata *Metadata) bool
Adapter() string Adapter() string
Payload() string Payload() string
NoResolveIP() bool
} }

21
rules/base.go Normal file
View File

@ -0,0 +1,21 @@
package rules
import (
"errors"
)
var (
errPayload = errors.New("payload error")
errParams = errors.New("params error")
noResolve = "no-resolve"
)
func HasNoResolve(params []string) bool {
for _, p := range params {
if p == noResolve {
return true
}
}
return false
}

View File

@ -15,7 +15,7 @@ func (d *Domain) RuleType() C.RuleType {
return C.Domain return C.Domain
} }
func (d *Domain) IsMatch(metadata *C.Metadata) bool { func (d *Domain) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName { if metadata.AddrType != C.AtypDomainName {
return false return false
} }
@ -30,6 +30,10 @@ func (d *Domain) Payload() string {
return d.domain return d.domain
} }
func (d *Domain) NoResolveIP() bool {
return false
}
func NewDomain(domain string, adapter string) *Domain { func NewDomain(domain string, adapter string) *Domain {
return &Domain{ return &Domain{
domain: strings.ToLower(domain), domain: strings.ToLower(domain),

View File

@ -15,7 +15,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
return C.DomainKeyword return C.DomainKeyword
} }
func (dk *DomainKeyword) IsMatch(metadata *C.Metadata) bool { func (dk *DomainKeyword) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName { if metadata.AddrType != C.AtypDomainName {
return false return false
} }
@ -31,6 +31,10 @@ func (dk *DomainKeyword) Payload() string {
return dk.keyword return dk.keyword
} }
func (dk *DomainKeyword) NoResolveIP() bool {
return false
}
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
return &DomainKeyword{ return &DomainKeyword{
keyword: strings.ToLower(keyword), keyword: strings.ToLower(keyword),

View File

@ -15,7 +15,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
return C.DomainSuffix return C.DomainSuffix
} }
func (ds *DomainSuffix) IsMatch(metadata *C.Metadata) bool { func (ds *DomainSuffix) Match(metadata *C.Metadata) bool {
if metadata.AddrType != C.AtypDomainName { if metadata.AddrType != C.AtypDomainName {
return false return false
} }
@ -31,6 +31,10 @@ func (ds *DomainSuffix) Payload() string {
return ds.suffix return ds.suffix
} }
func (ds *DomainSuffix) NoResolveIP() bool {
return false
}
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
return &DomainSuffix{ return &DomainSuffix{
suffix: strings.ToLower(suffix), suffix: strings.ToLower(suffix),

View File

@ -12,7 +12,7 @@ func (f *Match) RuleType() C.RuleType {
return C.MATCH return C.MATCH
} }
func (f *Match) IsMatch(metadata *C.Metadata) bool { func (f *Match) Match(metadata *C.Metadata) bool {
return true return true
} }
@ -24,6 +24,10 @@ func (f *Match) Payload() string {
return "" return ""
} }
func (f *Match) NoResolveIP() bool {
return false
}
func NewMatch(adapter string) *Match { func NewMatch(adapter string) *Match {
return &Match{ return &Match{
adapter: adapter, adapter: adapter,

View File

@ -17,17 +17,19 @@ var (
type GEOIP struct { type GEOIP struct {
country string country string
adapter string adapter string
noResolveIP bool
} }
func (g *GEOIP) RuleType() C.RuleType { func (g *GEOIP) RuleType() C.RuleType {
return C.GEOIP return C.GEOIP
} }
func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { func (g *GEOIP) Match(metadata *C.Metadata) bool {
if metadata.DstIP == nil { ip := metadata.DstIP
if ip == nil {
return false return false
} }
record, _ := mmdb.Country(metadata.DstIP) record, _ := mmdb.Country(ip)
return record.Country.IsoCode == g.country return record.Country.IsoCode == g.country
} }
@ -39,7 +41,11 @@ func (g *GEOIP) Payload() string {
return g.country return g.country
} }
func NewGEOIP(country string, adapter string) *GEOIP { func (g *GEOIP) NoResolveIP() bool {
return g.noResolveIP
}
func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP {
once.Do(func() { once.Do(func() {
var err error var err error
mmdb, err = geoip2.Open(C.Path.MMDB()) mmdb, err = geoip2.Open(C.Path.MMDB())
@ -47,8 +53,12 @@ func NewGEOIP(country string, adapter string) *GEOIP {
log.Fatalf("Can't load mmdb: %s", err.Error()) log.Fatalf("Can't load mmdb: %s", err.Error())
} }
}) })
return &GEOIP{
geoip := &GEOIP{
country: country, country: country,
adapter: adapter, adapter: adapter,
noResolveIP: noResolveIP,
} }
return geoip
} }

View File

@ -6,10 +6,25 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type IPCIDROption func(*IPCIDR)
func WithIPCIDRSourceIP(b bool) IPCIDROption {
return func(i *IPCIDR) {
i.isSourceIP = b
}
}
func WithIPCIDRNoResolve(noResolve bool) IPCIDROption {
return func(i *IPCIDR) {
i.noResolveIP = !noResolve
}
}
type IPCIDR struct { type IPCIDR struct {
ipnet *net.IPNet ipnet *net.IPNet
adapter string adapter string
isSourceIP bool isSourceIP bool
noResolveIP bool
} }
func (i *IPCIDR) RuleType() C.RuleType { func (i *IPCIDR) RuleType() C.RuleType {
@ -19,7 +34,7 @@ func (i *IPCIDR) RuleType() C.RuleType {
return C.IPCIDR return C.IPCIDR
} }
func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { func (i *IPCIDR) Match(metadata *C.Metadata) bool {
ip := metadata.DstIP ip := metadata.DstIP
if i.isSourceIP { if i.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@ -35,14 +50,24 @@ func (i *IPCIDR) Payload() string {
return i.ipnet.String() return i.ipnet.String()
} }
func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR { func (i *IPCIDR) NoResolveIP() bool {
return i.noResolveIP
}
func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) {
_, ipnet, err := net.ParseCIDR(s) _, ipnet, err := net.ParseCIDR(s)
if err != nil { if err != nil {
return nil return nil, errPayload
} }
return &IPCIDR{
ipcidr := &IPCIDR{
ipnet: ipnet, ipnet: ipnet,
adapter: adapter, adapter: adapter,
isSourceIP: isSourceIP,
} }
for _, o := range opts {
o(ipcidr)
}
return ipcidr, nil
} }

View File

@ -19,7 +19,7 @@ func (p *Port) RuleType() C.RuleType {
return C.DstPort return C.DstPort
} }
func (p *Port) IsMatch(metadata *C.Metadata) bool { func (p *Port) Match(metadata *C.Metadata) bool {
if p.isSource { if p.isSource {
return metadata.SrcPort == p.port return metadata.SrcPort == p.port
} }
@ -34,14 +34,18 @@ func (p *Port) Payload() string {
return p.port return p.port
} }
func NewPort(port string, adapter string, isSource bool) *Port { func (p *Port) NoResolveIP() bool {
return false
}
func NewPort(port string, adapter string, isSource bool) (*Port, error) {
_, err := strconv.Atoi(port) _, err := strconv.Atoi(port)
if err != nil { if err != nil {
return nil return nil, errPayload
} }
return &Port{ return &Port{
adapter: adapter, adapter: adapter,
port: port, port: port,
isSource: isSource, isSource: isSource,
} }, nil
} }

View File

@ -115,6 +115,11 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool {
} }
func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
// handle host equal IP string
if ip := net.ParseIP(metadata.Host); ip != nil {
metadata.DstIP = ip
}
// preprocess enhanced-mode metadata // preprocess enhanced-mode metadata
if t.needLookupIP(metadata) { if t.needLookupIP(metadata) {
host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP) host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP)
@ -243,7 +248,7 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) {
} }
func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool {
return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.DstIP == nil return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil
} }
func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
@ -273,7 +278,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
resolved = true resolved = true
} }
if rule.IsMatch(metadata) { if rule.Match(metadata) {
adapter, ok := t.proxies[rule.Adapter()] adapter, ok := t.proxies[rule.Adapter()]
if !ok { if !ok {
continue continue