uquic/integrationtests/self/mtu_test.go
Marten Seemann 79bae396b4
proxy: rename to Proxy, refactor initialization (#4921)
* proxy: rename to Proxy, refactor initialization

* improve documentation
2025-01-25 11:13:33 +01:00

195 lines
5.1 KiB
Go

package self_test
import (
"bytes"
"context"
"fmt"
"io"
"net"
"sync"
"testing"
"time"
"github.com/quic-go/quic-go"
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/logging"
"github.com/stretchr/testify/require"
)
func TestInitialPacketSize(t *testing.T) {
server := newUPDConnLocalhost(t)
client := newUPDConnLocalhost(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan struct{})
go func() {
defer close(done)
quic.Dial(ctx, client, server.LocalAddr(), getTLSClientConfig(), getQuicConfig(&quic.Config{
InitialPacketSize: 1337,
}))
}()
buf := make([]byte, 2000)
n, _, err := server.ReadFrom(buf)
require.NoError(t, err)
require.Equal(t, 1337, n)
cancel()
<-done
}
func TestPathMTUDiscovery(t *testing.T) {
rtt := scaleDuration(5 * time.Millisecond)
const mtu = 1400
ln, err := quic.Listen(
newUPDConnLocalhost(t),
getTLSConfig(),
getQuicConfig(&quic.Config{
InitialPacketSize: 1234,
DisablePathMTUDiscovery: true,
EnableDatagrams: true,
}),
)
require.NoError(t, err)
defer ln.Close()
serverErrChan := make(chan error, 1)
go func() {
conn, err := ln.Accept(context.Background())
if err != nil {
serverErrChan <- err
return
}
str, err := conn.AcceptStream(context.Background())
if err != nil {
serverErrChan <- err
return
}
defer str.Close()
if _, err := io.Copy(str, str); err != nil {
serverErrChan <- err
return
}
}()
var mx sync.Mutex
var maxPacketSizeServer int
var clientPacketSizes []int
proxy := &quicproxy.Proxy{
Conn: newUPDConnLocalhost(t),
ServerAddr: ln.Addr().(*net.UDPAddr),
DelayPacket: func(_ quicproxy.Direction, _ []byte) time.Duration { return rtt / 2 },
DropPacket: func(dir quicproxy.Direction, packet []byte) bool {
if len(packet) > mtu {
return true
}
mx.Lock()
defer mx.Unlock()
switch dir {
case quicproxy.DirectionIncoming:
clientPacketSizes = append(clientPacketSizes, len(packet))
case quicproxy.DirectionOutgoing:
if len(packet) > maxPacketSizeServer {
maxPacketSizeServer = len(packet)
}
}
return false
},
}
require.NoError(t, proxy.Start())
defer proxy.Close()
// Make sure to use v4-only socket here.
// We can't reliably set the DF bit on dual-stack sockets on older versions of macOS (before Sequoia).
tr := &quic.Transport{Conn: newUPDConnLocalhost(t)}
defer tr.Close()
var mtus []logging.ByteCount
conn, err := tr.Dial(
context.Background(),
proxy.LocalAddr(),
getTLSClientConfig(),
getQuicConfig(&quic.Config{
InitialPacketSize: protocol.MinInitialPacketSize,
EnableDatagrams: true,
Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
return &logging.ConnectionTracer{
UpdatedMTU: func(mtu logging.ByteCount, _ bool) { mtus = append(mtus, mtu) },
}
},
}),
)
require.NoError(t, err)
defer conn.CloseWithError(0, "")
err = conn.SendDatagram(make([]byte, 2000))
require.Error(t, err)
var datagramErr *quic.DatagramTooLargeError
require.ErrorAs(t, err, &datagramErr)
initialMaxDatagramSize := datagramErr.MaxDatagramPayloadSize
str, err := conn.OpenStream()
require.NoError(t, err)
clientErrChan := make(chan error, 1)
go func() {
data, err := io.ReadAll(str)
if err != nil {
clientErrChan <- err
return
}
if !bytes.Equal(data, PRDataLong) {
clientErrChan <- fmt.Errorf("echoed data doesn't match: %x", data)
return
}
clientErrChan <- nil
}()
_, err = str.Write(PRDataLong)
require.NoError(t, err)
str.Close()
select {
case err := <-clientErrChan:
require.NoError(t, err)
case err := <-serverErrChan:
t.Fatalf("server error: %v", err)
case <-time.After(20 * time.Second):
t.Fatal("timeout")
}
err = conn.SendDatagram(make([]byte, 2000))
require.Error(t, err)
require.ErrorAs(t, err, &datagramErr)
finalMaxDatagramSize := datagramErr.MaxDatagramPayloadSize
mx.Lock()
defer mx.Unlock()
require.NotEmpty(t, mtus)
maxPacketSizeClient := int(mtus[len(mtus)-1])
t.Logf("max client packet size: %d, MTU: %d", maxPacketSizeClient, mtu)
t.Logf("max datagram size: initial: %d, final: %d", initialMaxDatagramSize, finalMaxDatagramSize)
t.Logf("max server packet size: %d, MTU: %d", maxPacketSizeServer, mtu)
require.GreaterOrEqual(t, maxPacketSizeClient, mtu-25)
const maxDiff = 40 // this includes the 21 bytes for the short header, 16 bytes for the encryption tag, and framing overhead
require.GreaterOrEqual(t, int(initialMaxDatagramSize), protocol.MinInitialPacketSize-maxDiff)
require.GreaterOrEqual(t, int(finalMaxDatagramSize), maxPacketSizeClient-maxDiff)
// MTU discovery was disabled on the server side
require.Equal(t, 1234, maxPacketSizeServer)
var numPacketsLargerThanDiscoveredMTU int
for _, s := range clientPacketSizes {
if s > maxPacketSizeClient {
numPacketsLargerThanDiscoveredMTU++
}
}
// The client shouldn't have sent any packets larger than the MTU it discovered,
// except for at most one MTU probe packet.
require.LessOrEqual(t, numPacketsLargerThanDiscoveredMTU, 1)
}