github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/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/metacubex/quic-go" 12 quicproxy "github.com/metacubex/quic-go/integrationtests/tools/proxy" 13 "github.com/metacubex/quic-go/internal/protocol" 14 "github.com/metacubex/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 })