diff --git a/integrationtests/self/zero_rtt_test.go b/integrationtests/self/zero_rtt_test.go new file mode 100644 index 00000000..495d5f8c --- /dev/null +++ b/integrationtests/self/zero_rtt_test.go @@ -0,0 +1,221 @@ +package self_test + +import ( + "context" + "fmt" + "io/ioutil" + mrand "math/rand" + "net" + "sync" + "sync/atomic" + "time" + + quic "github.com/lucas-clemente/quic-go" + quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/wire" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("0-RTT", func() { + const rtt = 50 * time.Millisecond + for _, v := range protocol.SupportedVersions { + version := v + + Context(fmt.Sprintf("with QUIC version %s", version), func() { + runTest := func(ln quic.Listener, proxyPort int, testdata []byte) { + // dial the first session in order to receive a session ticket + go func() { + defer GinkgoRecover() + _, err := ln.Accept(context.Background()) + Expect(err).ToNot(HaveOccurred()) + }() + + clientConf := getTLSClientConfig() + gets := make(chan string, 100) + puts := make(chan string, 100) + clientConf.ClientSessionCache = newClientSessionCache(gets, puts) + sess, err := quic.DialAddr( + fmt.Sprintf("localhost:%d", proxyPort), + clientConf, + &quic.Config{Versions: []protocol.VersionNumber{version}}, + ) + Expect(err).ToNot(HaveOccurred()) + Eventually(puts).Should(Receive()) + // received the session ticket. We're done here. + Expect(sess.Close()).To(Succeed()) + + // now dial the second session, and use 0-RTT to send some data + done := make(chan struct{}) + go func() { + defer GinkgoRecover() + sess, err := ln.Accept(context.Background()) + Expect(err).ToNot(HaveOccurred()) + str, err := sess.AcceptUniStream(context.Background()) + Expect(err).ToNot(HaveOccurred()) + data, err := ioutil.ReadAll(str) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal(testdata)) + close(done) + }() + + sess, err = quic.DialAddrEarly( + fmt.Sprintf("localhost:%d", proxyPort), + clientConf, + &quic.Config{Versions: []protocol.VersionNumber{version}}, + ) + Expect(err).ToNot(HaveOccurred()) + str, err := sess.OpenUniStream() + Expect(err).ToNot(HaveOccurred()) + _, err = str.Write(testdata) + Expect(err).ToNot(HaveOccurred()) + Expect(str.Close()).To(Succeed()) + Eventually(done).Should(BeClosed()) + } + + It("transfers 0-RTT data", func() { + var num0RTTPackets uint32 // to be used as an atomic + + ln, err := quic.ListenAddr( + "localhost:0", + getTLSConfig(), + &quic.Config{ + Versions: []protocol.VersionNumber{version}, + AcceptToken: func(_ net.Addr, _ *quic.Token) bool { return true }, + }, + ) + Expect(err).ToNot(HaveOccurred()) + defer ln.Close() + serverPort := ln.Addr().(*net.UDPAddr).Port + + proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ + RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), + DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { + hdr, _, _, err := wire.ParsePacket(data, 0) + Expect(err).ToNot(HaveOccurred()) + if hdr.Type == protocol.PacketType0RTT { + atomic.AddUint32(&num0RTTPackets, 1) + } + return rtt / 2 + }, + }) + Expect(err).ToNot(HaveOccurred()) + defer proxy.Close() + + runTest(ln, proxy.LocalPort(), PRData) + + num0RTT := atomic.LoadUint32(&num0RTTPackets) + fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT) + Expect(num0RTT).ToNot(BeZero()) + }) + + It("transfers 0-RTT data, when 0-RTT packets are lost", func() { + var ( + num0RTTPackets uint32 // to be used as an atomic + num0RTTDropped uint32 + ) + + ln, err := quic.ListenAddr( + "localhost:0", + getTLSConfig(), + &quic.Config{ + Versions: []protocol.VersionNumber{version}, + AcceptToken: func(_ net.Addr, _ *quic.Token) bool { return true }, + }, + ) + Expect(err).ToNot(HaveOccurred()) + defer ln.Close() + serverPort := ln.Addr().(*net.UDPAddr).Port + + proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ + RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), + DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { + hdr, _, _, err := wire.ParsePacket(data, 0) + Expect(err).ToNot(HaveOccurred()) + if hdr.Type == protocol.PacketType0RTT { + atomic.AddUint32(&num0RTTPackets, 1) + } + return rtt / 2 + }, + DropPacket: func(_ quicproxy.Direction, data []byte) bool { + hdr, _, _, err := wire.ParsePacket(data, 0) + Expect(err).ToNot(HaveOccurred()) + if hdr.Type == protocol.PacketType0RTT { + // drop 25% of the 0-RTT packets + drop := mrand.Intn(4) == 0 + if drop { + atomic.AddUint32(&num0RTTDropped, 1) + } + return drop + } + return false + }, + }) + Expect(err).ToNot(HaveOccurred()) + defer proxy.Close() + + runTest(ln, proxy.LocalPort(), PRData) + + num0RTT := atomic.LoadUint32(&num0RTTPackets) + numDropped := atomic.LoadUint32(&num0RTTDropped) + fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped) + Expect(numDropped).ToNot(BeZero()) + Expect(num0RTT).ToNot(BeZero()) + }) + + It("retransmits all 0-RTT data when the server performs a Retry", func() { + var mutex sync.Mutex + var firstConnID, secondConnID protocol.ConnectionID + var firstCounter, secondCounter int + + ln, err := quic.ListenAddr( + "localhost:0", + getTLSConfig(), + &quic.Config{Versions: []protocol.VersionNumber{version}}, + ) + Expect(err).ToNot(HaveOccurred()) + defer ln.Close() + serverPort := ln.Addr().(*net.UDPAddr).Port + + proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ + RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), + DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { + hdr, _, _, err := wire.ParsePacket(data, 0) + Expect(err).ToNot(HaveOccurred()) + if hdr.Type == protocol.PacketType0RTT { + connID := hdr.DestConnectionID + mutex.Lock() + defer mutex.Unlock() + if firstConnID == nil { + firstConnID = connID + firstCounter++ + } else if firstConnID != nil && firstConnID.Equal(connID) { + Expect(secondConnID).To(BeNil()) + firstCounter++ + } else if secondConnID == nil { + secondConnID = connID + secondCounter++ + } else if secondConnID != nil && secondConnID.Equal(connID) { + secondCounter++ + } else { + Fail("received 3 connection IDs on 0-RTT packets") + } + } + return rtt / 2 + }, + }) + Expect(err).ToNot(HaveOccurred()) + defer proxy.Close() + + runTest(ln, proxy.LocalPort(), GeneratePRData(5*1100)) // ~5 packets + + mutex.Lock() + defer mutex.Unlock() + Expect(firstCounter).To(BeNumerically("~", 5, 1)) // the FIN bit might be sent extra + Expect(secondCounter).To(Equal(firstCounter)) + }) + }) + } +})