Skip to content

Instantly share code, notes, and snippets.

@tmc
Created September 5, 2026 18:30
Show Gist options
  • Select an option

  • Save tmc/0a5fbae12990ca8229f09a7155ed619b to your computer and use it in GitHub Desktop.

Select an option

Save tmc/0a5fbae12990ca8229f09a7155ed619b to your computer and use it in GitHub Desktop.
quic-go v0.62.0: path migration ignores DisablePathMTUDiscovery

quic-go: path migration ignores DisablePathMTUDiscovery

Save repro_test.go in an empty directory, then run:

go mod init example.com/mtu-repro
go get github.com/quic-go/quic-go@v0.62.0
go test -v repro_test.go

The test exchanges data between two loopback endpoints with MTU discovery disabled, migrates the client to a new socket, and checks for acknowledged MTU probes.

On unmodified v0.62.0, it fails:

before migration: client=0 server=0 acknowledged MTU probes
after migration: client=4 server=4 acknowledged MTU probes
migration enabled MTU discovery despite DisablePathMTUDiscovery: true

Both counts should remain zero after migration. Verified on macOS arm64 with Go 1.26.0 and Go 1.26.8.

package quic_test
import (
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"math/big"
"net"
"sync/atomic"
"testing"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
// Run in a module requiring github.com/quic-go/quic-go v0.62.0:
// go test -v repro_test.go
func TestMigrationKeepsMTUDiscoveryDisabled(t *testing.T) {
check := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// A self-signed certificate is sufficient for this loopback test.
pub, key, err := ed25519.GenerateKey(rand.Reader)
check(err)
template := &x509.Certificate{SerialNumber: big.NewInt(1)}
der, err := x509.CreateCertificate(rand.Reader, template, template, pub, key)
check(err)
serverTLS := &tls.Config{
Certificates: []tls.Certificate{{Certificate: [][]byte{der}, PrivateKey: key}},
NextProtos: []string{"mtu-repro"},
}
clientTLS := &tls.Config{InsecureSkipVerify: true, NextProtos: serverTLS.NextProtos}
newTransport := func() *quic.Transport {
pc, err := net.ListenPacket("udp4", "127.0.0.1:0")
check(err)
tr := &quic.Transport{Conn: pc}
t.Cleanup(func() {
tr.Close()
pc.Close()
})
return tr
}
config := func(probes *probeCounter) *quic.Config {
return &quic.Config{
DisablePathMTUDiscovery: true,
InitialPacketSize: 1200,
EnableDatagrams: true,
Tracer: func(context.Context, bool, quic.ConnectionID) qlogwriter.Trace {
return probes
},
}
}
var clientProbes, serverProbes probeCounter
ln, err := newTransport().Listen(serverTLS, config(&serverProbes))
check(err)
defer ln.Close()
client, err := newTransport().Dial(ctx, ln.Addr(), clientTLS, config(&clientProbes))
check(err)
defer client.CloseWithError(0, "")
server, err := ln.Accept(ctx)
check(err)
defer server.CloseWithError(0, "")
// Keep both send loops active and verify delivery before and after migration.
exchange := func() {
for until := time.Now().Add(500 * time.Millisecond); time.Now().Before(until); {
for _, pair := range [][2]*quic.Conn{{client, server}, {server, client}} {
check(pair[0].SendDatagram([]byte("hello")))
b, err := pair[1].ReceiveDatagram(ctx)
check(err)
if string(b) != "hello" {
t.Fatalf("received %q, want hello", b)
}
}
time.Sleep(5 * time.Millisecond)
}
}
exchange()
t.Logf("before migration: client=%d server=%d acknowledged MTU probes",
clientProbes.Load(), serverProbes.Load())
if clientProbes.Load() != 0 || serverProbes.Load() != 0 {
t.Fatal("unexpected MTU probes before migration")
}
next := newTransport()
path, err := client.AddPath(next)
check(err)
check(path.Probe(ctx))
check(path.Switch())
exchange()
if client.LocalAddr().String() != next.Conn.LocalAddr().String() ||
server.RemoteAddr().String() != next.Conn.LocalAddr().String() {
t.Fatal("connection did not migrate to the new socket")
}
c, s := clientProbes.Load(), serverProbes.Load()
t.Logf("after migration: client=%d server=%d acknowledged MTU probes", c, s)
if c != 0 || s != 0 {
t.Fatal("migration enabled MTU discovery despite DisablePathMTUDiscovery: true")
}
}
// MTUUpdated is emitted when an MTU probe is acknowledged. Path validation
// packets do not emit this event. The recorder is shared by qlog producers.
type probeCounter struct{ atomic.Int64 }
func (p *probeCounter) AddProducer() qlogwriter.Recorder { return p }
func (*probeCounter) SupportsSchemas(string) bool { return true }
func (*probeCounter) Close() error { return nil }
func (p *probeCounter) RecordEvent(ev qlogwriter.Event) {
if _, ok := ev.(qlog.MTUUpdated); ok {
p.Add(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment