github.com/daeuniverse/quic-go@v0.0.0-20240413031024-943f218e0810/integrationtests/self/datagram_test.go (about) 1 package self_test 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 mrand "math/rand" 8 "net" 9 "sync" 10 "sync/atomic" 11 "time" 12 13 "github.com/daeuniverse/quic-go" 14 quicproxy "github.com/daeuniverse/quic-go/integrationtests/tools/proxy" 15 "github.com/daeuniverse/quic-go/internal/wire" 16 17 . "github.com/onsi/ginkgo/v2" 18 . "github.com/onsi/gomega" 19 ) 20 21 var _ = Describe("Datagram test", func() { 22 const concurrentSends = 100 23 const maxDatagramSize = 250 24 25 var ( 26 serverConn, clientConn *net.UDPConn 27 dropped, total atomic.Int32 28 ) 29 30 startServerAndProxy := func(enableDatagram, expectDatagramSupport bool) (port int, closeFn func()) { 31 addr, err := net.ResolveUDPAddr("udp", "localhost:0") 32 Expect(err).ToNot(HaveOccurred()) 33 serverConn, err = net.ListenUDP("udp", addr) 34 Expect(err).ToNot(HaveOccurred()) 35 ln, err := quic.Listen( 36 serverConn, 37 getTLSConfig(), 38 getQuicConfig(&quic.Config{EnableDatagrams: enableDatagram}), 39 ) 40 Expect(err).ToNot(HaveOccurred()) 41 42 accepted := make(chan struct{}) 43 go func() { 44 defer GinkgoRecover() 45 defer close(accepted) 46 conn, err := ln.Accept(context.Background()) 47 Expect(err).ToNot(HaveOccurred()) 48 49 if expectDatagramSupport { 50 Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) 51 if enableDatagram { 52 f := &wire.DatagramFrame{DataLenPresent: true} 53 var wg sync.WaitGroup 54 wg.Add(concurrentSends) 55 for i := 0; i < concurrentSends; i++ { 56 go func(i int) { 57 defer GinkgoRecover() 58 defer wg.Done() 59 b := make([]byte, 8) 60 binary.BigEndian.PutUint64(b, uint64(i)) 61 Expect(conn.SendDatagram(b)).To(Succeed()) 62 }(i) 63 } 64 maxDatagramMessageSize := f.MaxDataLen(maxDatagramSize, conn.ConnectionState().Version) 65 b := make([]byte, maxDatagramMessageSize+1) 66 Expect(conn.SendDatagram(b)).To(MatchError(&quic.DatagramTooLargeError{ 67 MaxDataLen: int64(maxDatagramMessageSize), 68 })) 69 wg.Wait() 70 } 71 } else { 72 Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) 73 } 74 }() 75 76 serverPort := ln.Addr().(*net.UDPAddr).Port 77 proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ 78 RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), 79 // drop 10% of Short Header packets sent from the server 80 DropPacket: func(dir quicproxy.Direction, packet []byte) bool { 81 if dir == quicproxy.DirectionIncoming { 82 return false 83 } 84 // don't drop Long Header packets 85 if wire.IsLongHeaderPacket(packet[0]) { 86 return false 87 } 88 drop := mrand.Int()%10 == 0 89 if drop { 90 dropped.Add(1) 91 } 92 total.Add(1) 93 return drop 94 }, 95 }) 96 Expect(err).ToNot(HaveOccurred()) 97 return proxy.LocalPort(), func() { 98 Eventually(accepted).Should(BeClosed()) 99 proxy.Close() 100 ln.Close() 101 } 102 } 103 104 BeforeEach(func() { 105 addr, err := net.ResolveUDPAddr("udp", "localhost:0") 106 Expect(err).ToNot(HaveOccurred()) 107 clientConn, err = net.ListenUDP("udp", addr) 108 Expect(err).ToNot(HaveOccurred()) 109 }) 110 111 It("sends datagrams", func() { 112 oldMaxDatagramSize := wire.MaxDatagramSize 113 wire.MaxDatagramSize = maxDatagramSize 114 proxyPort, close := startServerAndProxy(true, true) 115 defer close() 116 raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort)) 117 Expect(err).ToNot(HaveOccurred()) 118 conn, err := quic.Dial( 119 context.Background(), 120 clientConn, 121 raddr, 122 getTLSClientConfig(), 123 getQuicConfig(&quic.Config{EnableDatagrams: true}), 124 ) 125 Expect(err).ToNot(HaveOccurred()) 126 Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue()) 127 var counter int 128 for { 129 // Close the connection if no message is received for 100 ms. 130 timer := time.AfterFunc(scaleDuration(100*time.Millisecond), func() { conn.CloseWithError(0, "") }) 131 if _, err := conn.ReceiveDatagram(context.Background()); err != nil { 132 break 133 } 134 timer.Stop() 135 counter++ 136 } 137 138 numDropped := int(dropped.Load()) 139 expVal := concurrentSends - numDropped 140 fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, total.Load()) 141 fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, concurrentSends) 142 Expect(counter).To(And( 143 BeNumerically(">", expVal*9/10), 144 BeNumerically("<", concurrentSends), 145 )) 146 Eventually(conn.Context().Done).Should(BeClosed()) 147 wire.MaxDatagramSize = oldMaxDatagramSize 148 }) 149 150 It("server can disable datagram", func() { 151 proxyPort, close := startServerAndProxy(false, true) 152 raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort)) 153 Expect(err).ToNot(HaveOccurred()) 154 conn, err := quic.Dial( 155 context.Background(), 156 clientConn, 157 raddr, 158 getTLSClientConfig(), 159 getQuicConfig(&quic.Config{EnableDatagrams: true}), 160 ) 161 Expect(err).ToNot(HaveOccurred()) 162 Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) 163 164 close() 165 conn.CloseWithError(0, "") 166 }) 167 168 It("client can disable datagram", func() { 169 proxyPort, close := startServerAndProxy(false, true) 170 raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort)) 171 Expect(err).ToNot(HaveOccurred()) 172 conn, err := quic.Dial( 173 context.Background(), 174 clientConn, 175 raddr, 176 getTLSClientConfig(), 177 getQuicConfig(&quic.Config{EnableDatagrams: true}), 178 ) 179 Expect(err).ToNot(HaveOccurred()) 180 Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse()) 181 182 Expect(conn.SendDatagram([]byte{0})).To(HaveOccurred()) 183 184 close() 185 conn.CloseWithError(0, "") 186 }) 187 })