From 74ed44d87dacf162e310d585475fb7e63d817e6c Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sun, 22 Apr 2018 09:24:33 +0800 Subject: [PATCH] add address bypass --- bypass.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ bypass_test.go | 91 ++++++++++++++++++++++++++++++++++ gost.go | 2 +- node_test.go | 2 +- snapcraft.yaml | 2 +- 5 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 bypass.go create mode 100644 bypass_test.go diff --git a/bypass.go b/bypass.go new file mode 100644 index 0000000..550d34a --- /dev/null +++ b/bypass.go @@ -0,0 +1,130 @@ +package gost + +import ( + "net" + + glob "github.com/gobwas/glob" +) + +// Matcher is a generic pattern matcher, +// it gives the match result of the given pattern for specific v. +type Matcher interface { + Match(v string) bool +} + +// NewMatcher creates a Matcher for the given pattern. +// The acutal Matcher depends on the pattern: +// IP Matcher if pattern is a valid IP address. +// CIDR Matcher if pattern is a valid CIDR address. +// Domain Matcher if both of the above are not. +func NewMatcher(pattern string) Matcher { + if pattern == "" { + return nil + } + if ip := net.ParseIP(pattern); ip != nil { + return IPMatcher(ip) + } + if _, inet, err := net.ParseCIDR(pattern); err == nil { + return CIDRMatcher(inet) + } + return DomainMatcher(pattern) +} + +type ipMatcher struct { + ip net.IP +} + +// IPMatcher creates a Matcher for a specific IP address. +func IPMatcher(ip net.IP) Matcher { + return &ipMatcher{ + ip: ip, + } +} + +func (m *ipMatcher) Match(ip string) bool { + if m == nil { + return false + } + return m.ip.Equal(net.ParseIP(ip)) +} + +type cidrMatcher struct { + ipNet *net.IPNet +} + +// CIDRMatcher creates a Matcher for a specific CIDR notation IP address. +func CIDRMatcher(inet *net.IPNet) Matcher { + return &cidrMatcher{ + ipNet: inet, + } +} + +func (m *cidrMatcher) Match(ip string) bool { + if m == nil || m.ipNet == nil { + return false + } + return m.ipNet.Contains(net.ParseIP(ip)) +} + +type domainMatcher struct { + glob glob.Glob +} + +// DomainMatcher creates a Matcher for a specific domain pattern, +// the pattern can be a plain domain such as 'example.com' +// or a wildcard such as '*.exmaple.com'. +func DomainMatcher(pattern string) Matcher { + return &domainMatcher{ + glob: glob.MustCompile(pattern), + } +} + +func (m *domainMatcher) Match(domain string) bool { + if m == nil || m.glob == nil { + return false + } + return m.glob.Match(domain) +} + +// Bypass is a filter for address (IP or domain). +// It contains a list of matchers. +type Bypass struct { + matchers []Matcher + reverse bool +} + +// NewBypass creates and initializes a new Bypass using matchers as its match rules. +// The rules will be reversed if the reversed is true. +func NewBypass(matchers []Matcher, reverse bool) *Bypass { + return &Bypass{ + matchers: matchers, + reverse: reverse, + } +} + +// NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules. +// The rules will be reversed if the reverse is true. +func NewBypassPatterns(patterns []string, reverse bool) *Bypass { + var matchers []Matcher + for _, pattern := range patterns { + if pattern != "" { + matchers = append(matchers, NewMatcher(pattern)) + } + } + return NewBypass(matchers, reverse) +} + +// Contains reports whether the bypass includes addr. +func (bp *Bypass) Contains(addr string) bool { + for _, matcher := range bp.matchers { + if matcher == nil { + continue + } + matched := matcher.Match(addr) + if (matched && !bp.reverse) || + (!matched && bp.reverse) { + return true + } + } + return false +} diff --git a/bypass_test.go b/bypass_test.go new file mode 100644 index 0000000..dbe4793 --- /dev/null +++ b/bypass_test.go @@ -0,0 +1,91 @@ +package gost + +import "testing" + +var bypassTests = []struct { + patterns []string + reversed bool + addr string + bypassed bool +}{ + // IP address + {[]string{"192.168.1.1"}, false, "192.168.1.1", true}, + {[]string{"192.168.1.1"}, false, "192.168.1.2", false}, + {[]string{"0.0.0.0"}, false, "0.0.0.0", true}, + + // CIDR address + {[]string{"192.168.1.0/0"}, false, "1.2.3.4", true}, + {[]string{"192.168.1.0/8"}, false, "192.1.0.255", true}, + {[]string{"192.168.1.0/16"}, false, "192.168.0.255", true}, + {[]string{"192.168.1.0/24"}, false, "192.168.1.255", true}, + {[]string{"192.168.1.1/32"}, false, "192.168.1.1", true}, + {[]string{"192.168.1.1/32"}, false, "192.168.1.2", false}, + + // plain domain + {[]string{"www.example.com"}, false, "www.example.com", true}, + {[]string{"http://www.example.com"}, false, "http://www.example.com", true}, + {[]string{"http://www.example.com"}, false, "http://example.com", false}, + {[]string{"www.example.com"}, false, "example.com", false}, + + // domain wildcard + + // sub-domain + {[]string{"*.example.com"}, false, "example.com", false}, + {[]string{"*.example.com"}, false, "http://example.com", false}, + {[]string{"*.example.com"}, false, "www.example.com", true}, + {[]string{"*.example.com"}, false, "http://www.example.com", true}, + {[]string{"*.example.com"}, false, "abc.def.example.com", true}, + + {[]string{"*.*.example.com"}, false, "example.com", false}, + {[]string{"*.*.example.com"}, false, "www.example.com", false}, + {[]string{"*.*.example.com"}, false, "abc.def.example.com", true}, + {[]string{"*.*.example.com"}, false, "abc.def.ghi.example.com", true}, + + {[]string{"**.example.com"}, false, "example.com", false}, + {[]string{"**.example.com"}, false, "www.example.com", true}, + {[]string{"**.example.com"}, false, "abc.def.ghi.example.com", true}, + + // prefix wildcard + {[]string{"*example.com"}, false, "example.com", true}, + {[]string{"*example.com"}, false, "www.example.com", true}, + {[]string{"*example.com"}, false, "abc.defexample.com", true}, + {[]string{"*example.com"}, false, "abc.def-example.com", true}, + {[]string{"*example.com"}, false, "abc.def.example.com", true}, + {[]string{"*example.com"}, false, "http://www.example.com", true}, + {[]string{"*example.com"}, false, "e-xample.com", false}, + + {[]string{"http://*.example.com"}, false, "example.com", false}, + {[]string{"http://*.example.com"}, false, "http://example.com", false}, + {[]string{"http://*.example.com"}, false, "http://www.example.com", true}, + {[]string{"http://*.example.com"}, false, "https://www.example.com", false}, + {[]string{"http://*.example.com"}, false, "http://abc.def.example.com", true}, + + {[]string{"www.*.com"}, false, "www.example.com", true}, + {[]string{"www.*.com"}, false, "www.abc.def.com", true}, + + {[]string{"www.*.*.com"}, false, "www.example.com", false}, + {[]string{"www.*.*.com"}, false, "www.abc.def.com", true}, + {[]string{"www.*.*.com"}, false, "www.abc.def.ghi.com", true}, + + {[]string{"www.*example*.com"}, false, "www.example.com", true}, + {[]string{"www.*example*.com"}, false, "www.abc.example.def.com", true}, + {[]string{"www.*example*.com"}, false, "www.e-xample.com", false}, + + {[]string{"www.example.*"}, false, "www.example.com", true}, + {[]string{"www.example.*"}, false, "www.example.io", true}, + {[]string{"www.example.*"}, false, "www.example.com.cn", true}, +} + +func TestBypass(t *testing.T) { + for i, test := range bypassTests { + bp := NewBypassPatterns(test.patterns, test.reversed) + if bp.Contains(test.addr) != test.bypassed { + t.Errorf("test %d failed", i) + } + + rbp := NewBypassPatterns(test.patterns, !test.reversed) + if rbp.Contains(test.addr) == test.bypassed { + t.Errorf("reverse test %d failed", i) + } + } +} diff --git a/gost.go b/gost.go index fbc0dae..1c672b3 100644 --- a/gost.go +++ b/gost.go @@ -14,7 +14,7 @@ import ( ) // Version is the gost version. -const Version = "2.5" +const Version = "2.6-dev" // Debug is a flag that enables the debug log. var Debug bool diff --git a/node_test.go b/node_test.go index fa83b98..48b8ec6 100644 --- a/node_test.go +++ b/node_test.go @@ -35,7 +35,7 @@ func TestParseNode(t *testing.T) { actual, err := ParseNode(test.in) if err != nil { if test.hasError { - t.Logf("ParseNode(%q) got expected error: %v", test.in, err) + // t.Logf("ParseNode(%q) got expected error: %v", test.in, err) continue } t.Errorf("ParseNode(%q) got error: %v", test.in, err) diff --git a/snapcraft.yaml b/snapcraft.yaml index 25f1252..dd3a1de 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: gost -version: '2.5' +version: '2.6' summary: GO Simple Tunnel description: | A simple tunnel written in golang