diff --git a/constant/rule.go b/constant/rule.go index 7e29a37b..f36a3b51 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -52,5 +52,5 @@ type Rule interface { Adapter() string Payload() string ShouldResolveIP() bool - NetWork() NetWork + RuleExtra() *RuleExtra } diff --git a/constant/rule_extra.go b/constant/rule_extra.go new file mode 100644 index 00000000..c27f276e --- /dev/null +++ b/constant/rule_extra.go @@ -0,0 +1,25 @@ +package constant + +import "net" + +type RuleExtra struct { + Network NetWork + SourceIPs []*net.IPNet +} + +func (re *RuleExtra) NotMatchNetwork(network NetWork) bool { + return re.Network != ALLNet && re.Network != network +} + +func (re *RuleExtra) NotMatchSourceIP(srcIP net.IP) bool { + if re.SourceIPs == nil { + return false + } + + for _, ips := range re.SourceIPs { + if ips.Contains(srcIP) { + return false + } + } + return true +} diff --git a/rule/base.go b/rule/base.go index 17238547..b44c6036 100644 --- a/rule/base.go +++ b/rule/base.go @@ -2,6 +2,8 @@ package rules import ( "errors" + "net" + "strings" C "github.com/Dreamacro/clash/constant" ) @@ -14,6 +16,7 @@ var ( func HasNoResolve(params []string) bool { for _, p := range params { + p = strings.Trim(p, " ") if p == noResolve { return true } @@ -23,6 +26,7 @@ func HasNoResolve(params []string) bool { func findNetwork(params []string) C.NetWork { for _, p := range params { + p = strings.Trim(p, " ") if p == "tcp" { return C.TCP } else if p == "udp" { @@ -31,3 +35,23 @@ func findNetwork(params []string) C.NetWork { } return C.ALLNet } + +func findSourceIPs(params []string) []*net.IPNet { + var ips []*net.IPNet + for _, p := range params { + p = strings.Trim(p, " ") + if p == noResolve || len(p) < 7 { + continue + } + _, ipnet, err := net.ParseCIDR(p) + if err != nil { + continue + } + ips = append(ips, ipnet) + } + + if len(ips) > 0 { + return ips + } + return nil +} diff --git a/rule/domain.go b/rule/domain.go index ae4c46d7..42281b5d 100644 --- a/rule/domain.go +++ b/rule/domain.go @@ -7,9 +7,9 @@ import ( ) type Domain struct { - domain string - adapter string - network C.NetWork + domain string + adapter string + ruleExtra *C.RuleExtra } func (d *Domain) RuleType() C.RuleType { @@ -35,14 +35,14 @@ func (d *Domain) ShouldResolveIP() bool { return false } -func (d *Domain) NetWork() C.NetWork { - return d.network +func (d *Domain) RuleExtra() *C.RuleExtra { + return d.ruleExtra } -func NewDomain(domain string, adapter string, network C.NetWork) *Domain { +func NewDomain(domain string, adapter string, ruleExtra *C.RuleExtra) *Domain { return &Domain{ - domain: strings.ToLower(domain), - adapter: adapter, - network: network, + domain: strings.ToLower(domain), + adapter: adapter, + ruleExtra: ruleExtra, } } diff --git a/rule/domain_keyword.go b/rule/domain_keyword.go index 3d55758c..933e2524 100644 --- a/rule/domain_keyword.go +++ b/rule/domain_keyword.go @@ -7,9 +7,9 @@ import ( ) type DomainKeyword struct { - keyword string - adapter string - network C.NetWork + keyword string + adapter string + ruleExtra *C.RuleExtra } func (dk *DomainKeyword) RuleType() C.RuleType { @@ -36,14 +36,14 @@ func (dk *DomainKeyword) ShouldResolveIP() bool { return false } -func (dk *DomainKeyword) NetWork() C.NetWork { - return dk.network +func (dk *DomainKeyword) RuleExtra() *C.RuleExtra { + return dk.ruleExtra } -func NewDomainKeyword(keyword string, adapter string, network C.NetWork) *DomainKeyword { +func NewDomainKeyword(keyword string, adapter string, ruleExtra *C.RuleExtra) *DomainKeyword { return &DomainKeyword{ - keyword: strings.ToLower(keyword), - adapter: adapter, - network: network, + keyword: strings.ToLower(keyword), + adapter: adapter, + ruleExtra: ruleExtra, } } diff --git a/rule/domain_suffix.go b/rule/domain_suffix.go index 1d1116bd..0ea4cea6 100644 --- a/rule/domain_suffix.go +++ b/rule/domain_suffix.go @@ -7,9 +7,9 @@ import ( ) type DomainSuffix struct { - suffix string - adapter string - network C.NetWork + suffix string + adapter string + ruleExtra *C.RuleExtra } func (ds *DomainSuffix) RuleType() C.RuleType { @@ -36,14 +36,14 @@ func (ds *DomainSuffix) ShouldResolveIP() bool { return false } -func (ds *DomainSuffix) NetWork() C.NetWork { - return ds.network +func (ds *DomainSuffix) RuleExtra() *C.RuleExtra { + return ds.ruleExtra } -func NewDomainSuffix(suffix string, adapter string, network C.NetWork) *DomainSuffix { +func NewDomainSuffix(suffix string, adapter string, ruleExtra *C.RuleExtra) *DomainSuffix { return &DomainSuffix{ - suffix: strings.ToLower(suffix), - adapter: adapter, - network: network, + suffix: strings.ToLower(suffix), + adapter: adapter, + ruleExtra: ruleExtra, } } diff --git a/rule/final.go b/rule/final.go index ffcd0ca8..17d1696d 100644 --- a/rule/final.go +++ b/rule/final.go @@ -28,8 +28,8 @@ func (f *Match) ShouldResolveIP() bool { return false } -func (f *Match) NetWork() C.NetWork { - return C.ALLNet +func (f *Match) RuleExtra() *C.RuleExtra { + return nil } func NewMatch(adapter string) *Match { diff --git a/rule/geoip.go b/rule/geoip.go index d494d46e..50c9b927 100644 --- a/rule/geoip.go +++ b/rule/geoip.go @@ -15,7 +15,7 @@ type GEOIP struct { country string adapter string noResolveIP bool - network C.NetWork + ruleExtra *C.RuleExtra geoIPMatcher *router.GeoIPMatcher } @@ -43,11 +43,11 @@ func (g *GEOIP) ShouldResolveIP() bool { return !g.noResolveIP } -func (g *GEOIP) NetWork() C.NetWork { - return g.network +func (g *GEOIP) RuleExtra() *C.RuleExtra { + return g.ruleExtra } -func NewGEOIP(country string, adapter string, noResolveIP bool, network C.NetWork) (*GEOIP, error) { +func NewGEOIP(country string, adapter string, noResolveIP bool, ruleExtra *C.RuleExtra) (*GEOIP, error) { geoLoaderName := "standard" //geoLoaderName := "memconservative" geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName) @@ -78,7 +78,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool, network C.NetWor country: country, adapter: adapter, noResolveIP: noResolveIP, - network: network, + ruleExtra: ruleExtra, geoIPMatcher: geoIPMatcher, } diff --git a/rule/geosite.go b/rule/geosite.go index 15d3c271..9351b01e 100644 --- a/rule/geosite.go +++ b/rule/geosite.go @@ -12,10 +12,10 @@ import ( ) type GEOSITE struct { - country string - adapter string - network C.NetWork - matcher *router.DomainMatcher + country string + adapter string + ruleExtra *C.RuleExtra + matcher *router.DomainMatcher } func (gs *GEOSITE) RuleType() C.RuleType { @@ -43,11 +43,11 @@ func (gs *GEOSITE) ShouldResolveIP() bool { return false } -func (gs *GEOSITE) NetWork() C.NetWork { - return gs.network +func (gs *GEOSITE) RuleExtra() *C.RuleExtra { + return gs.ruleExtra } -func NewGEOSITE(country string, adapter string, network C.NetWork) (*GEOSITE, error) { +func NewGEOSITE(country string, adapter string, ruleExtra *C.RuleExtra) (*GEOSITE, error) { geoLoaderName := "standard" //geoLoaderName := "memconservative" geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName) @@ -72,10 +72,10 @@ func NewGEOSITE(country string, adapter string, network C.NetWork) (*GEOSITE, er log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, len(domains)) geoSite := &GEOSITE{ - country: country, - adapter: adapter, - network: network, - matcher: matcher, + country: country, + adapter: adapter, + ruleExtra: ruleExtra, + matcher: matcher, } return geoSite, nil diff --git a/rule/ipcidr.go b/rule/ipcidr.go index 03fe2de3..d2d37327 100644 --- a/rule/ipcidr.go +++ b/rule/ipcidr.go @@ -23,7 +23,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { type IPCIDR struct { ipnet *net.IPNet adapter string - network C.NetWork + ruleExtra *C.RuleExtra isSourceIP bool noResolveIP bool } @@ -55,20 +55,20 @@ func (i *IPCIDR) ShouldResolveIP() bool { return !i.noResolveIP } -func (i *IPCIDR) NetWork() C.NetWork { - return i.network +func (i *IPCIDR) RuleExtra() *C.RuleExtra { + return i.ruleExtra } -func NewIPCIDR(s string, adapter string, network C.NetWork, opts ...IPCIDROption) (*IPCIDR, error) { +func NewIPCIDR(s string, adapter string, ruleExtra *C.RuleExtra, opts ...IPCIDROption) (*IPCIDR, error) { _, ipnet, err := net.ParseCIDR(s) if err != nil { return nil, errPayload } - + ruleExtra.SourceIPs = nil ipcidr := &IPCIDR{ - ipnet: ipnet, - adapter: adapter, - network: network, + ipnet: ipnet, + adapter: adapter, + ruleExtra: ruleExtra, } for _, o := range opts { diff --git a/rule/parser.go b/rule/parser.go index 91e3f8de..8e14010b 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -10,32 +10,36 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { var ( parseErr error parsed C.Rule - network = findNetwork(params) ) + ruleExtra := &C.RuleExtra{ + Network: findNetwork(params), + SourceIPs: findSourceIPs(params), + } + switch tp { case "DOMAIN": - parsed = NewDomain(payload, target, network) + parsed = NewDomain(payload, target, ruleExtra) case "DOMAIN-SUFFIX": - parsed = NewDomainSuffix(payload, target, network) + parsed = NewDomainSuffix(payload, target, ruleExtra) case "DOMAIN-KEYWORD": - parsed = NewDomainKeyword(payload, target, network) + parsed = NewDomainKeyword(payload, target, ruleExtra) case "GEOSITE": - parsed, parseErr = NewGEOSITE(payload, target, network) + parsed, parseErr = NewGEOSITE(payload, target, ruleExtra) case "GEOIP": noResolve := HasNoResolve(params) - parsed, parseErr = NewGEOIP(payload, target, noResolve, network) + parsed, parseErr = NewGEOIP(payload, target, noResolve, ruleExtra) case "IP-CIDR", "IP-CIDR6": noResolve := HasNoResolve(params) - parsed, parseErr = NewIPCIDR(payload, target, network, WithIPCIDRNoResolve(noResolve)) + parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": - parsed, parseErr = NewIPCIDR(payload, target, network, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) + parsed, parseErr = NewIPCIDR(payload, target, ruleExtra, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) case "SRC-PORT": - parsed, parseErr = NewPort(payload, target, true, network) + parsed, parseErr = NewPort(payload, target, true, ruleExtra) case "DST-PORT": - parsed, parseErr = NewPort(payload, target, false, network) + parsed, parseErr = NewPort(payload, target, false, ruleExtra) case "PROCESS-NAME": - parsed, parseErr = NewProcess(payload, target, network) + parsed, parseErr = NewProcess(payload, target, ruleExtra) case "MATCH": parsed = NewMatch(target) default: diff --git a/rule/port.go b/rule/port.go index 7a3610ff..ed774ff2 100644 --- a/rule/port.go +++ b/rule/port.go @@ -14,11 +14,11 @@ type portReal struct { } type Port struct { - adapter string - port string - isSource bool - portList []portReal - network C.NetWork + adapter string + port string + isSource bool + portList []portReal + ruleExtra *C.RuleExtra } func (p *Port) RuleType() C.RuleType { @@ -47,8 +47,8 @@ func (p *Port) ShouldResolveIP() bool { return false } -func (p *Port) NetWork() C.NetWork { - return p.network +func (p *Port) RuleExtra() *C.RuleExtra { + return p.ruleExtra } func (p *Port) matchPortReal(portRef string) bool { @@ -67,7 +67,7 @@ func (p *Port) matchPortReal(portRef string) bool { return false } -func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Port, error) { +func NewPort(port string, adapter string, isSource bool, ruleExtra *C.RuleExtra) (*Port, error) { //the port format should be like this: "123/136/137-139" or "[123]/[136-139]" ports := strings.Split(port, "/") if len(ports) > 28 { @@ -114,10 +114,10 @@ func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Po } return &Port{ - adapter: adapter, - port: port, - isSource: isSource, - portList: portList, - network: network, + adapter: adapter, + port: port, + isSource: isSource, + portList: portList, + ruleExtra: ruleExtra, }, nil } diff --git a/rule/process.go b/rule/process.go index 784b6ae6..95351289 100644 --- a/rule/process.go +++ b/rule/process.go @@ -14,9 +14,9 @@ import ( var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) type Process struct { - adapter string - process string - network C.NetWork + adapter string + process string + ruleExtra *C.RuleExtra } func (ps *Process) RuleType() C.RuleType { @@ -71,14 +71,14 @@ func (ps *Process) ShouldResolveIP() bool { return false } -func (ps *Process) NetWork() C.NetWork { - return ps.network +func (ps *Process) RuleExtra() *C.RuleExtra { + return ps.ruleExtra } -func NewProcess(process string, adapter string, network C.NetWork) (*Process, error) { +func NewProcess(process string, adapter string, ruleExtra *C.RuleExtra) (*Process, error) { return &Process{ - adapter: adapter, - process: process, - network: network, + adapter: adapter, + process: process, + ruleExtra: ruleExtra, }, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 94f64727..294b36a4 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -33,7 +33,7 @@ var ( // default timeout for UDP session udpTimeout = 60 * time.Second - preProcessCacheFinder, _ = R.NewProcess("", "", C.ALLNet) + preProcessCacheFinder, _ = R.NewProcess("", "", nil) tunBroadcastAddr = net.IPv4(198, 18, 255, 255) ) @@ -235,7 +235,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { switch true { case rule != nil: - log.Infoln("[UDP] %s(%s) --> %s:%s match %s(%s) %s using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), rule.NetWork().String(), rawPc.Chains().String()) + log.Infoln("[UDP] %s(%s) --> %s:%s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) case mode == Global: log.Infoln("[UDP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) case mode == Direct: @@ -285,7 +285,7 @@ func handleTCPConn(ctx C.ConnContext) { switch true { case rule != nil: - log.Infoln("[TCP] %s(%s) --> %s:%s match %s(%s) %s using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), rule.NetWork().String(), remoteConn.Chains().String()) + log.Infoln("[TCP] %s(%s) --> %s:%s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), metadata.DstPort, rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) case mode == Global: log.Infoln("[TCP] %s(%s) --> %s using GLOBAL", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress()) case mode == Direct: @@ -339,12 +339,21 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { continue } - if rule.NetWork() != C.ALLNet && rule.NetWork() != metadata.NetWork { - continue + extra := rule.RuleExtra() + if extra != nil { + if extra.NotMatchNetwork(metadata.NetWork) { + continue + } + + if extra.NotMatchSourceIP(metadata.SrcIP) { + continue + } } + return adapter, rule, nil } } - return proxies["DIRECT"], nil, nil + //return proxies["DIRECT"], nil, nil + return proxies["REJECT"], nil, nil }