From f046ad73d292d4dc6f08675c454854a7befa6583 Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Mon, 28 Oct 2019 00:02:23 +0800 Subject: [PATCH] Feature: add no-resolve for ip rules (#375) --- README.md | 3 ++- config/config.go | 42 +++++++++++++++++++++----------------- constant/rule.go | 3 ++- rules/base.go | 21 +++++++++++++++++++ rules/domain.go | 6 +++++- rules/domain_keyword.go | 6 +++++- rules/domain_suffix.go | 6 +++++- rules/final.go | 6 +++++- rules/geoip.go | 28 ++++++++++++++++--------- rules/ipcidr.go | 45 ++++++++++++++++++++++++++++++++--------- rules/port.go | 12 +++++++---- tunnel/tunnel.go | 9 +++++++-- 12 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 rules/base.go diff --git a/README.md b/README.md index b3aae22d..b85e5f01 100644 --- a/README.md +++ b/README.md @@ -279,9 +279,10 @@ Rule: - DOMAIN-KEYWORD,google,auto - DOMAIN,google.com,auto - DOMAIN-SUFFIX,ad.com,REJECT -- IP-CIDR,127.0.0.0/8,DIRECT # rename SOURCE-IP-CIDR and would remove after prerelease - 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 - DST-PORT,80,DIRECT - SRC-PORT,7777,DIRECT diff --git a/config/config.go b/config/config.go index 7a5bce4a..1cccdccd 100644 --- a/config/config.go +++ b/config/config.go @@ -427,14 +427,19 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { var ( payload string target string + params = []string{} ) - switch len(rule) { - case 2: + switch l := len(rule); { + case l == 2: target = rule[1] - case 3: + case l == 3: payload = rule[1] target = rule[2] + case l >= 4: + payload = rule[1] + target = rule[2] + params = rule[3:] default: 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) - var parsed C.Rule + params = trimArr(params) + var ( + parseErr error + parsed C.Rule + ) + switch rule[0] { case "DOMAIN": parsed = R.NewDomain(payload, target) @@ -453,26 +463,20 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { case "DOMAIN-KEYWORD": parsed = R.NewDomainKeyword(payload, target) case "GEOIP": - parsed = R.NewGEOIP(payload, target) + noResolve := R.HasNoResolve(params) + parsed = R.NewGEOIP(payload, target, noResolve) case "IP-CIDR", "IP-CIDR6": - if rule := R.NewIPCIDR(payload, target, false); rule != nil { - parsed = rule - } + noResolve := R.HasNoResolve(params) + parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve)) // deprecated when bump to 1.0 case "SOURCE-IP-CIDR": fallthrough case "SRC-IP-CIDR": - if rule := R.NewIPCIDR(payload, target, true); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true)) case "SRC-PORT": - if rule := R.NewPort(payload, target, true); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewPort(payload, target, true) case "DST-PORT": - if rule := R.NewPort(payload, target, false); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewPort(payload, target, false) case "MATCH": fallthrough // 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) } - if parsed == nil { - return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line) + if parseErr != nil { + return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } rules = append(rules, parsed) diff --git a/constant/rule.go b/constant/rule.go index 1ac599dd..4db333b4 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -42,7 +42,8 @@ func (rt RuleType) String() string { type Rule interface { RuleType() RuleType - IsMatch(metadata *Metadata) bool + Match(metadata *Metadata) bool Adapter() string Payload() string + NoResolveIP() bool } diff --git a/rules/base.go b/rules/base.go new file mode 100644 index 00000000..793cad7e --- /dev/null +++ b/rules/base.go @@ -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 +} diff --git a/rules/domain.go b/rules/domain.go index 1c0fbdd1..373761e5 100644 --- a/rules/domain.go +++ b/rules/domain.go @@ -15,7 +15,7 @@ func (d *Domain) RuleType() C.RuleType { return C.Domain } -func (d *Domain) IsMatch(metadata *C.Metadata) bool { +func (d *Domain) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -30,6 +30,10 @@ func (d *Domain) Payload() string { return d.domain } +func (d *Domain) NoResolveIP() bool { + return false +} + func NewDomain(domain string, adapter string) *Domain { return &Domain{ domain: strings.ToLower(domain), diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index 0708b101..35833625 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -15,7 +15,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType { return C.DomainKeyword } -func (dk *DomainKeyword) IsMatch(metadata *C.Metadata) bool { +func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -31,6 +31,10 @@ func (dk *DomainKeyword) Payload() string { return dk.keyword } +func (dk *DomainKeyword) NoResolveIP() bool { + return false +} + func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { return &DomainKeyword{ keyword: strings.ToLower(keyword), diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index 71004773..69bed9bb 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -15,7 +15,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType { return C.DomainSuffix } -func (ds *DomainSuffix) IsMatch(metadata *C.Metadata) bool { +func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -31,6 +31,10 @@ func (ds *DomainSuffix) Payload() string { return ds.suffix } +func (ds *DomainSuffix) NoResolveIP() bool { + return false +} + func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { return &DomainSuffix{ suffix: strings.ToLower(suffix), diff --git a/rules/final.go b/rules/final.go index 88df0680..dc97ffa5 100644 --- a/rules/final.go +++ b/rules/final.go @@ -12,7 +12,7 @@ func (f *Match) RuleType() C.RuleType { return C.MATCH } -func (f *Match) IsMatch(metadata *C.Metadata) bool { +func (f *Match) Match(metadata *C.Metadata) bool { return true } @@ -24,6 +24,10 @@ func (f *Match) Payload() string { return "" } +func (f *Match) NoResolveIP() bool { + return false +} + func NewMatch(adapter string) *Match { return &Match{ adapter: adapter, diff --git a/rules/geoip.go b/rules/geoip.go index 3ffce9e8..56b75fea 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -15,19 +15,21 @@ var ( ) type GEOIP struct { - country string - adapter string + country string + adapter string + noResolveIP bool } func (g *GEOIP) RuleType() C.RuleType { return C.GEOIP } -func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { - if metadata.DstIP == nil { +func (g *GEOIP) Match(metadata *C.Metadata) bool { + ip := metadata.DstIP + if ip == nil { return false } - record, _ := mmdb.Country(metadata.DstIP) + record, _ := mmdb.Country(ip) return record.Country.IsoCode == g.country } @@ -39,7 +41,11 @@ func (g *GEOIP) Payload() string { 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() { var err error 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()) } }) - return &GEOIP{ - country: country, - adapter: adapter, + + geoip := &GEOIP{ + country: country, + adapter: adapter, + noResolveIP: noResolveIP, } + + return geoip } diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 78a649ab..3e128f3e 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -6,10 +6,25 @@ import ( 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 { - ipnet *net.IPNet - adapter string - isSourceIP bool + ipnet *net.IPNet + adapter string + isSourceIP bool + noResolveIP bool } func (i *IPCIDR) RuleType() C.RuleType { @@ -19,7 +34,7 @@ func (i *IPCIDR) RuleType() C.RuleType { return C.IPCIDR } -func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { +func (i *IPCIDR) Match(metadata *C.Metadata) bool { ip := metadata.DstIP if i.isSourceIP { ip = metadata.SrcIP @@ -35,14 +50,24 @@ func (i *IPCIDR) Payload() 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) if err != nil { - return nil + return nil, errPayload } - return &IPCIDR{ - ipnet: ipnet, - adapter: adapter, - isSourceIP: isSourceIP, + + ipcidr := &IPCIDR{ + ipnet: ipnet, + adapter: adapter, } + + for _, o := range opts { + o(ipcidr) + } + + return ipcidr, nil } diff --git a/rules/port.go b/rules/port.go index ba9bad57..54eb2306 100644 --- a/rules/port.go +++ b/rules/port.go @@ -19,7 +19,7 @@ func (p *Port) RuleType() C.RuleType { return C.DstPort } -func (p *Port) IsMatch(metadata *C.Metadata) bool { +func (p *Port) Match(metadata *C.Metadata) bool { if p.isSource { return metadata.SrcPort == p.port } @@ -34,14 +34,18 @@ func (p *Port) Payload() string { 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) if err != nil { - return nil + return nil, errPayload } return &Port{ adapter: adapter, port: port, isSource: isSource, - } + }, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 7eb16af1..29196556 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -115,6 +115,11 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { } 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 if t.needLookupIP(metadata) { 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 { - 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) { @@ -273,7 +278,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { resolved = true } - if rule.IsMatch(metadata) { + if rule.Match(metadata) { adapter, ok := t.proxies[rule.Adapter()] if !ok { continue