mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-01 19:27:35 +03:00
300 lines
9 KiB
Go
300 lines
9 KiB
Go
package self_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
mrand "math/rand"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
tls "github.com/refraction-networking/utls"
|
|
|
|
quic "github.com/refraction-networking/uquic"
|
|
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
|
|
"github.com/refraction-networking/uquic/internal/wire"
|
|
"github.com/refraction-networking/uquic/quicvarint"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
)
|
|
|
|
var directions = []quicproxy.Direction{quicproxy.DirectionIncoming, quicproxy.DirectionOutgoing, quicproxy.DirectionBoth}
|
|
|
|
type applicationProtocol struct {
|
|
name string
|
|
run func(ln *quic.Listener, port int)
|
|
}
|
|
|
|
var _ = Describe("Handshake drop tests", func() {
|
|
data := GeneratePRData(5000)
|
|
const timeout = 2 * time.Minute
|
|
|
|
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) (ln *quic.Listener, proxyPort int, closeFn func()) {
|
|
conf := getQuicConfig(&quic.Config{
|
|
MaxIdleTimeout: timeout,
|
|
HandshakeIdleTimeout: timeout,
|
|
})
|
|
var tlsConf *tls.Config
|
|
if longCertChain {
|
|
tlsConf = getTLSConfigWithLongCertChain()
|
|
} else {
|
|
tlsConf = getTLSConfig()
|
|
}
|
|
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
conn, err := net.ListenUDP("udp", laddr)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
tr := &quic.Transport{Conn: conn}
|
|
if doRetry {
|
|
tr.VerifySourceAddress = func(net.Addr) bool { return true }
|
|
}
|
|
ln, err = tr.Listen(tlsConf, conf)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
serverPort := ln.Addr().(*net.UDPAddr).Port
|
|
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
|
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
|
|
DropPacket: dropCallback,
|
|
DelayPacket: func(dir quicproxy.Direction, packet []byte) time.Duration {
|
|
return 10 * time.Millisecond
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
return ln, proxy.LocalPort(), func() {
|
|
ln.Close()
|
|
tr.Close()
|
|
conn.Close()
|
|
proxy.Close()
|
|
}
|
|
}
|
|
|
|
clientSpeaksFirst := &applicationProtocol{
|
|
name: "client speaks first",
|
|
run: func(ln *quic.Listener, port int) {
|
|
serverConnChan := make(chan quic.Connection)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
conn, err := ln.Accept(context.Background())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer conn.CloseWithError(0, "")
|
|
str, err := conn.AcceptStream(context.Background())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b, err := io.ReadAll(gbytes.TimeoutReader(str, timeout))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(b).To(Equal(data))
|
|
serverConnChan <- conn
|
|
}()
|
|
conn, err := quic.DialAddr(
|
|
context.Background(),
|
|
fmt.Sprintf("localhost:%d", port),
|
|
getTLSClientConfig(),
|
|
getQuicConfig(&quic.Config{
|
|
MaxIdleTimeout: timeout,
|
|
HandshakeIdleTimeout: timeout,
|
|
}),
|
|
)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
str, err := conn.OpenStream()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = str.Write(data)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(str.Close()).To(Succeed())
|
|
|
|
var serverConn quic.Connection
|
|
Eventually(serverConnChan, timeout).Should(Receive(&serverConn))
|
|
conn.CloseWithError(0, "")
|
|
serverConn.CloseWithError(0, "")
|
|
},
|
|
}
|
|
|
|
serverSpeaksFirst := &applicationProtocol{
|
|
name: "server speaks first",
|
|
run: func(ln *quic.Listener, port int) {
|
|
serverConnChan := make(chan quic.Connection)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
conn, err := ln.Accept(context.Background())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
str, err := conn.OpenStream()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = str.Write(data)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(str.Close()).To(Succeed())
|
|
serverConnChan <- conn
|
|
}()
|
|
conn, err := quic.DialAddr(
|
|
context.Background(),
|
|
fmt.Sprintf("localhost:%d", port),
|
|
getTLSClientConfig(),
|
|
getQuicConfig(&quic.Config{
|
|
MaxIdleTimeout: timeout,
|
|
HandshakeIdleTimeout: timeout,
|
|
}),
|
|
)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
str, err := conn.AcceptStream(context.Background())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
b, err := io.ReadAll(gbytes.TimeoutReader(str, timeout))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(b).To(Equal(data))
|
|
|
|
var serverConn quic.Connection
|
|
Eventually(serverConnChan, timeout).Should(Receive(&serverConn))
|
|
conn.CloseWithError(0, "")
|
|
serverConn.CloseWithError(0, "")
|
|
},
|
|
}
|
|
|
|
nobodySpeaks := &applicationProtocol{
|
|
name: "nobody speaks",
|
|
run: func(ln *quic.Listener, port int) {
|
|
serverConnChan := make(chan quic.Connection)
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
conn, err := ln.Accept(context.Background())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
serverConnChan <- conn
|
|
}()
|
|
conn, err := quic.DialAddr(
|
|
context.Background(),
|
|
fmt.Sprintf("localhost:%d", port),
|
|
getTLSClientConfig(),
|
|
getQuicConfig(&quic.Config{
|
|
MaxIdleTimeout: timeout,
|
|
HandshakeIdleTimeout: timeout,
|
|
}),
|
|
)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
var serverConn quic.Connection
|
|
Eventually(serverConnChan, timeout).Should(Receive(&serverConn))
|
|
// both server and client accepted a connection. Close now.
|
|
conn.CloseWithError(0, "")
|
|
serverConn.CloseWithError(0, "")
|
|
},
|
|
}
|
|
|
|
for _, d := range directions {
|
|
direction := d
|
|
|
|
for _, dr := range []bool{true, false} {
|
|
doRetry := dr
|
|
desc := "when using Retry"
|
|
if !dr {
|
|
desc = "when not using Retry"
|
|
}
|
|
|
|
Context(desc, func() {
|
|
for _, lcc := range []bool{false, true} {
|
|
longCertChain := lcc
|
|
|
|
Context(fmt.Sprintf("using a long certificate chain: %t", longCertChain), func() {
|
|
for _, a := range []*applicationProtocol{clientSpeaksFirst, serverSpeaksFirst, nobodySpeaks} {
|
|
app := a
|
|
|
|
Context(app.name, func() {
|
|
It(fmt.Sprintf("establishes a connection when the first packet is lost in %s direction", direction), func() {
|
|
var incoming, outgoing atomic.Int32
|
|
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
|
var p int32
|
|
//nolint:exhaustive
|
|
switch d {
|
|
case quicproxy.DirectionIncoming:
|
|
p = incoming.Add(1)
|
|
case quicproxy.DirectionOutgoing:
|
|
p = outgoing.Add(1)
|
|
}
|
|
return p == 1 && d.Is(direction)
|
|
}, doRetry, longCertChain)
|
|
defer closeFn()
|
|
app.run(ln, proxyPort)
|
|
})
|
|
|
|
It(fmt.Sprintf("establishes a connection when the second packet is lost in %s direction", direction), func() {
|
|
var incoming, outgoing atomic.Int32
|
|
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
|
var p int32
|
|
//nolint:exhaustive
|
|
switch d {
|
|
case quicproxy.DirectionIncoming:
|
|
p = incoming.Add(1)
|
|
case quicproxy.DirectionOutgoing:
|
|
p = outgoing.Add(1)
|
|
}
|
|
return p == 2 && d.Is(direction)
|
|
}, doRetry, longCertChain)
|
|
defer closeFn()
|
|
app.run(ln, proxyPort)
|
|
})
|
|
|
|
It(fmt.Sprintf("establishes a connection when 1/3 of the packets are lost in %s direction", direction), func() {
|
|
const maxSequentiallyDropped = 10
|
|
var mx sync.Mutex
|
|
var incoming, outgoing int
|
|
|
|
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
|
drop := mrand.Int63n(int64(3)) == 0
|
|
|
|
mx.Lock()
|
|
defer mx.Unlock()
|
|
// never drop more than 10 consecutive packets
|
|
if d.Is(quicproxy.DirectionIncoming) {
|
|
if drop {
|
|
incoming++
|
|
if incoming > maxSequentiallyDropped {
|
|
drop = false
|
|
}
|
|
}
|
|
if !drop {
|
|
incoming = 0
|
|
}
|
|
}
|
|
if d.Is(quicproxy.DirectionOutgoing) {
|
|
if drop {
|
|
outgoing++
|
|
if outgoing > maxSequentiallyDropped {
|
|
drop = false
|
|
}
|
|
}
|
|
if !drop {
|
|
outgoing = 0
|
|
}
|
|
}
|
|
return drop
|
|
}, doRetry, longCertChain)
|
|
defer closeFn()
|
|
app.run(ln, proxyPort)
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
It("establishes a connection when the ClientHello is larger than 1 MTU (e.g. post-quantum)", func() {
|
|
origAdditionalTransportParametersClient := wire.AdditionalTransportParametersClient
|
|
defer func() {
|
|
wire.AdditionalTransportParametersClient = origAdditionalTransportParametersClient
|
|
}()
|
|
b := make([]byte, 2500) // the ClientHello will now span across 3 packets
|
|
mrand.New(mrand.NewSource(GinkgoRandomSeed())).Read(b)
|
|
wire.AdditionalTransportParametersClient = map[uint64][]byte{
|
|
// Avoid random collisions with the greased transport parameters.
|
|
uint64(27+31*(1000+mrand.Int63()/31)) % quicvarint.Max: b,
|
|
}
|
|
|
|
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
|
if d == quicproxy.DirectionOutgoing {
|
|
return false
|
|
}
|
|
return mrand.Intn(3) == 0
|
|
}, false, false)
|
|
defer closeFn()
|
|
clientSpeaksFirst.run(ln, proxyPort)
|
|
})
|
|
}
|
|
})
|