mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-04 12:47:36 +03:00
set the QUIC version for integration tests using a command line flag
This commit is contained in:
parent
0dbe595d9f
commit
2b0a03a988
21 changed files with 2471 additions and 2595 deletions
17
.github/workflows/integration.yml
vendored
17
.github/workflows/integration.yml
vendored
|
@ -20,19 +20,24 @@ jobs:
|
|||
- run: go version
|
||||
- name: set qlogger
|
||||
if: env.DEBUG == 'true'
|
||||
run: echo "QLOGFLAG=-- -qlog" >> $GITHUB_ENV
|
||||
- name: Run tests
|
||||
run: echo "QLOGFLAG= -qlog" >> $GITHUB_ENV
|
||||
- name: Run other tests
|
||||
run: |
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace -skip-package self,versionnegotiation integrationtests
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/versionnegotiation ${{ env.QLOGFLAG }}
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self ${{ env.QLOGFLAG }}
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/versionnegotiation -- ${{ env.QLOGFLAG }}
|
||||
- name: Run self tests, using draft-29
|
||||
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=draft29 ${{ env.QLOGFLAG }}
|
||||
- name: Run self tests, using QUIC v1
|
||||
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=1 ${{ env.QLOGFLAG }}
|
||||
- name: Run self tests, using QUIC v2
|
||||
run: go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- -version=2 ${{ env.QLOGFLAG }}
|
||||
- name: Run tests (32 bit)
|
||||
env:
|
||||
GOARCH: 386
|
||||
run: |
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace -skip-package self,versionnegotiation integrationtests
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/versionnegotiation ${{ env.QLOGFLAG }}
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self ${{ env.QLOGFLAG }}
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/versionnegotiation -- ${{ env.QLOGFLAG }}
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -r -v -randomize-all -randomize-suites -trace integrationtests/self -- ${{ env.QLOGFLAG }}
|
||||
- name: save qlogs
|
||||
if: ${{ always() && env.DEBUG == 'true' }}
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
|
@ -33,7 +33,6 @@ const (
|
|||
var defaultQuicConfig = &quic.Config{
|
||||
MaxIncomingStreams: -1, // don't allow the server to create bidirectional streams
|
||||
KeepAlivePeriod: 10 * time.Second,
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
}
|
||||
|
||||
type dialFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error)
|
||||
|
@ -74,9 +73,10 @@ var _ roundTripCloser = &client{}
|
|||
func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) {
|
||||
if conf == nil {
|
||||
conf = defaultQuicConfig.Clone()
|
||||
} else if len(conf.Versions) == 0 {
|
||||
}
|
||||
if len(conf.Versions) == 0 {
|
||||
conf = conf.Clone()
|
||||
conf.Versions = []quic.VersionNumber{defaultQuicConfig.Versions[0]}
|
||||
conf.Versions = []quic.VersionNumber{protocol.SupportedVersions[0]}
|
||||
}
|
||||
if len(conf.Versions) != 1 {
|
||||
return nil, errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
|
||||
|
|
|
@ -65,7 +65,7 @@ var _ = Describe("Client", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
var dialAddrCalled bool
|
||||
dialAddr = func(_ context.Context, _ string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
Expect(quicConf).To(Equal(defaultQuicConfig))
|
||||
Expect(quicConf.MaxIncomingStreams).To(Equal(defaultQuicConfig.MaxIncomingStreams))
|
||||
Expect(tlsConf.NextProtos).To(Equal([]string{NextProtoH3}))
|
||||
Expect(quicConf.Versions).To(Equal([]protocol.VersionNumber{protocol.Version1}))
|
||||
dialAddrCalled = true
|
||||
|
|
|
@ -9,17 +9,12 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Connection ID lengths tests", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
It("retransmits the CONNECTION_CLOSE packet", func() {
|
||||
server, err := quic.ListenAddr(
|
||||
"localhost:0",
|
||||
|
@ -51,7 +46,7 @@ var _ = Describe("Connection ID lengths tests", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
@ -78,5 +73,3 @@ var _ = Describe("Connection ID lengths tests", func() {
|
|||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -32,9 +32,7 @@ func (c *connIDGenerator) ConnectionIDLen() int {
|
|||
}
|
||||
|
||||
var _ = Describe("Connection ID lengths tests", func() {
|
||||
randomConnIDLen := func() int {
|
||||
return 4 + int(mrand.Int31n(15))
|
||||
}
|
||||
randomConnIDLen := func() int { return 4 + int(mrand.Int31n(15)) }
|
||||
|
||||
runServer := func(conf *quic.Config) quic.Listener {
|
||||
GinkgoWriter.Write([]byte(fmt.Sprintf("Using %d byte connection ID for the server\n", conf.ConnectionIDLength)))
|
||||
|
@ -77,46 +75,32 @@ var _ = Describe("Connection ID lengths tests", func() {
|
|||
}
|
||||
|
||||
It("downloads a file using a 0-byte connection ID for the client", func() {
|
||||
serverConf := getQuicConfig(&quic.Config{
|
||||
ConnectionIDLength: randomConnIDLen(),
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
})
|
||||
clientConf := getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
})
|
||||
|
||||
serverConf := getQuicConfig(&quic.Config{ConnectionIDLength: randomConnIDLen()})
|
||||
ln := runServer(serverConf)
|
||||
defer ln.Close()
|
||||
runClient(ln.Addr(), clientConf)
|
||||
|
||||
runClient(ln.Addr(), getQuicConfig(nil))
|
||||
})
|
||||
|
||||
It("downloads a file when both client and server use a random connection ID length", func() {
|
||||
serverConf := getQuicConfig(&quic.Config{
|
||||
ConnectionIDLength: randomConnIDLen(),
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
})
|
||||
clientConf := getQuicConfig(&quic.Config{
|
||||
ConnectionIDLength: randomConnIDLen(),
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
})
|
||||
|
||||
serverConf := getQuicConfig(&quic.Config{ConnectionIDLength: randomConnIDLen()})
|
||||
ln := runServer(serverConf)
|
||||
defer ln.Close()
|
||||
runClient(ln.Addr(), clientConf)
|
||||
|
||||
runClient(ln.Addr(), getQuicConfig(nil))
|
||||
})
|
||||
|
||||
It("downloads a file when both client and server use a custom connection ID generator", func() {
|
||||
serverConf := getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
ConnectionIDGenerator: &connIDGenerator{length: randomConnIDLen()},
|
||||
})
|
||||
clientConf := getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
ConnectionIDGenerator: &connIDGenerator{length: randomConnIDLen()},
|
||||
})
|
||||
|
||||
ln := runServer(serverConf)
|
||||
defer ln.Close()
|
||||
|
||||
runClient(ln.Addr(), clientConf)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -20,10 +19,6 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Datagram test", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
const num = 100
|
||||
|
||||
var (
|
||||
|
@ -40,10 +35,7 @@ var _ = Describe("Datagram test", func() {
|
|||
ln, err := quic.Listen(
|
||||
serverConn,
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: enableDatagram,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
getQuicConfig(&quic.Config{EnableDatagrams: enableDatagram}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
@ -117,10 +109,7 @@ var _ = Describe("Datagram test", func() {
|
|||
raddr,
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
getQuicConfig(&quic.Config{EnableDatagrams: true}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
|
@ -156,10 +145,7 @@ var _ = Describe("Datagram test", func() {
|
|||
raddr,
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
getQuicConfig(&quic.Config{EnableDatagrams: true}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
|
||||
|
@ -177,10 +163,7 @@ var _ = Describe("Datagram test", func() {
|
|||
raddr,
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
getQuicConfig(&quic.Config{EnableDatagrams: true}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
|
||||
|
@ -190,5 +173,3 @@ var _ = Describe("Datagram test", func() {
|
|||
<-time.After(10 * time.Millisecond)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -26,12 +25,12 @@ var _ = Describe("Drop Tests", func() {
|
|||
ln quic.Listener
|
||||
)
|
||||
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, version protocol.VersionNumber) {
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback) {
|
||||
var err error
|
||||
ln, err = quic.ListenAddr(
|
||||
"localhost:0",
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverPort := ln.Addr().(*net.UDPAddr).Port
|
||||
|
@ -51,10 +50,6 @@ var _ = Describe("Drop Tests", func() {
|
|||
Expect(ln.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
for _, d := range directions {
|
||||
direction := d
|
||||
|
||||
|
@ -82,7 +77,7 @@ var _ = Describe("Drop Tests", func() {
|
|||
atomic.AddInt32(&numDroppedPackets, 1)
|
||||
}
|
||||
return drop
|
||||
}, version)
|
||||
})
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
|
@ -104,7 +99,7 @@ var _ = Describe("Drop Tests", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
|
@ -125,5 +120,3 @@ var _ = Describe("Drop Tests", func() {
|
|||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -17,15 +16,12 @@ import (
|
|||
|
||||
var _ = Describe("early data", func() {
|
||||
const rtt = 80 * time.Millisecond
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
It("sends 0.5-RTT data", func() {
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done := make(chan struct{})
|
||||
|
@ -56,7 +52,7 @@ var _ = Describe("early data", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
|
@ -68,5 +64,3 @@ var _ = Describe("early data", func() {
|
|||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -27,7 +26,7 @@ var directions = []quicproxy.Direction{quicproxy.DirectionIncoming, quicproxy.Di
|
|||
|
||||
type applicationProtocol struct {
|
||||
name string
|
||||
run func(protocol.VersionNumber)
|
||||
run func()
|
||||
}
|
||||
|
||||
var _ = Describe("Handshake drop tests", func() {
|
||||
|
@ -39,11 +38,10 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
data := GeneratePRData(5000)
|
||||
const timeout = 2 * time.Minute
|
||||
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool, version protocol.VersionNumber) {
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) {
|
||||
conf := getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
RequireAddressValidation: func(net.Addr) bool { return doRetry },
|
||||
})
|
||||
var tlsConf *tls.Config
|
||||
|
@ -68,7 +66,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
clientSpeaksFirst := &applicationProtocol{
|
||||
name: "client speaks first",
|
||||
run: func(version protocol.VersionNumber) {
|
||||
run: func() {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -88,7 +86,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -107,7 +104,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
serverSpeaksFirst := &applicationProtocol{
|
||||
name: "server speaks first",
|
||||
run: func(version protocol.VersionNumber) {
|
||||
run: func() {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -126,7 +123,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -145,7 +141,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
nobodySpeaks := &applicationProtocol{
|
||||
name: "nobody speaks",
|
||||
run: func(version protocol.VersionNumber) {
|
||||
run: func() {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -159,7 +155,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -176,10 +171,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
Expect(proxy.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
for _, d := range directions {
|
||||
direction := d
|
||||
|
||||
|
@ -211,8 +202,8 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
p = atomic.AddInt32(&outgoing, 1)
|
||||
}
|
||||
return p == 1 && d.Is(direction)
|
||||
}, doRetry, longCertChain, version)
|
||||
app.run(version)
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("establishes a connection when the second packet is lost in %s direction", direction), func() {
|
||||
|
@ -227,8 +218,8 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
p = atomic.AddInt32(&outgoing, 1)
|
||||
}
|
||||
return p == 2 && d.Is(direction)
|
||||
}, doRetry, longCertChain, version)
|
||||
app.run(version)
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("establishes a connection when 1/3 of the packets are lost in %s direction", direction), func() {
|
||||
|
@ -265,8 +256,8 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}
|
||||
}
|
||||
return drop
|
||||
}, doRetry, longCertChain, version)
|
||||
app.run(version)
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -274,8 +265,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("establishes a connection when the ClientHello is larger than 1 MTU (e.g. post-quantum)", func() {
|
||||
origAdditionalTransportParametersClient := wire.AdditionalTransportParametersClient
|
||||
|
@ -294,8 +283,8 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
return false
|
||||
}
|
||||
return mrand.Intn(3) == 0
|
||||
}, false, false, version)
|
||||
clientSpeaksFirst.run(version)
|
||||
}, false, false)
|
||||
clientSpeaksFirst.run()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -53,35 +52,6 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
))
|
||||
}
|
||||
|
||||
It("fails when there's no matching version, after 1 RTT", func() {
|
||||
if len(protocol.SupportedVersions) == 1 {
|
||||
Skip("Test requires at least 2 supported versions.")
|
||||
}
|
||||
serverConfig.Versions = protocol.SupportedVersions[:1]
|
||||
ln, err := quic.ListenAddr("localhost:0", serverTLSConfig, serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
runProxy(ln.Addr())
|
||||
startTime := time.Now()
|
||||
_, err = quic.DialAddr(
|
||||
proxy.LocalAddr().String(),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: protocol.SupportedVersions[1:2]}),
|
||||
)
|
||||
Expect(err).To(HaveOccurred())
|
||||
expectDurationInRTTs(startTime, 1)
|
||||
})
|
||||
|
||||
var clientConfig *quic.Config
|
||||
|
||||
BeforeEach(func() {
|
||||
serverConfig.Versions = []protocol.VersionNumber{protocol.Version1}
|
||||
clientConfig = getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{protocol.Version1}})
|
||||
clientConfig := getTLSClientConfig()
|
||||
clientConfig.InsecureSkipVerify = true
|
||||
})
|
||||
|
||||
// 1 RTT for verifying the source address
|
||||
// 1 RTT for the TLS handshake
|
||||
It("is forward-secure after 2 RTTs", func() {
|
||||
|
@ -95,7 +65,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
_, err = quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
expectDurationInRTTs(startTime, 2)
|
||||
|
@ -111,7 +81,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
_, err = quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
expectDurationInRTTs(startTime, 1)
|
||||
|
@ -128,7 +98,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
_, err = quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
expectDurationInRTTs(startTime, 2)
|
||||
|
@ -138,6 +108,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
ln, err := quic.ListenAddr("localhost:0", serverTLSConfig, serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
|
@ -153,7 +124,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
|
@ -168,6 +139,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
ln, err := quic.ListenAddrEarly("localhost:0", serverTLSConfig, serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// Check the ALPN now. This is probably what an application would do.
|
||||
|
@ -186,7 +158,7 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
|
|
|
@ -128,23 +128,12 @@ var _ = Describe("Handshake tests", func() {
|
|||
})
|
||||
|
||||
Context("Certificate validation", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("using %s", version), func() {
|
||||
var clientConfig *quic.Config
|
||||
|
||||
BeforeEach(func() {
|
||||
serverConfig.Versions = []protocol.VersionNumber{version}
|
||||
clientConfig = getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}})
|
||||
})
|
||||
|
||||
It("accepts the certificate", func() {
|
||||
runServer(getTLSConfig())
|
||||
_, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
@ -154,7 +143,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
_, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
@ -168,7 +157,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
server.Addr(),
|
||||
"foo.bar",
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).To(HaveOccurred())
|
||||
var transportErr *quic.TransportError
|
||||
|
@ -185,7 +174,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
// Usually, the error will occur after the client already finished the handshake.
|
||||
// However, there's a race condition here. The server's CONNECTION_CLOSE might be
|
||||
|
@ -213,7 +202,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
_, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
clientConfig,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).To(HaveOccurred())
|
||||
var transportErr *quic.TransportError
|
||||
|
@ -222,8 +211,6 @@ var _ = Describe("Handshake tests", func() {
|
|||
Expect(transportErr.Error()).To(ContainSubstring("x509: certificate is valid for localhost, not foo.bar"))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Context("rate limiting", func() {
|
||||
var (
|
||||
|
|
|
@ -3,7 +3,6 @@ package self_test
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -13,7 +12,6 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/testdata"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -71,8 +69,6 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
port string
|
||||
)
|
||||
|
||||
versions := protocol.SupportedVersions
|
||||
|
||||
BeforeEach(func() {
|
||||
mux1 = http.NewServeMux()
|
||||
mux1.HandleFunc("/hello1", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -89,17 +85,17 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
server1 = &http3.Server{
|
||||
Handler: mux1,
|
||||
TLSConfig: testdata.GetTLSConfig(),
|
||||
QuicConfig: getQuicConfig(&quic.Config{Versions: versions}),
|
||||
QuicConfig: getQuicConfig(nil),
|
||||
}
|
||||
|
||||
server2 = &http3.Server{
|
||||
Handler: mux2,
|
||||
TLSConfig: testdata.GetTLSConfig(),
|
||||
QuicConfig: getQuicConfig(&quic.Config{Versions: versions}),
|
||||
QuicConfig: getQuicConfig(nil),
|
||||
}
|
||||
|
||||
tlsConf := http3.ConfigureTLSConfig(testdata.GetTLSConfig())
|
||||
quicln, err := quic.ListenAddrEarly("0.0.0.0:0", tlsConf, getQuicConfig(&quic.Config{Versions: versions}))
|
||||
quicln, err := quic.ListenAddrEarly("0.0.0.0:0", tlsConf, getQuicConfig(nil))
|
||||
ln = &listenerWrapper{EarlyListener: quicln}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
port = strconv.Itoa(ln.Addr().(*net.UDPAddr).Port)
|
||||
|
@ -109,10 +105,6 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
Expect(ln.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
for _, v := range versions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
BeforeEach(func() {
|
||||
client = &http.Client{
|
||||
Transport: &http3.RoundTripper{
|
||||
|
@ -120,10 +112,7 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
RootCAs: testdata.GetRootCA(),
|
||||
},
|
||||
DisableCompression: true,
|
||||
QuicConfig: getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIdleTimeout: 10 * time.Second,
|
||||
}),
|
||||
QuicConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -182,5 +171,3 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
Expect(ln.listenerClosed).To(BeTrue())
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -46,8 +46,6 @@ var _ = Describe("HTTP tests", func() {
|
|||
port string
|
||||
)
|
||||
|
||||
versions := protocol.SupportedVersions
|
||||
|
||||
BeforeEach(func() {
|
||||
mux = http.NewServeMux()
|
||||
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -83,7 +81,7 @@ var _ = Describe("HTTP tests", func() {
|
|||
server = &http3.Server{
|
||||
Handler: mux,
|
||||
TLSConfig: testdata.GetTLSConfig(),
|
||||
QuicConfig: getQuicConfig(&quic.Config{Versions: versions}),
|
||||
QuicConfig: getQuicConfig(nil),
|
||||
}
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:0")
|
||||
|
@ -106,10 +104,6 @@ var _ = Describe("HTTP tests", func() {
|
|||
Eventually(stoppedServing).Should(BeClosed())
|
||||
})
|
||||
|
||||
for _, v := range versions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
BeforeEach(func() {
|
||||
client = &http.Client{
|
||||
Transport: &http3.RoundTripper{
|
||||
|
@ -117,10 +111,7 @@ var _ = Describe("HTTP tests", func() {
|
|||
RootCAs: testdata.GetRootCA(),
|
||||
},
|
||||
DisableCompression: true,
|
||||
QuicConfig: getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIdleTimeout: 10 * time.Second,
|
||||
}),
|
||||
QuicConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -386,6 +377,30 @@ var _ = Describe("HTTP tests", func() {
|
|||
Expect(repl).To(Equal(data))
|
||||
})
|
||||
|
||||
It("serves other QUIC connections", func() {
|
||||
if version == protocol.VersionDraft29 {
|
||||
Skip("This test only works on RFC versions")
|
||||
}
|
||||
tlsConf := testdata.GetTLSConfig()
|
||||
tlsConf.NextProtos = []string{"h3"}
|
||||
ln, err := quic.ListenAddr("localhost:0", tlsConf, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(done)
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(server.ServeQUICConn(conn)).To(Succeed())
|
||||
}()
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", ln.Addr().(*net.UDPAddr).Port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||
client.Transport.(io.Closer).Close()
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
It("supports read deadlines", func() {
|
||||
if !go120 {
|
||||
Skip("This test requires Go 1.20+")
|
||||
|
@ -439,29 +454,4 @@ var _ = Describe("HTTP tests", func() {
|
|||
Expect(time.Now().After(expectedEnd)).To(BeTrue())
|
||||
Expect(string(body)).To(ContainSubstring("aa"))
|
||||
})
|
||||
|
||||
if version != protocol.VersionDraft29 {
|
||||
It("serves other QUIC connections", func() {
|
||||
tlsConf := testdata.GetTLSConfig()
|
||||
tlsConf.NextProtos = []string{"h3"}
|
||||
ln, err := quic.ListenAddr("localhost:0", tlsConf, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(done)
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(server.ServeQUICConn(conn)).To(Succeed())
|
||||
}()
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", ln.Addr().(*net.UDPAddr).Port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||
client.Transport.(io.Closer).Close()
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -22,10 +22,6 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("MITM test", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
const connIDLen = 6 // explicitly set the connection ID length, so the proxy can parse it
|
||||
|
||||
var (
|
||||
|
@ -72,10 +68,7 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
serverConfig = getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
ConnectionIDLength: connIDLen,
|
||||
})
|
||||
serverConfig = getQuicConfig(&quic.Config{ConnectionIDLength: connIDLen})
|
||||
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
clientUDPConn, err = net.ListenUDP("udp", addr)
|
||||
|
@ -116,7 +109,7 @@ var _ = Describe("MITM test", func() {
|
|||
for i := 0; i < numPackets; i++ {
|
||||
payloadLen := mrand.Int31n(100)
|
||||
replyHdr.Length = protocol.ByteCount(mrand.Int31n(payloadLen + 1))
|
||||
b, err := replyHdr.Append(nil, version)
|
||||
b, err := replyHdr.Append(nil, hdr.Version)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
r := make([]byte, payloadLen)
|
||||
mrand.Read(r)
|
||||
|
@ -158,10 +151,7 @@ var _ = Describe("MITM test", func() {
|
|||
raddr,
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
ConnectionIDLength: connIDLen,
|
||||
}),
|
||||
getQuicConfig(&quic.Config{ConnectionIDLength: connIDLen}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
|
@ -205,10 +195,7 @@ var _ = Describe("MITM test", func() {
|
|||
raddr,
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
ConnectionIDLength: connIDLen,
|
||||
}),
|
||||
getQuicConfig(&quic.Config{ConnectionIDLength: connIDLen}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
|
@ -324,7 +311,6 @@ var _ = Describe("MITM test", func() {
|
|||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
ConnectionIDLength: connIDLen,
|
||||
HandshakeIdleTimeout: 2 * time.Second,
|
||||
}),
|
||||
|
@ -468,5 +454,3 @@ var _ = Describe("MITM test", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -9,17 +9,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Multiplexing", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
runServer := func(ln quic.Listener) {
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -46,7 +41,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
addr,
|
||||
fmt.Sprintf("localhost:%d", addr.(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
|
@ -62,7 +57,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
ln, err := quic.ListenAddr(
|
||||
"localhost:0",
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
return ln
|
||||
|
@ -145,7 +140,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
server, err := quic.Listen(
|
||||
conn,
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runServer(server)
|
||||
|
@ -181,7 +176,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
server1, err := quic.Listen(
|
||||
conn1,
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runServer(server1)
|
||||
|
@ -190,7 +185,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
server2, err := quic.Listen(
|
||||
conn2,
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runServer(server2)
|
||||
|
@ -217,5 +212,3 @@ var _ = Describe("Multiplexing", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -9,21 +9,17 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("non-zero RTT", func() {
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
runServer := func() quic.Listener {
|
||||
ln, err := quic.ListenAddr(
|
||||
"localhost:0",
|
||||
getTLSConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go func() {
|
||||
|
@ -43,7 +39,7 @@ var _ = Describe("non-zero RTT", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptStream(context.Background())
|
||||
|
@ -54,7 +50,6 @@ var _ = Describe("non-zero RTT", func() {
|
|||
conn.CloseWithError(0, "")
|
||||
}
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
for _, r := range [...]time.Duration{
|
||||
10 * time.Millisecond,
|
||||
50 * time.Millisecond,
|
||||
|
@ -79,7 +74,7 @@ var _ = Describe("non-zero RTT", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptStream(context.Background())
|
||||
|
@ -114,5 +109,3 @@ var _ = Describe("non-zero RTT", func() {
|
|||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
|
@ -18,6 +19,7 @@ import (
|
|||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/integrationtests/tools"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
"github.com/quic-go/quic-go/internal/utils"
|
||||
"github.com/quic-go/quic-go/internal/wire"
|
||||
"github.com/quic-go/quic-go/logging"
|
||||
|
@ -83,10 +85,12 @@ var (
|
|||
logFileName string // the log file set in the ginkgo flags
|
||||
logBufOnce sync.Once
|
||||
logBuf *syncedBuffer
|
||||
versionParam string
|
||||
|
||||
qlogTracer logging.Tracer
|
||||
enableQlog bool
|
||||
|
||||
version quic.VersionNumber
|
||||
tlsConfig *tls.Config
|
||||
tlsConfigLongChain *tls.Config
|
||||
tlsClientConfig *tls.Config
|
||||
|
@ -96,6 +100,7 @@ var (
|
|||
// to set call ginkgo -- -logfile=log.txt
|
||||
func init() {
|
||||
flag.StringVar(&logFileName, "logfile", "", "log file")
|
||||
flag.StringVar(&versionParam, "version", "1", "QUIC version")
|
||||
flag.BoolVar(&enableQlog, "qlog", false, "enable qlog")
|
||||
|
||||
ca, caPrivateKey, err := tools.GenerateCA()
|
||||
|
@ -133,6 +138,18 @@ var _ = BeforeSuite(func() {
|
|||
if enableQlog {
|
||||
qlogTracer = tools.NewQlogger(GinkgoWriter)
|
||||
}
|
||||
switch versionParam {
|
||||
case "1":
|
||||
version = quic.Version1
|
||||
case "2":
|
||||
version = quic.Version2
|
||||
case "draft29":
|
||||
version = quic.VersionDraft29
|
||||
default:
|
||||
Fail(fmt.Sprintf("unknown QUIC version: %s", versionParam))
|
||||
}
|
||||
fmt.Printf("Using QUIC version: %s\n", version)
|
||||
protocol.SupportedVersions = []quic.VersionNumber{version}
|
||||
})
|
||||
|
||||
func getTLSConfig() *tls.Config {
|
||||
|
|
|
@ -7,11 +7,9 @@ import (
|
|||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
var _ = Describe("Bidirectional streams", func() {
|
||||
|
@ -20,20 +18,11 @@ var _ = Describe("Bidirectional streams", func() {
|
|||
var (
|
||||
server quic.Listener
|
||||
serverAddr string
|
||||
qconf *quic.Config
|
||||
)
|
||||
|
||||
for _, v := range []protocol.VersionNumber{protocol.Version1} {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC %s", version), func() {
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
qconf = &quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIncomingStreams: 0,
|
||||
}
|
||||
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(qconf))
|
||||
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverAddr = fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port)
|
||||
})
|
||||
|
@ -100,7 +89,7 @@ var _ = Describe("Bidirectional streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runSendingPeer(client)
|
||||
|
@ -118,7 +107,7 @@ var _ = Describe("Bidirectional streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runReceivingPeer(client)
|
||||
|
@ -145,7 +134,7 @@ var _ = Describe("Bidirectional streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done2 := make(chan struct{})
|
||||
|
@ -159,5 +148,3 @@ var _ = Describe("Bidirectional streams", func() {
|
|||
<-done2
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -20,13 +20,11 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
var (
|
||||
server quic.Listener
|
||||
serverAddr string
|
||||
qconf *quic.Config
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
qconf = &quic.Config{Versions: []protocol.VersionNumber{protocol.Version1}}
|
||||
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(qconf))
|
||||
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverAddr = fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port)
|
||||
})
|
||||
|
@ -81,7 +79,7 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runSendingPeer(client)
|
||||
|
@ -99,7 +97,7 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
runReceivingPeer(client)
|
||||
|
@ -125,7 +123,7 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
client, err := quic.DialAddr(
|
||||
serverAddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(qconf),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done2 := make(chan struct{})
|
||||
|
|
|
@ -25,10 +25,6 @@ import (
|
|||
var _ = Describe("0-RTT", func() {
|
||||
rtt := scaleDuration(5 * time.Millisecond)
|
||||
|
||||
for _, v := range protocol.SupportedVersions {
|
||||
version := v
|
||||
|
||||
Context(fmt.Sprintf("with QUIC version %s", version), func() {
|
||||
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
|
||||
var num0RTTPackets uint32 // to be used as an atomic
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
|
@ -58,7 +54,6 @@ var _ = Describe("0-RTT", func() {
|
|||
tlsConf := getTLSConfig()
|
||||
if serverConf == nil {
|
||||
serverConf = getQuicConfig(nil)
|
||||
serverConf.Versions = []protocol.VersionNumber{version}
|
||||
}
|
||||
serverConf.Allow0RTT = func(addr net.Addr) bool { return true }
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
|
@ -93,7 +88,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddr(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Eventually(puts).Should(Receive())
|
||||
|
@ -128,7 +123,7 @@ var _ = Describe("0-RTT", func() {
|
|||
}()
|
||||
|
||||
if clientConf == nil {
|
||||
clientConf = getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}})
|
||||
clientConf = getQuicConfig(nil)
|
||||
}
|
||||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
|
@ -158,7 +153,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
|
@ -202,7 +197,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(addr net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -217,10 +211,7 @@ var _ = Describe("0-RTT", func() {
|
|||
ln,
|
||||
proxy.LocalPort(),
|
||||
clientTLSConf,
|
||||
&quic.Config{
|
||||
ConnectionIDLength: connIDLen,
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
},
|
||||
&quic.Config{ConnectionIDLength: connIDLen},
|
||||
PRData,
|
||||
)
|
||||
|
||||
|
@ -260,7 +251,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -292,7 +282,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
firstStr, err := conn.OpenUniStream()
|
||||
|
@ -342,7 +332,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -418,7 +407,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
RequireAddressValidation: func(net.Addr) bool { return true },
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
|
@ -480,7 +468,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIncomingUniStreams: maxStreams + 1,
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
|
@ -494,7 +481,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
|
@ -525,7 +512,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIncomingStreams: maxStreams - 1,
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
|
@ -555,7 +541,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -583,7 +568,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return false }, // application rejects 0-RTT
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -605,15 +589,11 @@ var _ = Describe("0-RTT", func() {
|
|||
DescribeTable("flow control limits",
|
||||
func(addFlowControlLimit func(*quic.Config, uint64)) {
|
||||
tracer := newPacketTracer()
|
||||
firstConf := getQuicConfig(&quic.Config{
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
})
|
||||
firstConf := getQuicConfig(&quic.Config{Allow0RTT: func(net.Addr) bool { return true }})
|
||||
addFlowControlLimit(firstConf, 3)
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(firstConf)
|
||||
|
||||
secondConf := getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
})
|
||||
|
@ -631,7 +611,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
|
@ -690,7 +670,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
MaxIncomingUniStreams: 1,
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -703,7 +682,7 @@ var _ = Describe("0-RTT", func() {
|
|||
conn, err := quic.DialAddrEarly(
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(&quic.Config{Versions: []protocol.VersionNumber{version}}),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// The client remembers that it was allowed to open 2 uni-directional streams.
|
||||
|
@ -766,7 +745,6 @@ var _ = Describe("0-RTT", func() {
|
|||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Versions: []protocol.VersionNumber{version},
|
||||
Allow0RTT: func(net.Addr) bool { return true },
|
||||
Tracer: newTracer(func() logging.ConnectionTracer { return tracer }),
|
||||
}),
|
||||
|
@ -793,5 +771,3 @@ var _ = Describe("0-RTT", func() {
|
|||
Expect(zeroRTTPackets[0]).To(Equal(protocol.PacketNumber(0)))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
53
integrationtests/versionnegotiation/rtt_test.go
Normal file
53
integrationtests/versionnegotiation/rtt_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package versionnegotiation
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
"github.com/quic-go/quic-go/internal/protocol"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Handshake RTT tests", func() {
|
||||
const rtt = 400 * time.Millisecond
|
||||
|
||||
expectDurationInRTTs := func(startTime time.Time, num int) {
|
||||
testDuration := time.Since(startTime)
|
||||
rtts := float32(testDuration) / float32(rtt)
|
||||
Expect(rtts).To(SatisfyAll(
|
||||
BeNumerically(">=", num),
|
||||
BeNumerically("<", num+1),
|
||||
))
|
||||
}
|
||||
|
||||
It("fails when there's no matching version, after 1 RTT", func() {
|
||||
if len(protocol.SupportedVersions) == 1 {
|
||||
Skip("Test requires at least 2 supported versions.")
|
||||
}
|
||||
|
||||
serverConfig := &quic.Config{}
|
||||
serverConfig.Versions = protocol.SupportedVersions[:1]
|
||||
ln, err := quic.ListenAddr("localhost:0", getTLSConfig(), serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
// start the proxy
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: ln.Addr().String(),
|
||||
DelayPacket: func(_ quicproxy.Direction, _ []byte) time.Duration { return rtt / 2 },
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
startTime := time.Now()
|
||||
_, err = quic.DialAddr(
|
||||
proxy.LocalAddr().String(),
|
||||
getTLSClientConfig(),
|
||||
maybeAddQlogTracer(&quic.Config{Versions: protocol.SupportedVersions[1:2]}),
|
||||
)
|
||||
Expect(err).To(HaveOccurred())
|
||||
expectDurationInRTTs(startTime, 1)
|
||||
})
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue