net/tstun: add TSMPDiscoAdvertisement to TSMPPing (#17995)

Adds a new types of TSMP messages for advertising disco keys keys
to/from a peer, and implements the advertising triggered by a TSMP ping.

Needed as part of the effort to cache the netmap and still let clients
connect without control being reachable.

Updates #12639

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Co-authored-by: James Tucker <james@tailscale.com>
This commit is contained in:
Claus Lensbøl
2025-11-25 21:35:38 +01:00
committed by GitHub
parent b38dd1ae06
commit c54d243690
7 changed files with 280 additions and 25 deletions

View File

@@ -36,6 +36,8 @@ import (
"tailscale.com/types/netlogtype"
"tailscale.com/types/ptr"
"tailscale.com/types/views"
"tailscale.com/util/eventbus"
"tailscale.com/util/eventbus/eventbustest"
"tailscale.com/util/must"
"tailscale.com/util/usermetric"
"tailscale.com/wgengine/filter"
@@ -170,10 +172,10 @@ func setfilter(logf logger.Logf, tun *Wrapper) {
tun.SetFilter(filter.New(matches, nil, ipSet, ipSet, nil, logf))
}
func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *Wrapper) {
func newChannelTUN(logf logger.Logf, bus *eventbus.Bus, secure bool) (*tuntest.ChannelTUN, *Wrapper) {
chtun := tuntest.NewChannelTUN()
reg := new(usermetric.Registry)
tun := Wrap(logf, chtun.TUN(), reg)
tun := Wrap(logf, chtun.TUN(), reg, bus)
if secure {
setfilter(logf, tun)
} else {
@@ -183,10 +185,10 @@ func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *Wrapper
return chtun, tun
}
func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *Wrapper) {
func newFakeTUN(logf logger.Logf, bus *eventbus.Bus, secure bool) (*fakeTUN, *Wrapper) {
ftun := NewFake()
reg := new(usermetric.Registry)
tun := Wrap(logf, ftun, reg)
tun := Wrap(logf, ftun, reg, bus)
if secure {
setfilter(logf, tun)
} else {
@@ -196,7 +198,8 @@ func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *Wrapper) {
}
func TestReadAndInject(t *testing.T) {
chtun, tun := newChannelTUN(t.Logf, false)
bus := eventbustest.NewBus(t)
chtun, tun := newChannelTUN(t.Logf, bus, false)
defer tun.Close()
const size = 2 // all payloads have this size
@@ -221,7 +224,7 @@ func TestReadAndInject(t *testing.T) {
}
var buf [MaxPacketSize]byte
var seen = make(map[string]bool)
seen := make(map[string]bool)
sizes := make([]int, 1)
// We expect the same packets back, in no particular order.
for i := range len(written) + len(injected) {
@@ -257,7 +260,8 @@ func TestReadAndInject(t *testing.T) {
}
func TestWriteAndInject(t *testing.T) {
chtun, tun := newChannelTUN(t.Logf, false)
bus := eventbustest.NewBus(t)
chtun, tun := newChannelTUN(t.Logf, bus, false)
defer tun.Close()
written := []string{"w0", "w1"}
@@ -316,8 +320,8 @@ func mustHexDecode(s string) []byte {
}
func TestFilter(t *testing.T) {
chtun, tun := newChannelTUN(t.Logf, true)
bus := eventbustest.NewBus(t)
chtun, tun := newChannelTUN(t.Logf, bus, true)
defer tun.Close()
// Reset the metrics before test. These are global
@@ -462,7 +466,8 @@ func assertMetricPackets(t *testing.T, metricName string, want, got int64) {
}
func TestAllocs(t *testing.T) {
ftun, tun := newFakeTUN(t.Logf, false)
bus := eventbustest.NewBus(t)
ftun, tun := newFakeTUN(t.Logf, bus, false)
defer tun.Close()
buf := [][]byte{{0x00}}
@@ -473,14 +478,14 @@ func TestAllocs(t *testing.T) {
return
}
})
if err != nil {
t.Error(err)
}
}
func TestClose(t *testing.T) {
ftun, tun := newFakeTUN(t.Logf, false)
bus := eventbustest.NewBus(t)
ftun, tun := newFakeTUN(t.Logf, bus, false)
data := [][]byte{udp4("1.2.3.4", "5.6.7.8", 98, 98)}
_, err := ftun.Write(data, 0)
@@ -497,7 +502,8 @@ func TestClose(t *testing.T) {
func BenchmarkWrite(b *testing.B) {
b.ReportAllocs()
ftun, tun := newFakeTUN(b.Logf, true)
bus := eventbustest.NewBus(b)
ftun, tun := newFakeTUN(b.Logf, bus, true)
defer tun.Close()
packet := [][]byte{udp4("5.6.7.8", "1.2.3.4", 89, 89)}
@@ -887,7 +893,8 @@ func TestCaptureHook(t *testing.T) {
now := time.Unix(1682085856, 0)
_, w := newFakeTUN(t.Logf, true)
bus := eventbustest.NewBus(t)
_, w := newFakeTUN(t.Logf, bus, true)
w.timeNow = func() time.Time {
return now
}
@@ -957,3 +964,30 @@ func TestCaptureHook(t *testing.T) {
captured, want)
}
}
func TestTSMPDisco(t *testing.T) {
t.Run("IPv6DiscoAdvert", func(t *testing.T) {
src := netip.MustParseAddr("2001:db8::1")
dst := netip.MustParseAddr("2001:db8::2")
discoKey := key.NewDisco()
buf, _ := (&packet.TSMPDiscoKeyAdvertisement{
Src: src,
Dst: dst,
Key: discoKey.Public(),
}).Marshal()
var p packet.Parsed
p.Decode(buf)
tda, ok := p.AsTSMPDiscoAdvertisement()
if !ok {
t.Error("Unable to parse message as TSMPDiscoAdversitement")
}
if tda.Src != src {
t.Errorf("Src address did not match, expected %v, got %v", src, tda.Src)
}
if !reflect.DeepEqual(tda.Key, discoKey.Public()) {
t.Errorf("Key did not match, expected %q, got %q", discoKey.Public(), tda.Key)
}
})
}