github.com/quic-go/quic-go@v0.44.0/integrationtests/self/mtu_test.go (about)

     1  package self_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/quic-go/quic-go"
    12  	quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
    13  	"github.com/quic-go/quic-go/internal/protocol"
    14  	"github.com/quic-go/quic-go/logging"
    15  
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  var _ = Describe("DPLPMTUD", func() {
    21  	// This test is very sensitive to packet loss, as the loss of a single Path MTU probe packet makes DPLPMTUD
    22  	// clip the assumed MTU at that value.
    23  	It("discovers the MTU", FlakeAttempts(3), func() {
    24  		rtt := scaleDuration(5 * time.Millisecond)
    25  		const mtu = 1400
    26  
    27  		ln, err := quic.ListenAddr(
    28  			"localhost:0",
    29  			getTLSConfig(),
    30  			getQuicConfig(&quic.Config{
    31  				InitialPacketSize:       1234,
    32  				DisablePathMTUDiscovery: true,
    33  				EnableDatagrams:         true,
    34  			}),
    35  		)
    36  		Expect(err).ToNot(HaveOccurred())
    37  		defer ln.Close()
    38  		go func() {
    39  			defer GinkgoRecover()
    40  			conn, err := ln.Accept(context.Background())
    41  			Expect(err).ToNot(HaveOccurred())
    42  			str, err := conn.AcceptStream(context.Background())
    43  			Expect(err).ToNot(HaveOccurred())
    44  			_, err = io.Copy(str, str)
    45  			Expect(err).ToNot(HaveOccurred())
    46  			str.Close()
    47  		}()
    48  
    49  		var mx sync.Mutex
    50  		var maxPacketSizeServer int
    51  		var clientPacketSizes []int
    52  		serverPort := ln.Addr().(*net.UDPAddr).Port
    53  		proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
    54  			RemoteAddr:  fmt.Sprintf("localhost:%d", serverPort),
    55  			DelayPacket: func(quicproxy.Direction, []byte) time.Duration { return rtt / 2 },
    56  			DropPacket: func(dir quicproxy.Direction, packet []byte) bool {
    57  				if len(packet) > mtu {
    58  					return true
    59  				}
    60  				mx.Lock()
    61  				defer mx.Unlock()
    62  				switch dir {
    63  				case quicproxy.DirectionIncoming:
    64  					clientPacketSizes = append(clientPacketSizes, len(packet))
    65  				case quicproxy.DirectionOutgoing:
    66  					if len(packet) > maxPacketSizeServer {
    67  						maxPacketSizeServer = len(packet)
    68  					}
    69  				}
    70  				return false
    71  			},
    72  		})
    73  		Expect(err).ToNot(HaveOccurred())
    74  		defer proxy.Close()
    75  
    76  		// Make sure to use v4-only socket here.
    77  		// We can't reliably set the DF bit on dual-stack sockets on macOS.
    78  		udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
    79  		Expect(err).ToNot(HaveOccurred())
    80  		defer udpConn.Close()
    81  		tr := &quic.Transport{Conn: udpConn}
    82  		defer tr.Close()
    83  		var mtus []logging.ByteCount
    84  		mtuTracer := &logging.ConnectionTracer{
    85  			UpdatedMTU: func(mtu logging.ByteCount, _ bool) {
    86  				mtus = append(mtus, mtu)
    87  			},
    88  		}
    89  		conn, err := tr.Dial(
    90  			context.Background(),
    91  			proxy.LocalAddr(),
    92  			getTLSClientConfig(),
    93  			getQuicConfig(&quic.Config{
    94  				InitialPacketSize: protocol.MinInitialPacketSize,
    95  				EnableDatagrams:   true,
    96  				Tracer: func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
    97  					return mtuTracer
    98  				},
    99  			}),
   100  		)
   101  		Expect(err).ToNot(HaveOccurred())
   102  		defer conn.CloseWithError(0, "")
   103  		str, err := conn.OpenStream()
   104  		Expect(err).ToNot(HaveOccurred())
   105  		done := make(chan struct{})
   106  		go func() {
   107  			defer close(done)
   108  			defer GinkgoRecover()
   109  			data, err := io.ReadAll(str)
   110  			Expect(err).ToNot(HaveOccurred())
   111  			Expect(data).To(Equal(PRDataLong))
   112  		}()
   113  		err = conn.SendDatagram(make([]byte, 2000))
   114  		Expect(err).To(BeAssignableToTypeOf(&quic.DatagramTooLargeError{}))
   115  		initialMaxDatagramSize := err.(*quic.DatagramTooLargeError).MaxDatagramPayloadSize
   116  		_, err = str.Write(PRDataLong)
   117  		Expect(err).ToNot(HaveOccurred())
   118  		str.Close()
   119  		Eventually(done, 20*time.Second).Should(BeClosed())
   120  		err = conn.SendDatagram(make([]byte, 2000))
   121  		Expect(err).To(BeAssignableToTypeOf(&quic.DatagramTooLargeError{}))
   122  		finalMaxDatagramSize := err.(*quic.DatagramTooLargeError).MaxDatagramPayloadSize
   123  
   124  		mx.Lock()
   125  		defer mx.Unlock()
   126  		Expect(mtus).ToNot(BeEmpty())
   127  		maxPacketSizeClient := int(mtus[len(mtus)-1])
   128  		fmt.Fprintf(GinkgoWriter, "max client packet size: %d, MTU: %d\n", maxPacketSizeClient, mtu)
   129  		fmt.Fprintf(GinkgoWriter, "max datagram size: initial: %d, final: %d\n", initialMaxDatagramSize, finalMaxDatagramSize)
   130  		fmt.Fprintf(GinkgoWriter, "max server packet size: %d, MTU: %d\n", maxPacketSizeServer, mtu)
   131  		Expect(maxPacketSizeClient).To(BeNumerically(">=", mtu-25))
   132  		const maxDiff = 40 // this includes the 21 bytes for the short header, 16 bytes for the encryption tag, and framing overhead
   133  		Expect(initialMaxDatagramSize).To(BeNumerically(">=", protocol.MinInitialPacketSize-maxDiff))
   134  		Expect(finalMaxDatagramSize).To(BeNumerically(">=", maxPacketSizeClient-maxDiff))
   135  		// MTU discovery was disabled on the server side
   136  		Expect(maxPacketSizeServer).To(Equal(1234))
   137  
   138  		var numPacketsLargerThanDiscoveredMTU int
   139  		for _, s := range clientPacketSizes {
   140  			if s > maxPacketSizeClient {
   141  				numPacketsLargerThanDiscoveredMTU++
   142  			}
   143  		}
   144  		// The client shouldn't have sent any packets larger than the MTU it discovered,
   145  		// except for at most one MTU probe packet.
   146  		Expect(numPacketsLargerThanDiscoveredMTU).To(BeNumerically("<=", 1))
   147  	})
   148  
   149  	It("uses the initial packet size", func() {
   150  		c, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
   151  		Expect(err).ToNot(HaveOccurred())
   152  		defer c.Close()
   153  
   154  		cconn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
   155  		Expect(err).ToNot(HaveOccurred())
   156  		defer cconn.Close()
   157  
   158  		ctx, cancel := context.WithCancel(context.Background())
   159  		done := make(chan struct{})
   160  		go func() {
   161  			defer close(done)
   162  			quic.Dial(ctx, cconn, c.LocalAddr(), getTLSClientConfig(), getQuicConfig(&quic.Config{InitialPacketSize: 1337}))
   163  		}()
   164  
   165  		b := make([]byte, 2000)
   166  		n, _, err := c.ReadFrom(b)
   167  		Expect(err).ToNot(HaveOccurred())
   168  		Expect(n).To(Equal(1337))
   169  		cancel()
   170  		Eventually(done).Should(BeClosed())
   171  	})
   172  })