strmatcher

This commit is contained in:
Darien Raymond 2018-06-26 21:57:41 +02:00
parent ed34adf967
commit cb0eb91f2b
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
5 changed files with 214 additions and 111 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 &regexMatcher{
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
}