diff --git a/integrationtests/gquic/drop_test.go b/integrationtests/gquic/drop_test.go index 2d471261..cffa3fb4 100644 --- a/integrationtests/gquic/drop_test.go +++ b/integrationtests/gquic/drop_test.go @@ -3,6 +3,7 @@ package gquic_test import ( "bytes" "fmt" + mrand "math/rand" "os/exec" "strconv" @@ -13,20 +14,25 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" . "github.com/onsi/gomega/gexec" ) +var directions = []quicproxy.Direction{quicproxy.DirectionIncoming, quicproxy.DirectionOutgoing, quicproxy.DirectionBoth} + var _ = Describe("Drop tests", func() { var proxy *quicproxy.QuicProxy - runDropTest := func(dropCallback quicproxy.DropCallback, version protocol.VersionNumber) { + startProxy := func(dropCallback quicproxy.DropCallback, version protocol.VersionNumber) { var err error proxy, err = quicproxy.NewQuicProxy("localhost:0", version, &quicproxy.Opts{ RemoteAddr: "localhost:" + testserver.Port(), DropPacket: dropCallback, }) Expect(err).ToNot(HaveOccurred()) + } + downloadFile := func(version protocol.VersionNumber) { command := exec.Command( clientPath, "--quic-version="+strconv.Itoa(int(version)), @@ -34,7 +40,6 @@ var _ = Describe("Drop tests", func() { "--port="+strconv.Itoa(proxy.LocalPort()), "https://quic.clemente.io/prdata", ) - session, err := Start(command, nil, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) defer session.Kill() @@ -42,46 +47,91 @@ var _ = Describe("Drop tests", func() { Expect(bytes.Contains(session.Out.Contents(), testserver.PRData)).To(BeTrue()) } + downloadHello := func(version protocol.VersionNumber) { + command := exec.Command( + clientPath, + "--quic-version="+strconv.Itoa(int(version)), + "--host=127.0.0.1", + "--port="+strconv.Itoa(proxy.LocalPort()), + "https://quic.clemente.io/hello", + ) + session, err := Start(command, nil, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + defer session.Kill() + Eventually(session, 20).Should(Exit(0)) + Expect(session.Out).To(Say(":status 200")) + Expect(session.Out).To(Say("body: Hello, World!\n")) + } + + deterministicDropper := func(p, interval, dropInARow uint64) bool { + return (p % interval) < dropInARow + } + + stochasticDropper := func(freq int) bool { + return mrand.Int63n(int64(freq)) == 0 + } + AfterEach(func() { Expect(proxy.Close()).To(Succeed()) }) - Context("after the crypto handshake", func() { - for i := range protocol.SupportedVersions { - version := protocol.SupportedVersions[i] + for _, v := range protocol.SupportedVersions { + version := v - Context(fmt.Sprintf("with quic version %d", version), func() { - dropTests("dropping every 4th packet", 4, 1, runDropTest, version) - dropTests("dropping 10 packets every 100 packets", 100, 10, runDropTest, version) + Context(fmt.Sprintf("with QUIC version %d", version), func() { + Context("during the crypto handshake", func() { + for _, d := range directions { + direction := d + + It(fmt.Sprintf("establishes a connection when the first packet is lost in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return p == 1 && d.Is(direction) + }, version) + downloadHello(version) + }) + + It(fmt.Sprintf("establishes a connection when the second packet is lost in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return p == 2 && d.Is(direction) + }, version) + downloadHello(version) + }) + + It(fmt.Sprintf("establishes a connection when 1/5 of the packets are lost in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return d.Is(direction) && stochasticDropper(5) + }, version) + downloadHello(version) + }) + } }) - } - }) + + Context("after the crypto handshake", func() { + for _, d := range directions { + direction := d + + It(fmt.Sprintf("downloads a file when every 5th packet is dropped in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return p >= 10 && d.Is(direction) && deterministicDropper(p, 5, 1) + }, version) + downloadFile(version) + }) + + It(fmt.Sprintf("downloads a file when 1/5th of all packet are dropped randomly in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return p >= 10 && d.Is(direction) && stochasticDropper(5) + }, version) + downloadFile(version) + }) + + It(fmt.Sprintf("downloads a file when 10 packets every 100 packet are dropped in %s direction", d), func() { + startProxy(func(d quicproxy.Direction, p uint64) bool { + return p >= 10 && d.Is(direction) && deterministicDropper(p, 100, 10) + }, version) + downloadFile(version) + }) + } + }) + }) + } }) - -func dropTests( - context string, - interval uint64, - dropInARow uint64, - runDropTest func(dropCallback quicproxy.DropCallback, version protocol.VersionNumber), - version protocol.VersionNumber) { - Context(context, func() { - dropper := func(p uint64) bool { - if p <= 10 { // don't interfere with the crypto handshake - return false - } - return (p % interval) < dropInARow - } - - It("gets a file when many outgoing packets are dropped", func() { - runDropTest(func(d quicproxy.Direction, p uint64) bool { - return d == quicproxy.DirectionOutgoing && dropper(p) - }, version) - }) - - It("gets a file when many incoming packets are dropped", func() { - runDropTest(func(d quicproxy.Direction, p uint64) bool { - return d == quicproxy.DirectionIncoming && dropper(p) - }, version) - }) - }) -} diff --git a/integrationtests/tools/proxy/proxy.go b/integrationtests/tools/proxy/proxy.go index b8f2cf6b..a9d0d4cf 100644 --- a/integrationtests/tools/proxy/proxy.go +++ b/integrationtests/tools/proxy/proxy.go @@ -26,8 +26,30 @@ const ( DirectionIncoming Direction = iota // DirectionOutgoing is the direction from the server to the client. DirectionOutgoing + // DirectionBoth is both incoming and outgoing + DirectionBoth ) +func (d Direction) String() string { + switch d { + case DirectionIncoming: + return "incoming" + case DirectionOutgoing: + return "outgoing" + case DirectionBoth: + return "both" + default: + panic("unknown direction") + } +} + +func (d Direction) Is(dir Direction) bool { + if d == DirectionBoth || dir == DirectionBoth { + return true + } + return d == dir +} + // DropCallback is a callback that determines which packet gets dropped. type DropCallback func(dir Direction, packetCount uint64) bool