fb0ee8ead4
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.
148 lines
2.8 KiB
Go
148 lines
2.8 KiB
Go
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
|
|
}
|