Skip to content

fix: use tailscale that avoids small MTU paths #18323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ replace github.com/tcnksm/go-httpstat => github.com/coder/go-httpstat v0.0.0-202

// There are a few minor changes we make to Tailscale that we're slowly upstreaming. Compare here:
// https://github.com/tailscale/tailscale/compare/main...coder:tailscale:main
replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20250422090654-5090e715905e
replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20250611020837-f14d20d23d8c

// This is replaced to include
// 1. a fix for a data race: c.f. https://github.com/tailscale/wireguard-go/pull/25
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -920,8 +920,8 @@ github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM=
github.com/coder/serpent v0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
github.com/coder/ssh v0.0.0-20231128192721-70855dedb788 h1:YoUSJ19E8AtuUFVYBpXuOD6a/zVP3rcxezNsoDseTUw=
github.com/coder/ssh v0.0.0-20231128192721-70855dedb788/go.mod h1:aGQbuCLyhRLMzZF067xc84Lh7JDs1FKwCmF1Crl9dxQ=
github.com/coder/tailscale v1.1.1-0.20250422090654-5090e715905e h1:nope/SZfoLB9MCOB9wdCE6gW5+8l3PhFrDC5IWPL8bk=
github.com/coder/tailscale v1.1.1-0.20250422090654-5090e715905e/go.mod h1:1ggFFdHTRjPRu9Yc1yA7nVHBYB50w9Ce7VIXNqcW6Ko=
github.com/coder/tailscale v1.1.1-0.20250611020837-f14d20d23d8c h1:d/qBIi3Ez7KkopRgNtfdvTMqvqBg47d36qVfkd3C5EQ=
github.com/coder/tailscale v1.1.1-0.20250611020837-f14d20d23d8c/go.mod h1:l7ml5uu7lFh5hY28lGYM4b/oFSmuPHYX6uk4RAu23Lc=
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
github.com/coder/terraform-provider-coder/v2 v2.5.3 h1:EwqIIQKe/j8bsR4WyDJ3bD0dVdkfVqJ43TwClyGneUU=
Expand Down
11 changes: 11 additions & 0 deletions tailnet/test/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
"golang.org/x/xerrors"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
Expand Down Expand Up @@ -458,6 +459,16 @@ func (UDPEchoService) StartService(t *testing.T, logger slog.Logger, _ *tailnet.
Port: EchoPort,
})
require.NoError(t, err)

