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,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

View file

@ -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)

View file

@ -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
}

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
}
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),

View file

@ -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),

View file

@ -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),

View file

@ -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,

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

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) {
// 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