mirror of
https://github.com/qdm12/gluetun.git
synced 2026-05-06 20:10:11 +02:00
85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
package pmtud
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/qdm12/gluetun/internal/firewall"
|
|
"github.com/qdm12/gluetun/internal/pmtud/constants"
|
|
"github.com/qdm12/gluetun/internal/pmtud/icmp"
|
|
"github.com/qdm12/gluetun/internal/pmtud/tcp"
|
|
)
|
|
|
|
var ErrPMTUDFailICMPAndTCP = errors.New("PMTUD failed with both ICMP and TCP")
|
|
|
|
// PathMTUDiscover discovers the maximum MTU using both ICMP and TCP.
|
|
// Multiple ICMP addresses and TCP addresses can be specified for redundancy.
|
|
// ICMP PMTUD is run first. If successful, the range of possible MTU values to
|
|
// check for TCP PMTUD is reduced to [maxMTU-150, maxMTU] where maxMTU is the
|
|
// maximum MTU found with ICMP PMTUD. Otherwise, TCP PMTUD is run with the
|
|
// whole range of possible MTU values up to the physical link MTU to check.
|
|
// If the physicalLinkMTU is zero, it defaults to 1500 which is the ethernet standard MTU.
|
|
// If the pingTimeout is zero, it defaults to 1 second.
|
|
// If the logger is nil, a no-op logger is used.
|
|
// It returns [ErrMTUNotFound] if the MTU could not be determined.
|
|
func PathMTUDiscover(ctx context.Context, icmpAddrs []netip.Addr, tcpAddrs []netip.AddrPort,
|
|
physicalLinkMTU uint32, tryTimeout time.Duration, fw tcp.Firewall, logger Logger) (
|
|
mtu uint32, err error,
|
|
) {
|
|
if physicalLinkMTU == 0 {
|
|
const ethernetStandardMTU = 1500
|
|
physicalLinkMTU = ethernetStandardMTU
|
|
}
|
|
if tryTimeout == 0 {
|
|
tryTimeout = time.Second
|
|
}
|
|
if logger == nil {
|
|
logger = &noopLogger{}
|
|
}
|
|
|
|
// Try finding the MTU using ICMP
|
|
maxPossibleMTU := physicalLinkMTU
|
|
icmpSuccess := false
|
|
for _, icmpIP := range icmpAddrs {
|
|
mtu, err := icmp.PathMTUDiscover(ctx, icmpIP, physicalLinkMTU,
|
|
tryTimeout, logger)
|
|
switch {
|
|
case err == nil:
|
|
logger.Debugf("ICMP path MTU discovery against %s found maximum valid MTU %d", icmpIP, mtu)
|
|
icmpSuccess = true
|
|
maxPossibleMTU = mtu
|
|
case errors.Is(err, icmp.ErrNotPermitted), errors.Is(err, icmp.ErrMTUNotFound):
|
|
logger.Debugf("ICMP path MTU discovery failed: %s", err)
|
|
default:
|
|
return 0, fmt.Errorf("ICMP path MTU discovery: %w", err)
|
|
}
|
|
if icmpSuccess {
|
|
break
|
|
}
|
|
}
|
|
|
|
minMTU := constants.MinIPv4MTU
|
|
if tcpAddrs[0].Addr().Is6() {
|
|
minMTU = constants.MinIPv6MTU
|
|
}
|
|
if icmpSuccess {
|
|
const mtuMargin = 150
|
|
minMTU = max(maxPossibleMTU-mtuMargin, minMTU)
|
|
}
|
|
mtu, err = tcp.PathMTUDiscover(ctx, tcpAddrs, minMTU, maxPossibleMTU, tryTimeout, fw, logger)
|
|
if err != nil {
|
|
if errors.Is(err, firewall.ErrMarkMatchModuleMissing) {
|
|
logger.Debugf("aborting TCP path MTU discovery: %s", err)
|
|
if icmpSuccess {
|
|
return maxPossibleMTU, nil // only rely on ICMP PMTUD results
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("%w", ErrPMTUDFailICMPAndTCP)
|
|
}
|
|
logger.Debugf("TCP path MTU discovery found maximum valid MTU %d", mtu)
|
|
return mtu, nil
|
|
}
|