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