Feature: add no-resolve for ip rules (#375)
This commit is contained in:
parent
207371aeae
commit
82a8c03953
12 changed files with 137 additions and 50 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
21
rules/base.go
Normal 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
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,17 +17,19 @@ var (
|
|||
type GEOIP struct {
|
||||
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{
|
||||
|
||||
geoip := &GEOIP{
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
noResolveIP: noResolveIP,
|
||||
}
|
||||
|
||||
return geoip
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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{
|
||||
|
||||
ipcidr := &IPCIDR{
|
||||
ipnet: ipnet,
|
||||
adapter: adapter,
|
||||
isSourceIP: isSourceIP,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(ipcidr)
|
||||
}
|
||||
|
||||
return ipcidr, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue