mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	And enable U1000 check in staticcheck. Updates #cleanup Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
		
			
				
	
	
		
			324 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package art
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math/bits"
 | |
| 	"net/netip"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	debugStrideInsert = false
 | |
| 	debugStrideDelete = false
 | |
| )
 | |
| 
 | |
| // strideTable is a binary tree that implements an 8-bit routing table.
 | |
| //
 | |
| // The leaves of the binary tree are host routes (/8s). Each parent is a
 | |
| // successively larger prefix that encompasses its children (/7 through /0).
 | |
| type strideTable[T any] struct {
 | |
| 	// prefix is the prefix represented by the 0/0 route of this
 | |
| 	// strideTable. It is used in multi-level tables to support path
 | |
| 	// compression. All strideTables must have a valid prefix
 | |
| 	// (non-zero value, passes IsValid()) whose length is a multiple
 | |
| 	// of 8 (e.g. /8, /16, but not /15).
 | |
| 	prefix netip.Prefix
 | |
| 	// entries is the nodes of the binary tree, laid out in a flattened array.
 | |
| 	//
 | |
| 	// The array indices are arranged by the prefixIndex function, such that the
 | |
| 	// parent of the node at index i is located at index i>>1, and its children
 | |
| 	// at indices i<<1 and (i<<1)+1.
 | |
| 	//
 | |
| 	// A few consequences of this arrangement: host routes (/8) occupy
 | |
| 	// the last numChildren entries in the table; the single default
 | |
| 	// route /0 is at index 1, and index 0 is unused (in the original
 | |
| 	// paper, it's hijacked through sneaky C memory trickery to store
 | |
| 	// the refcount, but this is Go, where we don't store random bits
 | |
| 	// in pointers lest we confuse the GC)
 | |
| 	//
 | |
| 	// A nil value means no route matches the queried route.
 | |
| 	entries [lastHostIndex + 1]*T
 | |
| 	// children are the child tables of this table. Each child
 | |
| 	// represents the address space within one of this table's host
 | |
| 	// routes (/8).
 | |
| 	children [numChildren]*strideTable[T]
 | |
| 	// routeRefs is the number of route entries in this table.
 | |
| 	routeRefs uint16
 | |
| 	// childRefs is the number of child strideTables referenced by this table.
 | |
| 	childRefs uint16
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	// firstHostIndex is the array index of the first host route. This is hostIndex(0/8).
 | |
| 	firstHostIndex = 0b1_0000_0000
 | |
| 	// lastHostIndex is the array index of the last host route. This is hostIndex(0xFF/8).
 | |
| 	lastHostIndex = 0b1_1111_1111
 | |
| 
 | |
| 	// numChildren is the maximum number of child tables a strideTable can hold.
 | |
| 	numChildren = 256
 | |
| )
 | |
| 
 | |
| // getChild returns the child strideTable pointer for addr, or nil if none.
 | |
| func (t *strideTable[T]) getChild(addr uint8) *strideTable[T] {
 | |
| 	return t.children[addr]
 | |
| }
 | |
| 
 | |
| // deleteChild deletes the child strideTable at addr. It is valid to
 | |
| // delete a non-existent child.
 | |
| func (t *strideTable[T]) deleteChild(addr uint8) {
 | |
| 	if t.children[addr] != nil {
 | |
| 		t.childRefs--
 | |
| 	}
 | |
| 	t.children[addr] = nil
 | |
| }
 | |
| 
 | |
| // setChild sets the child strideTable for addr to child.
 | |
| func (t *strideTable[T]) setChild(addr uint8, child *strideTable[T]) {
 | |
| 	if t.children[addr] == nil {
 | |
| 		t.childRefs++
 | |
| 	}
 | |
| 	t.children[addr] = child
 | |
| }
 | |
| 
 | |
| // getOrCreateChild returns the child strideTable for addr, creating it if
 | |
| // necessary.
 | |
| func (t *strideTable[T]) getOrCreateChild(addr uint8) (child *strideTable[T], created bool) {
 | |
| 	ret := t.children[addr]
 | |
| 	if ret == nil {
 | |
| 		ret = &strideTable[T]{
 | |
| 			prefix: childPrefixOf(t.prefix, addr),
 | |
| 		}
 | |
| 		t.children[addr] = ret
 | |
| 		t.childRefs++
 | |
| 		return ret, true
 | |
| 	}
 | |
| 	return ret, false
 | |
| }
 | |
| 
 | |
| // findFirstChild returns the first child strideTable in t, or nil if
 | |
| // t has no children.
 | |
