diff --git a/wgengine/magicsock/debugknobs.go b/wgengine/magicsock/debugknobs.go index d23f2ccbe..55f05d55e 100644 --- a/wgengine/magicsock/debugknobs.go +++ b/wgengine/magicsock/debugknobs.go @@ -47,6 +47,8 @@ var ( // debugRingBufferMaxSizeBytes overrides the default size of the endpoint // history ringbuffer. debugRingBufferMaxSizeBytes = envknob.RegisterInt("TS_DEBUG_MAGICSOCK_RING_BUFFER_MAX_SIZE_BYTES") + // debugPMTUD enables path MTU discovery. Currently only sets the Don't Fragment sockopt. + debugPMTUD = envknob.RegisterBool("TS_DEBUG_ENABLE_PMTUD") // Hey you! Adding a new debugknob? Make sure to stub it out in the debugknob_stubs.go // file too. ) diff --git a/wgengine/magicsock/debugknobs_stubs.go b/wgengine/magicsock/debugknobs_stubs.go index ed966cf7b..9cb7ef675 100644 --- a/wgengine/magicsock/debugknobs_stubs.go +++ b/wgengine/magicsock/debugknobs_stubs.go @@ -20,6 +20,7 @@ func debugAlwaysDERP() bool { return false } func debugUseDERPHTTP() bool { return false } func debugEnableSilentDisco() bool { return false } func debugSendCallMeUnknownPeer() bool { return false } +func debugPMTUD() bool { return false } func debugUseDERPAddr() string { return "" } func debugUseDerpRouteEnv() string { return "" } func debugUseDerpRoute() opt.Bool { return "" } diff --git a/wgengine/magicsock/dontfrag_darwin.go b/wgengine/magicsock/dontfrag_darwin.go new file mode 100644 index 000000000..3b27bc4c6 --- /dev/null +++ b/wgengine/magicsock/dontfrag_darwin.go @@ -0,0 +1,35 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build darwin && !ios + +package magicsock + +import ( + "net" + "syscall" + + "golang.org/x/sys/unix" + "tailscale.com/types/nettype" +) + +func setDontFragment(pconn nettype.PacketConn, network string) (err error) { + if c, ok := pconn.(*net.UDPConn); ok { + rc, err := c.SyscallConn() + if err == nil { + rc.Control(func(fd uintptr) { + if network == "udp4" { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, unix.IP_DONTFRAG, 1) + } + if network == "udp6" { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, unix.IPV6_DONTFRAG, 1) + } + }) + } + } + return err +} + +func CanPMTUD() bool { + return debugPMTUD() // only if the envknob is for now. +} diff --git a/wgengine/magicsock/dontfrag_default.go b/wgengine/magicsock/dontfrag_default.go new file mode 100644 index 000000000..77a521e08 --- /dev/null +++ b/wgengine/magicsock/dontfrag_default.go @@ -0,0 +1,24 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build (!linux && !darwin) || android || ios + +package magicsock + +import ( + "errors" + + "tailscale.com/types/nettype" +) + +// setDontFragment sets the dontfragment sockopt on pconn on the platforms that support it, +// for both IPv4 and IPv6. +// (C.f. https://datatracker.ietf.org/doc/html/rfc3542#section-11.2 for IPv6 fragmentation) +func setDontFragment(pconn nettype.PacketConn, network string) (err error) { + return errors.New("setting don't fragment bit not supported on this OS") +} + +// CanPMTUD returns whether this platform supports performing peet path MTU discovery. +func CanPMTUD() bool { + return false +} diff --git a/wgengine/magicsock/dontfrag_linux.go b/wgengine/magicsock/dontfrag_linux.go new file mode 100644 index 000000000..a407ccc33 --- /dev/null +++ b/wgengine/magicsock/dontfrag_linux.go @@ -0,0 +1,34 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !android + +package magicsock + +import ( + "net" + "syscall" + + "tailscale.com/types/nettype" +) + +func setDontFragment(pconn nettype.PacketConn, network string) (err error) { + if c, ok := pconn.(*net.UDPConn); ok { + rc, err := c.SyscallConn() + if err == nil { + rc.Control(func(fd uintptr) { + if network == "udp4" { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO) + } + if network == "udp6" { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO) + } + }) + } + } + return err +} + +func CanPMTUD() bool { + return debugPMTUD() // only if the envknob is enabled, for now. +} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index a78003f06..b6ba2a553 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2233,6 +2233,16 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur continue } trySetSocketBuffer(pconn, c.logf) + + if CanPMTUD() { + err = setDontFragment(pconn, network) + if err != nil { + c.logf("magicsock: set dontfragment failed for %v port %d: %v", network, port, err) + // TODO disable PMTUD in this case. We don't expect the setsockopt to fail on + // supported platforms, but we might as well be paranoid. + } + } + // Success. if debugBindSocket() { c.logf("magicsock: bindSocket: successfully listened %v port %d", network, port)