refactor(matcher): replace cidranger with zero-alloc trie, migrate to netip
Replace cidranger third-party dependency with a custom binary trie for zero-allocation CIDR containment lookups. Migrate ipMatcher from string-keyed map to netip.Addr for correct IPv4/IPv6 normalization. Extract shared splitHostPort helper to eliminate repeated host:port parsing boilerplate across all matchers. Add comprehensive benchmarks.
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cidrTrie is a binary trie for zero-allocation netip.Prefix containment lookups.
|
||||||
|
// IPv4 and IPv6 prefixes are stored in separate sub-tries.
|
||||||
|
type cidrTrie struct {
|
||||||
|
root4 *cidrNode
|
||||||
|
root6 *cidrNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type cidrNode struct {
|
||||||
|
hasEntry bool
|
||||||
|
children [2]*cidrNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCIDRTrie() *cidrTrie {
|
||||||
|
return &cidrTrie{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert adds prefix to the trie.
|
||||||
|
func (t *cidrTrie) insert(prefix netip.Prefix) {
|
||||||
|
addr := prefix.Addr()
|
||||||
|
bits := prefix.Bits()
|
||||||
|
if bits == 0 {
|
||||||
|
// Match everything for this address family.
|
||||||
|
root := t.root(addr)
|
||||||
|
if root == nil {
|
||||||
|
root = &cidrNode{hasEntry: true}
|
||||||
|
t.setRoot(addr, root)
|
||||||
|
} else {
|
||||||
|
root.hasEntry = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
root := t.rootOrCreate(addr)
|
||||||
|
b := addr.As16()
|
||||||
|
maxBits := 128
|
||||||
|
if addr.Is4() || addr.Is4In6() {
|
||||||
|
maxBits = 32
|
||||||
|
}
|
||||||
|
if bits > maxBits {
|
||||||
|
bits = maxBits
|
||||||
|
}
|
||||||
|
|
||||||
|
node := root
|
||||||
|
for i := 0; i < bits; i++ {
|
||||||
|
bit := bitAt(b, i, addr.Is4() || addr.Is4In6())
|
||||||
|
if node.children[bit] == nil {
|
||||||
|
node.children[bit] = &cidrNode{}
|
||||||
|
}
|
||||||
|
node = node.children[bit]
|
||||||
|
}
|
||||||
|
node.hasEntry = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains reports whether any inserted prefix covers addr.
|
||||||
|
func (t *cidrTrie) contains(addr netip.Addr) bool {
|
||||||
|
root := t.root(addr)
|
||||||
|
if root == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if root.hasEntry {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
b := addr.As16()
|
||||||
|
is4 := addr.Is4() || addr.Is4In6()
|
||||||
|
maxBits := 128
|
||||||
|
if is4 {
|
||||||
|
maxBits = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
node := root
|
||||||
|
for i := 0; i < maxBits; i++ {
|
||||||
|
bit := bitAt(b, i, is4)
|
||||||
|
child := node.children[bit]
|
||||||
|
if child == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if child.hasEntry {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
node = child
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cidrTrie) root(addr netip.Addr) *cidrNode {
|
||||||
|
if addr.Is4() || addr.Is4In6() {
|
||||||
|
return t.root4
|
||||||
|
}
|
||||||
|
return t.root6
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cidrTrie) setRoot(addr netip.Addr, n *cidrNode) {
|
||||||
|
if addr.Is4() || addr.Is4In6() {
|
||||||
|
t.root4 = n
|
||||||
|
} else {
|
||||||
|
t.root6 = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cidrTrie) rootOrCreate(addr netip.Addr) *cidrNode {
|
||||||
|
if addr.Is4() || addr.Is4In6() {
|
||||||
|
if t.root4 == nil {
|
||||||
|
t.root4 = &cidrNode{}
|
||||||
|
}
|
||||||
|
return t.root4
|
||||||
|
}
|
||||||
|
if t.root6 == nil {
|
||||||
|
t.root6 = &cidrNode{}
|
||||||
|
}
|
||||||
|
return t.root6
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitAt returns the i-th bit (MSB-first) from a 16-byte address representation.
|
||||||
|
// For IPv4, bits start at byte index 12 (the last 4 bytes of the 16-byte form).
|
||||||
|
func bitAt(b [16]byte, i int, is4 bool) uint8 {
|
||||||
|
offset := 0
|
||||||
|
if is4 {
|
||||||
|
offset = 12
|
||||||
|
}
|
||||||
|
byteIdx := offset + i/8
|
||||||
|
bitIdx := 7 - i%8
|
||||||
|
return (b[byteIdx] >> bitIdx) & 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipNetToPrefix converts a net.IPNet to a netip.Prefix.
|
||||||
|
// Returns ok=false if conversion fails.
|
||||||
|
func ipNetToPrefix(inet *net.IPNet) (netip.Prefix, bool) {
|
||||||
|
addr, ok := netip.AddrFromSlice(inet.IP)
|
||||||
|
if !ok {
|
||||||
|
return netip.Prefix{}, false
|
||||||
|
}
|
||||||
|
addr = addr.Unmap()
|
||||||
|
ones, _ := inet.Mask.Size()
|
||||||
|
bits := addr.BitLen()
|
||||||
|
if ones > bits {
|
||||||
|
ones = bits
|
||||||
|
}
|
||||||
|
return netip.PrefixFrom(addr, ones), true
|
||||||
|
}
|
||||||
+38
-28
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
xnet "github.com/go-gost/x/internal/net"
|
xnet "github.com/go-gost/x/internal/net"
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
"github.com/yl2chen/cidranger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Matcher is a generic pattern matcher that tests whether a given value
|
// Matcher is a generic pattern matcher that tests whether a given value
|
||||||
@@ -19,17 +18,35 @@ type Matcher interface {
|
|||||||
Match(v string) bool
|
Match(v string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitHostPort splits addr into host and port without allocating when
|
||||||
|
// addr contains no colon (i.e. no port). For inputs with a colon,
|
||||||
|
// net.SplitHostPort is used as normal.
|
||||||
|
func splitHostPort(addr string) (host, port string) {
|
||||||
|
if !strings.Contains(addr, ":") {
|
||||||
|
return addr, ""
|
||||||
|
}
|
||||||
|
host, port, _ = net.SplitHostPort(addr)
|
||||||
|
if host == "" {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type ipMatcher struct {
|
type ipMatcher struct {
|
||||||
ips map[string]struct{}
|
ips map[netip.Addr]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPMatcher creates a Matcher for a list of IP addresses.
|
// IPMatcher creates a Matcher for a list of IP addresses.
|
||||||
func IPMatcher(ips []net.IP) Matcher {
|
func IPMatcher(ips []net.IP) Matcher {
|
||||||
matcher := &ipMatcher{
|
matcher := &ipMatcher{
|
||||||
ips: make(map[string]struct{}),
|
ips: make(map[netip.Addr]struct{}),
|
||||||
}
|
}
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
matcher.ips[ip.String()] = struct{}{}
|
addr, ok := netip.AddrFromSlice(ip)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matcher.ips[addr.Unmap()] = struct{}{}
|
||||||
}
|
}
|
||||||
return matcher
|
return matcher
|
||||||
}
|
}
|
||||||
@@ -42,7 +59,7 @@ func (m *ipMatcher) Match(ip string) bool {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_, ok := m.ips[addr.String()]
|
_, ok := m.ips[addr.Unmap()]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +78,7 @@ func AddrMatcher(addrs []string) Matcher {
|
|||||||
addrs: make(map[string][]*xnet.PortRange),
|
addrs: make(map[string][]*xnet.PortRange),
|
||||||
}
|
}
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
host, port, _ := net.SplitHostPort(addr)
|
host, port := splitHostPort(addr)
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = strings.ToLower(addr)
|
host = strings.ToLower(addr)
|
||||||
matcher.addrs[host] = append(matcher.addrs[host], nil)
|
matcher.addrs[host] = append(matcher.addrs[host], nil)
|
||||||
@@ -81,10 +98,7 @@ func (m *addrMatcher) Match(addr string) bool {
|
|||||||
if m == nil || len(m.addrs) == 0 {
|
if m == nil || len(m.addrs) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
host, sp, _ := net.SplitHostPort(addr)
|
host, sp := splitHostPort(addr)
|
||||||
if host == "" {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
host = strings.ToLower(host)
|
host = strings.ToLower(host)
|
||||||
port, _ := strconv.Atoi(sp)
|
port, _ := strconv.Atoi(sp)
|
||||||
|
|
||||||
@@ -128,28 +142,30 @@ func portRangeMatch(prs []*xnet.PortRange, port int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cidrMatcher struct {
|
type cidrMatcher struct {
|
||||||
ranger cidranger.Ranger
|
trie *cidrTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
// CIDRMatcher creates a Matcher for a list of CIDR notation IP addresses.
|
// CIDRMatcher creates a Matcher for a list of CIDR notation IP addresses.
|
||||||
func CIDRMatcher(inets []*net.IPNet) Matcher {
|
func CIDRMatcher(inets []*net.IPNet) Matcher {
|
||||||
ranger := cidranger.NewPCTrieRanger()
|
trie := newCIDRTrie()
|
||||||
for _, inet := range inets {
|
for _, inet := range inets {
|
||||||
ranger.Insert(cidranger.NewBasicRangerEntry(*inet))
|
if prefix, ok := ipNetToPrefix(inet); ok {
|
||||||
|
trie.insert(prefix)
|
||||||
}
|
}
|
||||||
return &cidrMatcher{ranger: ranger}
|
}
|
||||||
|
return &cidrMatcher{trie: trie}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *cidrMatcher) Match(ip string) bool {
|
func (m *cidrMatcher) Match(ip string) bool {
|
||||||
if m == nil || m.ranger == nil {
|
if m == nil || m.trie == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if netIP := net.ParseIP(ip); netIP != nil {
|
addr, err := netip.ParseAddr(ip)
|
||||||
b, _ := m.ranger.Contains(netIP)
|
if err != nil {
|
||||||
return b
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return m.trie.contains(addr.Unmap())
|
||||||
|
}
|
||||||
|
|
||||||
type domainMatcher struct {
|
type domainMatcher struct {
|
||||||
domains map[string]struct{}
|
domains map[string]struct{}
|
||||||
@@ -212,7 +228,7 @@ type wildcardMatcher struct {
|
|||||||
func WildcardMatcher(patterns []string) Matcher {
|
func WildcardMatcher(patterns []string) Matcher {
|
||||||
matcher := &wildcardMatcher{}
|
matcher := &wildcardMatcher{}
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
host, port, _ := net.SplitHostPort(pattern)
|
host, port := splitHostPort(pattern)
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = pattern
|
host = pattern
|
||||||
}
|
}
|
||||||
@@ -239,10 +255,7 @@ func (m *wildcardMatcher) Match(addr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
host, sp, _ := net.SplitHostPort(addr)
|
host, sp := splitHostPort(addr)
|
||||||
if host == "" {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
host = strings.ToLower(host)
|
host = strings.ToLower(host)
|
||||||
port, _ := strconv.Atoi(sp)
|
port, _ := strconv.Atoi(sp)
|
||||||
for _, pattern := range m.patterns {
|
for _, pattern := range m.patterns {
|
||||||
@@ -273,10 +286,7 @@ func (m *ipRangeMatcher) Match(addr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(addr)
|
host, _ := splitHostPort(addr)
|
||||||
if host == "" {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
adr, err := netip.ParseAddr(host)
|
adr, err := netip.ParseAddr(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
xnet "github.com/go-gost/x/internal/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// IPMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func BenchmarkIPMatcher(b *testing.B) {
|
||||||
|
ips := []net.IP{
|
||||||
|
net.ParseIP("192.168.1.1"),
|
||||||
|
net.ParseIP("10.0.0.1"),
|
||||||
|
net.ParseIP("::1"),
|
||||||
|
net.ParseIP("2001:db8::1"),
|
||||||
|
}
|
||||||
|
m := IPMatcher(ips)
|
||||||
|
|
||||||
|
b.Run("match", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("192.168.1.1")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("172.16.0.1")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("invalid", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("not-an-ip")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkIPMatcher_Nil(b *testing.B) {
|
||||||
|
var m *ipMatcher
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("192.168.1.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// AddrMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var benchAddrMatcher = AddrMatcher([]string{
|
||||||
|
"example.com:80",
|
||||||
|
"example.com:443",
|
||||||
|
".example.com",
|
||||||
|
"192.168.1.1:8080",
|
||||||
|
"10.0.0.1",
|
||||||
|
".sub.test.com:0-65535",
|
||||||
|
})
|
||||||
|
|
||||||
|
func BenchmarkAddrMatcher(b *testing.B) {
|
||||||
|
b.Run("exact_host_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("example.com:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("dot_prefix_subdomain", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("api.example.com:443")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("dot_prefix_exact", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("example.com:443")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("ip_with_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("192.168.1.1:8080")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("host_only", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("10.0.0.1")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("deep_subdomain", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("a.b.c.sub.test.com:1234")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchAddrMatcher.Match("other.com:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// CIDRMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var benchCIDRMatcher = mustCIDRMatcher([]*net.IPNet{
|
||||||
|
mustParseCIDR("192.168.0.0/16"),
|
||||||
|
mustParseCIDR("10.0.0.0/8"),
|
||||||
|
mustParseCIDR("2001:db8::/32"),
|
||||||
|
})
|
||||||
|
|
||||||
|
func BenchmarkCIDRMatcher(b *testing.B) {
|
||||||
|
b.Run("match_v4", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchCIDRMatcher.Match("192.168.100.50")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("match_v6", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchCIDRMatcher.Match("2001:db8::42")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchCIDRMatcher.Match("8.8.8.8")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("invalid", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchCIDRMatcher.Match("not-an-ip")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// DomainMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var benchDomainMatcher = DomainMatcher([]string{
|
||||||
|
"example.com",
|
||||||
|
".test.org",
|
||||||
|
"sub.domain.io",
|
||||||
|
".deep.sub.example.net",
|
||||||
|
})
|
||||||
|
|
||||||
|
func BenchmarkDomainMatcher(b *testing.B) {
|
||||||
|
b.Run("exact", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchDomainMatcher.Match("example.com")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("dot_prefix_subdomain", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchDomainMatcher.Match("api.test.org")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("dot_prefix_exact", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchDomainMatcher.Match("test.org")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("deep_walk", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchDomainMatcher.Match("a.b.c.deep.sub.example.net")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchDomainMatcher.Match("other.com")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// WildcardMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var benchWildcardMatcher = WildcardMatcher([]string{
|
||||||
|
"*.example.com",
|
||||||
|
"*.example.com:80",
|
||||||
|
"*.test.org:0-65535",
|
||||||
|
"mail.*.company.net",
|
||||||
|
})
|
||||||
|
|
||||||
|
func BenchmarkWildcardMatcher(b *testing.B) {
|
||||||
|
b.Run("match_any_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("api.example.com:443")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("match_specific_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("api.example.com:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("match_range", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("sub.test.org:8080")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("mid_wildcard", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("mail.eu.company.net:25")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("other.com:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("no_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchWildcardMatcher.Match("api.example.com")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// IPRangeMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var benchIPRangeMatcher = IPRangeMatcher([]xnet.IPRange{
|
||||||
|
mustIPRange("192.168.1.0", "192.168.1.255"),
|
||||||
|
mustIPRange("10.0.0.0", "10.255.255.255"),
|
||||||
|
})
|
||||||
|
|
||||||
|
func BenchmarkIPRangeMatcher(b *testing.B) {
|
||||||
|
b.Run("match_with_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchIPRangeMatcher.Match("192.168.1.100:8080")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("match_no_port", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchIPRangeMatcher.Match("10.50.0.1")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchIPRangeMatcher.Match("172.16.0.1:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("invalid", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchIPRangeMatcher.Match("not-an-ip:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// NopMatcher
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func BenchmarkNopMatcher(b *testing.B) {
|
||||||
|
m := NopMatcher()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("anything")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Large-scale AddrMatcher (many patterns)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func BenchmarkAddrMatcher_Large(b *testing.B) {
|
||||||
|
addrs := make([]string, 500)
|
||||||
|
for i := range addrs {
|
||||||
|
addrs[i] = net.JoinHostPort(
|
||||||
|
"host"+string(rune('a'+i%26))+".example.com",
|
||||||
|
strconv.Itoa(i%65536),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
m := AddrMatcher(addrs)
|
||||||
|
|
||||||
|
b.Run("first_match", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("host0.example.com:0")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("unknown.host:80")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Large-scale DomainMatcher (many patterns)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func BenchmarkDomainMatcher_Large(b *testing.B) {
|
||||||
|
domains := make([]string, 1000)
|
||||||
|
for i := range domains {
|
||||||
|
domains[i] = "domain" + string(rune('a'+i%26)) + ".example.com"
|
||||||
|
}
|
||||||
|
m := DomainMatcher(domains)
|
||||||
|
|
||||||
|
b.Run("match", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("domaina.example.com")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("miss", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m.Match("missing.example.com")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func mustParseCIDR(s string) *net.IPNet {
|
||||||
|
_, ipNet, err := net.ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ipNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCIDRMatcher(inets []*net.IPNet) Matcher {
|
||||||
|
return CIDRMatcher(inets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustIPRange(minStr, maxStr string) xnet.IPRange {
|
||||||
|
r := xnet.IPRange{}
|
||||||
|
if err := r.Parse(minStr + "-" + maxStr); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user