mirror of https://github.com/v2ray/v2ray-core
				
				
				
			strmatcher
							parent
							
								
									ed34adf967
								
							
						
					
					
						commit
						cb0eb91f2b
					
				| 
						 | 
				
			
			@ -2,13 +2,12 @@ package router
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"v2ray.com/core/common/net"
 | 
			
		||||
	"v2ray.com/core/common/protocol"
 | 
			
		||||
	"v2ray.com/core/common/strmatcher"
 | 
			
		||||
	"v2ray.com/core/proxy"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,44 +72,41 @@ type timedResult struct {
 | 
			
		|||
 | 
			
		||||
type CachableDomainMatcher struct {
 | 
			
		||||
	sync.Mutex
 | 
			
		||||
	matchers []domainMatcher
 | 
			
		||||
	matchers *strmatcher.MatcherGroup
 | 
			
		||||
	cache    map[string]timedResult
 | 
			
		||||
	lastScan time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCachableDomainMatcher() *CachableDomainMatcher {
 | 
			
		||||
	return &CachableDomainMatcher{
 | 
			
		||||
		matchers: make([]domainMatcher, 0, 64),
 | 
			
		||||
		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 {
 | 
			
		||||
	switch domain.Type {
 | 
			
		||||
	case Domain_Plain:
 | 
			
		||||
		m.matchers = append(m.matchers, NewPlainDomainMatcher(domain.Value))
 | 
			
		||||
	case Domain_Regex:
 | 
			
		||||
		rm, err := NewRegexpDomainMatcher(domain.Value)
 | 
			
		||||
	matcherType, f := matcherTypeMap[domain.Type]
 | 
			
		||||
	if !f {
 | 
			
		||||
		return newError("unsupported domain type", domain.Type)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	matcher, err := matcherType.New(domain.Value)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.matchers = append(m.matchers, rm)
 | 
			
		||||
	case Domain_Domain:
 | 
			
		||||
		m.matchers = append(m.matchers, NewSubDomainMatcher(domain.Value))
 | 
			
		||||
	default:
 | 
			
		||||
		return newError("unknown domain type: ", domain.Type).AtWarning()
 | 
			
		||||
		return newError("failed to create domain matcher").Base(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.matchers.Add(matcher)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *CachableDomainMatcher) applyInternal(domain string) bool {
 | 
			
		||||
	for _, matcher := range m.matchers {
 | 
			
		||||
		if matcher.Apply(domain) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
	return m.matchers.Match(domain) > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type cacheResult int
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +135,7 @@ func (m *CachableDomainMatcher) findInCache(domain string) cacheResult {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (m *CachableDomainMatcher) ApplyDomain(domain string) bool {
 | 
			
		||||
	if len(m.matchers) < 64 {
 | 
			
		||||
	if m.matchers.Size() < 64 {
 | 
			
		||||
		return m.applyInternal(domain)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -190,52 +186,6 @@ func (m *CachableDomainMatcher) Apply(ctx context.Context) bool {
 | 
			
		|||
	return m.ApplyDomain(dest.Address.Domain())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type domainMatcher interface {
 | 
			
		||||
	Apply(domain string) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PlainDomainMatcher string
 | 
			
		||||
 | 
			
		||||
func NewPlainDomainMatcher(pattern string) PlainDomainMatcher {
 | 
			
		||||
	return PlainDomainMatcher(pattern)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v PlainDomainMatcher) Apply(domain string) bool {
 | 
			
		||||
	return strings.Contains(domain, string(v))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RegexpDomainMatcher struct {
 | 
			
		||||
	pattern *regexp.Regexp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRegexpDomainMatcher(pattern string) (*RegexpDomainMatcher, error) {
 | 
			
		||||
	r, err := regexp.Compile(pattern)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &RegexpDomainMatcher{
 | 
			
		||||
		pattern: r,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *RegexpDomainMatcher) Apply(domain string) bool {
 | 
			
		||||
	return v.pattern.MatchString(strings.ToLower(domain))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SubDomainMatcher string
 | 
			
		||||
 | 
			
		||||
func NewSubDomainMatcher(p string) SubDomainMatcher {
 | 
			
		||||
	return SubDomainMatcher(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m SubDomainMatcher) Apply(domain string) bool {
 | 
			
		||||
	pattern := string(m)
 | 
			
		||||
	if !strings.HasSuffix(domain, pattern) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return len(domain) == len(pattern) || domain[len(domain)-len(pattern)-1] == '.'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CIDRMatcher struct {
 | 
			
		||||
	cidr     *net.IPNet
 | 
			
		||||
	onSource bool
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,46 +20,6 @@ import (
 | 
			
		|||
	"v2ray.com/ext/sysio"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSubDomainMatcher(t *testing.T) {
 | 
			
		||||
	assert := With(t)
 | 
			
		||||
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		pattern string
 | 
			
		||||
		input   string
 | 
			
		||||
		output  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			input:   "www.v2ray.com",
 | 
			
		||||
			output:  true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			input:   "v2ray.com",
 | 
			
		||||
			output:  true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			input:   "www.v3ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			input:   "2ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			input:   "xv2ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range cases {
 | 
			
		||||
		matcher := NewSubDomainMatcher(test.pattern)
 | 
			
		||||
		assert(matcher.Apply(test.input) == test.output, IsTrue)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRoutingRule(t *testing.T) {
 | 
			
		||||
	assert := With(t)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
package strmatcher
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type fullMatcher string
 | 
			
		||||
 | 
			
		||||
func (m fullMatcher) Match(s string) bool {
 | 
			
		||||
	return string(m) == s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type substrMatcher string
 | 
			
		||||
 | 
			
		||||
func (m substrMatcher) Match(s string) bool {
 | 
			
		||||
	return strings.Contains(s, string(m))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type domainMatcher string
 | 
			
		||||
 | 
			
		||||
func (m domainMatcher) Match(s string) bool {
 | 
			
		||||
	pattern := string(m)
 | 
			
		||||
	if !strings.HasSuffix(s, pattern) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type regexMatcher struct {
 | 
			
		||||
	pattern *regexp.Regexp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *regexMatcher) Match(s string) bool {
 | 
			
		||||
	return m.pattern.MatchString(s)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
package strmatcher_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"v2ray.com/core/common"
 | 
			
		||||
	. "v2ray.com/core/common/strmatcher"
 | 
			
		||||
	ast "v2ray.com/ext/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMatcher(t *testing.T) {
 | 
			
		||||
	assert := ast.With(t)
 | 
			
		||||
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		pattern string
 | 
			
		||||
		mType   Type
 | 
			
		||||
		input   string
 | 
			
		||||
		output  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Domain,
 | 
			
		||||
			input:   "www.v2ray.com",
 | 
			
		||||
			output:  true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Domain,
 | 
			
		||||
			input:   "v2ray.com",
 | 
			
		||||
			output:  true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Domain,
 | 
			
		||||
			input:   "www.v3ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Domain,
 | 
			
		||||
			input:   "2ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Domain,
 | 
			
		||||
			input:   "xv2ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Full,
 | 
			
		||||
			input:   "v2ray.com",
 | 
			
		||||
			output:  true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			pattern: "v2ray.com",
 | 
			
		||||
			mType:   Full,
 | 
			
		||||
			input:   "xv2ray.com",
 | 
			
		||||
			output:  false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range cases {
 | 
			
		||||
		matcher, err := test.mType.New(test.pattern)
 | 
			
		||||
		common.Must(err)
 | 
			
		||||
		assert(matcher.Match(test.input) == test.output, ast.IsTrue)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
package strmatcher
 | 
			
		||||
 | 
			
		||||
import "regexp"
 | 
			
		||||
 | 
			
		||||
type Matcher interface {
 | 
			
		||||
	Match(string) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Type byte
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Full Type = iota
 | 
			
		||||
	Substr
 | 
			
		||||
	Domain
 | 
			
		||||
	Regex
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (t Type) New(pattern string) (Matcher, error) {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case Full:
 | 
			
		||||
		return fullMatcher(pattern), nil
 | 
			
		||||
	case Substr:
 | 
			
		||||
		return substrMatcher(pattern), nil
 | 
			
		||||
	case Domain:
 | 
			
		||||
		return domainMatcher(pattern), nil
 | 
			
		||||
	case Regex:
 | 
			
		||||
		r, err := regexp.Compile(pattern)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return ®exMatcher{
 | 
			
		||||
			pattern: r,
 | 
			
		||||
		}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		panic("Unknown type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type matcherEntry struct {
 | 
			
		||||
	m  Matcher
 | 
			
		||||
	id uint32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MatcherGroup struct {
 | 
			
		||||
	count         uint32
 | 
			
		||||
	fullMatchers  map[string]uint32
 | 
			
		||||
	otherMatchers []matcherEntry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMatcherGroup() *MatcherGroup {
 | 
			
		||||
	return &MatcherGroup{
 | 
			
		||||
		count:        1,
 | 
			
		||||
		fullMatchers: make(map[string]uint32),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *MatcherGroup) Add(m Matcher) uint32 {
 | 
			
		||||
	c := g.count
 | 
			
		||||
	g.count++
 | 
			
		||||
 | 
			
		||||
	if fm, ok := m.(fullMatcher); ok {
 | 
			
		||||
		g.fullMatchers[string(fm)] = c
 | 
			
		||||
	} else {
 | 
			
		||||
		g.otherMatchers = append(g.otherMatchers, matcherEntry{
 | 
			
		||||
			m:  m,
 | 
			
		||||
			id: c,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *MatcherGroup) Match(pattern string) uint32 {
 | 
			
		||||
	if c, f := g.fullMatchers[pattern]; f {
 | 
			
		||||
		return c
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, e := range g.otherMatchers {
 | 
			
		||||
		if e.m.Match(pattern) {
 | 
			
		||||
			return e.id
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *MatcherGroup) Size() uint32 {
 | 
			
		||||
	return g.count
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue