tailscale/wgengine/magicsock/peermtu.go

131 lines
4.2 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build (darwin && !ios) || (linux && !android)
package magicsock
import (
"errors"
"golang.org/x/sys/unix"
"tailscale.com/disco"
"tailscale.com/net/tstun"
)
// Peer path MTU routines shared by platforms that implement it.
// DontFragSetting returns true if at least one of the underlying sockets of
// this connection is a UDP socket with the don't fragment bit set, otherwise it
// returns false. It also returns an error if either connection returned an error
// other than errUnsupportedConnType.
func (c *Conn) DontFragSetting() (bool, error) {
df4, err4 := c.getDontFragment("udp4")
df6, err6 := c.getDontFragment("udp6")
df := df4 || df6
err := err4
if err4 != nil && err4 != errUnsupportedConnType {
err = err6
}
if err == errUnsupportedConnType {
err = nil
}
return df, err
}
// ShouldPMTUD returns true if this client should try to enable peer MTU
// discovery, false otherwise.
func (c *Conn) ShouldPMTUD() bool {
if v, ok := debugEnablePMTUD().Get(); ok {
if debugPMTUD() {
c.logf("magicsock: peermtu: peer path MTU discovery set via envknob to %v", v)
}
return v
}
if c.controlKnobs != nil {
if v := c.controlKnobs.PeerMTUEnable.Load(); v {
if debugPMTUD() {
c.logf("magicsock: peermtu: peer path MTU discovery enabled by control")
}
return v
}
}
if debugPMTUD() {
c.logf("magicsock: peermtu: peer path MTU discovery set by default to false")
}
return false // Until we feel confident PMTUD is solid.
}
// PeerMTUEnabled reports whether peer path MTU discovery is enabled.
func (c *Conn) PeerMTUEnabled() bool {
return c.peerMTUEnabled.Load()
}
// UpdatePMTUD configures the underlying sockets of this Conn to enable or disable
// peer path MTU discovery according to the current configuration.
//
// Enabling or disabling peer path MTU discovery requires setting the don't
// fragment bit on its two underlying pconns. There are three distinct results
// for this operation on each pconn:
//
// 1. Success
// 2. Failure (not supported on this platform, or supported but failed)
// 3. Not a UDP socket (most likely one of IPv4 or IPv6 couldn't be used)
//
// To simplify the fast path for the most common case, we set the PMTUD status
// of the overall Conn according to the results of setting the sockopt on pconn
// as follows:
//
// 1. Both setsockopts succeed: PMTUD status update succeeds
// 2. One succeeds, one returns not a UDP socket: PMTUD status update succeeds
// 4. Neither setsockopt succeeds: PMTUD disabled
// 3. Either setsockopt fails: PMTUD disabled
//
// If the PMTUD settings changed, it resets the endpoint state so that it will
// re-probe path MTUs to this peer.
func (c *Conn) UpdatePMTUD() {
if debugPMTUD() {
df4, err4 := c.getDontFragment("udp4")
df6, err6 := c.getDontFragment("udp6")
c.logf("magicsock: peermtu: peer MTU status %v DF bit status: v4: %v (%v) v6: %v (%v)", c.peerMTUEnabled.Load(), df4, err4, df6, err6)
}
enable := c.ShouldPMTUD()
if c.peerMTUEnabled.Load() == enable {
c.logf("[v1] magicsock: peermtu: peer MTU status is %v", enable)
return
}
newStatus := enable
err4 := c.setDontFragment("udp4", enable)
err6 := c.setDontFragment("udp6", enable)
anySuccess := err4 == nil || err6 == nil
noFailures := (err4 == nil || err4 == errUnsupportedConnType) && (err6 == nil || err6 == errUnsupportedConnType)
if anySuccess && noFailures {
c.logf("magicsock: peermtu: peer MTU status updated to %v", newStatus)
} else {
c.logf("[unexpected] magicsock: peermtu: updating peer MTU status to %v failed (v4: %v, v6: %v), disabling", enable, err4, err6)
_ = c.setDontFragment("udp4", false)
_ = c.setDontFragment("udp6", false)
newStatus = false
}
if debugPMTUD() {
c.logf("magicsock: peermtu: peer MTU probes are %v", tstun.WireMTUsToProbe)
}
c.peerMTUEnabled.Store(newStatus)
c.resetEndpointStates()
}
var errEMSGSIZE error = unix.EMSGSIZE
func pmtuShouldLogDiscoTxErr(m disco.Message, err error) bool {
// Large disco.Ping packets used to probe path MTU may result in
// an EMSGSIZE error fairly regularly which can pollute logs.
p, ok := m.(*disco.Ping)
if !ok || p.Padding == 0 || !errors.Is(err, errEMSGSIZE) || debugPMTUD() {
return true
}
return false
}