From 759b7aa934405c123da4f37d1c50c5a36148f6dc Mon Sep 17 00:00:00 2001 From: gVisor bot Date: Thu, 28 May 2020 12:13:05 +0800 Subject: [PATCH] Feature: domain trie support wildcard alias --- README.md | 4 ++- component/fakeip/pool.go | 6 ++-- component/resolver/resolver.go | 2 +- .../{domain-trie/tire.go => trie/domain.go} | 33 +++++++++++++------ .../trie_test.go => trie/domain_test.go} | 3 ++ component/{domain-trie => trie}/node.go | 0 config/config.go | 8 ++--- hub/executor/executor.go | 4 +-- hub/hub.go | 2 +- 9 files changed, 40 insertions(+), 22 deletions(-) rename component/{domain-trie/tire.go => trie/domain.go} (75%) rename component/{domain-trie/trie_test.go => trie/domain_test.go} (94%) rename component/{domain-trie => trie}/node.go (100%) diff --git a/README.md b/README.md index 3c906c33..768cbbf4 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,14 @@ experimental: # - "user1:pass1" # - "user2:pass2" -# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) +# # hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) # # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com) +# # +.foo.com equal .foo.com and foo.com # hosts: # '*.clash.dev': 127.0.0.1 # '.dev': 127.0.0.1 # 'alpha.clash.dev': '::1' +# '+.foo.dev': 127.0.0.1 # dns: # enable: true # set true to enable dns (default is false) diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index b92b55b3..be7bdd8d 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/Dreamacro/clash/common/cache" - trie "github.com/Dreamacro/clash/component/domain-trie" + "github.com/Dreamacro/clash/component/trie" ) // Pool is a implementation about fake ip generator without storage @@ -16,7 +16,7 @@ type Pool struct { gateway uint32 offset uint32 mux sync.Mutex - host *trie.Trie + host *trie.DomainTrie cache *cache.LruCache } @@ -120,7 +120,7 @@ func uintToIP(v uint32) net.IP { } // New return Pool instance -func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) { +func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { min := ipToUint(ipnet.IP) + 2 ones, bits := ipnet.Mask.Size() diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index f7fc1e89..da9fed21 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -5,7 +5,7 @@ import ( "net" "strings" - trie "github.com/Dreamacro/clash/component/domain-trie" + "github.com/Dreamacro/clash/component/trie" ) var ( diff --git a/component/domain-trie/tire.go b/component/trie/domain.go similarity index 75% rename from component/domain-trie/tire.go rename to component/trie/domain.go index e7322e5d..5dc60553 100644 --- a/component/domain-trie/tire.go +++ b/component/trie/domain.go @@ -6,9 +6,10 @@ import ( ) const ( - wildcard = "*" - dotWildcard = "" - domainStep = "." + wildcard = "*" + dotWildcard = "" + complexWildcard = "+" + domainStep = "." ) var ( @@ -16,9 +17,9 @@ var ( ErrInvalidDomain = errors.New("invalid domain") ) -// Trie contains the main logic for adding and searching nodes for domain segments. +// DomainTrie contains the main logic for adding and searching nodes for domain segments. // support wildcard domain (e.g *.google.com) -type Trie struct { +type DomainTrie struct { root *Node } @@ -47,12 +48,25 @@ func validAndSplitDomain(domain string) ([]string, bool) { // 2. *.example.com // 3. subdomain.*.example.com // 4. .example.com -func (t *Trie) Insert(domain string, data interface{}) error { +// 5. +.example.com +func (t *DomainTrie) Insert(domain string, data interface{}) error { parts, valid := validAndSplitDomain(domain) if !valid { return ErrInvalidDomain } + if parts[0] == complexWildcard { + t.insert(parts[1:], data) + parts[0] = dotWildcard + t.insert(parts, data) + } else { + t.insert(parts, data) + } + + return nil +} + +func (t *DomainTrie) insert(parts []string, data interface{}) { node := t.root // reverse storage domain part to save space for i := len(parts) - 1; i >= 0; i-- { @@ -65,7 +79,6 @@ func (t *Trie) Insert(domain string, data interface{}) error { } node.Data = data - return nil } // Search is the most important part of the Trie. @@ -73,7 +86,7 @@ func (t *Trie) Insert(domain string, data interface{}) error { // 1. static part // 2. wildcard domain // 2. dot wildcard domain -func (t *Trie) Search(domain string) *Node { +func (t *DomainTrie) Search(domain string) *Node { parts, valid := validAndSplitDomain(domain) if !valid || parts[0] == "" { return nil @@ -121,6 +134,6 @@ func (t *Trie) Search(domain string) *Node { } // New returns a new, empty Trie. -func New() *Trie { - return &Trie{root: newNode(nil)} +func New() *DomainTrie { + return &DomainTrie{root: newNode(nil)} } diff --git a/component/domain-trie/trie_test.go b/component/trie/domain_test.go similarity index 94% rename from component/domain-trie/trie_test.go rename to component/trie/domain_test.go index fa0f88f7..ded5282b 100644 --- a/component/domain-trie/trie_test.go +++ b/component/trie/domain_test.go @@ -35,6 +35,7 @@ func TestTrie_Wildcard(t *testing.T) { ".org", ".example.net", ".apple.*", + "+.foo.com", } for _, domain := range domains { @@ -46,6 +47,8 @@ func TestTrie_Wildcard(t *testing.T) { assert.NotNil(t, tree.Search("test.org")) assert.NotNil(t, tree.Search("test.example.net")) assert.NotNil(t, tree.Search("test.apple.com")) + assert.NotNil(t, tree.Search("test.foo.com")) + assert.NotNil(t, tree.Search("foo.com")) assert.Nil(t, tree.Search("foo.sub.example.com")) assert.Nil(t, tree.Search("foo.example.dev")) assert.Nil(t, tree.Search("example.com")) diff --git a/component/domain-trie/node.go b/component/trie/node.go similarity index 100% rename from component/domain-trie/node.go rename to component/trie/node.go diff --git a/config/config.go b/config/config.go index 473e404c..c110d257 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,8 @@ import ( "github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" - trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" + "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" @@ -69,7 +69,7 @@ type Config struct { General *General DNS *DNS Experimental *Experimental - Hosts *trie.Trie + Hosts *trie.DomainTrie Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy @@ -450,7 +450,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { return rules, nil } -func parseHosts(cfg *RawConfig) (*trie.Trie, error) { +func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) { tree := trie.New() if len(cfg.Hosts) != 0 { for domain, ipStr := range cfg.Hosts { @@ -586,7 +586,7 @@ func parseDNS(cfg RawDNS) (*DNS, error) { return nil, err } - var host *trie.Trie + var host *trie.DomainTrie // fake ip skip host filter if len(cfg.FakeIPFilter) != 0 { host = trie.New() diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ab848848..2612ee9e 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -9,8 +9,8 @@ import ( "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" - trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -154,7 +154,7 @@ func updateDNS(c *config.DNS) { } } -func updateHosts(tree *trie.Trie) { +func updateHosts(tree *trie.DomainTrie) { resolver.DefaultHosts = tree } diff --git a/hub/hub.go b/hub/hub.go index b7cdea27..471fdb5e 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -1,9 +1,9 @@ package hub import ( + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/route" - "github.com/Dreamacro/clash/config" ) type Option func(*config.Config)