From 56dff65149b45222db1e833511dd71068c9bf7a2 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Tue, 6 Jul 2021 15:07:05 +0800 Subject: [PATCH] Feature: support multiport condition for rule SRC-PORT and DST-PORT --- README.md | 20 +++++++---- config/config.go | 3 ++ dns/filters.go | 37 ++++++++++++++------- listener/tun/dev/dev.go | 8 ++--- rule/port.go | 73 ++++++++++++++++++++++++++++++++++++++--- tunnel/tunnel.go | 5 ++- 6 files changed, 118 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 9b76c70a..d6f4849f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## Getting Started Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). -## Advanced usage for this fork repository +## Advanced usage for this fork branch ### TUN configuration Support macOS Linux and Windows. @@ -46,25 +46,33 @@ tun: ``` ### Rules configuration - Support rule `GEOSITE` -- Support rule `GEOIP` not match condition +- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT` +- Support not match condition for rule `GEOIP` - Support `network` condition for all rules The `GEOSITE` and `GEOIP` databases via https://github.com/Loyalsoldier/v2ray-rules-dat ```yaml rules: # network condition for rules - - DOMAIN-SUFFIX,tabao.com,DIRECT,tcp - - DST-PORT,123,DIRECT,udp + - DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp + - DOMAIN-SUFFIX,bilibili.com,REJECT,udp + + # multiport condition for rule SRC-PORT and DST-PORT + - DST-PORT,123/136/137-139,DIRECT,udp # rule GEOSITE - GEOSITE,category-ads-all,REJECT - GEOSITE,icloud@cn,DIRECT - GEOSITE,apple@cn,DIRECT - GEOSITE,microsoft@cn,DIRECT + - GEOSITE,facebook,PROXY - GEOSITE,youtube,PROXY - GEOSITE,geolocation-cn,DIRECT + - GEOSITE,gfw,PROXY + - GEOSITE,greatfire,PROXY #- GEOSITE,geolocation-!cn,PROXY + - GEOIP,telegram,PROXY,no-resolve - GEOIP,private,DIRECT,no-resolve - GEOIP,cn,DIRECT @@ -76,7 +84,7 @@ rules: ### IPTABLES auto-configuration Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero. -When `TPROXY` is enabled, the `TUN` must be disabled. +If `TPROXY` is enabled, the `TUN` must be disabled. ```yaml # Enable the TPROXY listener tproxy-port: 9898 @@ -84,7 +92,7 @@ tproxy-port: 9898 tun: enable: false ``` -Create user give name `clash`, run `$ sudo useradd -M clash` in command line. +Create user given name `clash` Run Clash by user `clash` as a daemon. diff --git a/config/config.go b/config/config.go index 8a1d4dbd..36ee0264 100644 --- a/config/config.go +++ b/config/config.go @@ -6,6 +6,7 @@ import ( "net" "net/url" "os" + "runtime" "strings" "github.com/Dreamacro/clash/adapter" @@ -433,6 +434,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rules = append(rules, parsed) } + runtime.GC() + return rules, nil } diff --git a/dns/filters.go b/dns/filters.go index 0381036f..3cf70ba4 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -10,7 +10,7 @@ import ( _ "github.com/Dreamacro/clash/rule/geodata/standard" ) -var geoIPMatcher *router.GeoIPMatcher +var multiGeoIPMatcher *router.MultiGeoIPMatcher type fallbackIPFilter interface { Match(net.IP) bool @@ -19,35 +19,49 @@ type fallbackIPFilter interface { type geoipFilter struct{} func (gf *geoipFilter) Match(ip net.IP) bool { - if geoIPMatcher == nil { - countryCode := "cn" + if multiGeoIPMatcher == nil { + countryCodeCN := "cn" + countryCodePrivate := "private" geoLoader, err := geodata.GetGeoDataLoader("standard") if err != nil { log.Errorln("[GeoIPFilter] GetGeoDataLoader error: %s", err.Error()) return false } - records, err := geoLoader.LoadGeoIP(countryCode) + recordsCN, err := geoLoader.LoadGeoIP(countryCodeCN) if err != nil { log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error()) return false } - geoIP := &router.GeoIP{ - CountryCode: countryCode, - Cidr: records, - ReverseMatch: false, + recordsPrivate, err := geoLoader.LoadGeoIP(countryCodePrivate) + if err != nil { + log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error()) + return false } - geoIPMatcher, err = router.NewGeoIPMatcher(geoIP) + geoips := []*router.GeoIP{ + { + CountryCode: countryCodeCN, + Cidr: recordsCN, + ReverseMatch: false, + }, + { + CountryCode: countryCodePrivate, + Cidr: recordsPrivate, + ReverseMatch: false, + }, + } + + multiGeoIPMatcher, err = router.NewMultiGeoIPMatcher(geoips) if err != nil { - log.Errorln("[GeoIPFilter] NewGeoIPMatcher error: %s", err.Error()) + log.Errorln("[GeoIPFilter] NewMultiGeoIPMatcher error: %s", err.Error()) return false } } - return !geoIPMatcher.Match(ip) + return !multiGeoIPMatcher.ApplyIp(ip) } type ipnetFilter struct { @@ -61,6 +75,7 @@ func (inf *ipnetFilter) Match(ip net.IP) bool { type fallbackDomainFilter interface { Match(domain string) bool } + type domainFilter struct { tree *trie.DomainTrie } diff --git a/listener/tun/dev/dev.go b/listener/tun/dev/dev.go index 6a5afd31..56d85f26 100644 --- a/listener/tun/dev/dev.go +++ b/listener/tun/dev/dev.go @@ -19,7 +19,7 @@ type TunDevice interface { } func SetLinuxAutoRoute() { - log.Infoln("Tun adapter auto setting MacOS route") + log.Infoln("Tun adapter auto setting global route") addLinuxSystemRoute("1") addLinuxSystemRoute("2/7") addLinuxSystemRoute("4/6") @@ -32,7 +32,7 @@ func SetLinuxAutoRoute() { } func RemoveLinuxAutoRoute() { - log.Infoln("Tun adapter removing MacOS route") + log.Infoln("Tun adapter removing global route") delLinuxSystemRoute("1") delLinuxSystemRoute("2/7") delLinuxSystemRoute("4/6") @@ -50,7 +50,7 @@ func addLinuxSystemRoute(net string) { } cmd := exec.Command("route", "add", "-net", net, "198.18.0.1") if err := cmd.Run(); err != nil { - log.Errorln("[MacOS auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String()) + log.Errorln("[auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String()) } } @@ -61,6 +61,6 @@ func delLinuxSystemRoute(net string) { cmd := exec.Command("route", "delete", "-net", net, "198.18.0.1") _ = cmd.Run() //if err := cmd.Run(); err != nil { - // log.Errorln("[MacOS auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String()) + // log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String()) //} } diff --git a/rule/port.go b/rule/port.go index df2ee25b..e6118fc6 100644 --- a/rule/port.go +++ b/rule/port.go @@ -1,15 +1,23 @@ package rules import ( + "fmt" "strconv" + "strings" C "github.com/Dreamacro/clash/constant" ) +type portReal struct { + portStart int + portEnd int +} + type Port struct { adapter string port string isSource bool + portList []portReal network C.NetWork } @@ -22,9 +30,9 @@ func (p *Port) RuleType() C.RuleType { func (p *Port) Match(metadata *C.Metadata) bool { if p.isSource { - return metadata.SrcPort == p.port + return p.matchPortReal(metadata.SrcPort) } - return metadata.DstPort == p.port + return p.matchPortReal(metadata.DstPort) } func (p *Port) Adapter() string { @@ -43,15 +51,72 @@ func (p *Port) NetWork() C.NetWork { return p.network } +func (p *Port) matchPortReal(portRef string) bool { + port, _ := strconv.Atoi(portRef) + var rs bool + for _, pr := range p.portList { + if pr.portEnd == -1 { + rs = port == pr.portStart + } else { + rs = port >= pr.portStart && port <= pr.portEnd + } + if rs { + return true + } + } + return false +} + func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Port, error) { - _, err := strconv.Atoi(port) - if err != nil { + ports := strings.Split(port, "/") + if len(ports) > 28 { + return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error()) + } + + var portList []portReal + for _, p := range ports { + if p == "" { + continue + } + + subPort := strings.Split(strings.Trim(p, "[ ]"), "-") + subPortLen := len(subPort) + if subPortLen > 2 { + return nil, errPayload + } + + portStart, err := strconv.Atoi(subPort[0]) + if err != nil || portStart < 0 || portStart > 65535 { + return nil, errPayload + } + + if subPortLen == 1 { + portList = append(portList, portReal{portStart, -1}) + + } else if subPortLen == 2 { + portEnd, err1 := strconv.Atoi(subPort[1]) + if err1 != nil || portEnd < 0 || portEnd > 65535 { + return nil, errPayload + } + + shouldReverse := portStart > portEnd + if shouldReverse { + portList = append(portList, portReal{portEnd, portStart}) + } else { + portList = append(portList, portReal{portStart, portEnd}) + } + } + } + + if len(portList) == 0 { return nil, errPayload } + return &Port{ adapter: adapter, port: port, isSource: isSource, + portList: portList, network: network, }, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 4bcb64da..14fb5025 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -35,8 +35,7 @@ var ( preProcessCacheFinder, _ = R.NewProcess("", "", C.ALLNet) - fakeIpMask = net.IPv4Mask(0, 0, 0xff, 0xff) - fakeIpMaxIp = net.IPv4(0, 0, 255, 255) + tunBroadcastAddr = net.IPv4(198, 18, 255, 255) ) func init() { @@ -144,7 +143,7 @@ func preHandleMetadata(metadata *C.Metadata) error { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) } - } else if resolver.IsFakeIP(metadata.DstIP) && !fakeIpMaxIp.Equal(metadata.DstIP.Mask(fakeIpMask)) { + } else if resolver.IsFakeIP(metadata.DstIP) && !tunBroadcastAddr.Equal(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) } }