Files
tailscale/net/traffic/traffic_test.go
T
Simon LawandGitHub 7ebca58042 net/traffic,ipn/ipnlocal: extract traffic steering utilities (#19682)
The traffic package contains helpers for evaluating traffic steering
scores and picking appropriate nodes. These were extracted from
ipnlocal.suggestExitNodeUsingTrafficSteering so they can be reused by
the new routecheck package to probe exit nodes in priority order.

Updates #17366
Updates tailscale/corp#33033

Signed-off-by: Simon Law <sfllaw@tailscale.com>
2026-05-21 08:28:27 -07:00

140 lines
3.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package traffic_test
import (
"maps"
"testing"
gocmp "github.com/google/go-cmp/cmp"
"tailscale.com/net/traffic"
"tailscale.com/tailcfg"
)
// WantScores is a convenience alias for the type of [traffic.Score.scores].
type wantScores = map[tailcfg.NodeID]traffic.Score
var scoresCases = []struct {
name string
peers []*tailcfg.Node
want wantScores
}{
{
name: "none",
peers: nil,
want: wantScores{},
},
{
name: "no-scores",
peers: []*tailcfg.Node{
{ID: 37},
{ID: 42},
},
want: wantScores{
37: 0,
42: 0,
},
},
{
name: "mixed-scores",
peers: []*tailcfg.Node{
{ID: 37},
{
ID: 42,
Hostinfo: (&tailcfg.Hostinfo{
Location: &tailcfg.Location{Priority: 1},
}).View(),
},
},
want: wantScores{
37: 0,
42: 1,
},
},
}
func TestScoreOne(t *testing.T) {
for _, tc := range scoresCases {
if len(tc.peers) == 0 {
continue
}
t.Run(tc.name, func(t *testing.T) {
selfID := tailcfg.NodeID(1)
ss := traffic.ScoresFor(selfID, nil)
for _, n := range tc.peers {
want := tc.want[n.ID]
score := ss.Score(n.View())
if score != want {
t.Errorf("initial Score for nodeid:%d: score %d, want %d", n.ID, score, want)
}
score = ss.Score(n.View())
if score != want {
t.Errorf("subsequent Score for nodeid:%d: score %d, want %d", n.ID, score, want)
}
}
got := maps.Collect(ss.All())
if diff := gocmp.Diff(tc.want, got); diff != "" {
t.Errorf("-want +got:\n%s", diff)
}
})
}
}
func TestScoreMany(t *testing.T) {
for _, tc := range scoresCases {
t.Run(tc.name, func(t *testing.T) {
selfID := tailcfg.NodeID(1)
var peers []tailcfg.NodeView
for _, n := range tc.peers {
peers = append(peers, n.View())
}
t.Run("ScoresFor", func(t *testing.T) {
ss := traffic.ScoresFor(selfID, peers)
got := maps.Collect(ss.All())
if diff := gocmp.Diff(tc.want, got); diff != "" {
t.Errorf("-want +got:\n%s", diff)
}
})
t.Run("ScorePeers", func(t *testing.T) {
ss := traffic.ScoresFor(selfID, nil)
ss.ScorePeers(peers)
got := maps.Collect(ss.All())
if diff := gocmp.Diff(tc.want, got); diff != "" {
t.Errorf("-want +got:\n%s", diff)
}
})
})
}
}
func FuzzNodeHasherCompare(f *testing.F) {
for _, seed := range [][]uint64{
{0, 0, 0},
{1, 1, 1},
{1, 10, 11},
{1, 11, 10},
{2, 10, 11},
} {
selfID, aID, bID := seed[0], seed[1], seed[2]
f.Add(selfID, aID, bID)
}
f.Fuzz(func(t *testing.T, selfID, aID, bID uint64) {
t.Logf("selfID %d, aID %d, bID %d", selfID, aID, bID)
h := traffic.MakeRendezvousHasher(tailcfg.NodeID(selfID))
a, b := tailcfg.NodeID(aID), tailcfg.NodeID(bID)
c := h.Compare(a, b)
if c == 0 && a != b {
t.Fatalf("got %d: expected different hashes because a ≠ b, ", c)
}
if cc := h.Compare(a, b); c != cc {
t.Fatalf("c %d, cc %d: expected matching values", c, cc)
}
if d := h.Compare(b, a); c != -d {
t.Fatalf("c %d, d %d: expected inverse values", c, d)
}
})
}