types: avoid NodeView clone in CanAccess

NodeView.CanAccess called node2.AsStruct() on every check. In peer-map construction we run CanAccess in O(n^2) pair scans (often twice per pair), so that per-call clone multiplied into large heap churn
This commit is contained in:
DM
2026-02-26 08:39:04 +03:00
committed by Kristoffer Dalby
parent 84adda226b
commit 610c1daa4d
2 changed files with 75 additions and 2 deletions

View File

@@ -800,11 +800,11 @@ func (nv NodeView) InIPSet(set *netipx.IPSet) bool {
}
func (nv NodeView) CanAccess(matchers []matcher.Match, node2 NodeView) bool {
if !nv.Valid() {
if !nv.Valid() || !node2.Valid() {
return false
}
return nv.ж.CanAccess(matchers, node2.AsStruct())
return nv.ж.CanAccess(matchers, node2.ж)
}
func (nv NodeView) CanAccessRoute(matchers []matcher.Match, route netip.Prefix) bool {

View File

@@ -0,0 +1,73 @@
package types
import (
"fmt"
"net/netip"
"testing"
"github.com/juanfont/headscale/hscontrol/policy/matcher"
"tailscale.com/tailcfg"
)
func BenchmarkNodeViewCanAccess(b *testing.B) {
addr := func(ip string) *netip.Addr {
parsed := netip.MustParseAddr(ip)
return &parsed
}
rules := []tailcfg.FilterRule{
{
SrcIPs: []string{"100.64.0.1/32"},
DstPorts: []tailcfg.NetPortRange{
{
IP: "100.64.0.2/32",
Ports: tailcfg.PortRangeAny,
},
},
},
}
matchers := matcher.MatchesFromFilterRules(rules)
derpLatency := make(map[string]float64, 256)
for i := range 128 {
derpLatency[fmt.Sprintf("%d-v4", i)] = float64(i) / 10
derpLatency[fmt.Sprintf("%d-v6", i)] = float64(i) / 10
}
src := Node{
IPv4: addr("100.64.0.1"),
}
dst := Node{
IPv4: addr("100.64.0.2"),
Hostinfo: &tailcfg.Hostinfo{
NetInfo: &tailcfg.NetInfo{
DERPLatency: derpLatency,
},
},
}
srcView := src.View()
dstView := dst.View()
if !srcView.CanAccess(matchers, dstView) {
b.Fatal("benchmark setup error: expected source to access destination")
}
b.Run("pointer", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
srcView.CanAccess(matchers, dstView)
}
})
b.Run("struct clone", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
src.CanAccess(matchers, dstView.AsStruct())
}
})
}