Files
x/internal/matcher/cidr_trie.go
T
ginuerzh fb0ee8ead4 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.
2026-05-26 21:38:55 +08:00

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
}