|  | @@ -3,8 +3,6 @@ package router
 | 
	
		
			
				|  |  |  import (
 | 
	
		
			
				|  |  |  	"context"
 | 
	
		
			
				|  |  |  	"strings"
 | 
	
		
			
				|  |  | -	"sync"
 | 
	
		
			
				|  |  | -	"time"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	"v2ray.com/core/app/dispatcher"
 | 
	
		
			
				|  |  |  	"v2ray.com/core/common/net"
 | 
	
	
		
			
				|  | @@ -67,116 +65,56 @@ func (v *AnyCondition) Len() int {
 | 
	
		
			
				|  |  |  	return len(*v)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -type timedResult struct {
 | 
	
		
			
				|  |  | -	timestamp time.Time
 | 
	
		
			
				|  |  | -	result    bool
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type CachableDomainMatcher struct {
 | 
	
		
			
				|  |  | -	sync.Mutex
 | 
	
		
			
				|  |  | -	matchers *strmatcher.MatcherGroup
 | 
	
		
			
				|  |  | -	cache    map[string]timedResult
 | 
	
		
			
				|  |  | -	lastScan time.Time
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func NewCachableDomainMatcher() *CachableDomainMatcher {
 | 
	
		
			
				|  |  | -	return &CachableDomainMatcher{
 | 
	
		
			
				|  |  | -		matchers: strmatcher.NewMatcherGroup(),
 | 
	
		
			
				|  |  | -		cache:    make(map[string]timedResult, 512),
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  var matcherTypeMap = map[Domain_Type]strmatcher.Type{
 | 
	
		
			
				|  |  |  	Domain_Plain:  strmatcher.Substr,
 | 
	
		
			
				|  |  |  	Domain_Regex:  strmatcher.Regex,
 | 
	
		
			
				|  |  |  	Domain_Domain: strmatcher.Domain,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func (m *CachableDomainMatcher) Add(domain *Domain) error {
 | 
	
		
			
				|  |  | +func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
 | 
	
		
			
				|  |  |  	matcherType, f := matcherTypeMap[domain.Type]
 | 
	
		
			
				|  |  |  	if !f {
 | 
	
		
			
				|  |  | -		return newError("unsupported domain type", domain.Type)
 | 
	
		
			
				|  |  | +		return nil, newError("unsupported domain type", domain.Type)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	matcher, err := matcherType.New(domain.Value)
 | 
	
		
			
				|  |  |  	if err != nil {
 | 
	
		
			
				|  |  | -		return newError("failed to create domain matcher").Base(err)
 | 
	
		
			
				|  |  | +		return nil, newError("failed to create domain matcher").Base(err)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	m.matchers.Add(matcher)
 | 
	
		
			
				|  |  | -	return nil
 | 
	
		
			
				|  |  | +	return matcher, nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func (m *CachableDomainMatcher) applyInternal(domain string) bool {
 | 
	
		
			
				|  |  | -	return m.matchers.Match(domain) > 0
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type cacheResult int
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const (
 | 
	
		
			
				|  |  | -	cacheMiss cacheResult = iota
 | 
	
		
			
				|  |  | -	cacheHitTrue
 | 
	
		
			
				|  |  | -	cacheHitFalse
 | 
	
		
			
				|  |  | -)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func (m *CachableDomainMatcher) findInCache(domain string) cacheResult {
 | 
	
		
			
				|  |  | -	m.Lock()
 | 
	
		
			
				|  |  | -	defer m.Unlock()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	r, f := m.cache[domain]
 | 
	
		
			
				|  |  | -	if !f {
 | 
	
		
			
				|  |  | -		return cacheMiss
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	r.timestamp = time.Now()
 | 
	
		
			
				|  |  | -	m.cache[domain] = r
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if r.result {
 | 
	
		
			
				|  |  | -		return cacheHitTrue
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return cacheHitFalse
 | 
	
		
			
				|  |  | +type DomainMatcher struct {
 | 
	
		
			
				|  |  | +	matchers strmatcher.IndexMatcher
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func (m *CachableDomainMatcher) ApplyDomain(domain string) bool {
 | 
	
		
			
				|  |  | -	if m.matchers.Size() < 64 {
 | 
	
		
			
				|  |  | -		return m.applyInternal(domain)
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	cr := m.findInCache(domain)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if cr == cacheHitTrue {
 | 
	
		
			
				|  |  | -		return true
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if cr == cacheHitFalse {
 | 
	
		
			
				|  |  | -		return false
 | 
	
		
			
				|  |  | +func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
 | 
	
		
			
				|  |  | +	g := strmatcher.NewMatcherGroup()
 | 
	
		
			
				|  |  | +	for _, d := range domains {
 | 
	
		
			
				|  |  | +		m, err := domainToMatcher(d)
 | 
	
		
			
				|  |  | +		if err != nil {
 | 
	
		
			
				|  |  | +			return nil, err
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		g.Add(m)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	r := m.applyInternal(domain)
 | 
	
		
			
				|  |  | -	m.Lock()
 | 
	
		
			
				|  |  | -	defer m.Unlock()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	m.cache[domain] = timedResult{
 | 
	
		
			
				|  |  | -		result:    r,
 | 
	
		
			
				|  |  | -		timestamp: time.Now(),
 | 
	
		
			
				|  |  | +	if len(domains) < 64 {
 | 
	
		
			
				|  |  | +		return &DomainMatcher{
 | 
	
		
			
				|  |  | +			matchers: g,
 | 
	
		
			
				|  |  | +		}, nil
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	now := time.Now()
 | 
	
		
			
				|  |  | -	if len(m.cache) > 256 && now.Sub(m.lastScan)/time.Second > 5 {
 | 
	
		
			
				|  |  | -		now := time.Now()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		for k, v := range m.cache {
 | 
	
		
			
				|  |  | -			if now.Sub(v.timestamp)/time.Second > 60 {
 | 
	
		
			
				|  |  | -				delete(m.cache, k)
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		m.lastScan = now
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +	return &DomainMatcher{
 | 
	
		
			
				|  |  | +		matchers: strmatcher.NewCachedMatcherGroup(g),
 | 
	
		
			
				|  |  | +	}, nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	return r
 | 
	
		
			
				|  |  | +func (m *DomainMatcher) ApplyDomain(domain string) bool {
 | 
	
		
			
				|  |  | +	return m.matchers.Match(domain) > 0
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func (m *CachableDomainMatcher) Apply(ctx context.Context) bool {
 | 
	
		
			
				|  |  | +func (m *DomainMatcher) Apply(ctx context.Context) bool {
 | 
	
		
			
				|  |  |  	dest, ok := proxy.TargetFromContext(ctx)
 | 
	
		
			
				|  |  |  	if !ok {
 | 
	
		
			
				|  |  |  		return false
 |