|
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) |
|
} |
|
} |