Feature: add no-resolve for ip rules (#375)
This commit is contained in:
parent
93f13c627c
commit
f046ad73d2
12 changed files with 137 additions and 50 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
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
|
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),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue