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) | ||||
| 		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() | ||||
| 	matcherType, f := matcherTypeMap[domain.Type] | ||||
| 	if !f { | ||||
| 		return newError("unsupported domain type", domain.Type) | ||||
| 	} | ||||
| 
 | ||||
| 	matcher, err := matcherType.New(domain.Value) | ||||
| 	if err != nil { | ||||
| 		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
	
	 Darien Raymond
						Darien Raymond