net/art: allow non-pointers as values

Values are still turned into pointers internally to maintain the
invariants of strideTable, but from the user's perspective it's
now possible to tbl.Insert(pfx, true) rather than
tbl.Insert(pfx, ptr.To(true)).

Updates #7781

Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
David Anderson
2023-08-16 15:51:57 -07:00
committed by Dave Anderson
parent bc0eb6b914
commit e92adfe5e4
4 changed files with 246 additions and 220 deletions

View File

@@ -12,8 +12,6 @@ import (
"strconv"
"testing"
"time"
"tailscale.com/types/ptr"
)
func TestRegression(t *testing.T) {
@@ -30,17 +28,16 @@ func TestRegression(t *testing.T) {
slow := slowPrefixTable[int]{}
p := netip.MustParsePrefix
v := ptr.To(1)
tbl.Insert(p("226.205.197.0/24"), v)
slow.insert(p("226.205.197.0/24"), v)
v = ptr.To(2)
tbl.Insert(p("226.205.0.0/16"), v)
slow.insert(p("226.205.0.0/16"), v)
tbl.Insert(p("226.205.197.0/24"), 1)
slow.insert(p("226.205.197.0/24"), 1)
tbl.Insert(p("226.205.0.0/16"), 2)
slow.insert(p("226.205.0.0/16"), 2)
probe := netip.MustParseAddr("226.205.121.152")
got, want := tbl.Get(probe), slow.get(probe)
if got != want {
t.Fatalf("got %v, want %v", got, want)
got, gotOK := tbl.Get(probe)
want, wantOK := slow.get(probe)
if !getsEqual(got, gotOK, want, wantOK) {
t.Fatalf("got (%v, %v), want (%v, %v)", got, gotOK, want, wantOK)
}
})
@@ -49,18 +46,18 @@ func TestRegression(t *testing.T) {
// within computePrefixSplit.
t1, t2 := &Table[int]{}, &Table[int]{}
p := netip.MustParsePrefix
v1, v2 := ptr.To(1), ptr.To(2)
t1.Insert(p("136.20.0.0/16"), v1)
t1.Insert(p("136.20.201.62/32"), v2)
t1.Insert(p("136.20.0.0/16"), 1)
t1.Insert(p("136.20.201.62/32"), 2)
t2.Insert(p("136.20.201.62/32"), v2)
t2.Insert(p("136.20.0.0/16"), v1)
t2.Insert(p("136.20.201.62/32"), 2)
t2.Insert(p("136.20.0.0/16"), 1)
a := netip.MustParseAddr("136.20.54.139")
got, want := t2.Get(a), t1.Get(a)
if got != want {
t.Errorf("Get(%q) is insertion order dependent (t1=%v, t2=%v)", a, want, got)
got1, ok1 := t1.Get(a)
got2, ok2 := t2.Get(a)
if !getsEqual(got1, ok1, got2, ok2) {
t.Errorf("Get(%q) is insertion order dependent: t1=(%v, %v), t2=(%v, %v)", a, got1, ok1, got2, ok2)
}
})
}
@@ -99,7 +96,7 @@ func TestInsert(t *testing.T) {
p := netip.MustParsePrefix
// Create a new leaf strideTable, with compressed path
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.1/32"), 1)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", -1},
@@ -114,7 +111,7 @@ func TestInsert(t *testing.T) {
})
// Insert into previous leaf, no tree changes
tbl.Insert(p("192.168.0.2/32"), ptr.To(2))
tbl.Insert(p("192.168.0.2/32"), 2)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -129,7 +126,7 @@ func TestInsert(t *testing.T) {
})
// Insert into previous leaf, unaligned prefix covering the /32s
tbl.Insert(p("192.168.0.0/26"), ptr.To(7))
tbl.Insert(p("192.168.0.0/26"), 7)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -144,7 +141,7 @@ func TestInsert(t *testing.T) {
})
// Create a different leaf elsewhere
tbl.Insert(p("10.0.0.0/27"), ptr.To(3))
tbl.Insert(p("10.0.0.0/27"), 3)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -159,7 +156,7 @@ func TestInsert(t *testing.T) {
})
// Insert that creates a new intermediate table and a new child
tbl.Insert(p("192.168.1.1/32"), ptr.To(4))
tbl.Insert(p("192.168.1.1/32"), 4)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -174,7 +171,7 @@ func TestInsert(t *testing.T) {
})
// Insert that creates a new intermediate table but no new child
tbl.Insert(p("192.170.0.0/16"), ptr.To(5))
tbl.Insert(p("192.170.0.0/16"), 5)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -190,7 +187,7 @@ func TestInsert(t *testing.T) {
// New leaf in a different subtree, so the next insert can test a
// variant of decompression.
tbl.Insert(p("192.180.0.1/32"), ptr.To(8))
tbl.Insert(p("192.180.0.1/32"), 8)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -206,7 +203,7 @@ func TestInsert(t *testing.T) {
// Insert that creates a new intermediate table but no new child,
// with an unaligned intermediate
tbl.Insert(p("192.180.0.0/21"), ptr.To(9))
tbl.Insert(p("192.180.0.0/21"), 9)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -221,7 +218,7 @@ func TestInsert(t *testing.T) {
})
// Insert a default route, those have their own codepath.
tbl.Insert(p("0.0.0.0/0"), ptr.To(6))
tbl.Insert(p("0.0.0.0/0"), 6)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -238,7 +235,7 @@ func TestInsert(t *testing.T) {
// Now all of the above again, but for IPv6.
// Create a new leaf strideTable, with compressed path
tbl.Insert(p("ff:aaaa::1/128"), ptr.To(1))
tbl.Insert(p("ff:aaaa::1/128"), 1)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", -1},
@@ -253,7 +250,7 @@ func TestInsert(t *testing.T) {
})
// Insert into previous leaf, no tree changes
tbl.Insert(p("ff:aaaa::2/128"), ptr.To(2))
tbl.Insert(p("ff:aaaa::2/128"), 2)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -268,7 +265,7 @@ func TestInsert(t *testing.T) {
})
// Insert into previous leaf, unaligned prefix covering the /128s
tbl.Insert(p("ff:aaaa::/125"), ptr.To(7))
tbl.Insert(p("ff:aaaa::/125"), 7)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -283,7 +280,7 @@ func TestInsert(t *testing.T) {
})
// Create a different leaf elsewhere
tbl.Insert(p("ffff:bbbb::/120"), ptr.To(3))
tbl.Insert(p("ffff:bbbb::/120"), 3)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -298,7 +295,7 @@ func TestInsert(t *testing.T) {
})
// Insert that creates a new intermediate table and a new child
tbl.Insert(p("ff:aaaa:aaaa::1/128"), ptr.To(4))
tbl.Insert(p("ff:aaaa:aaaa::1/128"), 4)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -313,7 +310,7 @@ func TestInsert(t *testing.T) {
})
// Insert that creates a new intermediate table but no new child
tbl.Insert(p("ff:aaaa:aaaa:bb00::/56"), ptr.To(5))
tbl.Insert(p("ff:aaaa:aaaa:bb00::/56"), 5)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -329,7 +326,7 @@ func TestInsert(t *testing.T) {
// New leaf in a different subtree, so the next insert can test a
// variant of decompression.
tbl.Insert(p("ff:cccc::1/128"), ptr.To(8))
tbl.Insert(p("ff:cccc::1/128"), 8)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -345,7 +342,7 @@ func TestInsert(t *testing.T) {
// Insert that creates a new intermediate table but no new child,
// with an unaligned intermediate
tbl.Insert(p("ff:cccc::/37"), ptr.To(9))
tbl.Insert(p("ff:cccc::/37"), 9)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -360,7 +357,7 @@ func TestInsert(t *testing.T) {
})
// Insert a default route, those have their own codepath.
tbl.Insert(p("::/0"), ptr.To(6))
tbl.Insert(p("::/0"), 6)
checkRoutes(t, tbl, []tableTest{
{"ff:aaaa::1", 1},
{"ff:aaaa::2", 2},
@@ -384,7 +381,7 @@ func TestDelete(t *testing.T) {
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("10.0.0.0/8"), ptr.To(1))
tbl.Insert(p("10.0.0.0/8"), 1)
checkRoutes(t, tbl, []tableTest{
{"10.0.0.1", 1},
{"255.255.255.255", -1},
@@ -403,7 +400,7 @@ func TestDelete(t *testing.T) {
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.1/32"), 1)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"255.255.255.255", -1},
@@ -421,8 +418,8 @@ func TestDelete(t *testing.T) {
// Create an intermediate with 2 children, then delete one leaf.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.180.0.1/32"), ptr.To(2))
tbl.Insert(p("192.168.0.1/32"), 1)
tbl.Insert(p("192.180.0.1/32"), 2)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.180.0.1", 2},
@@ -442,9 +439,9 @@ func TestDelete(t *testing.T) {
// Same, but the intermediate carries a route as well.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.180.0.1/32"), ptr.To(2))
tbl.Insert(p("192.0.0.0/10"), ptr.To(3))
tbl.Insert(p("192.168.0.1/32"), 1)
tbl.Insert(p("192.180.0.1/32"), 2)
tbl.Insert(p("192.0.0.0/10"), 3)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.180.0.1", 2},
@@ -466,9 +463,9 @@ func TestDelete(t *testing.T) {
// Intermediate with 3 leaves, then delete one leaf.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.180.0.1/32"), ptr.To(2))
tbl.Insert(p("192.200.0.1/32"), ptr.To(3))
tbl.Insert(p("192.168.0.1/32"), 1)
tbl.Insert(p("192.180.0.1/32"), 2)
tbl.Insert(p("192.200.0.1/32"), 3)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.180.0.1", 2},
@@ -490,7 +487,7 @@ func TestDelete(t *testing.T) {
// Delete non-existent prefix, missing strideTable path.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.1/32"), 1)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.255.0.1", -1},
@@ -509,7 +506,7 @@ func TestDelete(t *testing.T) {
// with a wrong turn.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.1/32"), 1)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.255.0.1", -1},
@@ -528,7 +525,7 @@ func TestDelete(t *testing.T) {
// leaf doesn't contain route.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.1/32"), 1)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.255.0.1", -1},
@@ -547,8 +544,8 @@ func TestDelete(t *testing.T) {
// compactable.
tbl := &Table[int]{}
checkSize(t, tbl, 2)
tbl.Insert(p("192.168.0.1/32"), ptr.To(1))
tbl.Insert(p("192.168.0.0/22"), ptr.To(2))
tbl.Insert(p("192.168.0.1/32"), 1)
tbl.Insert(p("192.168.0.0/22"), 2)
checkRoutes(t, tbl, []tableTest{
{"192.168.0.1", 1},
{"192.168.0.2", 2},
@@ -568,7 +565,7 @@ func TestDelete(t *testing.T) {
// Default routes have a special case in the code.
tbl := &Table[int]{}
tbl.Insert(p("0.0.0.0/0"), ptr.To(1))
tbl.Insert(p("0.0.0.0/0"), 1)
tbl.Delete(p("0.0.0.0/0"))
checkRoutes(t, tbl, []tableTest{
@@ -595,20 +592,20 @@ func TestInsertCompare(t *testing.T) {
t.Logf(fast.debugSummary())
}
seenVals4 := map[*int]bool{}
seenVals6 := map[*int]bool{}
seenVals4 := map[int]bool{}
seenVals6 := map[int]bool{}
for i := 0; i < 10_000; i++ {
a := randomAddr()
slowVal := slow.get(a)
fastVal := fast.Get(a)
slowVal, slowOK := slow.get(a)
fastVal, fastOK := fast.Get(a)
if !getsEqual(slowVal, slowOK, fastVal, fastOK) {
t.Fatalf("get(%q) = (%v, %v), want (%v, %v)", a, fastVal, fastOK, slowVal, slowOK)
}
if a.Is6() {
seenVals6[fastVal] = true
} else {
seenVals4[fastVal] = true
}
if slowVal != fastVal {
t.Fatalf("get(%q) = %p, want %p", a, fastVal, slowVal)
}
}
// Empirically, 10k probes into 5k v4 prefixes and 5k v6 prefixes results in
@@ -667,13 +664,10 @@ func TestInsertShuffled(t *testing.T) {
}
for _, a := range addrs {
val1 := rt.Get(a)
val2 := rt2.Get(a)
if val1 == nil && val2 == nil {
continue
}
if (val1 == nil && val2 != nil) || (val1 != nil && val2 == nil) || (*val1 != *val2) {
t.Fatalf("get(%q) = %s, want %s", a, printIntPtr(val2), printIntPtr(val1))
val1, ok1 := rt.Get(a)
val2, ok2 := rt2.Get(a)
if !getsEqual(val1, ok1, val2, ok2) {
t.Fatalf("get(%q) = (%v, %v), want (%v, %v)", a, val2, ok2, val1, ok1)
}
}
}
@@ -727,20 +721,20 @@ func TestDeleteCompare(t *testing.T) {
fast.Delete(pfx.pfx)
}
seenVals4 := map[*int]bool{}
seenVals6 := map[*int]bool{}
seenVals4 := map[int]bool{}
seenVals6 := map[int]bool{}
for i := 0; i < numProbes; i++ {
a := randomAddr()
slowVal := slow.get(a)
fastVal := fast.Get(a)
slowVal, slowOK := slow.get(a)
fastVal, fastOK := fast.Get(a)
if !getsEqual(slowVal, slowOK, fastVal, fastOK) {
t.Fatalf("get(%q) = (%v, %v), want (%v, %v)", a, fastVal, fastOK, slowVal, slowOK)
}
if a.Is6() {
seenVals6[fastVal] = true
} else {
seenVals4[fastVal] = true
}
if slowVal != fastVal {
t.Fatalf("get(%q) = %p, want %p", a, fastVal, slowVal)
}
}
// Empirically, 10k probes into 5k v4 prefixes and 5k v6 prefixes results in
// ~1k distinct values for v4 and ~300 for v6. distinct routes. This sanity
@@ -814,13 +808,10 @@ func TestDeleteShuffled(t *testing.T) {
// test for equivalence statistically with random probes instead.
for i := 0; i < numProbes; i++ {
a := randomAddr()
val1 := rt.Get(a)
val2 := rt2.Get(a)
if val1 == nil && val2 == nil {
continue
}
if (val1 == nil && val2 != nil) || (val1 != nil && val2 == nil) || (*val1 != *val2) {
t.Errorf("get(%q) = %s, want %s", a, printIntPtr(val2), printIntPtr(val1))
val1, ok1 := rt.Get(a)
val2, ok2 := rt2.Get(a)
if !getsEqual(val1, ok1, val2, ok2) {
t.Errorf("get(%q) = (%v, %v), want (%v, %v)", a, val2, ok2, val1, ok1)
}
}
}
@@ -868,12 +859,12 @@ type tableTest struct {
func checkRoutes(t *testing.T, tbl *Table[int], tt []tableTest) {
t.Helper()
for _, tc := range tt {
v := tbl.Get(netip.MustParseAddr(tc.addr))
if v == nil && tc.want != -1 {
t.Errorf("lookup %q got nil, want %d", tc.addr, tc.want)
v, ok := tbl.Get(netip.MustParseAddr(tc.addr))
if !ok && tc.want != -1 {
t.Errorf("lookup %q got (%v, %v), want (_, false)", tc.addr, v, ok)
}
if v != nil && *v != tc.want {
t.Errorf("lookup %q got %d, want %d", tc.addr, *v, tc.want)
if ok && v != tc.want {
t.Errorf("lookup %q got (%v, %v), want (%v, true)", tc.addr, v, ok, tc.want)
}
}
}
@@ -1005,7 +996,7 @@ func BenchmarkTableGet(b *testing.B) {
for i := 0; i < b.N; i++ {
addr := genAddr()
t.Start()
writeSink = rt.Get(addr)
writeSink, _ = rt.Get(addr)
t.Stop()
}
})
@@ -1112,7 +1103,7 @@ type slowPrefixTable[T any] struct {
type slowPrefixEntry[T any] struct {
pfx netip.Prefix
val *T
val T
}
func (t *slowPrefixTable[T]) delete(pfx netip.Prefix) {
@@ -1127,7 +1118,7 @@ func (t *slowPrefixTable[T]) delete(pfx netip.Prefix) {
t.prefixes = ret
}
func (t *slowPrefixTable[T]) insert(pfx netip.Prefix, val *T) {
func (t *slowPrefixTable[T]) insert(pfx netip.Prefix, val T) {
pfx = pfx.Masked()
for i, ent := range t.prefixes {
if ent.pfx == pfx {
@@ -1138,11 +1129,8 @@ func (t *slowPrefixTable[T]) insert(pfx netip.Prefix, val *T) {
t.prefixes = append(t.prefixes, slowPrefixEntry[T]{pfx, val})
}
func (t *slowPrefixTable[T]) get(addr netip.Addr) *T {
var (
ret *T
bestLen = -1
)
func (t *slowPrefixTable[T]) get(addr netip.Addr) (ret T, ok bool) {
bestLen := -1
for _, pfx := range t.prefixes {
if pfx.pfx.Contains(addr) && pfx.pfx.Bits() > bestLen {
@@ -1150,7 +1138,7 @@ func (t *slowPrefixTable[T]) get(addr netip.Addr) *T {
bestLen = pfx.pfx.Bits()
}
}
return ret
return ret, bestLen != -1
}
// randomPrefixes returns n randomly generated prefixes and associated values,
@@ -1176,7 +1164,7 @@ func randomPrefixes4(n int) []slowPrefixEntry[int] {
ret := make([]slowPrefixEntry[int], 0, len(pfxs))
for pfx := range pfxs {
ret = append(ret, slowPrefixEntry[int]{pfx, ptr.To(rand.Int())})
ret = append(ret, slowPrefixEntry[int]{pfx, rand.Int()})
}
return ret
@@ -1197,7 +1185,7 @@ func randomPrefixes6(n int) []slowPrefixEntry[int] {
ret := make([]slowPrefixEntry[int], 0, len(pfxs))
for pfx := range pfxs {
ret = append(ret, slowPrefixEntry[int]{pfx, ptr.To(rand.Int())})
ret = append(ret, slowPrefixEntry[int]{pfx, rand.Int()})
}
return ret
@@ -1230,14 +1218,6 @@ func randomAddr6() netip.Addr {
return netip.AddrFrom16(b)
}
// printIntPtr returns *v as a string, or the literal "<nil>" if v is nil.
func printIntPtr(v *int) string {
if v == nil {
return "<nil>"
}
return fmt.Sprint(*v)
}
// roundFloat64 rounds f to 2 decimal places, for display.
//
// It round-trips through a float->string->float conversion, so should not be