| func (t *strideTable[T]) findFirstChild() *strideTable[T] {
 | |
| 	for _, child := range t.children {
 | |
| 		if child != nil {
 | |
| 			return child
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // hasPrefixRootedAt reports whether t.entries[idx] is the root node of
 | |
| // a prefix.
 | |
| func (t *strideTable[T]) hasPrefixRootedAt(idx int) bool {
 | |
| 	val := t.entries[idx]
 | |
| 	if val == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	parentIdx := parentIndex(idx)
 | |
| 	if parentIdx == 0 {
 | |
| 		// idx is non-nil, and is at the 0/0 route position.
 | |
| 		return true
 | |
| 	}
 | |
| 	if parent := t.entries[parentIdx]; val != parent {
 | |
| 		// parent node in the tree isn't the same prefix, so idx must
 | |
| 		// be a root.
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // allot updates entries whose stored prefixIndex matches oldPrefixIndex, in the
 | |
| // subtree rooted at idx. Matching entries have their stored prefixIndex set to
 | |
| // newPrefixIndex, and their value set to val.
 | |
| //
 | |
| // allot is the core of the ART algorithm, enabling efficient insertion/deletion
 | |
| // while preserving very fast lookups.
 | |
| func (t *strideTable[T]) allot(idx int, old, new *T) {
 | |
| 	if t.entries[idx] != old {
 | |
| 		// current idx isn't what we expect. This is a recursive call
 | |
| 		// that found a child subtree that already has a more specific
 | |
| 		// route installed. Don't touch it.
 | |
| 		return
 | |
| 	}
 | |
| 	t.entries[idx] = new
 | |
| 	if idx >= firstHostIndex {
 | |
| 		// The entry we just updated was a host route, we're at the bottom of
 | |
| 		// the binary tree.
 | |
| 		return
 | |
| 	}
 | |
| 	// Propagate the allotment to this node's children.
 | |
| 	left := idx << 1
 | |
| 	t.allot(left, old, new)
 | |
| 	right := left + 1
 | |
| 	t.allot(right, old, new)
 | |
| }
 | |
| 
 | |
| // insert adds the route addr/prefixLen to t, with value val.
 | |
| func (t *strideTable[T]) insert(addr uint8, prefixLen int, val T) {
 | |
| 	idx := prefixIndex(addr, prefixLen)
 | |
| 	if !t.hasPrefixRootedAt(idx) {
 | |
| 		// This route entry is being freshly created (not just
 | |
| 		// updated), that's a new reference.
 | |
| 		t.routeRefs++
 | |
| 	}
 | |
| 
 | |
| 	old := t.entries[idx]
 | |
| 
 | |
| 	// For allot to work correctly, each distinct prefix in the
 | |
| 	// strideTable must have a different value pointer, even if val is
 | |
| 	// identical. This new()+assignment guarantees that each inserted
 | |
| 	// prefix gets a unique address.
 | |
| 	p := new(T)
 | |
| 	*p = val
 | |
| 
 | |
| 	t.allot(idx, old, p)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // delete removes the route addr/prefixLen from t. Reports whether the
 | |
| // prefix existed in the table prior to deletion.
 | |
| func (t *strideTable[T]) delete(addr uint8, prefixLen int) (wasPresent bool) {
 | |
| 	idx := prefixIndex(addr, prefixLen)
 | |
| 	if !t.hasPrefixRootedAt(idx) {
 | |
| 		// Route entry doesn't exist
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	val := t.entries[idx]
 | |
| 	var parentVal *T
 | |
| 	if parentIdx := parentIndex(idx); parentIdx != 0 {
 | |
| 		parentVal = t.entries[parentIdx]
 | |
| 	}
 | |
| 
 | |
| 	t.allot(idx, val, parentVal)
 | |
| 	t.routeRefs--
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // get does a route lookup for addr and (value, true) if a matching
 | |
| // route exists, or (zero, false) otherwise.
 | |
| func (t *strideTable[T]) get(addr uint8) (ret T, ok bool) {
 | |
| 	if val := t.entries[hostIndex(addr)]; val != nil {
 | |
| 		return *val, true
 | |
| 	}
 | |
| 	return ret, false
 | |
| }
 | |
| 
 | |
| // getValAndChild returns both the prefix value and child strideTable
 | |
| // for addr. valOK reports whether a prefix value exists for addr, and
 | |
| // child is non-nil if a child exists for addr.
 | |
| func (t *strideTable[T]) getValAndChild(addr uint8) (val T, valOK bool, child *strideTable[T]) {
 | |
| 	vp := t.entries[hostIndex(addr)]
 | |
| 	if vp != nil {
 | |
| 		val = *vp
 | |
| 		valOK = true
 | |
| 	}
 | |
| 	child = t.children[addr]
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // TableDebugString returns the contents of t, formatted as a table with one
 | |
| // line per entry.
 | |
| func (t *strideTable[T]) tableDebugString() string {
 | |
| 	var ret bytes.Buffer
 | |
| 	for i, ent := range t.entries {
 | |
| 		if i == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		v := "(nil)"
 | |
| 		if ent != nil {
 | |
| 			v = fmt.Sprint(*ent)
 | |
| 		}
 | |
| 		fmt.Fprintf(&ret, "idx=%3d (%s), val=%v\n", i, formatPrefixTable(inversePrefixIndex(i)), v)
 | |
| 	}
 | |
| 	return ret.String()
 | |
| }
 | |
| 
 | |
| func (t *strideTable[T]) treeDebugStringRec(w io.Writer, idx, indent int) {
 | |
| 	addr, len := inversePrefixIndex(idx)
 | |
| 	if t.hasPrefixRootedAt(idx) {
 | |
| 		fmt.Fprintf(w, "%s%d/%d (%02x/%d) = %v\n", strings.Repeat(" ", indent), addr, len, addr, len, *t.entries[idx])
 | |
| 		indent += 2
 | |
| 	}
 | |
| 	if idx >= firstHostIndex {
 | |
| 		return
 | |
| 	}
 | |
| 	left := idx << 1
 | |
| 	t.treeDebugStringRec(w, left, indent)
 | |
| 	right := left + 1
 | |
| 	t.treeDebugStringRec(w, right, indent)
 | |
| }
 | |
| 
 | |
| // prefixIndex returns the array index of the tree node for addr/prefixLen.
 | |
| func prefixIndex(addr uint8, prefixLen int) int {
 | |
| 	// the prefixIndex of addr/prefixLen is the prefixLen most significant bits
 | |
| 	// of addr, with a 1 tacked onto the left-hand side. For example:
 | |
| 	//
 | |
| 	//   - 0/0 is 1: 0 bits of the addr, with a 1 tacked on
 | |
| 	//   - 42/8 is 1_00101010 (298): all bits of 42, with a 1 tacked on
 | |
| 	//   - 48/4 is 1_0011 (19): 4 most-significant bits of 48, with a 1 tacked on
 | |
| 	return (int(addr) >> (8 - prefixLen)) + (1 << prefixLen)
 | |
| }
 | |
| 
 | |
| // parentIndex returns the index of idx's parent prefix, or 0 if idx
 | |
| // is the index of 0/0.
 | |
| func parentIndex(idx int) int {
 | |
| 	return idx >> 1
 | |
| }
 | |
| 
 | |
| // hostIndex returns the array index of the host route for addr.
 | |
| // It is equivalent to prefixIndex(addr, 8).
 | |
| func hostIndex(addr uint8) int {
 | |
| 	return int(addr) + 1<<8
 | |
| }
 | |
| 
 | |
| // inversePrefixIndex returns the address and prefix length of idx. It is the
 | |
| // inverse of prefixIndex. Only used for debugging and in tests.
 | |
| func inversePrefixIndex(idx int) (addr uint8, len int) {
 | |
| 	lz := bits.LeadingZeros(uint(idx))
 | |
| 	len = strconv.IntSize - lz - 1
 | |
| 	addr = uint8(idx&(0xFF>>(8-len))) << (8 - len)
 | |
| 	return addr, len
 | |
| }
 | |
| 
 | |
| // formatPrefixTable formats addr and len as addr/len, with a constant width
 | |
| // suitable for use in table formatting.
 | |
| func formatPrefixTable(addr uint8, len int) string {
 | |
| 	if len < 0 { // this happens for inversePrefixIndex(0)
 | |
| 		return "<nil>"
 | |
| 	}
 | |
| 	return fmt.Sprintf("%3d/%d", addr, len)
 | |
| }
 | |
| 
 | |
| // childPrefixOf returns the child prefix of parent whose final byte
 | |
| // is stride. The parent prefix must be byte-aligned
 | |
| // (i.e. parent.Bits() must be a multiple of 8), and be no more
 | |
| // specific than /24 for IPv4 or /120 for IPv6.
 | |
| //
 | |
| // For example, childPrefixOf("192.168.0.0/16", 8) == "192.168.8.0/24".
 | |
| func childPrefixOf(parent netip.Prefix, stride uint8) netip.Prefix {
 | |
| 	l := parent.Bits()
 | |
| 	if l%8 != 0 {
 | |
| 		panic("parent prefix is not 8-bit aligned")
 | |
| 	}
 | |
| 	if l >= parent.Addr().BitLen() {
 | |
| 		panic("parent prefix cannot be extended further")
 | |
| 	}
 | |
| 	off := l / 8
 | |
| 	if parent.Addr().Is4() {
 | |
| 		bs := parent.Addr().As4()
 | |
| 		bs[off] = stride
 | |
| 		return netip.PrefixFrom(netip.AddrFrom4(bs), l+8)
 | |
| 	} else {
 | |
| 		bs := parent.Addr().As16()
 | |
| 		bs[off] = stride
 | |
| 		return netip.PrefixFrom(netip.AddrFrom16(bs), l+8)
 | |
| 	}
 | |
| }
 |