From 9175fe2675264bbb08a9d6d8830e282c0b68f17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Fri, 17 Jul 2026 15:10:08 -0400 Subject: [PATCH] net/tstun: drop TSMP messages injected into TUN (#20511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enforce that TSMP messages are only accepted for transmission over the wireguard connection from within the client. Updates tailscale/corp#45059 Signed-off-by: Claus Lensbøl --- net/tstun/wrap.go | 9 +++++++++ net/tstun/wrap_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index bcc12f141..e63dccd49 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -761,6 +761,14 @@ func (t *Wrapper) filterPacketOutboundToWireGuard(p *packet.Parsed, pc *peerConf } } + // TSMP traffic should only originate from tailscaled, not from the host + // itself. + if p.IPProto == ipproto.TSMP { + t.limitedLogf("[unexpected] received TSMP out packet over tstun; dropping") + metricPacketOutDropTSMP.Add(1) + return filter.DropSilently, gro + } + // Issue 1526 workaround: if we sent disco packets over // Tailscale from ourselves, then drop them, as that shouldn't // happen unless a networking stack is confused, as it seems @@ -1507,6 +1515,7 @@ func (t *Wrapper) SetConnectionCounter(fn netlogfunc.ConnectionCounter) { metricPacketOutDrop = clientmetric.NewCounter("tstun_out_to_wg_drop") metricPacketOutDropFilter = clientmetric.NewCounter("tstun_out_to_wg_drop_filter") metricPacketOutDropSelfDisco = clientmetric.NewCounter("tstun_out_to_wg_drop_self_disco") + metricPacketOutDropTSMP = clientmetric.NewCounter("tstun_out_to_wg_drop_tsmp") ) func (t *Wrapper) InstallCaptureHook(cb packet.CaptureCallback) { diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 3690ce907..76edf7563 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -1236,3 +1236,36 @@ func TestFilterDropEmptyTSMPDiscoAdvertInbound(t *testing.T) { t.Errorf("got %v; want DropSilently", got) } } + +// Drop TSMP packets from ourselves. +func TestFilterDropTSMP(t *testing.T) { + var memLog tstest.MemLogger + tw := &Wrapper{logf: memLog.Logf, limitedLogf: memLog.Logf} + ipHdr := packet.IP4Header{ + IPProto: ipproto.TSMP, + Src: netaddr.IPv4(1, 2, 3, 4), + Dst: netaddr.IPv4(5, 6, 7, 8), + } + tsmpPayload := make([]byte, 33) + tsmpPayload[0] = byte(packet.TSMPTypeDiscoAdvertisement) + pkt := make([]byte, ipHdr.Len()+len(tsmpPayload)) + ipHdr.Marshal(pkt) + copy(pkt[ipHdr.Len():], tsmpPayload) + + wantMetric := metricPacketOutDropTSMP.Value() + 1 + + pp := new(packet.Parsed) + pp.Decode(pkt) + got, _ := tw.filterPacketOutboundToWireGuard(pp, nil, nil) + if got != filter.DropSilently { + t.Errorf("got %v; want DropSilently", got) + } + if got, want := memLog.String(), "[unexpected] received TSMP out packet over tstun; dropping\n"; got != want { + t.Errorf("log output mismatch\n got: %q\nwant: %q\n", got, want) + } + + if metricPacketOutDropTSMP.Value() != wantMetric { + t.Errorf("expected metric\n got: %d\nwant: %d\n", + metricPacketOutDropTSMP.Value(), wantMetric) + } +}