// set path MTU discovery so that we don't fragment the responses.
c, err := l.SyscallConn()
require.NoError(t, err)
var sockErr error
err = c.Control(func(fd uintptr) {
sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_MTU_DISCOVER, unix.IP_PMTUDISC_DO)
})
require.NoError(t, err)
require.NoError(t, sockErr)
logger.Info(context.Background(), "started UDPEcho server")
t.Cleanup(func() {
lCloseErr := l.Close()
Expand Down
4 changes: 2 additions & 2 deletions tailnet/test/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ var topologies = []integration.TestTopology{
{
// Test that direct over normal MTU works.
Name: "DirectMTU1500",
NetworkingProvider: integration.TriangleNetwork{InterClientMTU: 1500},
NetworkingProvider: integration.TriangleNetwork{Client1MTU: 1500},
Server: integration.SimpleServerOptions{},
ClientStarter: integration.BasicClientStarter{
WaitForDirect: true,
Expand All @@ -124,7 +124,7 @@ var topologies = []integration.TestTopology{
{
// Test that small MTU works.
Name: "MTU1280",
NetworkingProvider: integration.TriangleNetwork{InterClientMTU: 1280},
NetworkingProvider: integration.TriangleNetwork{Client1MTU: 1280},
Server: integration.SimpleServerOptions{},
ClientStarter: integration.BasicClientStarter{Service: integration.UDPEchoService{}, LogPackets: true},
RunTests: integration.TestBigUDP,
Expand Down
173 changes: 93 additions & 80 deletions tailnet/test/integration/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,33 +390,38 @@ func createFakeInternet(t *testing.T) fakeInternet {
}

type TriangleNetwork struct {
InterClientMTU int
Client1MTU int
}

type fakeTriangleNetwork struct {
NamePrefix string
ServerNetNS *os.File
Client1NetNS *os.File
Client2NetNS *os.File
ServerClient1VethPair vethPair
ServerClient2VethPair vethPair
Client1Client2VethPair vethPair
NamePrefix string
ServerNetNS *os.File
Client1NetNS *os.File
Client2NetNS *os.File
RouterNetNS *os.File
ServerVethPair vethPair
Client1VethPair vethPair
Client2VethPair vethPair
}

// SetupNetworking creates multiple namespaces with veth pairs between them
// with the following topology:
// SetupNetworking creates multiple namespaces with a central router in the following topology
// .
// . ┌────────────────────────────────────────────┐
// . │ Server │
// . └─────┬───────────────────────────────────┬──┘
// . │fdac:38fa:ffff:2::3 │fdac:38fa:ffff:3::3
// . veth│ veth│
// . │fdac:38fa:ffff:2::1 │fdac:38fa:ffff:3::2
// . ┌───────┴──────┐ ┌─────┴───────┐
// . │ │ fdac:38fa:ffff:1::2│ │
// . │ Client 1 ├──────────────────────┤ Client 2 │
// . │ │fdac:38fa:ffff:1::1 │ │
// . └──────────────┘ └─────────────┘
// . ┌──────────────┐
// . │ │
// . │ Server ├─────────────────────────────────────┐
// . │ │fdac:38fa:ffff:3::2 │
// . └──────────────┘ │ fdac:38fa:ffff:3::1
// . ┌──────────────┐ ┌─────┴───────┐
// . │ │ fdac:38fa:ffff:1::1│ │
// . │ Client 1 ├───────────────────────────────┤ Router │
// . │ │fdac:38fa:ffff:1::2 │ │
// . └──────────────┘ └─────┬───────┘
// . ┌──────────────┐ │ fdac:38fa:ffff:2::1
// . │ │ │
// . │ Client 2 ├─────────────────────────────────────┘
// . │ │fdac:38fa:ffff:2::2
// . └──────────────┘
// The veth link between Client 1 and the router has a configurable MTU via Client1MTU.
func (n TriangleNetwork) SetupNetworking(t *testing.T, l slog.Logger) TestNetworking {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really a triangle anymore unfortunately, more of a pitchfork 🤣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untitled.jpg

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should redo the diagram to be triforce 🤣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure unicode has the correct (60°) diagonals

logger := l.Named("setup-networking").Leveled(slog.LevelDebug)
t.Helper()
Expand All @@ -433,101 +438,109 @@ func (n TriangleNetwork) SetupNetworking(t *testing.T, l slog.Logger) TestNetwor
network.ServerNetNS = createNetNS(t, namePrefix+"server")
network.Client1NetNS = createNetNS(t, namePrefix+"client1")
network.Client2NetNS = createNetNS(t, namePrefix+"client2")
network.RouterNetNS = createNetNS(t, namePrefix+"router")

// Create veth pair between server and client1
network.ServerClient1VethPair = vethPair{
Outer: namePrefix + "s-1",
Inner: namePrefix + "1-s",
// Create veth pair between server and router
network.ServerVethPair = vethPair{
Outer: namePrefix + "s-r",
Inner: namePrefix + "r-s",
}
err := createVethPair(network.ServerClient1VethPair.Outer, network.ServerClient1VethPair.Inner)
err := createVethPair(network.ServerVethPair.Outer, network.ServerVethPair.Inner)
require.NoErrorf(t, err, "create veth pair %q <-> %q",
network.ServerClient1VethPair.Outer, network.ServerClient1VethPair.Inner)
network.ServerVethPair.Outer, network.ServerVethPair.Inner)

// Move server-client1 veth ends to their respective namespaces
err = setVethNetNS(network.ServerClient1VethPair.Outer, int(network.ServerNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to server NetNS", network.ServerClient1VethPair.Outer)
err = setVethNetNS(network.ServerClient1VethPair.Inner, int(network.Client1NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client1 NetNS", network.ServerClient1VethPair.Inner)
// Move server-router veth ends to their respective namespaces
err = setVethNetNS(network.ServerVethPair.Outer, int(network.ServerNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to server NetNS", network.ServerVethPair.Outer)
err = setVethNetNS(network.ServerVethPair.Inner, int(network.RouterNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to router NetNS", network.ServerVethPair.Inner)

// Create veth pair between server and client2
network.ServerClient2VethPair = vethPair{
Outer: namePrefix + "s-2",
Inner: namePrefix + "2-s",
// Create veth pair between client1 and router
network.Client1VethPair = vethPair{
Outer: namePrefix + "1-r",
Inner: namePrefix + "r-1",
}
err = createVethPair(network.ServerClient2VethPair.Outer, network.ServerClient2VethPair.Inner)
logger.Debug(context.Background(), "creating client1 link", slog.F("mtu", n.Client1MTU))
err = createVethPair(network.Client1VethPair.Outer, network.Client1VethPair.Inner, withMTU(n.Client1MTU))
require.NoErrorf(t, err, "create veth pair %q <-> %q",
network.ServerClient2VethPair.Outer, network.ServerClient2VethPair.Inner)
network.Client1VethPair.Outer, network.Client1VethPair.Inner)

// Move server-client2 veth ends to their respective namespaces
err = setVethNetNS(network.ServerClient2VethPair.Outer, int(network.ServerNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to server NetNS", network.ServerClient2VethPair.Outer)
err = setVethNetNS(network.ServerClient2VethPair.Inner, int(network.Client2NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client2 NetNS", network.ServerClient2VethPair.Inner)
// Move client1-router veth ends to their respective namespaces
err = setVethNetNS(network.Client1VethPair.Outer, int(network.Client1NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to server NetNS", network.Client1VethPair.Outer)
err = setVethNetNS(network.Client1VethPair.Inner, int(network.RouterNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client2 NetNS", network.Client1VethPair.Inner)

// Create veth pair between client1 and client2
network.Client1Client2VethPair = vethPair{
Outer: namePrefix + "1-2",
Inner: namePrefix + "2-1",
network.Client2VethPair = vethPair{
Outer: namePrefix + "2-r",
Inner: namePrefix + "r-2",
}
logger.Debug(context.Background(), "creating inter-client link", slog.F("mtu", n.InterClientMTU))
err = createVethPair(network.Client1Client2VethPair.Outer, network.Client1Client2VethPair.Inner,
withMTU(n.InterClientMTU))

err = createVethPair(network.Client2VethPair.Outer, network.Client2VethPair.Inner)
require.NoErrorf(t, err, "create veth pair %q <-> %q",
network.Client1Client2VethPair.Outer, network.Client1Client2VethPair.Inner)
network.Client2VethPair.Outer, network.Client2VethPair.Inner)

// Move client1-client2 veth ends to their respective namespaces
err = setVethNetNS(network.Client1Client2VethPair.Outer, int(network.Client1NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client1 NetNS", network.Client1Client2VethPair.Outer)
err = setVethNetNS(network.Client1Client2VethPair.Inner, int(network.Client2NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client2 NetNS", network.Client1Client2VethPair.Inner)
err = setVethNetNS(network.Client2VethPair.Outer, int(network.Client2NetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client1 NetNS", network.Client2VethPair.Outer)
err = setVethNetNS(network.Client2VethPair.Inner, int(network.RouterNetNS.Fd()))
require.NoErrorf(t, err, "set veth %q to client2 NetNS", network.Client2VethPair.Inner)

// Set IP addresses according to the diagram:
err = setInterfaceIP6(network.ServerNetNS, network.ServerClient1VethPair.Outer, ula+"2::3")
require.NoErrorf(t, err, "set IP on server-client1 interface")
err = setInterfaceIP6(network.ServerNetNS, network.ServerClient2VethPair.Outer, ula+"3::3")
require.NoErrorf(t, err, "set IP on server-client2 interface")

err = setInterfaceIP6(network.Client1NetNS, network.ServerClient1VethPair.Inner, ula+"2::1")
require.NoErrorf(t, err, "set IP on client1-server interface")
err = setInterfaceIP6(network.Client1NetNS, network.Client1Client2VethPair.Outer, ula+"1::1")
require.NoErrorf(t, err, "set IP on client1-client2 interface")

err = setInterfaceIP6(network.Client2NetNS, network.ServerClient2VethPair.Inner, ula+"3::2")
require.NoErrorf(t, err, "set IP on client2-server interface")
err = setInterfaceIP6(network.Client2NetNS, network.Client1Client2VethPair.Inner, ula+"1::2")
require.NoErrorf(t, err, "set IP on client2-client1 interface")
err = setInterfaceIP6(network.ServerNetNS, network.ServerVethPair.Outer, ula+"3::2")
require.NoErrorf(t, err, "set IP on server interface")
err = setInterfaceIP6(network.Client1NetNS, network.Client1VethPair.Outer, ula+"1::2")
require.NoErrorf(t, err, "set IP on client1 interface")
err = setInterfaceIP6(network.Client2NetNS, network.Client2VethPair.Outer, ula+"2::2")
require.NoErrorf(t, err, "set IP on client2 interface")

err = setInterfaceIP6(network.RouterNetNS, network.ServerVethPair.Inner, ula+"3::1")
require.NoErrorf(t, err, "set IP on router-server interface")
err = setInterfaceIP6(network.RouterNetNS, network.Client1VethPair.Inner, ula+"1::1")
require.NoErrorf(t, err, "set IP on router-client1 interface")
err = setInterfaceIP6(network.RouterNetNS, network.Client2VethPair.Inner, ula+"2::1")
require.NoErrorf(t, err, "set IP on router-client2 interface")

// Bring up all interfaces
interfaces := []struct {
netNS *os.File
ifaceName string
netNS *os.File
ifaceName string
defaultRoute string
}{
{network.ServerNetNS, network.ServerClient1VethPair.Outer},
{network.ServerNetNS, network.ServerClient2VethPair.Outer},
{network.Client1NetNS, network.ServerClient1VethPair.Inner},
{network.Client1NetNS, network.Client1Client2VethPair.Outer},
{network.Client2NetNS, network.ServerClient2VethPair.Inner},
{network.Client2NetNS, network.Client1Client2VethPair.Inner},
{network.ServerNetNS, network.ServerVethPair.Outer, ula + "3::1"},
{network.Client1NetNS, network.Client1VethPair.Outer, ula + "1::1"},
{network.Client2NetNS, network.Client2VethPair.Outer, ula + "2::1"},
{network.RouterNetNS, network.ServerVethPair.Inner, ""},
{network.RouterNetNS, network.Client1VethPair.Inner, ""},
{network.RouterNetNS, network.Client2VethPair.Inner, ""},
}
for _, iface := range interfaces {
err = setInterfaceUp(iface.netNS, iface.ifaceName)
require.NoErrorf(t, err, "bring up interface %q", iface.ifaceName)
// Note: routes are not needed as we are fully connected, so nothing needs to forward IP to a further
// destination.

if iface.defaultRoute != "" {
err = addRouteInNetNS(iface.netNS, []string{"default", "via", iface.defaultRoute, "dev", iface.ifaceName})
require.NoErrorf(t, err, "add peer default route to %s", iface.defaultRoute)
}
}

// enable IP forwarding in the router
_, err = commandInNetNS(network.RouterNetNS, "sysctl", []string{"-w", "net.ipv6.conf.all.forwarding=1"}).Output()
require.NoError(t, wrapExitErr(err), "enable IPv6 forwarding in router NetNS")

return TestNetworking{
Server: TestNetworkingServer{
Process: TestNetworkingProcess{NetNS: network.ServerNetNS},
ListenAddr: "[::]:8080", // Server listens on all IPs
},
Client1: TestNetworkingClient{
Process: TestNetworkingProcess{NetNS: network.Client1NetNS},
ServerAccessURL: "http://[" + ula + "2::3]:8080", // Client1 accesses server directly
ServerAccessURL: "http://[" + ula + "3::2]:8080",
},
Client2: TestNetworkingClient{
Process: TestNetworkingProcess{NetNS: network.Client2NetNS},
ServerAccessURL: "http://[" + ula + "3::3]:8080", // Client2 accesses server directly
ServerAccessURL: "http://[" + ula + "3::2]:8080",
},
}
}
Expand Down
Loading