mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-03-31 10:47:35 +03:00
sync: quic-go 0.42.0
Signed-off-by: Gaukas Wang <i@gaukas.wang>
This commit is contained in:
parent
d40dde9b9b
commit
4973374ea5
252 changed files with 13121 additions and 5437 deletions
|
@ -3,7 +3,7 @@ FROM gcr.io/oss-fuzz-base/base-builder-go:v1
|
|||
ARG TARGETPLATFORM
|
||||
RUN echo "TARGETPLATFORM: ${TARGETPLATFORM}"
|
||||
|
||||
ENV GOVERSION=1.20.7
|
||||
ENV GOVERSION=1.22.0
|
||||
|
||||
RUN platform=$(echo ${TARGETPLATFORM} | tr '/' '-') && \
|
||||
filename="go${GOVERSION}.${platform}.tar.gz" && \
|
||||
|
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
|
@ -2,16 +2,6 @@ run:
|
|||
skip-files:
|
||||
- internal/handshake/cipher_suite.go
|
||||
linters-settings:
|
||||
depguard:
|
||||
type: blacklist
|
||||
packages:
|
||||
- github.com/marten-seemann/qtls
|
||||
- github.com/quic-go/qtls-go1-19
|
||||
- github.com/quic-go/qtls-go1-20
|
||||
packages-with-error-message:
|
||||
- github.com/marten-seemann/qtls: "importing qtls only allowed in internal/qtls"
|
||||
- github.com/quic-go/qtls-go1-19: "importing qtls only allowed in internal/qtls"
|
||||
- github.com/quic-go/qtls-go1-20: "importing qtls only allowed in internal/qtls"
|
||||
misspell:
|
||||
ignore-words:
|
||||
- ect
|
||||
|
@ -20,7 +10,6 @@ linters:
|
|||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- depguard
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- goimports
|
||||
|
|
|
@ -29,7 +29,7 @@ type client struct {
|
|||
|
||||
initialPacketNumber protocol.PacketNumber
|
||||
hasNegotiatedVersion bool
|
||||
version protocol.VersionNumber
|
||||
version protocol.Version
|
||||
|
||||
handshakeChan chan struct{}
|
||||
|
||||
|
@ -237,7 +237,7 @@ func (c *client) dial(ctx context.Context) error {
|
|||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.conn.shutdown()
|
||||
c.conn.destroy(nil)
|
||||
return context.Cause(ctx)
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
|
|
|
@ -47,7 +47,7 @@ var _ = Describe("Client", func() {
|
|||
tracer *logging.ConnectionTracer,
|
||||
tracingID uint64,
|
||||
logger utils.Logger,
|
||||
v protocol.VersionNumber,
|
||||
v protocol.Version,
|
||||
) quicConn
|
||||
)
|
||||
|
||||
|
@ -61,7 +61,7 @@ var _ = Describe("Client", func() {
|
|||
Tracer: func(ctx context.Context, perspective logging.Perspective, id ConnectionID) *logging.ConnectionTracer {
|
||||
return tr
|
||||
},
|
||||
Versions: []protocol.VersionNumber{protocol.Version1},
|
||||
Versions: []protocol.Version{protocol.Version1},
|
||||
}
|
||||
Eventually(areConnsRunning).Should(BeFalse())
|
||||
packetConn = NewMockSendConn(mockCtrl)
|
||||
|
@ -88,7 +88,7 @@ var _ = Describe("Client", func() {
|
|||
|
||||
AfterEach(func() {
|
||||
if s, ok := cl.conn.(*connection); ok {
|
||||
s.shutdown()
|
||||
s.destroy(nil)
|
||||
}
|
||||
Eventually(areConnsRunning).Should(BeFalse())
|
||||
})
|
||||
|
@ -126,11 +126,11 @@ var _ = Describe("Client", func() {
|
|||
_ *logging.ConnectionTracer,
|
||||
_ uint64,
|
||||
_ utils.Logger,
|
||||
_ protocol.VersionNumber,
|
||||
_ protocol.Version,
|
||||
) quicConn {
|
||||
Expect(enable0RTT).To(BeFalse())
|
||||
conn := NewMockQUICConn(mockCtrl)
|
||||
conn.EXPECT().run().Do(func() { close(run) })
|
||||
conn.EXPECT().run().Do(func() error { close(run); return nil })
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
conn.EXPECT().HandshakeComplete().Return(c)
|
||||
|
@ -163,11 +163,11 @@ var _ = Describe("Client", func() {
|
|||
_ *logging.ConnectionTracer,
|
||||
_ uint64,
|
||||
_ utils.Logger,
|
||||
_ protocol.VersionNumber,
|
||||
_ protocol.Version,
|
||||
) quicConn {
|
||||
Expect(enable0RTT).To(BeTrue())
|
||||
conn := NewMockQUICConn(mockCtrl)
|
||||
conn.EXPECT().run().Do(func() { close(done) })
|
||||
conn.EXPECT().run().Do(func() error { close(done); return nil })
|
||||
conn.EXPECT().HandshakeComplete().Return(make(chan struct{}))
|
||||
conn.EXPECT().earlyConnReady().Return(readyChan)
|
||||
return conn
|
||||
|
@ -200,7 +200,7 @@ var _ = Describe("Client", func() {
|
|||
_ *logging.ConnectionTracer,
|
||||
_ uint64,
|
||||
_ utils.Logger,
|
||||
_ protocol.VersionNumber,
|
||||
_ protocol.Version,
|
||||
) quicConn {
|
||||
conn := NewMockQUICConn(mockCtrl)
|
||||
conn.EXPECT().run().Return(testErr)
|
||||
|
@ -266,9 +266,9 @@ var _ = Describe("Client", func() {
|
|||
})
|
||||
|
||||
It("creates new connections with the right parameters", func() {
|
||||
config := &Config{Versions: []protocol.VersionNumber{protocol.Version1}}
|
||||
config := &Config{Versions: []protocol.Version{protocol.Version1}}
|
||||
c := make(chan struct{})
|
||||
var version protocol.VersionNumber
|
||||
var version protocol.Version
|
||||
var conf *Config
|
||||
done := make(chan struct{})
|
||||
newClientConnection = func(
|
||||
|
@ -285,7 +285,7 @@ var _ = Describe("Client", func() {
|
|||
_ *logging.ConnectionTracer,
|
||||
_ uint64,
|
||||
_ utils.Logger,
|
||||
versionP protocol.VersionNumber,
|
||||
versionP protocol.Version,
|
||||
) quicConn {
|
||||
version = versionP
|
||||
conf = configP
|
||||
|
@ -328,7 +328,7 @@ var _ = Describe("Client", func() {
|
|||
_ *logging.ConnectionTracer,
|
||||
_ uint64,
|
||||
_ utils.Logger,
|
||||
versionP protocol.VersionNumber,
|
||||
versionP protocol.Version,
|
||||
) quicConn {
|
||||
conn := NewMockQUICConn(mockCtrl)
|
||||
conn.EXPECT().HandshakeComplete().Return(make(chan struct{}))
|
||||
|
@ -352,7 +352,7 @@ var _ = Describe("Client", func() {
|
|||
return conn
|
||||
}
|
||||
|
||||
config := &Config{Tracer: config.Tracer, Versions: []protocol.VersionNumber{protocol.Version1}}
|
||||
config := &Config{Tracer: config.Tracer, Versions: []protocol.Version{protocol.Version1}}
|
||||
tracer.EXPECT().StartedConnection(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
_, err := DialAddr(context.Background(), "localhost:7890", tlsConf, config)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"math/bits"
|
||||
"net"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
|
@ -12,9 +11,8 @@ import (
|
|||
// When receiving packets for such a connection, we need to retransmit the packet containing the CONNECTION_CLOSE frame,
|
||||
// with an exponential backoff.
|
||||
type closedLocalConn struct {
|
||||
counter uint32
|
||||
perspective protocol.Perspective
|
||||
logger utils.Logger
|
||||
counter uint32
|
||||
logger utils.Logger
|
||||
|
||||
sendPacket func(net.Addr, packetInfo)
|
||||
}
|
||||
|
@ -22,11 +20,10 @@ type closedLocalConn struct {
|
|||
var _ packetHandler = &closedLocalConn{}
|
||||
|
||||
// newClosedLocalConn creates a new closedLocalConn and runs it.
|
||||
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), pers protocol.Perspective, logger utils.Logger) packetHandler {
|
||||
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), logger utils.Logger) packetHandler {
|
||||
return &closedLocalConn{
|
||||
sendPacket: sendPacket,
|
||||
perspective: pers,
|
||||
logger: logger,
|
||||
sendPacket: sendPacket,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,24 +38,20 @@ func (c *closedLocalConn) handlePacket(p receivedPacket) {
|
|||
c.sendPacket(p.remoteAddr, p.info)
|
||||
}
|
||||
|
||||
func (c *closedLocalConn) shutdown() {}
|
||||
func (c *closedLocalConn) destroy(error) {}
|
||||
func (c *closedLocalConn) getPerspective() protocol.Perspective { return c.perspective }
|
||||
func (c *closedLocalConn) destroy(error) {}
|
||||
func (c *closedLocalConn) closeWithTransportError(TransportErrorCode) {}
|
||||
|
||||
// A closedRemoteConn is a connection that was closed remotely.
|
||||
// For such a connection, we might receive reordered packets that were sent before the CONNECTION_CLOSE.
|
||||
// We can just ignore those packets.
|
||||
type closedRemoteConn struct {
|
||||
perspective protocol.Perspective
|
||||
}
|
||||
type closedRemoteConn struct{}
|
||||
|
||||
var _ packetHandler = &closedRemoteConn{}
|
||||
|
||||
func newClosedRemoteConn(pers protocol.Perspective) packetHandler {
|
||||
return &closedRemoteConn{perspective: pers}
|
||||
func newClosedRemoteConn() packetHandler {
|
||||
return &closedRemoteConn{}
|
||||
}
|
||||
|
||||
func (s *closedRemoteConn) handlePacket(receivedPacket) {}
|
||||
func (s *closedRemoteConn) shutdown() {}
|
||||
func (s *closedRemoteConn) destroy(error) {}
|
||||
func (s *closedRemoteConn) getPerspective() protocol.Perspective { return s.perspective }
|
||||
func (c *closedRemoteConn) handlePacket(receivedPacket) {}
|
||||
func (c *closedRemoteConn) destroy(error) {}
|
||||
func (c *closedRemoteConn) closeWithTransportError(TransportErrorCode) {}
|
||||
|
|
|
@ -3,7 +3,6 @@ package quic
|
|||
import (
|
||||
"net"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -11,20 +10,9 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Closed local connection", func() {
|
||||
It("tells its perspective", func() {
|
||||
conn := newClosedLocalConn(nil, protocol.PerspectiveClient, utils.DefaultLogger)
|
||||
Expect(conn.getPerspective()).To(Equal(protocol.PerspectiveClient))
|
||||
// stop the connection
|
||||
conn.shutdown()
|
||||
})
|
||||
|
||||
It("repeats the packet containing the CONNECTION_CLOSE frame", func() {
|
||||
written := make(chan net.Addr, 1)
|
||||
conn := newClosedLocalConn(
|
||||
func(addr net.Addr, _ packetInfo) { written <- addr },
|
||||
protocol.PerspectiveClient,
|
||||
utils.DefaultLogger,
|
||||
)
|
||||
conn := newClosedLocalConn(func(addr net.Addr, _ packetInfo) { written <- addr }, utils.DefaultLogger)
|
||||
addr := &net.UDPAddr{IP: net.IPv4(127, 1, 2, 3), Port: 1337}
|
||||
for i := 1; i <= 20; i++ {
|
||||
conn.handlePacket(receivedPacket{remoteAddr: addr})
|
||||
|
|
|
@ -5,6 +5,8 @@ coverage:
|
|||
- interop/
|
||||
- internal/handshake/cipher_suite.go
|
||||
- internal/utils/linkedlist/linkedlist.go
|
||||
- internal/testdata
|
||||
- testutils/
|
||||
- fuzzing/
|
||||
- metrics/
|
||||
status:
|
||||
|
|
12
config.go
12
config.go
|
@ -2,7 +2,6 @@ package quic
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
|
@ -49,16 +48,6 @@ func validateConfig(config *Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// populateServerConfig populates fields in the quic.Config with their default values, if none are set
|
||||
// it may be called with nil
|
||||
func populateServerConfig(config *Config) *Config {
|
||||
config = populateConfig(config)
|
||||
if config.RequireAddressValidation == nil {
|
||||
config.RequireAddressValidation = func(net.Addr) bool { return false }
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// populateConfig populates fields in the quic.Config with their default values, if none are set
|
||||
// it may be called with nil
|
||||
func populateConfig(config *Config) *Config {
|
||||
|
@ -111,7 +100,6 @@ func populateConfig(config *Config) *Config {
|
|||
Versions: versions,
|
||||
HandshakeIdleTimeout: handshakeIdleTimeout,
|
||||
MaxIdleTimeout: idleTimeout,
|
||||
RequireAddressValidation: config.RequireAddressValidation,
|
||||
KeepAlivePeriod: config.KeepAlivePeriod,
|
||||
InitialStreamReceiveWindow: initialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: maxStreamReceiveWindow,
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
|
@ -23,7 +22,7 @@ var _ = Describe("Config", func() {
|
|||
})
|
||||
|
||||
It("validates a config with normal values", func() {
|
||||
conf := populateServerConfig(&Config{
|
||||
conf := populateConfig(&Config{
|
||||
MaxIncomingStreams: 5,
|
||||
MaxStreamReceiveWindow: 10,
|
||||
})
|
||||
|
@ -69,7 +68,7 @@ var _ = Describe("Config", func() {
|
|||
case "GetConfigForClient", "RequireAddressValidation", "GetLogWriter", "AllowConnectionWindowIncrease", "Tracer":
|
||||
// Can't compare functions.
|
||||
case "Versions":
|
||||
f.Set(reflect.ValueOf([]VersionNumber{1, 2, 3}))
|
||||
f.Set(reflect.ValueOf([]Version{1, 2, 3}))
|
||||
case "ConnectionIDLength":
|
||||
f.Set(reflect.ValueOf(8))
|
||||
case "ConnectionIDGenerator":
|
||||
|
@ -118,19 +117,16 @@ var _ = Describe("Config", func() {
|
|||
|
||||
Context("cloning", func() {
|
||||
It("clones function fields", func() {
|
||||
var calledAddrValidation, calledAllowConnectionWindowIncrease, calledTracer bool
|
||||
var calledAllowConnectionWindowIncrease, calledTracer bool
|
||||
c1 := &Config{
|
||||
GetConfigForClient: func(info *ClientHelloInfo) (*Config, error) { return nil, errors.New("nope") },
|
||||
AllowConnectionWindowIncrease: func(Connection, uint64) bool { calledAllowConnectionWindowIncrease = true; return true },
|
||||
RequireAddressValidation: func(net.Addr) bool { calledAddrValidation = true; return true },
|
||||
Tracer: func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer {
|
||||
calledTracer = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
c2 := c1.Clone()
|
||||
c2.RequireAddressValidation(&net.UDPAddr{})
|
||||
Expect(calledAddrValidation).To(BeTrue())
|
||||
c2.AllowConnectionWindowIncrease(nil, 1234)
|
||||
Expect(calledAllowConnectionWindowIncrease).To(BeTrue())
|
||||
_, err := c2.GetConfigForClient(&ClientHelloInfo{})
|
||||
|
@ -145,29 +141,15 @@ var _ = Describe("Config", func() {
|
|||
})
|
||||
|
||||
It("returns a copy", func() {
|
||||
c1 := &Config{
|
||||
MaxIncomingStreams: 100,
|
||||
RequireAddressValidation: func(net.Addr) bool { return true },
|
||||
}
|
||||
c1 := &Config{MaxIncomingStreams: 100}
|
||||
c2 := c1.Clone()
|
||||
c2.MaxIncomingStreams = 200
|
||||
c2.RequireAddressValidation = func(net.Addr) bool { return false }
|
||||
|
||||
Expect(c1.MaxIncomingStreams).To(BeEquivalentTo(100))
|
||||
Expect(c1.RequireAddressValidation(&net.UDPAddr{})).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("populating", func() {
|
||||
It("populates function fields", func() {
|
||||
var calledAddrValidation bool
|
||||
c1 := &Config{}
|
||||
c1.RequireAddressValidation = func(net.Addr) bool { calledAddrValidation = true; return true }
|
||||
c2 := populateConfig(c1)
|
||||
c2.RequireAddressValidation(&net.UDPAddr{})
|
||||
Expect(calledAddrValidation).To(BeTrue())
|
||||
})
|
||||
|
||||
It("copies non-function fields", func() {
|
||||
c := configWithNonZeroNonFunctionFields()
|
||||
Expect(populateConfig(c)).To(Equal(c))
|
||||
|
@ -186,10 +168,5 @@ var _ = Describe("Config", func() {
|
|||
Expect(c.DisablePathMTUDiscovery).To(BeFalse())
|
||||
Expect(c.GetConfigForClient).To(BeNil())
|
||||
})
|
||||
|
||||
It("populates empty fields with default values, for the server", func() {
|
||||
c := populateServerConfig(&Config{})
|
||||
Expect(c.RequireAddressValidation).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
)
|
||||
|
||||
|
@ -20,7 +19,7 @@ type connIDGenerator struct {
|
|||
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken
|
||||
removeConnectionID func(protocol.ConnectionID)
|
||||
retireConnectionID func(protocol.ConnectionID)
|
||||
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte)
|
||||
replaceWithClosed func([]protocol.ConnectionID, []byte)
|
||||
queueControlFrame func(wire.Frame)
|
||||
}
|
||||
|
||||
|
@ -31,7 +30,7 @@ func newConnIDGenerator(
|
|||
getStatelessResetToken func(protocol.ConnectionID) protocol.StatelessResetToken,
|
||||
removeConnectionID func(protocol.ConnectionID),
|
||||
retireConnectionID func(protocol.ConnectionID),
|
||||
replaceWithClosed func([]protocol.ConnectionID, protocol.Perspective, []byte),
|
||||
replaceWithClosed func([]protocol.ConnectionID, []byte),
|
||||
queueControlFrame func(wire.Frame),
|
||||
generator ConnectionIDGenerator,
|
||||
) *connIDGenerator {
|
||||
|
@ -60,7 +59,7 @@ func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
|
|||
// transport parameter.
|
||||
// We currently don't send the preferred_address transport parameter,
|
||||
// so we can issue (limit - 1) connection IDs.
|
||||
for i := uint64(len(m.activeSrcConnIDs)); i < utils.Min(limit, protocol.MaxIssuedConnectionIDs); i++ {
|
||||
for i := uint64(len(m.activeSrcConnIDs)); i < min(limit, protocol.MaxIssuedConnectionIDs); i++ {
|
||||
if err := m.issueNewConnID(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -127,7 +126,7 @@ func (m *connIDGenerator) RemoveAll() {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose []byte) {
|
||||
func (m *connIDGenerator) ReplaceWithClosed(connClose []byte) {
|
||||
connIDs := make([]protocol.ConnectionID, 0, len(m.activeSrcConnIDs)+1)
|
||||
if m.initialClientDestConnID != nil {
|
||||
connIDs = append(connIDs, *m.initialClientDestConnID)
|
||||
|
@ -135,5 +134,5 @@ func (m *connIDGenerator) ReplaceWithClosed(pers protocol.Perspective, connClose
|
|||
for _, connID := range m.activeSrcConnIDs {
|
||||
connIDs = append(connIDs, connID)
|
||||
}
|
||||
m.replaceWithClosed(connIDs, pers, connClose)
|
||||
m.replaceWithClosed(connIDs, connClose)
|
||||
}
|
||||
|
|
|
@ -41,9 +41,7 @@ var _ = Describe("Connection ID Generator", func() {
|
|||
connIDToToken,
|
||||
func(c protocol.ConnectionID) { removedConnIDs = append(removedConnIDs, c) },
|
||||
func(c protocol.ConnectionID) { retiredConnIDs = append(retiredConnIDs, c) },
|
||||
func(cs []protocol.ConnectionID, _ protocol.Perspective, _ []byte) {
|
||||
replacedWithClosed = append(replacedWithClosed, cs...)
|
||||
},
|
||||
func(cs []protocol.ConnectionID, _ []byte) { replacedWithClosed = append(replacedWithClosed, cs...) },
|
||||
func(f wire.Frame) { queuedFrames = append(queuedFrames, f) },
|
||||
&protocol.DefaultConnectionIDGenerator{ConnLen: initialConnID.Len()},
|
||||
)
|
||||
|
@ -177,7 +175,7 @@ var _ = Describe("Connection ID Generator", func() {
|
|||
It("replaces with a closed connection for all connection IDs", func() {
|
||||
Expect(g.SetMaxActiveConnIDs(5)).To(Succeed())
|
||||
Expect(queuedFrames).To(HaveLen(4))
|
||||
g.ReplaceWithClosed(protocol.PerspectiveClient, []byte("foobar"))
|
||||
g.ReplaceWithClosed([]byte("foobar"))
|
||||
Expect(replacedWithClosed).To(HaveLen(6)) // initial conn ID, initial client dest conn id, and newly issued ones
|
||||
Expect(replacedWithClosed).To(ContainElement(initialClientDestConnID))
|
||||
Expect(replacedWithClosed).To(ContainElement(initialConnID))
|
||||
|
|
|
@ -155,7 +155,7 @@ func (h *connIDManager) updateConnectionID() {
|
|||
h.queueControlFrame(&wire.RetireConnectionIDFrame{
|
||||
SequenceNumber: h.activeSequenceNumber,
|
||||
})
|
||||
h.highestRetired = utils.Max(h.highestRetired, h.activeSequenceNumber)
|
||||
h.highestRetired = max(h.highestRetired, h.activeSequenceNumber)
|
||||
if h.activeStatelessResetToken != nil {
|
||||
h.removeStatelessResetToken(*h.activeStatelessResetToken)
|
||||
}
|
||||
|
|
132
connection.go
132
connection.go
|
@ -26,7 +26,7 @@ import (
|
|||
)
|
||||
|
||||
type unpacker interface {
|
||||
UnpackLongHeader(hdr *wire.Header, rcvTime time.Time, data []byte, v protocol.VersionNumber) (*unpackedPacket, error)
|
||||
UnpackLongHeader(hdr *wire.Header, rcvTime time.Time, data []byte, v protocol.Version) (*unpackedPacket, error)
|
||||
UnpackShortHeader(rcvTime time.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error)
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ type connRunner interface {
|
|||
GetStatelessResetToken(protocol.ConnectionID) protocol.StatelessResetToken
|
||||
Retire(protocol.ConnectionID)
|
||||
Remove(protocol.ConnectionID)
|
||||
ReplaceWithClosed([]protocol.ConnectionID, protocol.Perspective, []byte)
|
||||
ReplaceWithClosed([]protocol.ConnectionID, []byte)
|
||||
AddResetToken(protocol.StatelessResetToken, packetHandler)
|
||||
RemoveResetToken(protocol.StatelessResetToken)
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ type closeError struct {
|
|||
|
||||
type errCloseForRecreating struct {
|
||||
nextPacketNumber protocol.PacketNumber
|
||||
nextVersion protocol.VersionNumber
|
||||
nextVersion protocol.Version
|
||||
}
|
||||
|
||||
func (e *errCloseForRecreating) Error() string {
|
||||
|
@ -129,7 +129,7 @@ type connection struct {
|
|||
srcConnIDLen int
|
||||
|
||||
perspective protocol.Perspective
|
||||
version protocol.VersionNumber
|
||||
version protocol.Version
|
||||
config *Config
|
||||
|
||||
conn sendConn
|
||||
|
@ -178,6 +178,7 @@ type connection struct {
|
|||
|
||||
earlyConnReadyChan chan struct{}
|
||||
sentFirstPacket bool
|
||||
droppedInitialKeys bool
|
||||
handshakeComplete bool
|
||||
handshakeConfirmed bool
|
||||
|
||||
|
@ -236,7 +237,7 @@ var newConnection = func(
|
|||
tracer *logging.ConnectionTracer,
|
||||
tracingID uint64,
|
||||
logger utils.Logger,
|
||||
v protocol.VersionNumber,
|
||||
v protocol.Version,
|
||||
) quicConn {
|
||||
s := &connection{
|
||||
conn: conn,
|
||||
|
@ -308,7 +309,7 @@ var newConnection = func(
|
|||
RetrySourceConnectionID: retrySrcConnID,
|
||||
}
|
||||
if s.config.EnableDatagrams {
|
||||
params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
|
||||
params.MaxDatagramFrameSize = wire.MaxDatagramSize
|
||||
} else {
|
||||
params.MaxDatagramFrameSize = protocol.InvalidByteCount
|
||||
}
|
||||
|
@ -349,7 +350,7 @@ var newClientConnection = func(
|
|||
tracer *logging.ConnectionTracer,
|
||||
tracingID uint64,
|
||||
logger utils.Logger,
|
||||
v protocol.VersionNumber,
|
||||
v protocol.Version,
|
||||
) quicConn {
|
||||
s := &connection{
|
||||
conn: conn,
|
||||
|
@ -417,7 +418,7 @@ var newClientConnection = func(
|
|||
InitialSourceConnectionID: srcConnID,
|
||||
}
|
||||
if s.config.EnableDatagrams {
|
||||
params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
|
||||
params.MaxDatagramFrameSize = wire.MaxDatagramSize
|
||||
} else {
|
||||
params.MaxDatagramFrameSize = protocol.InvalidByteCount
|
||||
}
|
||||
|
@ -457,7 +458,7 @@ func (s *connection) preSetup() {
|
|||
s.handshakeStream = newCryptoStream()
|
||||
s.sendQueue = newSendQueue(s.conn)
|
||||
s.retransmissionQueue = newRetransmissionQueue()
|
||||
s.frameParser = wire.NewFrameParser(s.config.EnableDatagrams)
|
||||
s.frameParser = *wire.NewFrameParser(s.config.EnableDatagrams)
|
||||
s.rttStats = &utils.RTTStats{}
|
||||
s.connFlowController = flowcontrol.NewConnectionFlowController(
|
||||
protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
|
||||
|
@ -524,6 +525,9 @@ func (s *connection) run() error {
|
|||
|
||||
runLoop:
|
||||
for {
|
||||
if s.framer.QueuedTooManyControlFrames() {
|
||||
s.closeLocal(&qerr.TransportError{ErrorCode: InternalError})
|
||||
}
|
||||
// Close immediately if requested
|
||||
select {
|
||||
case closeErr = <-s.closeChan:
|
||||
|
@ -633,7 +637,7 @@ runLoop:
|
|||
sendQueueAvailable = s.sendQueue.Available()
|
||||
continue
|
||||
}
|
||||
if err := s.triggerSending(); err != nil {
|
||||
if err := s.triggerSending(now); err != nil {
|
||||
s.closeLocal(err)
|
||||
}
|
||||
if s.sendQueue.WouldBlock() {
|
||||
|
@ -685,7 +689,7 @@ func (s *connection) ConnectionState() ConnectionState {
|
|||
|
||||
// Time when the connection should time out
|
||||
func (s *connection) nextIdleTimeoutTime() time.Time {
|
||||
idleTimeout := utils.Max(s.idleTimeout, s.rttStats.PTO(true)*3)
|
||||
idleTimeout := max(s.idleTimeout, s.rttStats.PTO(true)*3)
|
||||
return s.idleTimeoutStartTime().Add(idleTimeout)
|
||||
}
|
||||
|
||||
|
@ -695,7 +699,7 @@ func (s *connection) nextKeepAliveTime() time.Time {
|
|||
if s.config.KeepAlivePeriod == 0 || s.keepAlivePingSent || !s.firstAckElicitingPacketAfterIdleSentTime.IsZero() {
|
||||
return time.Time{}
|
||||
}
|
||||
keepAliveInterval := utils.Max(s.keepAliveInterval, s.rttStats.PTO(true)*3/2)
|
||||
keepAliveInterval := max(s.keepAliveInterval, s.rttStats.PTO(true)*3/2)
|
||||
return s.lastPacketReceivedTime.Add(keepAliveInterval)
|
||||
}
|
||||
|
||||
|
@ -735,6 +739,10 @@ func (s *connection) handleHandshakeComplete() error {
|
|||
s.connIDManager.SetHandshakeComplete()
|
||||
s.connIDGenerator.SetHandshakeComplete()
|
||||
|
||||
if s.tracer != nil && s.tracer.ChoseALPN != nil {
|
||||
s.tracer.ChoseALPN(s.cryptoStreamHandler.ConnectionState().NegotiatedProtocol)
|
||||
}
|
||||
|
||||
// The server applies transport parameters right away, but the client side has to wait for handshake completion.
|
||||
// During a 0-RTT connection, the client is only allowed to use the new transport parameters for 1-RTT packets.
|
||||
if s.perspective == protocol.PerspectiveClient {
|
||||
|
@ -780,7 +788,7 @@ func (s *connection) handleHandshakeConfirmed() error {
|
|||
if maxPacketSize == 0 {
|
||||
maxPacketSize = protocol.MaxByteCount
|
||||
}
|
||||
s.mtuDiscoverer.Start(utils.Min(maxPacketSize, protocol.MaxPacketBufferSize))
|
||||
s.mtuDiscoverer.Start(min(maxPacketSize, protocol.MaxPacketBufferSize))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -808,14 +816,14 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
|
|||
destConnID, err = wire.ParseConnectionID(p.data, s.srcConnIDLen)
|
||||
if err != nil {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), logging.PacketDropHeaderParseError)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropHeaderParseError)
|
||||
}
|
||||
s.logger.Debugf("error parsing packet, couldn't parse connection ID: %s", err)
|
||||
break
|
||||
}
|
||||
if destConnID != lastConnID {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), logging.PacketDropUnknownConnectionID)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnknownConnectionID)
|
||||
}
|
||||
s.logger.Debugf("coalesced packet has different destination connection ID: %s, expected %s", destConnID, lastConnID)
|
||||
break
|
||||
|
@ -830,7 +838,7 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
|
|||
if err == wire.ErrUnsupportedVersion {
|
||||
dropReason = logging.PacketDropUnsupportedVersion
|
||||
}
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.ByteCount(len(data)), dropReason)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), dropReason)
|
||||
}
|
||||
s.logger.Debugf("error parsing packet: %s", err)
|
||||
break
|
||||
|
@ -839,7 +847,7 @@ func (s *connection) handlePacketImpl(rp receivedPacket) bool {
|
|||
|
||||
if hdr.Version != s.version {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), protocol.ByteCount(len(data)), logging.PacketDropUnexpectedVersion)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedVersion)
|
||||
}
|
||||
s.logger.Debugf("Dropping packet with version %x. Expected %x.", hdr.Version, s.version)
|
||||
break
|
||||
|
@ -898,7 +906,7 @@ func (s *connection) handleShortHeaderPacket(p receivedPacket, destConnID protoc
|
|||
if s.receivedPacketHandler.IsPotentiallyDuplicate(pn, protocol.Encryption1RTT) {
|
||||
s.logger.Debugf("Dropping (potentially) duplicate packet.")
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketType1RTT, p.Size(), logging.PacketDropDuplicate)
|
||||
s.tracer.DroppedPacket(logging.PacketType1RTT, pn, p.Size(), logging.PacketDropDuplicate)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -944,7 +952,7 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
|
|||
// After this, all packets with a different source connection have to be ignored.
|
||||
if s.receivedFirstPacket && hdr.Type == protocol.PacketTypeInitial && hdr.SrcConnectionID != s.handshakeDestConnID {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeInitial, p.Size(), logging.PacketDropUnknownConnectionID)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeInitial, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnknownConnectionID)
|
||||
}
|
||||
s.logger.Debugf("Dropping Initial packet (%d bytes) with unexpected source connection ID: %s (expected %s)", p.Size(), hdr.SrcConnectionID, s.handshakeDestConnID)
|
||||
return false
|
||||
|
@ -952,7 +960,7 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
|
|||
// drop 0-RTT packets, if we are a client
|
||||
if s.perspective == protocol.PerspectiveClient && hdr.Type == protocol.PacketType0RTT {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketType0RTT, p.Size(), logging.PacketDropKeyUnavailable)
|
||||
s.tracer.DroppedPacket(logging.PacketType0RTT, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropKeyUnavailable)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -968,10 +976,10 @@ func (s *connection) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header)
|
|||
packet.hdr.Log(s.logger)
|
||||
}
|
||||
|
||||
if s.receivedPacketHandler.IsPotentiallyDuplicate(packet.hdr.PacketNumber, packet.encryptionLevel) {
|
||||
if pn := packet.hdr.PacketNumber; s.receivedPacketHandler.IsPotentiallyDuplicate(pn, packet.encryptionLevel) {
|
||||
s.logger.Debugf("Dropping (potentially) duplicate packet.")
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), p.Size(), logging.PacketDropDuplicate)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeFromHeader(hdr), pn, p.Size(), logging.PacketDropDuplicate)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -987,7 +995,7 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
|
|||
switch err {
|
||||
case handshake.ErrKeysDropped:
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropKeyUnavailable)
|
||||
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropKeyUnavailable)
|
||||
}
|
||||
s.logger.Debugf("Dropping %s packet (%d bytes) because we already dropped the keys.", pt, p.Size())
|
||||
case handshake.ErrKeysNotYetAvailable:
|
||||
|
@ -1003,7 +1011,7 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
|
|||
case handshake.ErrDecryptionFailed:
|
||||
// This might be a packet injected by an attacker. Drop it.
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
}
|
||||
s.logger.Debugf("Dropping %s packet (%d bytes) that could not be unpacked. Error: %s", pt, p.Size(), err)
|
||||
default:
|
||||
|
@ -1011,7 +1019,7 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
|
|||
if errors.As(err, &headerErr) {
|
||||
// This might be a packet injected by an attacker. Drop it.
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropHeaderParseError)
|
||||
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
|
||||
}
|
||||
s.logger.Debugf("Dropping %s packet (%d bytes) for which we couldn't unpack the header. Error: %s", pt, p.Size(), err)
|
||||
} else {
|
||||
|
@ -1026,14 +1034,14 @@ func (s *connection) handleUnpackError(err error, p receivedPacket, pt logging.P
|
|||
func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime time.Time) bool /* was this a valid Retry */ {
|
||||
if s.perspective == protocol.PerspectiveServer {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
}
|
||||
s.logger.Debugf("Ignoring Retry.")
|
||||
return false
|
||||
}
|
||||
if s.receivedFirstPacket {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
}
|
||||
s.logger.Debugf("Ignoring Retry, since we already received a packet.")
|
||||
return false
|
||||
|
@ -1041,7 +1049,7 @@ func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime ti
|
|||
destConnID := s.connIDManager.Get()
|
||||
if hdr.SrcConnectionID == destConnID {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropUnexpectedPacket)
|
||||
}
|
||||
s.logger.Debugf("Ignoring Retry, since the server didn't change the Source Connection ID.")
|
||||
return false
|
||||
|
@ -1056,7 +1064,7 @@ func (s *connection) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime ti
|
|||
tag := handshake.GetRetryIntegrityTag(data[:len(data)-16], destConnID, hdr.Version)
|
||||
if !bytes.Equal(data[len(data)-16:], tag[:]) {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.ByteCount(len(data)), logging.PacketDropPayloadDecryptError)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, protocol.ByteCount(len(data)), logging.PacketDropPayloadDecryptError)
|
||||
}
|
||||
s.logger.Debugf("Ignoring spoofed Retry. Integrity Tag doesn't match.")
|
||||
return false
|
||||
|
@ -1089,7 +1097,7 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
|
|||
if s.perspective == protocol.PerspectiveServer || // servers never receive version negotiation packets
|
||||
s.receivedFirstPacket || s.versionNegotiated { // ignore delayed / duplicated version negotiation packets
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1097,7 +1105,7 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
|
|||
src, dest, supportedVersions, err := wire.ParseVersionNegotiationPacket(p.data)
|
||||
if err != nil {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropHeaderParseError)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
|
||||
}
|
||||
s.logger.Debugf("Error parsing Version Negotiation packet: %s", err)
|
||||
return
|
||||
|
@ -1106,7 +1114,7 @@ func (s *connection) handleVersionNegotiationPacket(p receivedPacket) {
|
|||
for _, v := range supportedVersions {
|
||||
if v == s.version {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
}
|
||||
// The Version Negotiation packet contains the version that we offered.
|
||||
// This might be a packet sent by an attacker, or it was corrupted.
|
||||
|
@ -1148,7 +1156,7 @@ func (s *connection) handleUnpackedLongHeaderPacket(
|
|||
if !s.receivedFirstPacket {
|
||||
s.receivedFirstPacket = true
|
||||
if !s.versionNegotiated && s.tracer != nil && s.tracer.NegotiatedVersion != nil {
|
||||
var clientVersions, serverVersions []protocol.VersionNumber
|
||||
var clientVersions, serverVersions []protocol.Version
|
||||
switch s.perspective {
|
||||
case protocol.PerspectiveClient:
|
||||
clientVersions = s.config.Versions
|
||||
|
@ -1185,7 +1193,8 @@ func (s *connection) handleUnpackedLongHeaderPacket(
|
|||
}
|
||||
}
|
||||
|
||||
if s.perspective == protocol.PerspectiveServer && packet.encryptionLevel == protocol.EncryptionHandshake {
|
||||
if s.perspective == protocol.PerspectiveServer && packet.encryptionLevel == protocol.EncryptionHandshake &&
|
||||
!s.droppedInitialKeys {
|
||||
// On the server side, Initial keys are dropped as soon as the first Handshake packet is received.
|
||||
// See Section 4.9.1 of RFC 9001.
|
||||
if err := s.dropEncryptionLevel(protocol.EncryptionInitial); err != nil {
|
||||
|
@ -1347,7 +1356,7 @@ func (s *connection) handlePacket(p receivedPacket) {
|
|||
case s.receivedPackets <- p:
|
||||
default:
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention)
|
||||
s.tracer.DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1526,7 +1535,7 @@ func (s *connection) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encr
|
|||
}
|
||||
|
||||
func (s *connection) handleDatagramFrame(f *wire.DatagramFrame) error {
|
||||
if f.Length(s.version) > protocol.MaxDatagramFrameSize {
|
||||
if f.Length(s.version) > wire.MaxDatagramSize {
|
||||
return &qerr.TransportError{
|
||||
ErrorCode: qerr.ProtocolViolation,
|
||||
ErrorMessage: "DATAGRAM frame too large",
|
||||
|
@ -1572,13 +1581,6 @@ func (s *connection) closeRemote(e error) {
|
|||
})
|
||||
}
|
||||
|
||||
// Close the connection. It sends a NO_ERROR application error.
|
||||
// It waits until the run loop has stopped before returning
|
||||
func (s *connection) shutdown() {
|
||||
s.closeLocal(nil)
|
||||
<-s.ctx.Done()
|
||||
}
|
||||
|
||||
func (s *connection) CloseWithError(code ApplicationErrorCode, desc string) error {
|
||||
s.closeLocal(&qerr.ApplicationError{
|
||||
ErrorCode: code,
|
||||
|
@ -1588,6 +1590,11 @@ func (s *connection) CloseWithError(code ApplicationErrorCode, desc string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *connection) closeWithTransportError(code TransportErrorCode) {
|
||||
s.closeLocal(&qerr.TransportError{ErrorCode: code})
|
||||
<-s.ctx.Done()
|
||||
}
|
||||
|
||||
func (s *connection) handleCloseError(closeErr *closeError) {
|
||||
e := closeErr.err
|
||||
if e == nil {
|
||||
|
@ -1632,7 +1639,7 @@ func (s *connection) handleCloseError(closeErr *closeError) {
|
|||
|
||||
// If this is a remote close we're done here
|
||||
if closeErr.remote {
|
||||
s.connIDGenerator.ReplaceWithClosed(s.perspective, nil)
|
||||
s.connIDGenerator.ReplaceWithClosed(nil)
|
||||
return
|
||||
}
|
||||
if closeErr.immediate {
|
||||
|
@ -1649,7 +1656,7 @@ func (s *connection) handleCloseError(closeErr *closeError) {
|
|||
if err != nil {
|
||||
s.logger.Debugf("Error sending CONNECTION_CLOSE: %s", err)
|
||||
}
|
||||
s.connIDGenerator.ReplaceWithClosed(s.perspective, connClosePacket)
|
||||
s.connIDGenerator.ReplaceWithClosed(connClosePacket)
|
||||
}
|
||||
|
||||
func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel) error {
|
||||
|
@ -1661,6 +1668,7 @@ func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel) erro
|
|||
//nolint:exhaustive // only Initial and 0-RTT need special treatment
|
||||
switch encLevel {
|
||||
case protocol.EncryptionInitial:
|
||||
s.droppedInitialKeys = true
|
||||
s.cryptoStreamHandler.DiscardInitialKeys()
|
||||
case protocol.Encryption0RTT:
|
||||
s.streamsMap.ResetFor0RTT()
|
||||
|
@ -1755,7 +1763,7 @@ func (s *connection) applyTransportParameters() {
|
|||
params := s.peerParams
|
||||
// Our local idle timeout will always be > 0.
|
||||
s.idleTimeout = utils.MinNonZeroDuration(s.config.MaxIdleTimeout, params.MaxIdleTimeout)
|
||||
s.keepAliveInterval = utils.Min(s.config.KeepAlivePeriod, utils.Min(s.idleTimeout/2, protocol.MaxKeepAliveInterval))
|
||||
s.keepAliveInterval = min(s.config.KeepAlivePeriod, min(s.idleTimeout/2, protocol.MaxKeepAliveInterval))
|
||||
s.streamsMap.UpdateLimits(params)
|
||||
s.frameParser.SetAckDelayExponent(params.AckDelayExponent)
|
||||
s.connFlowController.UpdateSendWindow(params.InitialMaxData)
|
||||
|
@ -1771,9 +1779,8 @@ func (s *connection) applyTransportParameters() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *connection) triggerSending() error {
|
||||
func (s *connection) triggerSending(now time.Time) error {
|
||||
s.pacingDeadline = time.Time{}
|
||||
now := time.Now()
|
||||
|
||||
sendMode := s.sentPacketHandler.SendMode(now)
|
||||
//nolint:exhaustive // No need to handle pacing limited here.
|
||||
|
@ -1805,7 +1812,7 @@ func (s *connection) triggerSending() error {
|
|||
s.scheduleSending()
|
||||
return nil
|
||||
}
|
||||
return s.triggerSending()
|
||||
return s.triggerSending(now)
|
||||
case ackhandler.SendPTOHandshake:
|
||||
if err := s.sendProbePacket(protocol.EncryptionHandshake, now); err != nil {
|
||||
return err
|
||||
|
@ -1814,7 +1821,7 @@ func (s *connection) triggerSending() error {
|
|||
s.scheduleSending()
|
||||
return nil
|
||||
}
|
||||
return s.triggerSending()
|
||||
return s.triggerSending(now)
|
||||
case ackhandler.SendPTOAppData:
|
||||
if err := s.sendProbePacket(protocol.Encryption1RTT, now); err != nil {
|
||||
return err
|
||||
|
@ -1823,7 +1830,7 @@ func (s *connection) triggerSending() error {
|
|||
s.scheduleSending()
|
||||
return nil
|
||||
}
|
||||
return s.triggerSending()
|
||||
return s.triggerSending(now)
|
||||
default:
|
||||
return fmt.Errorf("BUG: invalid send mode %d", sendMode)
|
||||
}
|
||||
|
@ -1992,7 +1999,7 @@ func (s *connection) maybeSendAckOnlyPacket(now time.Time) error {
|
|||
if packet == nil {
|
||||
return nil
|
||||
}
|
||||
return s.sendPackedCoalescedPacket(packet, ecn, time.Now())
|
||||
return s.sendPackedCoalescedPacket(packet, ecn, now)
|
||||
}
|
||||
|
||||
ecn := s.sentPacketHandler.ECNMode(true)
|
||||
|
@ -2078,7 +2085,8 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, ecn prot
|
|||
largestAcked = p.ack.LargestAcked()
|
||||
}
|
||||
s.sentPacketHandler.SentPacket(now, p.header.PacketNumber, largestAcked, p.streamFrames, p.frames, p.EncryptionLevel(), ecn, p.length, false)
|
||||
if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake {
|
||||
if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake &&
|
||||
!s.droppedInitialKeys {
|
||||
// On the client side, Initial keys are dropped as soon as the first Handshake packet is sent.
|
||||
// See Section 4.9.1 of RFC 9001.
|
||||
if err := s.dropEncryptionLevel(protocol.EncryptionInitial); err != nil {
|
||||
|
@ -2309,7 +2317,7 @@ func (s *connection) tryQueueingUndecryptablePacket(p receivedPacket, pt logging
|
|||
}
|
||||
if len(s.undecryptablePackets)+1 > protocol.MaxUndecryptablePackets {
|
||||
if s.tracer != nil && s.tracer.DroppedPacket != nil {
|
||||
s.tracer.DroppedPacket(pt, p.Size(), logging.PacketDropDOSPrevention)
|
||||
s.tracer.DroppedPacket(pt, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropDOSPrevention)
|
||||
}
|
||||
s.logger.Infof("Dropping undecryptable packet (%d bytes). Undecryptable packet queue full.", p.Size())
|
||||
return
|
||||
|
@ -2347,21 +2355,23 @@ func (s *connection) onStreamCompleted(id protocol.StreamID) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *connection) SendMessage(p []byte) error {
|
||||
func (s *connection) SendDatagram(p []byte) error {
|
||||
if !s.supportsDatagrams() {
|
||||
return errors.New("datagram support disabled")
|
||||
}
|
||||
|
||||
f := &wire.DatagramFrame{DataLenPresent: true}
|
||||
if protocol.ByteCount(len(p)) > f.MaxDataLen(s.peerParams.MaxDatagramFrameSize, s.version) {
|
||||
return errors.New("message too large")
|
||||
return &DatagramTooLargeError{
|
||||
PeerMaxDatagramFrameSize: int64(s.peerParams.MaxDatagramFrameSize),
|
||||
}
|
||||
}
|
||||
f.Data = make([]byte, len(p))
|
||||
copy(f.Data, p)
|
||||
return s.datagramQueue.AddAndWait(f)
|
||||
return s.datagramQueue.Add(f)
|
||||
}
|
||||
|
||||
func (s *connection) ReceiveMessage(ctx context.Context) ([]byte, error) {
|
||||
func (s *connection) ReceiveDatagram(ctx context.Context) ([]byte, error) {
|
||||
if !s.config.EnableDatagrams {
|
||||
return nil, errors.New("datagram support disabled")
|
||||
}
|
||||
|
@ -2376,11 +2386,7 @@ func (s *connection) RemoteAddr() net.Addr {
|
|||
return s.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (s *connection) getPerspective() protocol.Perspective {
|
||||
return s.perspective
|
||||
}
|
||||
|
||||
func (s *connection) GetVersion() protocol.VersionNumber {
|
||||
func (s *connection) GetVersion() protocol.Version {
|
||||
return s.version
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -21,10 +22,10 @@ import (
|
|||
mocklogging "github.com/refraction-networking/uquic/internal/mocks/logging"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/testutils"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
"github.com/refraction-networking/uquic/logging"
|
||||
"github.com/refraction-networking/uquic/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -76,7 +77,7 @@ var _ = Describe("Connection", func() {
|
|||
}
|
||||
|
||||
expectReplaceWithClosed := func() {
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ protocol.Perspective, _ []byte) {
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ []byte) {
|
||||
Expect(connIDs).To(ContainElement(srcConnID))
|
||||
if len(connIDs) > 1 {
|
||||
Expect(connIDs).To(ContainElement(clientDestConnID))
|
||||
|
@ -84,8 +85,8 @@ var _ = Describe("Connection", func() {
|
|||
})
|
||||
}
|
||||
|
||||
expectAppendPacket := func(packer *MockPacker, p shortHeaderPacket, b []byte) *gomock.Call {
|
||||
return packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), Version1).DoAndReturn(func(buf *packetBuffer, _ protocol.ByteCount, _ protocol.VersionNumber) (shortHeaderPacket, error) {
|
||||
expectAppendPacket := func(packer *MockPacker, p shortHeaderPacket, b []byte) *MockPackerAppendPacketCall {
|
||||
return packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), Version1).DoAndReturn(func(buf *packetBuffer, _ protocol.ByteCount, _ protocol.Version) (shortHeaderPacket, error) {
|
||||
buf.Data = append(buf.Data, b...)
|
||||
return p, nil
|
||||
})
|
||||
|
@ -118,7 +119,7 @@ var _ = Describe("Connection", func() {
|
|||
srcConnID,
|
||||
&protocol.DefaultConnectionIDGenerator{},
|
||||
protocol.StatelessResetToken{},
|
||||
populateServerConfig(&Config{DisablePathMTUDiscovery: true}),
|
||||
populateConfig(&Config{DisablePathMTUDiscovery: true}),
|
||||
&tls.Config{},
|
||||
tokenGenerator,
|
||||
false,
|
||||
|
@ -346,7 +347,7 @@ var _ = Describe("Connection", func() {
|
|||
ErrorMessage: "foobar",
|
||||
}
|
||||
streamManager.EXPECT().CloseWithError(expectedErr)
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ protocol.Perspective, _ []byte) {
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ []byte) {
|
||||
Expect(connIDs).To(ConsistOf(clientDestConnID, srcConnID))
|
||||
})
|
||||
cryptoSetup.EXPECT().Close()
|
||||
|
@ -375,7 +376,7 @@ var _ = Describe("Connection", func() {
|
|||
ErrorMessage: "foobar",
|
||||
}
|
||||
streamManager.EXPECT().CloseWithError(testErr)
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ protocol.Perspective, _ []byte) {
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).Do(func(connIDs []protocol.ConnectionID, _ []byte) {
|
||||
Expect(connIDs).To(ConsistOf(clientDestConnID, srcConnID))
|
||||
})
|
||||
cryptoSetup.EXPECT().Close()
|
||||
|
@ -410,7 +411,7 @@ var _ = Describe("Connection", func() {
|
|||
|
||||
It("tells its versions", func() {
|
||||
conn.version = 4242
|
||||
Expect(conn.GetVersion()).To(Equal(protocol.VersionNumber(4242)))
|
||||
Expect(conn.GetVersion()).To(Equal(protocol.Version(4242)))
|
||||
})
|
||||
|
||||
Context("closing", func() {
|
||||
|
@ -450,7 +451,7 @@ var _ = Describe("Connection", func() {
|
|||
cryptoSetup.EXPECT().Close()
|
||||
buffer := getPacketBuffer()
|
||||
buffer.Data = append(buffer.Data, []byte("connection close")...)
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(e *qerr.ApplicationError, _ protocol.ByteCount, _ protocol.VersionNumber) (*coalescedPacket, error) {
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(e *qerr.ApplicationError, _ protocol.ByteCount, _ protocol.Version) (*coalescedPacket, error) {
|
||||
Expect(e.ErrorCode).To(BeEquivalentTo(qerr.NoError))
|
||||
Expect(e.ErrorMessage).To(BeEmpty())
|
||||
return &coalescedPacket{buffer: buffer}, nil
|
||||
|
@ -465,7 +466,7 @@ var _ = Describe("Connection", func() {
|
|||
}),
|
||||
tracer.EXPECT().Close(),
|
||||
)
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(areConnsRunning).Should(BeFalse())
|
||||
Expect(conn.Context().Done()).To(BeClosed())
|
||||
})
|
||||
|
@ -479,8 +480,8 @@ var _ = Describe("Connection", func() {
|
|||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(areConnsRunning).Should(BeFalse())
|
||||
Expect(conn.Context().Done()).To(BeClosed())
|
||||
})
|
||||
|
@ -551,29 +552,6 @@ var _ = Describe("Connection", func() {
|
|||
}
|
||||
})
|
||||
|
||||
It("cancels the context when the run loop exists", func() {
|
||||
runConn()
|
||||
streamManager.EXPECT().CloseWithError(gomock.Any())
|
||||
expectReplaceWithClosed()
|
||||
cryptoSetup.EXPECT().Close()
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
|
||||
returned := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
ctx := conn.Context()
|
||||
<-ctx.Done()
|
||||
Expect(ctx.Err()).To(MatchError(context.Canceled))
|
||||
close(returned)
|
||||
}()
|
||||
Consistently(returned).ShouldNot(BeClosed())
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
Eventually(returned).Should(BeClosed())
|
||||
Expect(context.Cause(conn.Context())).To(MatchError(context.Canceled))
|
||||
})
|
||||
|
||||
It("doesn't send any more packets after receiving a CONNECTION_CLOSE", func() {
|
||||
unpacker := NewMockUnpacker(mockCtrl)
|
||||
conn.handshakeConfirmed = true
|
||||
|
@ -581,7 +559,7 @@ var _ = Describe("Connection", func() {
|
|||
runConn()
|
||||
cryptoSetup.EXPECT().Close()
|
||||
streamManager.EXPECT().CloseWithError(gomock.Any())
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).AnyTimes()
|
||||
b, err := wire.AppendShortHeader(nil, srcConnID, 42, protocol.PacketNumberLen2, protocol.KeyPhaseOne)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
@ -687,7 +665,7 @@ var _ = Describe("Connection", func() {
|
|||
Version: conn.version,
|
||||
Token: []byte("foobar"),
|
||||
}}, make([]byte, 16) /* Retry integrity tag */)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -697,7 +675,7 @@ var _ = Describe("Connection", func() {
|
|||
protocol.ArbitraryLenConnectionID(destConnID.Bytes()),
|
||||
conn.config.Versions,
|
||||
)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.ByteCount(len(b)), logging.PacketDropUnexpectedPacket)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, protocol.ByteCount(len(b)), logging.PacketDropUnexpectedPacket)
|
||||
Expect(conn.handlePacketImpl(receivedPacket{
|
||||
data: b,
|
||||
buffer: getPacketBuffer(),
|
||||
|
@ -713,7 +691,7 @@ var _ = Describe("Connection", func() {
|
|||
PacketNumberLen: protocol.PacketNumberLen2,
|
||||
}, nil)
|
||||
p.data[0] ^= 0x40 // unset the QUIC bit
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -725,12 +703,12 @@ var _ = Describe("Connection", func() {
|
|||
},
|
||||
PacketNumberLen: protocol.PacketNumberLen2,
|
||||
}, nil)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnsupportedVersion)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnsupportedVersion)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("drops packets with an unsupported version", func() {
|
||||
origSupportedVersions := make([]protocol.VersionNumber, len(protocol.SupportedVersions))
|
||||
origSupportedVersions := make([]protocol.Version, len(protocol.SupportedVersions))
|
||||
copy(origSupportedVersions, protocol.SupportedVersions)
|
||||
defer func() {
|
||||
protocol.SupportedVersions = origSupportedVersions
|
||||
|
@ -746,7 +724,7 @@ var _ = Describe("Connection", func() {
|
|||
},
|
||||
PacketNumberLen: protocol.PacketNumberLen2,
|
||||
}, nil)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeHandshake, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeHandshake, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -812,7 +790,7 @@ var _ = Describe("Connection", func() {
|
|||
rph := mockackhandler.NewMockReceivedPacketHandler(mockCtrl)
|
||||
rph.EXPECT().IsPotentiallyDuplicate(protocol.PacketNumber(0x1337), protocol.Encryption1RTT).Return(true)
|
||||
conn.receivedPacketHandler = rph
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType1RTT, protocol.ByteCount(len(packet.data)), logging.PacketDropDuplicate)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType1RTT, protocol.PacketNumber(0x1337), protocol.ByteCount(len(packet.data)), logging.PacketDropDuplicate)
|
||||
Expect(conn.handlePacketImpl(packet)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -838,7 +816,7 @@ var _ = Describe("Connection", func() {
|
|||
PacketNumber: 0x1337,
|
||||
PacketNumberLen: protocol.PacketNumberLen2,
|
||||
}, []byte("foobar"))
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeHandshake, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeHandshake, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
conn.handlePacket(p)
|
||||
Consistently(conn.Context().Done()).ShouldNot(BeClosed())
|
||||
// make the go routine return
|
||||
|
@ -956,7 +934,7 @@ var _ = Describe("Connection", func() {
|
|||
runErr <- conn.run()
|
||||
}()
|
||||
expectReplaceWithClosed()
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType1RTT, gomock.Any(), logging.PacketDropHeaderParseError)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType1RTT, protocol.InvalidPacketNumber, gomock.Any(), logging.PacketDropHeaderParseError)
|
||||
conn.handlePacket(getShortHeaderPacket(srcConnID, 0x42, nil))
|
||||
Consistently(runErr).ShouldNot(Receive())
|
||||
// make the go routine return
|
||||
|
@ -964,7 +942,7 @@ var _ = Describe("Connection", func() {
|
|||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -1029,7 +1007,7 @@ var _ = Describe("Connection", func() {
|
|||
Expect(conn.handlePacketImpl(p1)).To(BeTrue())
|
||||
// The next packet has to be ignored, since the source connection ID doesn't match.
|
||||
p2 := getLongHeaderPacket(hdr2, nil)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeInitial, protocol.ByteCount(len(p2.data)), logging.PacketDropUnknownConnectionID)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeInitial, protocol.InvalidPacketNumber, protocol.ByteCount(len(p2.data)), logging.PacketDropUnknownConnectionID)
|
||||
Expect(conn.handlePacketImpl(p2)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -1088,7 +1066,7 @@ var _ = Describe("Connection", func() {
|
|||
|
||||
It("cuts packets to the right length", func() {
|
||||
hdrLen, packet := getPacketWithLength(srcConnID, 456)
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
Expect(data).To(HaveLen(hdrLen + 456 - 3))
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.EncryptionHandshake,
|
||||
|
@ -1105,7 +1083,7 @@ var _ = Describe("Connection", func() {
|
|||
It("handles coalesced packets", func() {
|
||||
hdrLen1, packet1 := getPacketWithLength(srcConnID, 456)
|
||||
packet1.ecn = protocol.ECT1
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
Expect(data).To(HaveLen(hdrLen1 + 456 - 3))
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.EncryptionHandshake,
|
||||
|
@ -1117,7 +1095,7 @@ var _ = Describe("Connection", func() {
|
|||
}, nil
|
||||
})
|
||||
hdrLen2, packet2 := getPacketWithLength(srcConnID, 123)
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
Expect(data).To(HaveLen(hdrLen2 + 123 - 3))
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.EncryptionHandshake,
|
||||
|
@ -1144,7 +1122,7 @@ var _ = Describe("Connection", func() {
|
|||
hdrLen2, packet2 := getPacketWithLength(srcConnID, 123)
|
||||
gomock.InOrder(
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).Return(nil, handshake.ErrKeysNotYetAvailable),
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
Expect(data).To(HaveLen(hdrLen2 + 123 - 3))
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.EncryptionHandshake,
|
||||
|
@ -1170,7 +1148,7 @@ var _ = Describe("Connection", func() {
|
|||
wrongConnID := protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef})
|
||||
Expect(srcConnID).ToNot(Equal(wrongConnID))
|
||||
hdrLen1, packet1 := getPacketWithLength(srcConnID, 456)
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
Expect(data).To(HaveLen(hdrLen1 + 456 - 3))
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.EncryptionHandshake,
|
||||
|
@ -1184,7 +1162,7 @@ var _ = Describe("Connection", func() {
|
|||
// don't EXPECT any more calls to unpacker.UnpackLongHeader()
|
||||
gomock.InOrder(
|
||||
tracer.EXPECT().ReceivedLongHeaderPacket(gomock.Any(), protocol.ByteCount(len(packet1.data)), gomock.Any(), gomock.Any()),
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), protocol.ByteCount(len(packet2.data)), logging.PacketDropUnknownConnectionID),
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), protocol.InvalidPacketNumber, protocol.ByteCount(len(packet2.data)), logging.PacketDropUnknownConnectionID),
|
||||
)
|
||||
packet1.data = append(packet1.data, packet2.data...)
|
||||
Expect(conn.handlePacketImpl(packet1)).To(BeTrue())
|
||||
|
@ -1219,7 +1197,7 @@ var _ = Describe("Connection", func() {
|
|||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
sender.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
Eventually(connDone).Should(BeClosed())
|
||||
})
|
||||
|
@ -1270,7 +1248,6 @@ var _ = Describe("Connection", func() {
|
|||
sph.EXPECT().ECNMode(true).AnyTimes()
|
||||
runConn()
|
||||
packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), conn.version).Return(shortHeaderPacket{}, errNothingToPack).AnyTimes()
|
||||
conn.receivedPacketHandler.ReceivedPacket(0x035e, protocol.ECNNon, protocol.Encryption1RTT, time.Now(), true)
|
||||
conn.scheduleSending()
|
||||
time.Sleep(50 * time.Millisecond) // make sure there are no calls to mconn.Write()
|
||||
})
|
||||
|
@ -1281,7 +1258,10 @@ var _ = Describe("Connection", func() {
|
|||
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAck)
|
||||
sph.EXPECT().ECNMode(gomock.Any()).Return(protocol.ECT1).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
packer.EXPECT().PackCoalescedPacket(true, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.VersionNumber) { close(done) })
|
||||
packer.EXPECT().PackCoalescedPacket(true, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.Version) (*coalescedPacket, error) {
|
||||
close(done)
|
||||
return nil, nil
|
||||
})
|
||||
runConn()
|
||||
conn.scheduleSending()
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -1323,7 +1303,7 @@ var _ = Describe("Connection", func() {
|
|||
|
||||
Context(fmt.Sprintf("sending %s probe packets", encLevel), func() {
|
||||
var sendMode ackhandler.SendMode
|
||||
var getFrame func(protocol.ByteCount, protocol.VersionNumber) wire.Frame
|
||||
var getFrame func(protocol.ByteCount, protocol.Version) wire.Frame
|
||||
|
||||
BeforeEach(func() {
|
||||
//nolint:exhaustive
|
||||
|
@ -1420,7 +1400,7 @@ var _ = Describe("Connection", func() {
|
|||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
sender.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -1809,7 +1789,7 @@ var _ = Describe("Connection", func() {
|
|||
sender.EXPECT().Close()
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -1915,7 +1895,7 @@ var _ = Describe("Connection", func() {
|
|||
)
|
||||
|
||||
sent := make(chan struct{})
|
||||
mconn.EXPECT().Write([]byte("foobar"), uint16(0), protocol.ECT1).Do(func([]byte, uint16, protocol.ECN) { close(sent) })
|
||||
mconn.EXPECT().Write([]byte("foobar"), uint16(0), protocol.ECT1).Do(func([]byte, uint16, protocol.ECN) error { close(sent); return nil })
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -1935,7 +1915,7 @@ var _ = Describe("Connection", func() {
|
|||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -1944,6 +1924,7 @@ var _ = Describe("Connection", func() {
|
|||
sph := mockackhandler.NewMockSentPacketHandler(mockCtrl)
|
||||
conn.sentPacketHandler = sph
|
||||
tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
|
||||
tracer.EXPECT().ChoseALPN(gomock.Any())
|
||||
sph.EXPECT().GetLossDetectionTimeout().AnyTimes()
|
||||
sph.EXPECT().TimeUntilSend().AnyTimes()
|
||||
sph.EXPECT().SendMode(gomock.Any()).AnyTimes()
|
||||
|
@ -1952,6 +1933,7 @@ var _ = Describe("Connection", func() {
|
|||
connRunner.EXPECT().Retire(clientDestConnID)
|
||||
cryptoSetup.EXPECT().SetHandshakeConfirmed()
|
||||
cryptoSetup.EXPECT().GetSessionTicket()
|
||||
cryptoSetup.EXPECT().ConnectionState()
|
||||
handshakeCtx := conn.HandshakeComplete()
|
||||
Consistently(handshakeCtx).ShouldNot(BeClosed())
|
||||
Expect(conn.handleHandshakeComplete()).To(Succeed())
|
||||
|
@ -1964,8 +1946,10 @@ var _ = Describe("Connection", func() {
|
|||
connRunner.EXPECT().Retire(clientDestConnID)
|
||||
conn.sentPacketHandler.DropPackets(protocol.EncryptionInitial)
|
||||
tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
|
||||
tracer.EXPECT().ChoseALPN(gomock.Any())
|
||||
cryptoSetup.EXPECT().SetHandshakeConfirmed()
|
||||
cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
|
||||
cryptoSetup.EXPECT().ConnectionState()
|
||||
|
||||
handshakeCtx := conn.HandshakeComplete()
|
||||
Consistently(handshakeCtx).ShouldNot(BeClosed())
|
||||
|
@ -2019,10 +2003,11 @@ var _ = Describe("Connection", func() {
|
|||
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().SentShortHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ChoseALPN(gomock.Any())
|
||||
conn.sentPacketHandler = sph
|
||||
done := make(chan struct{})
|
||||
connRunner.EXPECT().Retire(clientDestConnID)
|
||||
packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *packetBuffer, _ protocol.ByteCount, v protocol.VersionNumber) (shortHeaderPacket, error) {
|
||||
packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(_ *packetBuffer, _ protocol.ByteCount, v protocol.Version) (shortHeaderPacket, error) {
|
||||
frames, _ := conn.framer.AppendControlFrames(nil, protocol.MaxByteCount, v)
|
||||
Expect(frames).ToNot(BeEmpty())
|
||||
Expect(frames[0].Frame).To(BeEquivalentTo(&wire.HandshakeDoneFrame{}))
|
||||
|
@ -2039,6 +2024,7 @@ var _ = Describe("Connection", func() {
|
|||
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
|
||||
cryptoSetup.EXPECT().SetHandshakeConfirmed()
|
||||
cryptoSetup.EXPECT().GetSessionTicket()
|
||||
cryptoSetup.EXPECT().ConnectionState()
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
Expect(conn.handleHandshakeComplete()).To(Succeed())
|
||||
conn.run()
|
||||
|
@ -2051,7 +2037,7 @@ var _ = Describe("Connection", func() {
|
|||
cryptoSetup.EXPECT().Close()
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -2065,13 +2051,11 @@ var _ = Describe("Connection", func() {
|
|||
close(done)
|
||||
}()
|
||||
streamManager.EXPECT().CloseWithError(gomock.Any())
|
||||
expectReplaceWithClosed()
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
|
||||
cryptoSetup.EXPECT().Close()
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
connRunner.EXPECT().Remove(gomock.Any()).AnyTimes()
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.destroy(nil)
|
||||
Eventually(done).Should(BeClosed())
|
||||
Expect(context.Cause(conn.Context())).To(MatchError(context.Canceled))
|
||||
})
|
||||
|
@ -2157,7 +2141,7 @@ var _ = Describe("Connection", func() {
|
|||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -2165,7 +2149,7 @@ var _ = Describe("Connection", func() {
|
|||
setRemoteIdleTimeout(5 * time.Second)
|
||||
conn.lastPacketReceivedTime = time.Now().Add(-5 * time.Second / 2)
|
||||
sent := make(chan struct{})
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error) {
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.Version) (*coalescedPacket, error) {
|
||||
close(sent)
|
||||
return nil, nil
|
||||
})
|
||||
|
@ -2178,7 +2162,7 @@ var _ = Describe("Connection", func() {
|
|||
setRemoteIdleTimeout(time.Hour)
|
||||
conn.lastPacketReceivedTime = time.Now().Add(-protocol.MaxKeepAliveInterval).Add(-time.Millisecond)
|
||||
sent := make(chan struct{})
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error) {
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.Version) (*coalescedPacket, error) {
|
||||
close(sent)
|
||||
return nil, nil
|
||||
})
|
||||
|
@ -2211,7 +2195,7 @@ var _ = Describe("Connection", func() {
|
|||
pto := conn.rttStats.PTO(true)
|
||||
conn.lastPacketReceivedTime = time.Now()
|
||||
sentPingTimeChan := make(chan time.Time)
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.VersionNumber) (*coalescedPacket, error) {
|
||||
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).Do(func(bool, protocol.ByteCount, protocol.Version) (*coalescedPacket, error) {
|
||||
sentPingTimeChan <- time.Now()
|
||||
return nil, nil
|
||||
})
|
||||
|
@ -2282,7 +2266,7 @@ var _ = Describe("Connection", func() {
|
|||
conn.config.HandshakeIdleTimeout = 9999 * time.Second
|
||||
conn.config.MaxIdleTimeout = 9999 * time.Second
|
||||
conn.lastPacketReceivedTime = time.Now().Add(-time.Minute)
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(e *qerr.ApplicationError, _ protocol.ByteCount, _ protocol.VersionNumber) (*coalescedPacket, error) {
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(e *qerr.ApplicationError, _ protocol.ByteCount, _ protocol.Version) (*coalescedPacket, error) {
|
||||
Expect(e.ErrorCode).To(BeZero())
|
||||
return &coalescedPacket{buffer: getPacketBuffer()}, nil
|
||||
})
|
||||
|
@ -2308,7 +2292,7 @@ var _ = Describe("Connection", func() {
|
|||
expectReplaceWithClosed()
|
||||
cryptoSetup.EXPECT().Close()
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -2349,6 +2333,7 @@ var _ = Describe("Connection", func() {
|
|||
)
|
||||
cryptoSetup.EXPECT().Close()
|
||||
gomock.InOrder(
|
||||
tracer.EXPECT().ChoseALPN(gomock.Any()),
|
||||
tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake),
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any()).Do(func(e error) {
|
||||
Expect(e).To(MatchError(&IdleTimeoutError{}))
|
||||
|
@ -2364,6 +2349,7 @@ var _ = Describe("Connection", func() {
|
|||
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
|
||||
cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
|
||||
cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1)
|
||||
cryptoSetup.EXPECT().ConnectionState()
|
||||
Expect(conn.handleHandshakeComplete()).To(Succeed())
|
||||
err := conn.run()
|
||||
nerr, ok := err.(net.Error)
|
||||
|
@ -2393,7 +2379,7 @@ var _ = Describe("Connection", func() {
|
|||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -2428,7 +2414,7 @@ var _ = Describe("Connection", func() {
|
|||
|
||||
It("stores up to MaxConnUnprocessedPackets packets", func() {
|
||||
done := make(chan struct{})
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, logging.ByteCount(6), logging.PacketDropDOSPrevention).Do(func(logging.PacketType, logging.ByteCount, logging.PacketDropReason) {
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeNotDetermined, protocol.InvalidPacketNumber, logging.ByteCount(6), logging.PacketDropDOSPrevention).Do(func(logging.PacketType, logging.PacketNumber, logging.ByteCount, logging.PacketDropReason) {
|
||||
close(done)
|
||||
})
|
||||
// Nothing here should block
|
||||
|
@ -2573,7 +2559,7 @@ var _ = Describe("Client Connection", func() {
|
|||
|
||||
It("changes the connection ID when receiving the first packet from the server", func() {
|
||||
unpacker := NewMockUnpacker(mockCtrl)
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(hdr *wire.Header, _ time.Time, data []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(hdr *wire.Header, _ time.Time, data []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
return &unpackedPacket{
|
||||
encryptionLevel: protocol.Encryption1RTT,
|
||||
hdr: &wire.ExtendedHeader{Header: *hdr},
|
||||
|
@ -2582,7 +2568,10 @@ var _ = Describe("Client Connection", func() {
|
|||
})
|
||||
conn.unpacker = unpacker
|
||||
done := make(chan struct{})
|
||||
packer.EXPECT().PackCoalescedPacket(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) { close(done) })
|
||||
packer.EXPECT().PackCoalescedPacket(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.Version) (*coalescedPacket, error) {
|
||||
close(done)
|
||||
return nil, nil
|
||||
})
|
||||
newConnID := protocol.ParseConnectionID([]byte{1, 3, 3, 7, 1, 3, 3, 7})
|
||||
p := getPacket(&wire.ExtendedHeader{
|
||||
Header: wire.Header{
|
||||
|
@ -2606,11 +2595,11 @@ var _ = Describe("Client Connection", func() {
|
|||
// make sure the go routine returns
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
|
||||
cryptoSetup.EXPECT().Close()
|
||||
connRunner.EXPECT().ReplaceWithClosed([]protocol.ConnectionID{srcConnID}, gomock.Any(), gomock.Any())
|
||||
connRunner.EXPECT().ReplaceWithClosed([]protocol.ConnectionID{srcConnID}, gomock.Any())
|
||||
mconn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
|
||||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
})
|
||||
|
@ -2626,7 +2615,7 @@ var _ = Describe("Client Connection", func() {
|
|||
})
|
||||
Expect(conn.connIDManager.Get()).To(Equal(protocol.ParseConnectionID([]byte{1, 2, 3, 4, 5})))
|
||||
// now receive a packet with the original source connection ID
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(hdr *wire.Header, _ time.Time, _ []byte, _ protocol.VersionNumber) (*unpackedPacket, error) {
|
||||
unpacker.EXPECT().UnpackLongHeader(gomock.Any(), gomock.Any(), gomock.Any(), conn.version).DoAndReturn(func(hdr *wire.Header, _ time.Time, _ []byte, _ protocol.Version) (*unpackedPacket, error) {
|
||||
return &unpackedPacket{
|
||||
hdr: &wire.ExtendedHeader{Header: *hdr},
|
||||
data: []byte{0},
|
||||
|
@ -2672,9 +2661,10 @@ var _ = Describe("Client Connection", func() {
|
|||
tracer.EXPECT().ClosedConnection(gomock.Any())
|
||||
tracer.EXPECT().Close()
|
||||
running := make(chan struct{})
|
||||
cryptoSetup.EXPECT().StartHandshake().Do(func() {
|
||||
cryptoSetup.EXPECT().StartHandshake().Do(func() error {
|
||||
close(running)
|
||||
conn.closeLocal(errors.New("early error"))
|
||||
return nil
|
||||
})
|
||||
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
|
||||
cryptoSetup.EXPECT().Close()
|
||||
|
@ -2705,7 +2695,7 @@ var _ = Describe("Client Connection", func() {
|
|||
})
|
||||
|
||||
Context("handling Version Negotiation", func() {
|
||||
getVNP := func(versions ...protocol.VersionNumber) receivedPacket {
|
||||
getVNP := func(versions ...protocol.Version) receivedPacket {
|
||||
b := wire.ComposeVersionNegotiation(
|
||||
protocol.ArbitraryLenConnectionID(srcConnID.Bytes()),
|
||||
protocol.ArbitraryLenConnectionID(destConnID.Bytes()),
|
||||
|
@ -2723,7 +2713,7 @@ var _ = Describe("Client Connection", func() {
|
|||
conn.sentPacketHandler = sph
|
||||
sph.EXPECT().ReceivedBytes(gomock.Any())
|
||||
sph.EXPECT().PeekPacketNumber(protocol.EncryptionInitial).Return(protocol.PacketNumber(128), protocol.PacketNumberLen4)
|
||||
conn.config.Versions = []protocol.VersionNumber{1234, 4321}
|
||||
conn.config.Versions = []protocol.Version{1234, 4321}
|
||||
errChan := make(chan error, 1)
|
||||
start := make(chan struct{})
|
||||
go func() {
|
||||
|
@ -2738,8 +2728,8 @@ var _ = Describe("Client Connection", func() {
|
|||
connRunner.EXPECT().Remove(srcConnID)
|
||||
tracer.EXPECT().ReceivedVersionNegotiationPacket(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(_, _ protocol.ArbitraryLenConnectionID, versions []logging.VersionNumber) {
|
||||
Expect(versions).To(And(
|
||||
ContainElement(protocol.VersionNumber(4321)),
|
||||
ContainElement(protocol.VersionNumber(1337)),
|
||||
ContainElement(protocol.Version(4321)),
|
||||
ContainElement(protocol.Version(1337)),
|
||||
))
|
||||
})
|
||||
cryptoSetup.EXPECT().Close()
|
||||
|
@ -2750,7 +2740,7 @@ var _ = Describe("Client Connection", func() {
|
|||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(BeAssignableToTypeOf(&errCloseForRecreating{}))
|
||||
recreateErr := err.(*errCloseForRecreating)
|
||||
Expect(recreateErr.nextVersion).To(Equal(protocol.VersionNumber(4321)))
|
||||
Expect(recreateErr.nextVersion).To(Equal(protocol.Version(4321)))
|
||||
Expect(recreateErr.nextPacketNumber).To(Equal(protocol.PacketNumber(128)))
|
||||
})
|
||||
|
||||
|
@ -2784,14 +2774,14 @@ var _ = Describe("Client Connection", func() {
|
|||
|
||||
It("ignores Version Negotiation packets that offer the current version", func() {
|
||||
p := getVNP(conn.version)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedVersion)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("ignores unparseable Version Negotiation packets", func() {
|
||||
p := getVNP(conn.version)
|
||||
p.data = p.data[:len(p.data)-2]
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropHeaderParseError)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeVersionNegotiation, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropHeaderParseError)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
@ -2840,14 +2830,14 @@ var _ = Describe("Client Connection", func() {
|
|||
It("ignores Retry packets after receiving a regular packet", func() {
|
||||
conn.receivedFirstPacket = true
|
||||
p := getPacket(retryHdr, getRetryTag(retryHdr))
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("ignores Retry packets if the server didn't change the connection ID", func() {
|
||||
retryHdr.SrcConnectionID = destConnID
|
||||
p := getPacket(retryHdr, getRetryTag(retryHdr))
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropUnexpectedPacket)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -2855,7 +2845,7 @@ var _ = Describe("Client Connection", func() {
|
|||
tag := getRetryTag(retryHdr)
|
||||
tag[0]++
|
||||
p := getPacket(retryHdr, tag)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketTypeRetry, protocol.InvalidPacketNumber, p.Size(), logging.PacketDropPayloadDecryptError)
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
@ -2890,6 +2880,8 @@ var _ = Describe("Client Connection", func() {
|
|||
defer GinkgoRecover()
|
||||
Expect(conn.handleHandshakeComplete()).To(Succeed())
|
||||
})
|
||||
tracer.EXPECT().ChoseALPN(gomock.Any()).MaxTimes(1)
|
||||
cryptoSetup.EXPECT().ConnectionState().MaxTimes(1)
|
||||
errChan <- conn.run()
|
||||
close(errChan)
|
||||
}()
|
||||
|
@ -2897,7 +2889,7 @@ var _ = Describe("Client Connection", func() {
|
|||
|
||||
expectClose := func(applicationClose, errored bool) {
|
||||
if !closed && !errored {
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any())
|
||||
if applicationClose {
|
||||
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil).MaxTimes(1)
|
||||
} else {
|
||||
|
@ -2914,7 +2906,7 @@ var _ = Describe("Client Connection", func() {
|
|||
}
|
||||
|
||||
AfterEach(func() {
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
Eventually(errChan).Should(BeClosed())
|
||||
})
|
||||
|
@ -2924,8 +2916,8 @@ var _ = Describe("Client Connection", func() {
|
|||
OriginalDestinationConnectionID: destConnID,
|
||||
InitialSourceConnectionID: destConnID,
|
||||
PreferredAddress: &wire.PreferredAddress{
|
||||
IPv4: net.IPv4(127, 0, 0, 1),
|
||||
IPv6: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
|
||||
IPv4: netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 42),
|
||||
IPv6: netip.AddrPortFrom(netip.AddrFrom16([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 13),
|
||||
ConnectionID: protocol.ParseConnectionID([]byte{1, 2, 3, 4}),
|
||||
StatelessResetToken: protocol.StatelessResetToken{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
||||
},
|
||||
|
@ -2958,7 +2950,7 @@ var _ = Describe("Client Connection", func() {
|
|||
Eventually(processed).Should(BeClosed())
|
||||
// close first
|
||||
expectClose(true, false)
|
||||
conn.shutdown()
|
||||
conn.CloseWithError(0, "")
|
||||
// then check. Avoids race condition when accessing idleTimeout
|
||||
Expect(conn.idleTimeout).To(Equal(18 * time.Second))
|
||||
})
|
||||
|
@ -3153,7 +3145,7 @@ var _ = Describe("Client Connection", func() {
|
|||
tracer.EXPECT().ReceivedLongHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(getPacket(hdr1, nil))).To(BeTrue())
|
||||
// The next packet has to be ignored, since the source connection ID doesn't match.
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), protocol.InvalidPacketNumber, gomock.Any(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(getPacket(hdr2, nil))).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -3168,7 +3160,7 @@ var _ = Describe("Client Connection", func() {
|
|||
PacketNumber: 0x42,
|
||||
PacketNumberLen: protocol.PacketNumberLen2,
|
||||
}, []byte("foobar"))
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType0RTT, p.Size(), gomock.Any())
|
||||
tracer.EXPECT().DroppedPacket(logging.PacketType0RTT, protocol.InvalidPacketNumber, p.Size(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(p)).To(BeFalse())
|
||||
})
|
||||
|
||||
|
@ -3176,7 +3168,7 @@ var _ = Describe("Client Connection", func() {
|
|||
// the connection to immediately break down
|
||||
It("fails on Initial-level ACK for unsent packet", func() {
|
||||
ack := &wire.AckFrame{AckRanges: []wire.AckRange{{Smallest: 2, Largest: 2}}}
|
||||
initialPacket := testutils.ComposeInitialPacket(destConnID, srcConnID, conn.version, destConnID, []wire.Frame{ack})
|
||||
initialPacket := testutils.ComposeInitialPacket(destConnID, srcConnID, destConnID, []wire.Frame{ack}, protocol.PerspectiveServer, conn.version)
|
||||
tracer.EXPECT().ReceivedLongHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(wrapPacket(initialPacket))).To(BeFalse())
|
||||
})
|
||||
|
@ -3188,7 +3180,7 @@ var _ = Describe("Client Connection", func() {
|
|||
IsApplicationError: true,
|
||||
ReasonPhrase: "mitm attacker",
|
||||
}
|
||||
initialPacket := testutils.ComposeInitialPacket(destConnID, srcConnID, conn.version, destConnID, []wire.Frame{connCloseFrame})
|
||||
initialPacket := testutils.ComposeInitialPacket(destConnID, srcConnID, destConnID, []wire.Frame{connCloseFrame}, protocol.PerspectiveServer, conn.version)
|
||||
tracer.EXPECT().ReceivedLongHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(wrapPacket(initialPacket))).To(BeTrue())
|
||||
})
|
||||
|
@ -3206,8 +3198,8 @@ var _ = Describe("Client Connection", func() {
|
|||
|
||||
tracer.EXPECT().ReceivedRetry(gomock.Any())
|
||||
conn.handlePacketImpl(wrapPacket(testutils.ComposeRetryPacket(newSrcConnID, destConnID, destConnID, []byte("foobar"), conn.version)))
|
||||
initialPacket := testutils.ComposeInitialPacket(conn.connIDManager.Get(), srcConnID, conn.version, conn.connIDManager.Get(), nil)
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
initialPacket := testutils.ComposeInitialPacket(conn.connIDManager.Get(), srcConnID, conn.connIDManager.Get(), nil, protocol.PerspectiveServer, conn.version)
|
||||
tracer.EXPECT().DroppedPacket(gomock.Any(), protocol.InvalidPacketNumber, gomock.Any(), gomock.Any())
|
||||
Expect(conn.handlePacketImpl(wrapPacket(initialPacket))).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
)
|
||||
|
||||
|
@ -56,7 +55,7 @@ func (s *cryptoStreamImpl) HandleCryptoFrame(f *wire.CryptoFrame) error {
|
|||
// could e.g. be a retransmission
|
||||
return nil
|
||||
}
|
||||
s.highestOffset = utils.Max(s.highestOffset, highestOffset)
|
||||
s.highestOffset = max(s.highestOffset, highestOffset)
|
||||
if err := s.queue.Push(f.Data, f.Offset, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -99,7 +98,7 @@ func (s *cryptoStreamImpl) HasData() bool {
|
|||
|
||||
func (s *cryptoStreamImpl) PopCryptoFrame(maxLen protocol.ByteCount) *wire.CryptoFrame {
|
||||
f := &wire.CryptoFrame{Offset: s.writeOffset}
|
||||
n := utils.Min(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
|
||||
n := min(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
|
||||
f.Data = s.writeBuf[:n]
|
||||
s.writeBuf = s.writeBuf[n:]
|
||||
s.writeOffset += n
|
||||
|
|
|
@ -4,14 +4,20 @@ import (
|
|||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/internal/utils/ringbuffer"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
maxDatagramSendQueueLen = 32
|
||||
maxDatagramRcvQueueLen = 128
|
||||
)
|
||||
|
||||
type datagramQueue struct {
|
||||
sendQueue chan *wire.DatagramFrame
|
||||
nextFrame *wire.DatagramFrame
|
||||
sendMx sync.Mutex
|
||||
sendQueue ringbuffer.RingBuffer[*wire.DatagramFrame]
|
||||
sent chan struct{} // used to notify Add that a datagram was dequeued
|
||||
|
||||
rcvMx sync.Mutex
|
||||
rcvQueue [][]byte
|
||||
|
@ -22,60 +28,65 @@ type datagramQueue struct {
|
|||
|
||||
hasData func()
|
||||
|
||||
dequeued chan struct{}
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
func newDatagramQueue(hasData func(), logger utils.Logger) *datagramQueue {
|
||||
return &datagramQueue{
|
||||
hasData: hasData,
|
||||
sendQueue: make(chan *wire.DatagramFrame, 1),
|
||||
rcvd: make(chan struct{}, 1),
|
||||
dequeued: make(chan struct{}),
|
||||
closed: make(chan struct{}),
|
||||
logger: logger,
|
||||
hasData: hasData,
|
||||
rcvd: make(chan struct{}, 1),
|
||||
sent: make(chan struct{}, 1),
|
||||
closed: make(chan struct{}),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// AddAndWait queues a new DATAGRAM frame for sending.
|
||||
// It blocks until the frame has been dequeued.
|
||||
func (h *datagramQueue) AddAndWait(f *wire.DatagramFrame) error {
|
||||
select {
|
||||
case h.sendQueue <- f:
|
||||
h.hasData()
|
||||
case <-h.closed:
|
||||
return h.closeErr
|
||||
}
|
||||
// Add queues a new DATAGRAM frame for sending.
|
||||
// Up to 32 DATAGRAM frames will be queued.
|
||||
// Once that limit is reached, Add blocks until the queue size has reduced.
|
||||
func (h *datagramQueue) Add(f *wire.DatagramFrame) error {
|
||||
h.sendMx.Lock()
|
||||
|
||||
select {
|
||||
case <-h.dequeued:
|
||||
return nil
|
||||
case <-h.closed:
|
||||
return h.closeErr
|
||||
for {
|
||||
if h.sendQueue.Len() < maxDatagramSendQueueLen {
|
||||
h.sendQueue.PushBack(f)
|
||||
h.sendMx.Unlock()
|
||||
h.hasData()
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-h.sent: // drain the queue so we don't loop immediately
|
||||
default:
|
||||
}
|
||||
h.sendMx.Unlock()
|
||||
select {
|
||||
case <-h.closed:
|
||||
return h.closeErr
|
||||
case <-h.sent:
|
||||
}
|
||||
h.sendMx.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Peek gets the next DATAGRAM frame for sending.
|
||||
// If actually sent out, Pop needs to be called before the next call to Peek.
|
||||
func (h *datagramQueue) Peek() *wire.DatagramFrame {
|
||||
if h.nextFrame != nil {
|
||||
return h.nextFrame
|
||||
}
|
||||
select {
|
||||
case h.nextFrame = <-h.sendQueue:
|
||||
h.dequeued <- struct{}{}
|
||||
default:
|
||||
h.sendMx.Lock()
|
||||
defer h.sendMx.Unlock()
|
||||
if h.sendQueue.Empty() {
|
||||
return nil
|
||||
}
|
||||
return h.nextFrame
|
||||
return h.sendQueue.PeekFront()
|
||||
}
|
||||
|
||||
func (h *datagramQueue) Pop() {
|
||||
if h.nextFrame == nil {
|
||||
panic("datagramQueue BUG: Pop called for nil frame")
|
||||
h.sendMx.Lock()
|
||||
defer h.sendMx.Unlock()
|
||||
_ = h.sendQueue.PopFront()
|
||||
select {
|
||||
case h.sent <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
h.nextFrame = nil
|
||||
}
|
||||
|
||||
// HandleDatagramFrame handles a received DATAGRAM frame.
|
||||
|
@ -84,7 +95,7 @@ func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
|
|||
copy(data, f.Data)
|
||||
var queued bool
|
||||
h.rcvMx.Lock()
|
||||
if len(h.rcvQueue) < protocol.DatagramRcvQueueLen {
|
||||
if len(h.rcvQueue) < maxDatagramRcvQueueLen {
|
||||
h.rcvQueue = append(h.rcvQueue, data)
|
||||
queued = true
|
||||
select {
|
||||
|
@ -94,7 +105,7 @@ func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
|
|||
}
|
||||
h.rcvMx.Unlock()
|
||||
if !queued && h.logger.Debug() {
|
||||
h.logger.Debugf("Discarding DATAGRAM frame (%d bytes payload)", len(f.Data))
|
||||
h.logger.Debugf("Discarding received DATAGRAM frame (%d bytes payload)", len(f.Data))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package quic
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
|
@ -26,55 +27,65 @@ var _ = Describe("Datagram Queue", func() {
|
|||
})
|
||||
|
||||
It("queues a datagram", func() {
|
||||
done := make(chan struct{})
|
||||
frame := &wire.DatagramFrame{Data: []byte("foobar")}
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(done)
|
||||
Expect(queue.AddAndWait(frame)).To(Succeed())
|
||||
}()
|
||||
|
||||
Eventually(queued).Should(HaveLen(1))
|
||||
Consistently(done).ShouldNot(BeClosed())
|
||||
Expect(queue.Add(frame)).To(Succeed())
|
||||
Expect(queued).To(HaveLen(1))
|
||||
f := queue.Peek()
|
||||
Expect(f.Data).To(Equal([]byte("foobar")))
|
||||
Eventually(done).Should(BeClosed())
|
||||
queue.Pop()
|
||||
Expect(queue.Peek()).To(BeNil())
|
||||
})
|
||||
|
||||
It("returns the same datagram multiple times, when Pop isn't called", func() {
|
||||
sent := make(chan struct{}, 1)
|
||||
It("blocks when the maximum number of datagrams have been queued", func() {
|
||||
for i := 0; i < maxDatagramSendQueueLen; i++ {
|
||||
Expect(queue.Add(&wire.DatagramFrame{Data: []byte{0}})).To(Succeed())
|
||||
}
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
Expect(queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
|
||||
sent <- struct{}{}
|
||||
Expect(queue.AddAndWait(&wire.DatagramFrame{Data: []byte("bar")})).To(Succeed())
|
||||
sent <- struct{}{}
|
||||
errChan <- queue.Add(&wire.DatagramFrame{Data: []byte("foobar")})
|
||||
}()
|
||||
Consistently(errChan, 50*time.Millisecond).ShouldNot(Receive())
|
||||
Expect(queue.Peek()).ToNot(BeNil())
|
||||
Consistently(errChan, 50*time.Millisecond).ShouldNot(Receive())
|
||||
queue.Pop()
|
||||
Eventually(errChan).Should(Receive(BeNil()))
|
||||
for i := 1; i < maxDatagramSendQueueLen; i++ {
|
||||
queue.Pop()
|
||||
}
|
||||
f := queue.Peek()
|
||||
Expect(f).ToNot(BeNil())
|
||||
Expect(f.Data).To(Equal([]byte("foobar")))
|
||||
})
|
||||
|
||||
Eventually(queued).Should(HaveLen(1))
|
||||
It("returns the same datagram multiple times, when Pop isn't called", func() {
|
||||
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
|
||||
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("bar")})).To(Succeed())
|
||||
|
||||
Eventually(queued).Should(HaveLen(2))
|
||||
f := queue.Peek()
|
||||
Expect(f.Data).To(Equal([]byte("foo")))
|
||||
Eventually(sent).Should(Receive())
|
||||
Expect(queue.Peek()).To(Equal(f))
|
||||
Expect(queue.Peek()).To(Equal(f))
|
||||
queue.Pop()
|
||||
Eventually(func() *wire.DatagramFrame { f = queue.Peek(); return f }).ShouldNot(BeNil())
|
||||
f = queue.Peek()
|
||||
Expect(f).ToNot(BeNil())
|
||||
Expect(f.Data).To(Equal([]byte("bar")))
|
||||
})
|
||||
|
||||
It("closes", func() {
|
||||
for i := 0; i < maxDatagramSendQueueLen; i++ {
|
||||
Expect(queue.Add(&wire.DatagramFrame{Data: []byte("foo")})).To(Succeed())
|
||||
}
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
errChan <- queue.AddAndWait(&wire.DatagramFrame{Data: []byte("foobar")})
|
||||
errChan <- queue.Add(&wire.DatagramFrame{Data: []byte("foo")})
|
||||
}()
|
||||
|
||||
Consistently(errChan).ShouldNot(Receive())
|
||||
queue.CloseWithError(errors.New("test error"))
|
||||
Eventually(errChan).Should(Receive(MatchError("test error")))
|
||||
Consistently(errChan, 25*time.Millisecond).ShouldNot(Receive())
|
||||
testErr := errors.New("test error")
|
||||
queue.CloseWithError(testErr)
|
||||
Eventually(errChan).Should(Receive(MatchError(testErr)))
|
||||
})
|
||||
})
|
||||
|
||||
|
|
12
errors.go
12
errors.go
|
@ -61,3 +61,15 @@ func (e *StreamError) Error() string {
|
|||
}
|
||||
return fmt.Sprintf("stream %d canceled by %s with error code %d", e.StreamID, pers, e.ErrorCode)
|
||||
}
|
||||
|
||||
// DatagramTooLargeError is returned from Connection.SendDatagram if the payload is too large to be sent.
|
||||
type DatagramTooLargeError struct {
|
||||
PeerMaxDatagramFrameSize int64
|
||||
}
|
||||
|
||||
func (e *DatagramTooLargeError) Is(target error) bool {
|
||||
_, ok := target.(*DatagramTooLargeError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (e *DatagramTooLargeError) Error() string { return "DATAGRAM frame too large" }
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -18,29 +15,16 @@ import (
|
|||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
"github.com/refraction-networking/uquic/internal/testdata"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/logging"
|
||||
"github.com/refraction-networking/uquic/qlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
verbose := flag.Bool("v", false, "verbose")
|
||||
quiet := flag.Bool("q", false, "don't print the data")
|
||||
keyLogFile := flag.String("keylog", "", "key log file")
|
||||
insecure := flag.Bool("insecure", false, "skip certificate verification")
|
||||
enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)")
|
||||
flag.Parse()
|
||||
urls := flag.Args()
|
||||
|
||||
logger := utils.DefaultLogger
|
||||
|
||||
if *verbose {
|
||||
logger.SetLogLevel(utils.LogLevelDebug)
|
||||
} else {
|
||||
logger.SetLogLevel(utils.LogLevelInfo)
|
||||
}
|
||||
logger.SetLogTimeFormat("")
|
||||
|
||||
var keyLog io.Writer
|
||||
if len(*keyLogFile) > 0 {
|
||||
f, err := os.Create(*keyLogFile)
|
||||
|
@ -57,25 +41,15 @@ func main() {
|
|||
}
|
||||
testdata.AddRootCA(pool)
|
||||
|
||||
var qconf quic.Config
|
||||
if *enableQlog {
|
||||
qconf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
filename := fmt.Sprintf("client_%x.qlog", connID)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Creating qlog file %s.\n", filename)
|
||||
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
|
||||
}
|
||||
}
|
||||
roundTripper := &http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: pool,
|
||||
InsecureSkipVerify: *insecure,
|
||||
KeyLogWriter: keyLog,
|
||||
},
|
||||
QuicConfig: &qconf,
|
||||
QuicConfig: &quic.Config{
|
||||
Tracer: qlog.DefaultTracer,
|
||||
},
|
||||
}
|
||||
defer roundTripper.Close()
|
||||
hclient := &http.Client{
|
||||
|
@ -85,13 +59,13 @@ func main() {
|
|||
var wg sync.WaitGroup
|
||||
wg.Add(len(urls))
|
||||
for _, addr := range urls {
|
||||
logger.Infof("GET %s", addr)
|
||||
log.Printf("GET %s", addr)
|
||||
go func(addr string) {
|
||||
rsp, err := hclient.Get(addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
logger.Infof("Got response for %s: %#v", addr, rsp)
|
||||
log.Printf("Got response for %s: %#v", addr, rsp)
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
_, err = io.Copy(body, rsp.Body)
|
||||
|
@ -99,10 +73,9 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
if *quiet {
|
||||
logger.Infof("Response Body: %d bytes", body.Len())
|
||||
log.Printf("Response Body: %d bytes", body.Len())
|
||||
} else {
|
||||
logger.Infof("Response Body:")
|
||||
logger.Infof("%s", body.Bytes())
|
||||
log.Printf("Response Body (%d bytes):\n%s", body.Len(), body.Bytes())
|
||||
}
|
||||
wg.Done()
|
||||
}(addr)
|
||||
|
|
|
@ -37,14 +37,19 @@ func echoServer() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
conn, err := listener.Accept(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stream, err := conn.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// Echo through the loggingWriter
|
||||
_, err = io.Copy(loggingWriter{stream}, stream)
|
||||
return err
|
||||
|
@ -59,11 +64,13 @@ func clientMain() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.CloseWithError(0, "")
|
||||
|
||||
stream, err := conn.OpenStreamSync(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
fmt.Printf("Client: Sending '%s'\n", message)
|
||||
_, err = stream.Write([]byte(message))
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"flag"
|
||||
|
@ -11,7 +9,6 @@ import (
|
|||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -21,8 +18,6 @@ import (
|
|||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
"github.com/refraction-networking/uquic/internal/testdata"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/logging"
|
||||
"github.com/refraction-networking/uquic/qlog"
|
||||
)
|
||||
|
||||
|
@ -121,7 +116,7 @@ func setupHandler(www string) http.Handler {
|
|||
err = errors.New("couldn't get uploaded file size")
|
||||
}
|
||||
}
|
||||
utils.DefaultLogger.Infof("Error receiving upload: %#v", err)
|
||||
log.Printf("Error receiving upload: %#v", err)
|
||||
}
|
||||
io.WriteString(w, `<html><body><form action="/demo/upload" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="uploadfile"><br>
|
||||
|
@ -139,57 +134,45 @@ func main() {
|
|||
}()
|
||||
// runtime.SetBlockProfileRate(1)
|
||||
|
||||
verbose := flag.Bool("v", false, "verbose")
|
||||
bs := binds{}
|
||||
flag.Var(&bs, "bind", "bind to")
|
||||
www := flag.String("www", "", "www data")
|
||||
tcp := flag.Bool("tcp", false, "also listen on TCP")
|
||||
enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)")
|
||||
key := flag.String("key", "", "TLS key (requires -cert option)")
|
||||
cert := flag.String("cert", "", "TLS certificate (requires -key option)")
|
||||
flag.Parse()
|
||||
|
||||
logger := utils.DefaultLogger
|
||||
|
||||
if *verbose {
|
||||
logger.SetLogLevel(utils.LogLevelDebug)
|
||||
} else {
|
||||
logger.SetLogLevel(utils.LogLevelInfo)
|
||||
}
|
||||
logger.SetLogTimeFormat("")
|
||||
|
||||
if len(bs) == 0 {
|
||||
bs = binds{"localhost:6121"}
|
||||
}
|
||||
|
||||
handler := setupHandler(*www)
|
||||
quicConf := &quic.Config{}
|
||||
if *enableQlog {
|
||||
quicConf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
filename := fmt.Sprintf("server_%x.qlog", connID)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Creating qlog file %s.\n", filename)
|
||||
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(bs))
|
||||
var certFile, keyFile string
|
||||
if *key != "" && *cert != "" {
|
||||
keyFile = *key
|
||||
certFile = *cert
|
||||
} else {
|
||||
certFile, keyFile = testdata.GetCertificatePaths()
|
||||
}
|
||||
for _, b := range bs {
|
||||
fmt.Println("listening on", b)
|
||||
bCap := b
|
||||
go func() {
|
||||
var err error
|
||||
if *tcp {
|
||||
certFile, keyFile := testdata.GetCertificatePaths()
|
||||
err = http3.ListenAndServe(bCap, certFile, keyFile, handler)
|
||||
} else {
|
||||
server := http3.Server{
|
||||
Handler: handler,
|
||||
Addr: bCap,
|
||||
QuicConfig: quicConf,
|
||||
Handler: handler,
|
||||
Addr: bCap,
|
||||
QuicConfig: &quic.Config{
|
||||
Tracer: qlog.DefaultTracer,
|
||||
},
|
||||
}
|
||||
err = server.ListenAndServeTLS(testdata.GetCertificatePaths())
|
||||
err = server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
|
69
framer.go
69
framer.go
|
@ -15,14 +15,26 @@ type framer interface {
|
|||
HasData() bool
|
||||
|
||||
QueueControlFrame(wire.Frame)
|
||||
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount)
|
||||
AppendControlFrames([]ackhandler.Frame, protocol.ByteCount, protocol.Version) ([]ackhandler.Frame, protocol.ByteCount)
|
||||
|
||||
AddActiveStream(protocol.StreamID)
|
||||
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount)
|
||||
AppendStreamFrames([]ackhandler.StreamFrame, protocol.ByteCount, protocol.Version) ([]ackhandler.StreamFrame, protocol.ByteCount)
|
||||
|
||||
Handle0RTTRejection() error
|
||||
|
||||
// QueuedTooManyControlFrames says if the control frame queue exceeded its maximum queue length.
|
||||
// This is a hack.
|
||||
// It is easier to implement than propagating an error return value in QueueControlFrame.
|
||||
// The correct solution would be to queue frames with their respective structs.
|
||||
// See https://github.com/quic-go/quic-go/issues/4271 for the queueing of stream-related control frames.
|
||||
QueuedTooManyControlFrames() bool
|
||||
}
|
||||
|
||||
const (
|
||||
maxPathResponses = 256
|
||||
maxControlFrames = 16 << 10
|
||||
)
|
||||
|
||||
type framerI struct {
|
||||
mutex sync.Mutex
|
||||
|
||||
|
@ -31,8 +43,10 @@ type framerI struct {
|
|||
activeStreams map[protocol.StreamID]struct{}
|
||||
streamQueue ringbuffer.RingBuffer[protocol.StreamID]
|
||||
|
||||
controlFrameMutex sync.Mutex
|
||||
controlFrames []wire.Frame
|
||||
controlFrameMutex sync.Mutex
|
||||
controlFrames []wire.Frame
|
||||
pathResponses []*wire.PathResponseFrame
|
||||
queuedTooManyControlFrames bool
|
||||
}
|
||||
|
||||
var _ framer = &framerI{}
|
||||
|
@ -52,20 +66,48 @@ func (f *framerI) HasData() bool {
|
|||
return true
|
||||
}
|
||||
f.controlFrameMutex.Lock()
|
||||
hasData = len(f.controlFrames) > 0
|
||||
f.controlFrameMutex.Unlock()
|
||||
return hasData
|
||||
defer f.controlFrameMutex.Unlock()
|
||||
return len(f.controlFrames) > 0 || len(f.pathResponses) > 0
|
||||
}
|
||||
|
||||
func (f *framerI) QueueControlFrame(frame wire.Frame) {
|
||||
f.controlFrameMutex.Lock()
|
||||
defer f.controlFrameMutex.Unlock()
|
||||
|
||||
if pr, ok := frame.(*wire.PathResponseFrame); ok {
|
||||
// Only queue up to maxPathResponses PATH_RESPONSE frames.
|
||||
// This limit should be high enough to never be hit in practice,
|
||||
// unless the peer is doing something malicious.
|
||||
if len(f.pathResponses) >= maxPathResponses {
|
||||
return
|
||||
}
|
||||
f.pathResponses = append(f.pathResponses, pr)
|
||||
return
|
||||
}
|
||||
// This is a hack.
|
||||
if len(f.controlFrames) >= maxControlFrames {
|
||||
f.queuedTooManyControlFrames = true
|
||||
return
|
||||
}
|
||||
f.controlFrames = append(f.controlFrames, frame)
|
||||
f.controlFrameMutex.Unlock()
|
||||
}
|
||||
|
||||
func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.Frame, protocol.ByteCount) {
|
||||
var length protocol.ByteCount
|
||||
func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol.ByteCount, v protocol.Version) ([]ackhandler.Frame, protocol.ByteCount) {
|
||||
f.controlFrameMutex.Lock()
|
||||
defer f.controlFrameMutex.Unlock()
|
||||
|
||||
var length protocol.ByteCount
|
||||
// add a PATH_RESPONSE first, but only pack a single PATH_RESPONSE per packet
|
||||
if len(f.pathResponses) > 0 {
|
||||
frame := f.pathResponses[0]
|
||||
frameLen := frame.Length(v)
|
||||
if frameLen <= maxLen {
|
||||
frames = append(frames, ackhandler.Frame{Frame: frame})
|
||||
length += frameLen
|
||||
f.pathResponses = f.pathResponses[1:]
|
||||
}
|
||||
}
|
||||
|
||||
for len(f.controlFrames) > 0 {
|
||||
frame := f.controlFrames[len(f.controlFrames)-1]
|
||||
frameLen := frame.Length(v)
|
||||
|
@ -76,10 +118,13 @@ func (f *framerI) AppendControlFrames(frames []ackhandler.Frame, maxLen protocol
|
|||
length += frameLen
|
||||
f.controlFrames = f.controlFrames[:len(f.controlFrames)-1]
|
||||
}
|
||||
f.controlFrameMutex.Unlock()
|
||||
return frames, length
|
||||
}
|
||||
|
||||
func (f *framerI) QueuedTooManyControlFrames() bool {
|
||||
return f.queuedTooManyControlFrames
|
||||
}
|
||||
|
||||
func (f *framerI) AddActiveStream(id protocol.StreamID) {
|
||||
f.mutex.Lock()
|
||||
if _, ok := f.activeStreams[id]; !ok {
|
||||
|
@ -89,7 +134,7 @@ func (f *framerI) AddActiveStream(id protocol.StreamID) {
|
|||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen protocol.ByteCount, v protocol.VersionNumber) ([]ackhandler.StreamFrame, protocol.ByteCount) {
|
||||
func (f *framerI) AppendStreamFrames(frames []ackhandler.StreamFrame, maxLen protocol.ByteCount, v protocol.Version) ([]ackhandler.StreamFrame, protocol.ByteCount) {
|
||||
startLen := len(frames)
|
||||
var length protocol.ByteCount
|
||||
f.mutex.Lock()
|
||||
|
|
|
@ -2,7 +2,8 @@ package quic
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/ackhandler"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
|
@ -23,7 +24,7 @@ var _ = Describe("Framer", func() {
|
|||
framer framer
|
||||
stream1, stream2 *MockSendStreamI
|
||||
streamGetter *MockStreamGetter
|
||||
version protocol.VersionNumber
|
||||
version protocol.Version
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
|
@ -108,6 +109,72 @@ var _ = Describe("Framer", func() {
|
|||
Expect(fs).To(HaveLen(2))
|
||||
Expect(length).To(Equal(ping.Length(version) + ncid.Length(version)))
|
||||
})
|
||||
|
||||
It("detects when too many frames are queued", func() {
|
||||
for i := 0; i < maxControlFrames-1; i++ {
|
||||
framer.QueueControlFrame(&wire.PingFrame{})
|
||||
framer.QueueControlFrame(&wire.PingFrame{})
|
||||
Expect(framer.QueuedTooManyControlFrames()).To(BeFalse())
|
||||
frames, _ := framer.AppendControlFrames([]ackhandler.Frame{}, 1, protocol.Version1)
|
||||
Expect(frames).To(HaveLen(1))
|
||||
Expect(framer.(*framerI).controlFrames).To(HaveLen(i + 1))
|
||||
}
|
||||
framer.QueueControlFrame(&wire.PingFrame{})
|
||||
Expect(framer.QueuedTooManyControlFrames()).To(BeFalse())
|
||||
Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames))
|
||||
framer.QueueControlFrame(&wire.PingFrame{})
|
||||
Expect(framer.QueuedTooManyControlFrames()).To(BeTrue())
|
||||
Expect(framer.(*framerI).controlFrames).To(HaveLen(maxControlFrames))
|
||||
})
|
||||
})
|
||||
|
||||
Context("handling PATH_RESPONSE frames", func() {
|
||||
It("packs a single PATH_RESPONSE per packet", func() {
|
||||
f1 := &wire.PathResponseFrame{Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}}
|
||||
f2 := &wire.PathResponseFrame{Data: [8]byte{2, 3, 4, 5, 6, 7, 8, 9}}
|
||||
cf1 := &wire.DataBlockedFrame{MaximumData: 1337}
|
||||
cf2 := &wire.HandshakeDoneFrame{}
|
||||
framer.QueueControlFrame(f1)
|
||||
framer.QueueControlFrame(f2)
|
||||
framer.QueueControlFrame(cf1)
|
||||
framer.QueueControlFrame(cf2)
|
||||
// the first packet should contain a single PATH_RESPONSE frame, but all the other control frames
|
||||
Expect(framer.HasData()).To(BeTrue())
|
||||
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
|
||||
Expect(frames).To(HaveLen(3))
|
||||
Expect(frames[0].Frame).To(Equal(f1))
|
||||
Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf1))
|
||||
Expect([]wire.Frame{frames[1].Frame, frames[2].Frame}).To(ContainElement(cf2))
|
||||
Expect(length).To(Equal(f1.Length(protocol.Version1) + cf1.Length(protocol.Version1) + cf2.Length(protocol.Version1)))
|
||||
// the second packet should contain the other PATH_RESPONSE frame
|
||||
Expect(framer.HasData()).To(BeTrue())
|
||||
frames, length = framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
|
||||
Expect(frames).To(HaveLen(1))
|
||||
Expect(frames[0].Frame).To(Equal(f2))
|
||||
Expect(length).To(Equal(f2.Length(protocol.Version1)))
|
||||
Expect(framer.HasData()).To(BeFalse())
|
||||
})
|
||||
|
||||
It("limits the number of queued PATH_RESPONSE frames", func() {
|
||||
var pathResponses []*wire.PathResponseFrame
|
||||
for i := 0; i < 2*maxPathResponses; i++ {
|
||||
var f wire.PathResponseFrame
|
||||
rand.Read(f.Data[:])
|
||||
pathResponses = append(pathResponses, &f)
|
||||
framer.QueueControlFrame(&f)
|
||||
}
|
||||
for i := 0; i < maxPathResponses; i++ {
|
||||
Expect(framer.HasData()).To(BeTrue())
|
||||
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
|
||||
Expect(frames).To(HaveLen(1))
|
||||
Expect(frames[0].Frame).To(Equal(pathResponses[i]))
|
||||
Expect(length).To(Equal(pathResponses[i].Length(protocol.Version1)))
|
||||
}
|
||||
Expect(framer.HasData()).To(BeFalse())
|
||||
frames, length := framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
|
||||
Expect(frames).To(BeEmpty())
|
||||
Expect(length).To(BeZero())
|
||||
})
|
||||
})
|
||||
|
||||
Context("popping STREAM frames", func() {
|
||||
|
@ -296,7 +363,7 @@ var _ = Describe("Framer", func() {
|
|||
It("pops maximum size STREAM frames", func() {
|
||||
for i := protocol.MinStreamFrameSize; i < 2000; i++ {
|
||||
streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil)
|
||||
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
|
||||
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
|
||||
f := &wire.StreamFrame{
|
||||
StreamID: id1,
|
||||
DataLenPresent: true,
|
||||
|
@ -318,7 +385,7 @@ var _ = Describe("Framer", func() {
|
|||
for i := 2 * protocol.MinStreamFrameSize; i < 2000; i++ {
|
||||
streamGetter.EXPECT().GetOrOpenSendStream(id1).Return(stream1, nil)
|
||||
streamGetter.EXPECT().GetOrOpenSendStream(id2).Return(stream2, nil)
|
||||
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
|
||||
stream1.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
|
||||
f := &wire.StreamFrame{
|
||||
StreamID: id2,
|
||||
DataLenPresent: true,
|
||||
|
@ -326,7 +393,7 @@ var _ = Describe("Framer", func() {
|
|||
f.Data = make([]byte, f.MaxDataLen(protocol.MinStreamFrameSize, v))
|
||||
return ackhandler.StreamFrame{Frame: f}, true, false
|
||||
})
|
||||
stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.VersionNumber) (ackhandler.StreamFrame, bool, bool) {
|
||||
stream2.EXPECT().popStreamFrame(gomock.Any(), protocol.Version1).DoAndReturn(func(size protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, bool, bool) {
|
||||
f := &wire.StreamFrame{
|
||||
StreamID: id2,
|
||||
DataLenPresent: true,
|
||||
|
|
|
@ -56,22 +56,23 @@ func Fuzz(data []byte) int {
|
|||
continue
|
||||
}
|
||||
}
|
||||
validateFrame(f)
|
||||
|
||||
startLen := len(b)
|
||||
parsedLen := initialLen - len(data)
|
||||
b, err = f.Append(b, version)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error writing frame %#v: %s", f, err))
|
||||
panic(fmt.Sprintf("error writing frame %#v: %s", f, err))
|
||||
}
|
||||
frameLen := protocol.ByteCount(len(b) - startLen)
|
||||
if f.Length(version) != frameLen {
|
||||
panic(fmt.Sprintf("Inconsistent frame length for %#v: expected %d, got %d", f, frameLen, f.Length(version)))
|
||||
panic(fmt.Sprintf("inconsistent frame length for %#v: expected %d, got %d", f, frameLen, f.Length(version)))
|
||||
}
|
||||
if sf, ok := f.(*wire.StreamFrame); ok {
|
||||
sf.PutBack()
|
||||
}
|
||||
if frameLen > protocol.ByteCount(parsedLen) {
|
||||
panic(fmt.Sprintf("Serialized length (%d) is longer than parsed length (%d)", len(b), parsedLen))
|
||||
panic(fmt.Sprintf("serialized length (%d) is longer than parsed length (%d)", len(b), parsedLen))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,3 +81,52 @@ func Fuzz(data []byte) int {
|
|||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func validateFrame(frame wire.Frame) {
|
||||
switch f := frame.(type) {
|
||||
case *wire.StreamFrame:
|
||||
if protocol.ByteCount(len(f.Data)) != f.DataLen() {
|
||||
panic("STREAM frame: inconsistent data length")
|
||||
}
|
||||
case *wire.AckFrame:
|
||||
if f.DelayTime < 0 {
|
||||
panic(fmt.Sprintf("invalid ACK delay_time: %s", f.DelayTime))
|
||||
}
|
||||
if f.LargestAcked() < f.LowestAcked() {
|
||||
panic("ACK: largest acknowledged is smaller than lowest acknowledged")
|
||||
}
|
||||
for _, r := range f.AckRanges {
|
||||
if r.Largest < 0 || r.Smallest < 0 {
|
||||
panic("ACK range contains a negative packet number")
|
||||
}
|
||||
}
|
||||
if !f.AcksPacket(f.LargestAcked()) {
|
||||
panic("ACK frame claims that largest acknowledged is not acknowledged")
|
||||
}
|
||||
if !f.AcksPacket(f.LowestAcked()) {
|
||||
panic("ACK frame claims that lowest acknowledged is not acknowledged")
|
||||
}
|
||||
_ = f.AcksPacket(100)
|
||||
_ = f.AcksPacket((f.LargestAcked() + f.LowestAcked()) / 2)
|
||||
case *wire.NewConnectionIDFrame:
|
||||
if f.ConnectionID.Len() < 1 || f.ConnectionID.Len() > 20 {
|
||||
panic(fmt.Sprintf("invalid NEW_CONNECTION_ID frame length: %s", f.ConnectionID))
|
||||
}
|
||||
case *wire.NewTokenFrame:
|
||||
if len(f.Token) == 0 {
|
||||
panic("NEW_TOKEN frame with an empty token")
|
||||
}
|
||||
case *wire.MaxStreamsFrame:
|
||||
if f.MaxStreamNum > protocol.MaxStreamCount {
|
||||
panic("MAX_STREAMS frame with an invalid Maximum Streams value")
|
||||
}
|
||||
case *wire.StreamsBlockedFrame:
|
||||
if f.StreamLimit > protocol.MaxStreamCount {
|
||||
panic("STREAMS_BLOCKED frame with an invalid Maximum Streams value")
|
||||
}
|
||||
case *wire.ConnectionCloseFrame:
|
||||
if f.IsApplicationError && f.FrameType != 0 {
|
||||
panic("CONNECTION_CLOSE for an application error containing a frame type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ func getRandomData(l int) []byte {
|
|||
}
|
||||
|
||||
func getVNP(src, dest protocol.ArbitraryLenConnectionID, numVersions int) []byte {
|
||||
versions := make([]protocol.VersionNumber, numVersions)
|
||||
versions := make([]protocol.Version, numVersions)
|
||||
for i := 0; i < numVersions; i++ {
|
||||
versions[i] = protocol.VersionNumber(rand.Uint32())
|
||||
versions[i] = protocol.Version(rand.Uint32())
|
||||
}
|
||||
return wire.ComposeVersionNegotiation(src, dest, versions)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
|
@ -59,11 +59,13 @@ func main() {
|
|||
if rand.Int()%2 == 0 {
|
||||
var token protocol.StatelessResetToken
|
||||
rand.Read(token[:])
|
||||
var ip4 [4]byte
|
||||
rand.Read(ip4[:])
|
||||
var ip6 [16]byte
|
||||
rand.Read(ip6[:])
|
||||
tp.PreferredAddress = &wire.PreferredAddress{
|
||||
IPv4: net.IPv4(uint8(rand.Int()), uint8(rand.Int()), uint8(rand.Int()), uint8(rand.Int())),
|
||||
IPv4Port: uint16(rand.Int()),
|
||||
IPv6: net.IP(getRandomData(16)),
|
||||
IPv6Port: uint16(rand.Int()),
|
||||
IPv4: netip.AddrPortFrom(netip.AddrFrom4(ip4), uint16(rand.Int())),
|
||||
IPv6: netip.AddrPortFrom(netip.AddrFrom16(ip6), uint16(rand.Int())),
|
||||
ConnectionID: protocol.ParseConnectionID(getRandomData(rand.Intn(21))),
|
||||
StatelessResetToken: token,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package transportparameters
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/refraction-networking/uquic/fuzzing/internal/helper"
|
||||
|
@ -26,23 +27,29 @@ func Fuzz(data []byte) int {
|
|||
return fuzzTransportParameters(data[PrefixLen:], helper.NthBit(data[0], 1))
|
||||
}
|
||||
|
||||
func fuzzTransportParameters(data []byte, isServer bool) int {
|
||||
perspective := protocol.PerspectiveClient
|
||||
if isServer {
|
||||
perspective = protocol.PerspectiveServer
|
||||
func fuzzTransportParameters(data []byte, sentByServer bool) int {
|
||||
sentBy := protocol.PerspectiveClient
|
||||
if sentByServer {
|
||||
sentBy = protocol.PerspectiveServer
|
||||
}
|
||||
|
||||
tp := &wire.TransportParameters{}
|
||||
if err := tp.Unmarshal(data, perspective); err != nil {
|
||||
if err := tp.Unmarshal(data, sentBy); err != nil {
|
||||
return 0
|
||||
}
|
||||
_ = tp.String()
|
||||
if err := validateTransportParameters(tp, sentBy); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tp2 := &wire.TransportParameters{}
|
||||
if err := tp2.Unmarshal(tp.Marshal(perspective), perspective); err != nil {
|
||||
if err := tp2.Unmarshal(tp.Marshal(sentBy), sentBy); err != nil {
|
||||
fmt.Printf("%#v\n", tp)
|
||||
panic(err)
|
||||
}
|
||||
if err := validateTransportParameters(tp2, sentBy); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
|
@ -58,3 +65,34 @@ func fuzzTransportParametersForSessionTicket(data []byte) int {
|
|||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func validateTransportParameters(tp *wire.TransportParameters, sentBy protocol.Perspective) error {
|
||||
if sentBy == protocol.PerspectiveClient && tp.StatelessResetToken != nil {
|
||||
return errors.New("client's transport parameters contained stateless reset token")
|
||||
}
|
||||
if tp.MaxIdleTimeout < 0 {
|
||||
return fmt.Errorf("negative max_idle_timeout: %s", tp.MaxIdleTimeout)
|
||||
}
|
||||
if tp.AckDelayExponent > 20 {
|
||||
return fmt.Errorf("invalid ack_delay_exponent: %d", tp.AckDelayExponent)
|
||||
}
|
||||
if tp.MaxUDPPayloadSize < 1200 {
|
||||
return fmt.Errorf("invalid max_udp_payload_size: %d", tp.MaxUDPPayloadSize)
|
||||
}
|
||||
if tp.ActiveConnectionIDLimit < 2 {
|
||||
return fmt.Errorf("invalid active_connection_id_limit: %d", tp.ActiveConnectionIDLimit)
|
||||
}
|
||||
if tp.OriginalDestinationConnectionID.Len() > 20 {
|
||||
return fmt.Errorf("invalid original_destination_connection_id length: %s", tp.InitialSourceConnectionID)
|
||||
}
|
||||
if tp.InitialSourceConnectionID.Len() > 20 {
|
||||
return fmt.Errorf("invalid initial_source_connection_id length: %s", tp.InitialSourceConnectionID)
|
||||
}
|
||||
if tp.RetrySourceConnectionID != nil && tp.RetrySourceConnectionID.Len() > 20 {
|
||||
return fmt.Errorf("invalid retry_source_connection_id length: %s", tp.RetrySourceConnectionID)
|
||||
}
|
||||
if tp.PreferredAddress != nil && tp.PreferredAddress.ConnectionID.Len() > 20 {
|
||||
return fmt.Errorf("invalid preferred_address connection ID length: %s", tp.PreferredAddress.ConnectionID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
21
go.mod
21
go.mod
|
@ -8,13 +8,14 @@ require (
|
|||
github.com/onsi/ginkgo/v2 v2.13.0
|
||||
github.com/onsi/gomega v1.29.0
|
||||
github.com/quic-go/qpack v0.4.0
|
||||
github.com/refraction-networking/utls v1.5.4
|
||||
github.com/refraction-networking/utls v1.6.4
|
||||
go.uber.org/mock v0.4.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sync v0.4.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/time v0.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -26,10 +27,10 @@ require (
|
|||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/quic-go/quic-go v0.42.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
40
go.sum
40
go.sum
|
@ -67,8 +67,8 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
|
|||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
@ -97,10 +97,8 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
|
|||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
|
||||
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
|
||||
github.com/refraction-networking/utls v1.6.4 h1:aeynTroaYn7y+mFtqv8D0bQ4bw0y9nJHneGxJ7lvRDM=
|
||||
github.com/refraction-networking/utls v1.6.4/go.mod h1:2VL2xfiqgFAZtJKeUTlf+PSYFs3Eu7km0gCtXJ3m8zs=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
|
@ -143,18 +141,18 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -165,8 +163,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -177,29 +175,31 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
|
|
104
http3/README.md
Normal file
104
http3/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# HTTP/3
|
||||
|
||||
[](https://pkg.go.dev/github.com/quic-go/quic-go/http3)
|
||||
|
||||
This package implements HTTP/3 ([RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)), including QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)).
|
||||
It aims to provide feature parity with the standard library's HTTP/1.1 and HTTP/2 implementation.
|
||||
|
||||
## Serving HTTP/3
|
||||
|
||||
The easiest way to start an HTTP/3 server is using
|
||||
```go
|
||||
mux := http.NewServeMux()
|
||||
// ... add HTTP handlers to mux ...
|
||||
// If mux is nil, the http.DefaultServeMux is used.
|
||||
http3.ListenAndServeQUIC("0.0.0.0:443", "/path/to/cert", "/path/to/key", mux)
|
||||
```
|
||||
|
||||
`ListenAndServeQUIC` is a convenience function. For more configurability, set up an `http3.Server` explicitly:
|
||||
```go
|
||||
server := http3.Server{
|
||||
Handler: mux,
|
||||
Addr: "0.0.0.0:443",
|
||||
TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}), // use your tls.Config here
|
||||
QuicConfig: &quic.Config{},
|
||||
}
|
||||
err := server.ListenAndServe()
|
||||
```
|
||||
|
||||
The `http3.Server` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#Server) for a complete list. The `QuicConfig` is used to configure the underlying QUIC connection. More details can be found in the documentation of the QUIC package.
|
||||
|
||||
It is also possible to manually set up a `quic.Transport`, and then pass the listener to the server. This is useful when you want to set configuration options on the `quic.Transport`.
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
ln, _ := tr.ListenEarly(tlsConf, quicConf)
|
||||
server.ServeListener(ln)
|
||||
```
|
||||
|
||||
Alternatively, it is also possible to pass fully established QUIC connections to the HTTP/3 server. This is useful if the QUIC server offers multiple ALPNs (via `NextProtos` in the `tls.Config`).
|
||||
```go
|
||||
tr := quic.Transport{Conn: conn}
|
||||
tlsConf := http3.ConfigureTLSConfig(&tls.Config{}) // use your tls.Config here
|
||||
quicConf := &quic.Config{} // QUIC connection options
|
||||
server := http3.Server{}
|
||||
// alternatively, use tr.ListenEarly to accept 0-RTT connections
|
||||
ln, _ := tr.Listen(tlsConf, quicConf)
|
||||
for {
|
||||
c, _ := ln.Accept()
|
||||
switch c.ConnectionState().TLS.NegotiatedProtocol {
|
||||
case http3.NextProtoH3:
|
||||
go server.ServeQUICConn(c)
|
||||
// ... handle other protocols ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dialing HTTP/3
|
||||
|
||||
This package provides a `http.RoundTripper` implementation that can be used on the `http.Client`:
|
||||
|
||||
```go
|
||||
&http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
}
|
||||
defer roundTripper.Close()
|
||||
client := &http.Client{
|
||||
Transport: roundTripper,
|
||||
}
|
||||
```
|
||||
|
||||
The `http3.RoundTripper` provides a number of configuration options, please refer to the [documentation](https://pkg.go.dev/github.com/quic-go/quic-go/http3#RoundTripper) for a complete list.
|
||||
|
||||
To use a custom `quic.Transport`, the function used to dial new QUIC connections can be configured:
|
||||
```go
|
||||
tr := quic.Transport{}
|
||||
roundTripper := &http3.RoundTripper{
|
||||
TLSClientConfig: &tls.Config{}, // set a TLS client config, if desired
|
||||
QuicConfig: &quic.Config{}, // QUIC connection options
|
||||
Dial: func(ctx context.Context, addr string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
a, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr.DialEarly(ctx, a, tlsConf, quicConf)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Using the same UDP Socket for Server and Roundtripper
|
||||
|
||||
Since QUIC demultiplexes packets based on their connection IDs, it is possible allows running a QUIC server and client on the same UDP socket. This also works when using HTTP/3: HTTP requests can be sent from the same socket that a server is listening on.
|
||||
|
||||
To achieve this using this package, first initialize a single `quic.Transport`, and pass a `quic.EarlyListner` obtained from that transport to `http3.Server.ServeListener`, and use the `DialEarly` function of the transport as the `Dial` function for the `http3.RoundTripper`.
|
||||
|
||||
## QPACK
|
||||
|
||||
HTTP/3 utilizes QPACK ([RFC 9204](https://datatracker.ietf.org/doc/html/rfc9204)) for efficient HTTP header field compression. Our implementation, available at[quic-go/qpack](https://github.com/quic-go/qpack), provides a minimal implementation of the protocol.
|
||||
|
||||
While the current implementation is a fully interoperable implementation of the QPACK protocol, it only uses the static compression table. The dynamic table would allow for more effective compression of frequently transmitted header fields. This can be particularly beneficial in scenarios where headers have considerable redundancy or in high-throughput environments.
|
||||
|
||||
If you think that your application would benefit from higher compression efficiency, or if you're interested in contributing improvements here, please let us know in [#2424](https://github.com/quic-go/quic-go/issues/2424).
|
|
@ -61,6 +61,9 @@ type client struct {
|
|||
dialer dialFunc
|
||||
handshakeErr error
|
||||
|
||||
receivedSettings chan struct{} // closed once the server's SETTINGS frame was processed
|
||||
settings *Settings // set once receivedSettings is closed
|
||||
|
||||
requestWriter *requestWriter
|
||||
|
||||
decoder *qpack.Decoder
|
||||
|
@ -76,10 +79,14 @@ 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()
|
||||
conf.EnableDatagrams = opts.EnableDatagram
|
||||
}
|
||||
if opts.EnableDatagram && !conf.EnableDatagrams {
|
||||
return nil, errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
|
||||
}
|
||||
if len(conf.Versions) == 0 {
|
||||
conf = conf.Clone()
|
||||
conf.Versions = []quic.VersionNumber{protocol.SupportedVersions[0]}
|
||||
conf.Versions = []quic.Version{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")
|
||||
|
@ -87,7 +94,6 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
|
|||
if conf.MaxIncomingStreams == 0 {
|
||||
conf.MaxIncomingStreams = -1 // don't allow any bidirectional streams
|
||||
}
|
||||
conf.EnableDatagrams = opts.EnableDatagram
|
||||
logger := utils.DefaultLogger.WithPrefix("h3 client")
|
||||
|
||||
if tlsConf == nil {
|
||||
|
@ -107,14 +113,15 @@ func newClient(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, con
|
|||
tlsConf.NextProtos = []string{versionToALPN(conf.Versions[0])}
|
||||
|
||||
return &client{
|
||||
hostname: authorityAddr("https", hostname),
|
||||
tlsConf: tlsConf,
|
||||
requestWriter: newRequestWriter(logger),
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
config: conf,
|
||||
opts: opts,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
hostname: authorityAddr("https", hostname),
|
||||
tlsConf: tlsConf,
|
||||
requestWriter: newRequestWriter(logger),
|
||||
receivedSettings: make(chan struct{}),
|
||||
decoder: qpack.NewDecoder(func(hf qpack.HeaderField) {}),
|
||||
config: conf,
|
||||
opts: opts,
|
||||
dialer: dialer,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -183,6 +190,8 @@ func (c *client) handleBidirectionalStreams(conn quic.EarlyConnection) {
|
|||
}
|
||||
|
||||
func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
|
@ -217,6 +226,11 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
|||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
|
@ -227,6 +241,12 @@ func (c *client) handleUnidirectionalStreams(conn quic.EarlyConnection) {
|
|||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
|
||||
return
|
||||
}
|
||||
c.settings = &Settings{
|
||||
EnableDatagram: sf.Datagram,
|
||||
EnableExtendedConnect: sf.ExtendedConnect,
|
||||
Other: sf.Other,
|
||||
}
|
||||
close(c.receivedSettings)
|
||||
if !sf.Datagram {
|
||||
return
|
||||
}
|
||||
|
@ -257,6 +277,15 @@ func (c *client) maxHeaderBytes() uint64 {
|
|||
|
||||
// RoundTripOpt executes a request and returns a response
|
||||
func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
rsp, err := c.roundTripOpt(req, opt)
|
||||
if err != nil && req.Context().Err() != nil {
|
||||
// if the context was canceled, return the context cancellation error
|
||||
err = req.Context().Err()
|
||||
}
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||
if authorityAddr("https", hostnameFromRequest(req)) != c.hostname {
|
||||
return nil, fmt.Errorf("http3 client BUG: RoundTripOpt called for the wrong client (expected %s, got %s)", c.hostname, req.Host)
|
||||
}
|
||||
|
@ -283,6 +312,18 @@ func (c *client) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon
|
|||
}
|
||||
}
|
||||
|
||||
if opt.CheckSettings != nil {
|
||||
// wait for the server's SETTINGS frame to arrive
|
||||
select {
|
||||
case <-c.receivedSettings:
|
||||
case <-conn.Context().Done():
|
||||
return nil, context.Cause(conn.Context())
|
||||
}
|
||||
if err := opt.CheckSettings(*c.settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
str, err := conn.OpenStreamSync(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -11,11 +11,12 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/quicvarint"
|
||||
|
||||
|
@ -56,7 +57,7 @@ var _ = Describe("Client", func() {
|
|||
|
||||
It("rejects quic.Configs that allow multiple QUIC versions", func() {
|
||||
qconf := &quic.Config{
|
||||
Versions: []quic.VersionNumber{protocol.Version2, protocol.Version1},
|
||||
Versions: []quic.Version{protocol.Version2, protocol.Version1},
|
||||
}
|
||||
_, err := newClient("localhost:1337", nil, &roundTripperOpts{}, qconf, nil)
|
||||
Expect(err).To(MatchError("can only use a single QUIC version for dialing a HTTP/3 connection"))
|
||||
|
@ -69,7 +70,7 @@ var _ = Describe("Client", func() {
|
|||
dialAddr = func(_ context.Context, _ string, tlsConf *tls.Config, quicConf *quic.Config) (quic.EarlyConnection, error) {
|
||||
Expect(quicConf.MaxIncomingStreams).To(Equal(defaultQuicConfig.MaxIncomingStreams))
|
||||
Expect(tlsConf.NextProtos).To(Equal([]string{NextProtoH3}))
|
||||
Expect(quicConf.Versions).To(Equal([]protocol.VersionNumber{protocol.Version1}))
|
||||
Expect(quicConf.Versions).To(Equal([]protocol.Version{protocol.Version1}))
|
||||
dialAddrCalled = true
|
||||
return nil, errors.New("test done")
|
||||
}
|
||||
|
@ -214,9 +215,10 @@ var _ = Describe("Client", func() {
|
|||
testDone = make(chan struct{})
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
|
@ -340,9 +342,10 @@ var _ = Describe("Client", func() {
|
|||
testDone = make(chan struct{})
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
|
@ -441,19 +444,19 @@ var _ = Describe("Client", func() {
|
|||
conn *mockquic.MockEarlyConnection
|
||||
settingsFrameWritten chan struct{}
|
||||
)
|
||||
testDone := make(chan struct{})
|
||||
testDone := make(chan struct{}, 1)
|
||||
|
||||
BeforeEach(func() {
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
})
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
conn.EXPECT().OpenUniStream().Return(controlStr, nil)
|
||||
conn.EXPECT().HandshakeComplete().Return(handshakeChan)
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
dialAddr = func(context.Context, string, *tls.Config, *quic.Config) (quic.EarlyConnection, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
@ -469,10 +472,15 @@ var _ = Describe("Client", func() {
|
|||
|
||||
It("parses the SETTINGS frame", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
b = (&settingsFrame{
|
||||
Datagram: true,
|
||||
ExtendedConnect: true,
|
||||
Other: map[uint64]uint64{1337: 42},
|
||||
}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -480,11 +488,72 @@ var _ = Describe("Client", func() {
|
|||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
conn.EXPECT().Context().Return(context.Background())
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
|
||||
defer GinkgoRecover()
|
||||
Expect(settings.EnableDatagram).To(BeTrue())
|
||||
Expect(settings.EnableExtendedConnect).To(BeTrue())
|
||||
Expect(settings.Other).To(HaveLen(1))
|
||||
Expect(settings.Other).To(HaveKeyWithValue(uint64(1337), uint64(42)))
|
||||
return nil
|
||||
}})
|
||||
Expect(err).To(MatchError("done"))
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("allows the client to reject the SETTINGS using the CheckSettings RoundTripOpt", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
// Don't EXPECT any call to OpenStreamSync.
|
||||
// When the SETTINGS are rejected, we don't even open the request stream.
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
conn.EXPECT().Context().Return(context.Background())
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{CheckSettings: func(settings Settings) error {
|
||||
return errors.New("wrong settings")
|
||||
}})
|
||||
Expect(err).To(MatchError("wrong settings"))
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("rejects duplicate control streams", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r1 := bytes.NewReader(b)
|
||||
controlStr1 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
|
||||
r2 := bytes.NewReader(b)
|
||||
controlStr2 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr1, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr2, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-done
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
|
||||
streamType := t
|
||||
name := "encoder"
|
||||
|
@ -497,6 +566,7 @@ var _ = Describe("Client", func() {
|
|||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
})
|
||||
|
@ -510,15 +580,14 @@ var _ = Describe("Client", func() {
|
|||
})
|
||||
}
|
||||
|
||||
It("resets streams Other than the control stream and the QPACK streams", func() {
|
||||
It("resets streams other than the control stream and the QPACK streams", func() {
|
||||
buf := bytes.NewBuffer(quicvarint.Append(nil, 0x1337))
|
||||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
|
||||
close(done)
|
||||
})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
})
|
||||
|
@ -537,6 +606,8 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -545,22 +616,53 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
It("errors when the first frame on the control stream is not a SETTINGS frame, when checking SETTINGS", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&dataFrame{}).Append(b)
|
||||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
|
||||
// Don't EXPECT any calls to OpenStreamSync.
|
||||
// We fail before we even get the chance to open the request stream.
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-testDone
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
doneCtx, doneCancel := context.WithCancelCause(context.Background())
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
doneCancel(errors.New("done"))
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().Context().Return(doneCtx).Times(2)
|
||||
var checked bool
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{
|
||||
CheckSettings: func(Settings) error { checked = true; return nil },
|
||||
})
|
||||
Expect(checked).To(BeFalse())
|
||||
Expect(err).To(MatchError("done"))
|
||||
Eventually(doneCtx.Done()).Should(BeClosed())
|
||||
})
|
||||
|
||||
It("errors when parsing the frame on the control stream fails", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r := bytes.NewReader(b[:len(b)-1])
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -569,10 +671,9 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -583,6 +684,7 @@ var _ = Describe("Client", func() {
|
|||
buf := bytes.NewBuffer(quicvarint.Append(nil, streamTypePushStream))
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -591,10 +693,9 @@ var _ = Describe("Client", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeIDError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -608,6 +709,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
conn.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr, nil
|
||||
})
|
||||
|
@ -617,11 +719,9 @@ var _ = Describe("Client", func() {
|
|||
})
|
||||
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
|
||||
Expect(reason).To(Equal("missing QUIC Datagram support"))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("done"))
|
||||
|
@ -670,13 +770,14 @@ var _ = Describe("Client", func() {
|
|||
BeforeEach(func() {
|
||||
settingsFrameWritten = make(chan struct{})
|
||||
controlStr := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
|
||||
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) (int, error) {
|
||||
defer GinkgoRecover()
|
||||
r := bytes.NewReader(b)
|
||||
streamType, err := quicvarint.Read(r)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(streamType).To(BeEquivalentTo(streamTypeControlStream))
|
||||
close(settingsFrameWritten)
|
||||
return len(b), nil
|
||||
}) // SETTINGS frame
|
||||
str = mockquic.NewMockStream(mockCtrl)
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
|
@ -778,7 +879,7 @@ var _ = Describe("Client", func() {
|
|||
It("sends a request", func() {
|
||||
done := make(chan struct{})
|
||||
gomock.InOrder(
|
||||
str.EXPECT().Close().Do(func() { close(done) }),
|
||||
str.EXPECT().Close().Do(func() error { close(done); return nil }),
|
||||
str.EXPECT().CancelWrite(gomock.Any()).MaxTimes(1), // when reading the response errors
|
||||
)
|
||||
// the response body is sent asynchronously, while already reading the response
|
||||
|
@ -832,7 +933,7 @@ var _ = Describe("Client", func() {
|
|||
return 0, errors.New("test done")
|
||||
})
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("test done"))
|
||||
Eventually(closed).Should(BeClosed())
|
||||
|
@ -843,7 +944,7 @@ var _ = Describe("Client", func() {
|
|||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any())
|
||||
closed := make(chan struct{})
|
||||
r := bytes.NewReader(b)
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("expected first frame to be a HEADERS frame"))
|
||||
|
@ -861,7 +962,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
@ -873,7 +974,7 @@ var _ = Describe("Client", func() {
|
|||
r := bytes.NewReader(b)
|
||||
str.EXPECT().CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
|
||||
closed := make(chan struct{})
|
||||
str.EXPECT().Close().Do(func() { close(closed) })
|
||||
str.EXPECT().Close().Do(func() error { close(closed); return nil })
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(r.Read).AnyTimes()
|
||||
_, err := cl.RoundTripOpt(req, RoundTripOpt{})
|
||||
Expect(err).To(MatchError("HEADERS frame too large: 1338 bytes (max: 1337)"))
|
||||
|
@ -926,7 +1027,7 @@ var _ = Describe("Client", func() {
|
|||
return 0, errors.New("test done")
|
||||
})
|
||||
_, err := cl.RoundTripOpt(req, roundTripOpt)
|
||||
Expect(err).To(MatchError("test done"))
|
||||
Expect(err).To(MatchError(context.Canceled))
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -88,11 +88,18 @@ func (f *headersFrame) Append(b []byte) []byte {
|
|||
return quicvarint.Append(b, f.Length)
|
||||
}
|
||||
|
||||
const settingDatagram = 0x33
|
||||
const (
|
||||
// Extended CONNECT, RFC 9220
|
||||
settingExtendedConnect = 0x8
|
||||
// HTTP Datagrams, RFC 9297
|
||||
settingDatagram = 0x33
|
||||
)
|
||||
|
||||
type settingsFrame struct {
|
||||
Datagram bool
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
Datagram bool // HTTP Datagrams, RFC 9297
|
||||
ExtendedConnect bool // Extended CONNECT, RFC 9220
|
||||
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
}
|
||||
|
||||
func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
||||
|
@ -108,7 +115,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
|||
}
|
||||
frame := &settingsFrame{}
|
||||
b := bytes.NewReader(buf)
|
||||
var readDatagram bool
|
||||
var readDatagram, readExtendedConnect bool
|
||||
for b.Len() > 0 {
|
||||
id, err := quicvarint.Read(b)
|
||||
if err != nil { // should not happen. We allocated the whole frame already.
|
||||
|
@ -120,13 +127,22 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
|
|||
}
|
||||
|
||||
switch id {
|
||||
case settingExtendedConnect:
|
||||
if readExtendedConnect {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
}
|
||||
readExtendedConnect = true
|
||||
if val != 0 && val != 1 {
|
||||
return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", val)
|
||||
}
|
||||
frame.ExtendedConnect = val == 1
|
||||
case settingDatagram:
|
||||
if readDatagram {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
}
|
||||
readDatagram = true
|
||||
if val != 0 && val != 1 {
|
||||
return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val)
|
||||
return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", val)
|
||||
}
|
||||
frame.Datagram = val == 1
|
||||
default:
|
||||
|
@ -151,11 +167,18 @@ func (f *settingsFrame) Append(b []byte) []byte {
|
|||
if f.Datagram {
|
||||
l += quicvarint.Len(settingDatagram) + quicvarint.Len(1)
|
||||
}
|
||||
if f.ExtendedConnect {
|
||||
l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
|
||||
}
|
||||
b = quicvarint.Append(b, uint64(l))
|
||||
if f.Datagram {
|
||||
b = quicvarint.Append(b, settingDatagram)
|
||||
b = quicvarint.Append(b, 1)
|
||||
}
|
||||
if f.ExtendedConnect {
|
||||
b = quicvarint.Append(b, settingExtendedConnect)
|
||||
b = quicvarint.Append(b, 1)
|
||||
}
|
||||
for id, val := range f.Other {
|
||||
b = quicvarint.Append(b, id)
|
||||
b = quicvarint.Append(b, val)
|
||||
|
|
|
@ -127,8 +127,8 @@ var _ = Describe("Frames", func() {
|
|||
}
|
||||
})
|
||||
|
||||
Context("H3_DATAGRAM", func() {
|
||||
It("reads the H3_DATAGRAM value", func() {
|
||||
Context("HTTP Datagrams", func() {
|
||||
It("reads the SETTINGS_H3_DATAGRAM value", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
|
@ -141,7 +141,7 @@ var _ = Describe("Frames", func() {
|
|||
Expect(sf.Datagram).To(BeTrue())
|
||||
})
|
||||
|
||||
It("rejects duplicate H3_DATAGRAM entries", func() {
|
||||
It("rejects duplicate SETTINGS_H3_DATAGRAM entries", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
settings = quicvarint.Append(settings, settingDatagram)
|
||||
|
@ -153,23 +153,67 @@ var _ = Describe("Frames", func() {
|
|||
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingDatagram)))
|
||||
})
|
||||
|
||||
It("rejects invalid values for the H3_DATAGRAM entry", func() {
|
||||
It("rejects invalid values for the SETTINGS_H3_DATAGRAM entry", func() {
|
||||
settings := quicvarint.Append(nil, settingDatagram)
|
||||
settings = quicvarint.Append(settings, 1337)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError("invalid value for H3_DATAGRAM: 1337"))
|
||||
Expect(err).To(MatchError("invalid value for SETTINGS_H3_DATAGRAM: 1337"))
|
||||
})
|
||||
|
||||
It("writes the H3_DATAGRAM setting", func() {
|
||||
It("writes the SETTINGS_H3_DATAGRAM setting", func() {
|
||||
sf := &settingsFrame{Datagram: true}
|
||||
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(frame).To(Equal(sf))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Extended Connect", func() {
|
||||
It("reads the SETTINGS_ENABLE_CONNECT_PROTOCOL value", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
f, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(f).To(BeAssignableToTypeOf(&settingsFrame{}))
|
||||
sf := f.(*settingsFrame)
|
||||
Expect(sf.ExtendedConnect).To(BeTrue())
|
||||
})
|
||||
|
||||
It("rejects duplicate SETTINGS_ENABLE_CONNECT_PROTOCOL entries", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
settings = quicvarint.Append(settings, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingExtendedConnect)))
|
||||
})
|
||||
|
||||
It("rejects invalid values for the SETTINGS_ENABLE_CONNECT_PROTOCOL entry", func() {
|
||||
settings := quicvarint.Append(nil, settingExtendedConnect)
|
||||
settings = quicvarint.Append(settings, 1337)
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
_, err := parseNextFrame(bytes.NewReader(data), nil)
|
||||
Expect(err).To(MatchError("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337"))
|
||||
})
|
||||
|
||||
It("writes the SETTINGS_ENABLE_CONNECT_PROTOCOL setting", func() {
|
||||
sf := &settingsFrame{ExtendedConnect: true}
|
||||
frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(frame).To(Equal(sf))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("hijacking", func() {
|
||||
|
|
|
@ -126,9 +126,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
|
|||
return nil, errors.New(":path, :authority and :method must not be empty")
|
||||
}
|
||||
|
||||
if !isExtendedConnected && len(hdr.Protocol) > 0 {
|
||||
return nil, errors.New(":protocol must be empty")
|
||||
}
|
||||
|
||||
var u *url.URL
|
||||
var requestURI string
|
||||
var protocol string
|
||||
|
||||
protocol := "HTTP/3.0"
|
||||
|
||||
if isConnect {
|
||||
u = &url.URL{}
|
||||
|
@ -137,15 +142,14 @@ func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocol = hdr.Protocol
|
||||
} else {
|
||||
u.Path = hdr.Path
|
||||
}
|
||||
u.Scheme = hdr.Scheme
|
||||
u.Host = hdr.Authority
|
||||
requestURI = hdr.Authority
|
||||
protocol = hdr.Protocol
|
||||
} else {
|
||||
protocol = "HTTP/3.0"
|
||||
u, err = url.ParseRequestURI(hdr.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid content length: %w", err)
|
||||
|
|
|
@ -212,6 +212,17 @@ var _ = Describe("Request", func() {
|
|||
Expect(err).To(MatchError(":path, :authority and :method must not be empty"))
|
||||
})
|
||||
|
||||
It("errors with invalid protocol", func() {
|
||||
headers := []qpack.HeaderField{
|
||||
{Name: ":path", Value: "/foo"},
|
||||
{Name: ":authority", Value: "quic.clemente.io"},
|
||||
{Name: ":method", Value: "GET"},
|
||||
{Name: ":protocol", Value: "connect-udp"},
|
||||
}
|
||||
_, err := requestFromHeaders(headers)
|
||||
Expect(err).To(MatchError(":protocol must be empty"))
|
||||
})
|
||||
|
||||
Context("regular HTTP CONNECT", func() {
|
||||
It("handles CONNECT method", func() {
|
||||
headers := []qpack.HeaderField{
|
||||
|
@ -221,6 +232,7 @@ var _ = Describe("Request", func() {
|
|||
req, err := requestFromHeaders(headers)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(req.Method).To(Equal(http.MethodConnect))
|
||||
Expect(req.Proto).To(Equal("HTTP/3.0"))
|
||||
Expect(req.RequestURI).To(Equal("quic.clemente.io"))
|
||||
})
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
// A Stream is a HTTP/3 stream.
|
||||
|
@ -115,7 +114,7 @@ func (s *lengthLimitedStream) Read(b []byte) (int, error) {
|
|||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := s.stream.Read(b[:utils.Min(int64(len(b)), s.contentLength-s.read)])
|
||||
n, err := s.stream.Read(b[:min(int64(len(b)), s.contentLength-s.read)])
|
||||
s.read += int64(n)
|
||||
if err := s.checkContentLengthViolation(); err != nil {
|
||||
return n, err
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package http3 -destination mock_quic_early_listener_test.go github.com/refraction-networking/uquic/http3 QUICEarlyListener
|
||||
// mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener
|
||||
//
|
||||
|
||||
// Package http3 is a generated GoMock package.
|
||||
package http3
|
||||
|
||||
|
@ -50,9 +51,33 @@ func (m *MockQUICEarlyListener) Accept(arg0 context.Context) (quic.EarlyConnecti
|
|||
}
|
||||
|
||||
// Accept indicates an expected call of Accept.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 any) *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Accept(arg0 any) *MockQUICEarlyListenerAcceptCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockQUICEarlyListener)(nil).Accept), arg0)
|
||||
return &MockQUICEarlyListenerAcceptCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerAcceptCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerAcceptCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerAcceptCall) Return(arg0 quic.EarlyConnection, arg1 error) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerAcceptCall) Do(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerAcceptCall) DoAndReturn(f func(context.Context) (quic.EarlyConnection, error)) *MockQUICEarlyListenerAcceptCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Addr mocks base method.
|
||||
|
@ -64,9 +89,33 @@ func (m *MockQUICEarlyListener) Addr() net.Addr {
|
|||
}
|
||||
|
||||
// Addr indicates an expected call of Addr.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Addr() *MockQUICEarlyListenerAddrCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addr", reflect.TypeOf((*MockQUICEarlyListener)(nil).Addr))
|
||||
return &MockQUICEarlyListenerAddrCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerAddrCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerAddrCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerAddrCall) Return(arg0 net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerAddrCall) Do(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerAddrCall) DoAndReturn(f func() net.Addr) *MockQUICEarlyListenerAddrCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
|
@ -78,7 +127,31 @@ func (m *MockQUICEarlyListener) Close() error {
|
|||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Close() *gomock.Call {
|
||||
func (mr *MockQUICEarlyListenerMockRecorder) Close() *MockQUICEarlyListenerCloseCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockQUICEarlyListener)(nil).Close))
|
||||
return &MockQUICEarlyListenerCloseCall{Call: call}
|
||||
}
|
||||
|
||||
// MockQUICEarlyListenerCloseCall wrap *gomock.Call
|
||||
type MockQUICEarlyListenerCloseCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockQUICEarlyListenerCloseCall) Return(arg0 error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockQUICEarlyListenerCloseCall) Do(f func() error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockQUICEarlyListenerCloseCall) DoAndReturn(f func() error) *MockQUICEarlyListenerCloseCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -build_flags=-tags=gomock -package http3 -destination mock_roundtripcloser_test.go github.com/refraction-networking/uquic/http3 RoundTripCloser
|
||||
// mockgen -typed -build_flags=-tags=gomock -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser
|
||||
//
|
||||
|
||||
// Package http3 is a generated GoMock package.
|
||||
package http3
|
||||
|
||||
|
@ -47,9 +48,33 @@ func (m *MockRoundTripCloser) Close() error {
|
|||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockRoundTripCloserMockRecorder) Close() *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) Close() *MockRoundTripCloserCloseCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRoundTripCloser)(nil).Close))
|
||||
return &MockRoundTripCloserCloseCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserCloseCall wrap *gomock.Call
|
||||
type MockRoundTripCloserCloseCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserCloseCall) Return(arg0 error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserCloseCall) Do(f func() error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserCloseCall) DoAndReturn(f func() error) *MockRoundTripCloserCloseCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// HandshakeComplete mocks base method.
|
||||
|
@ -61,9 +86,33 @@ func (m *MockRoundTripCloser) HandshakeComplete() bool {
|
|||
}
|
||||
|
||||
// HandshakeComplete indicates an expected call of HandshakeComplete.
|
||||
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) HandshakeComplete() *MockRoundTripCloserHandshakeCompleteCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandshakeComplete", reflect.TypeOf((*MockRoundTripCloser)(nil).HandshakeComplete))
|
||||
return &MockRoundTripCloserHandshakeCompleteCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserHandshakeCompleteCall wrap *gomock.Call
|
||||
type MockRoundTripCloserHandshakeCompleteCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) Return(arg0 bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) Do(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserHandshakeCompleteCall) DoAndReturn(f func() bool) *MockRoundTripCloserHandshakeCompleteCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RoundTripOpt mocks base method.
|
||||
|
@ -76,7 +125,31 @@ func (m *MockRoundTripCloser) RoundTripOpt(arg0 *http.Request, arg1 RoundTripOpt
|
|||
}
|
||||
|
||||
// RoundTripOpt indicates an expected call of RoundTripOpt.
|
||||
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 any) *gomock.Call {
|
||||
func (mr *MockRoundTripCloserMockRecorder) RoundTripOpt(arg0, arg1 any) *MockRoundTripCloserRoundTripOptCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTripOpt", reflect.TypeOf((*MockRoundTripCloser)(nil).RoundTripOpt), arg0, arg1)
|
||||
return &MockRoundTripCloserRoundTripOptCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRoundTripCloserRoundTripOptCall wrap *gomock.Call
|
||||
type MockRoundTripCloserRoundTripOptCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) Return(arg0 *http.Response, arg1 error) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) Do(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRoundTripCloserRoundTripOptCall) DoAndReturn(f func(*http.Request, RoundTripOpt) (*http.Response, error)) *MockRoundTripCloserRoundTripOptCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package http3
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/refraction-networking/uquic/http3 RoundTripCloser"
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package http3 -destination mock_roundtripcloser_test.go github.com/quic-go/quic-go/http3 RoundTripCloser"
|
||||
type RoundTripCloser = roundTripCloser
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -package http3 -destination mock_quic_early_listener_test.go github.com/refraction-networking/uquic/http3 QUICEarlyListener"
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package http3 -destination mock_quic_early_listener_test.go github.com/quic-go/quic-go/http3 QUICEarlyListener"
|
||||
|
|
|
@ -67,9 +67,10 @@ type responseWriter struct {
|
|||
bufferedStr *bufio.Writer
|
||||
buf []byte
|
||||
|
||||
headerWritten bool
|
||||
contentLen int64 // if handler set valid Content-Length header
|
||||
numWritten int64 // bytes written
|
||||
headerWritten bool
|
||||
isHead bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -162,6 +163,10 @@ func (w *responseWriter) Write(p []byte) (int, error) {
|
|||
return 0, http.ErrContentLength
|
||||
}
|
||||
|
||||
if w.isHead {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
df := &dataFrame{Length: uint64(len(p))}
|
||||
w.buf = w.buf[:0]
|
||||
w.buf = df.Append(w.buf)
|
||||
|
|
|
@ -17,6 +17,30 @@ import (
|
|||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// Settings are HTTP/3 settings that apply to the underlying connection.
|
||||
type Settings struct {
|
||||
// Support for HTTP/3 datagrams (RFC 9297)
|
||||
EnableDatagram bool
|
||||
// Extended CONNECT, RFC 9220
|
||||
EnableExtendedConnect bool
|
||||
// Other settings, defined by the application
|
||||
Other map[uint64]uint64
|
||||
}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
|
||||
// If set, context cancellations have no effect after the response headers are received.
|
||||
DontCloseRequestStream bool
|
||||
// CheckSettings is run before the request is sent to the server.
|
||||
// If not yet received, it blocks until the server's SETTINGS frame is received.
|
||||
// If an error is returned, the request won't be sent to the server, and the error is returned.
|
||||
CheckSettings func(Settings) error
|
||||
}
|
||||
|
||||
type roundTripCloser interface {
|
||||
RoundTripOpt(*http.Request, RoundTripOpt) (*http.Response, error)
|
||||
HandshakeComplete() bool
|
||||
|
@ -50,9 +74,8 @@ type RoundTripper struct {
|
|||
// If nil, reasonable default values will be used.
|
||||
QuicConfig *quic.Config
|
||||
|
||||
// Enable support for HTTP/3 datagrams.
|
||||
// If set to true, QuicConfig.EnableDatagram will be set.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc9297.
|
||||
// Enable support for HTTP/3 datagrams (RFC 9297).
|
||||
// If a QuicConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
|
||||
EnableDatagrams bool
|
||||
|
||||
// Additional HTTP/3 settings.
|
||||
|
@ -89,16 +112,6 @@ type RoundTripper struct {
|
|||
transport *quic.Transport
|
||||
}
|
||||
|
||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||
type RoundTripOpt struct {
|
||||
// OnlyCachedConn controls whether the RoundTripper may create a new QUIC connection.
|
||||
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
|
||||
OnlyCachedConn bool
|
||||
// DontCloseRequestStream controls whether the request stream is closed after sending the request.
|
||||
// If set, context cancellations have no effect after the response headers are received.
|
||||
DontCloseRequestStream bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ http.RoundTripper = &RoundTripper{}
|
||||
_ io.Closer = &RoundTripper{}
|
||||
|
@ -204,6 +217,7 @@ func (r *RoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTr
|
|||
MaxHeaderBytes: r.MaxResponseHeaderBytes,
|
||||
StreamHijacker: r.StreamHijacker,
|
||||
UniStreamHijacker: r.UniStreamHijacker,
|
||||
AdditionalSettings: r.AdditionalSettings,
|
||||
},
|
||||
r.QuicConfig,
|
||||
dial,
|
||||
|
|
|
@ -72,6 +72,21 @@ var _ = Describe("RoundTripper", func() {
|
|||
Expect(err).To(MatchError(testErr))
|
||||
})
|
||||
|
||||
It("creates new clients with additional settings", func() {
|
||||
testErr := errors.New("test err")
|
||||
req, err := http.NewRequest("GET", "https://quic.clemente.io/foobar.html", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
rt.AdditionalSettings = map[uint64]uint64{1337: 42}
|
||||
rt.newClient = func(_ string, _ *tls.Config, opts *roundTripperOpts, conf *quic.Config, _ dialFunc) (roundTripCloser, error) {
|
||||
cl := NewMockRoundTripCloser(mockCtrl)
|
||||
cl.EXPECT().RoundTripOpt(gomock.Any(), gomock.Any()).Return(nil, testErr)
|
||||
Expect(opts.AdditionalSettings).To(HaveKeyWithValue(uint64(1337), uint64(42)))
|
||||
return cl, nil
|
||||
}
|
||||
_, err = rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError(testErr))
|
||||
})
|
||||
|
||||
It("uses the quic.Config, if provided", func() {
|
||||
config := &quic.Config{HandshakeIdleTimeout: time.Millisecond}
|
||||
var receivedConfig *quic.Config
|
||||
|
@ -85,6 +100,19 @@ var _ = Describe("RoundTripper", func() {
|
|||
Expect(receivedConfig.HandshakeIdleTimeout).To(Equal(config.HandshakeIdleTimeout))
|
||||
})
|
||||
|
||||
It("requires quic.Config.EnableDatagram if HTTP datagrams are enabled", func() {
|
||||
rt.QuicConfig = &quic.Config{EnableDatagrams: false}
|
||||
rt.Dial = func(_ context.Context, _ string, _ *tls.Config, config *quic.Config) (quic.EarlyConnection, error) {
|
||||
return nil, errors.New("handshake error")
|
||||
}
|
||||
rt.EnableDatagrams = true
|
||||
_, err := rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError("HTTP Datagrams enabled, but QUIC Datagrams disabled"))
|
||||
rt.QuicConfig.EnableDatagrams = true
|
||||
_, err = rt.RoundTrip(req)
|
||||
Expect(err).To(MatchError("handshake error"))
|
||||
})
|
||||
|
||||
It("uses the custom dialer, if provided", func() {
|
||||
var dialed bool
|
||||
dialer := func(_ context.Context, _ string, tlsCfgP *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
@ -32,6 +33,7 @@ var (
|
|||
quicListenAddr = func(addr string, tlsConf *tls.Config, config *quic.Config) (QUICEarlyListener, error) {
|
||||
return quic.ListenAddrEarly(addr, tlsConf, config)
|
||||
}
|
||||
errPanicked = errors.New("panicked")
|
||||
)
|
||||
|
||||
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
|
||||
|
@ -56,7 +58,7 @@ type QUICEarlyListener interface {
|
|||
|
||||
var _ QUICEarlyListener = &quic.EarlyListener{}
|
||||
|
||||
func versionToALPN(v protocol.VersionNumber) string {
|
||||
func versionToALPN(v protocol.Version) string {
|
||||
//nolint:exhaustive // These are all the versions we care about.
|
||||
switch v {
|
||||
case protocol.Version1, protocol.Version2:
|
||||
|
@ -78,7 +80,7 @@ func ConfigureTLSConfig(tlsConf *tls.Config) *tls.Config {
|
|||
// determine the ALPN from the QUIC version used
|
||||
proto := NextProtoH3
|
||||
val := ch.Context().Value(quic.QUICVersionContextKey)
|
||||
if v, ok := val.(quic.VersionNumber); ok {
|
||||
if v, ok := val.(quic.Version); ok {
|
||||
proto = versionToALPN(v)
|
||||
}
|
||||
config := tlsConf
|
||||
|
@ -117,6 +119,16 @@ func (k *contextKey) String() string { return "quic-go/http3 context value " + k
|
|||
// type *http3.Server.
|
||||
var ServerContextKey = &contextKey{"http3-server"}
|
||||
|
||||
// RemoteAddrContextKey is a context key. It can be used in
|
||||
// HTTP handlers with Context.Value to access the remote
|
||||
// address of the connection. The associated value will be of
|
||||
// type net.Addr.
|
||||
//
|
||||
// Use this value instead of [http.Request.RemoteAddr] if you
|
||||
// require access to the remote address of the connection rather
|
||||
// than its string representation.
|
||||
var RemoteAddrContextKey = &contextKey{"remote-addr"}
|
||||
|
||||
type requestError struct {
|
||||
err error
|
||||
streamErr ErrCode
|
||||
|
@ -202,6 +214,11 @@ type Server struct {
|
|||
// In that case, the stream type will not be set.
|
||||
UniStreamHijacker func(StreamType, quic.Connection, quic.ReceiveStream, error) (hijacked bool)
|
||||
|
||||
// ConnContext optionally specifies a function that modifies
|
||||
// the context used for a new connection c. The provided ctx
|
||||
// has a ServerContextKey value.
|
||||
ConnContext func(ctx context.Context, c quic.Connection) context.Context
|
||||
|
||||
mutex sync.RWMutex
|
||||
listeners map[*QUICEarlyListener]listenerInfo
|
||||
|
||||
|
@ -275,7 +292,7 @@ func (s *Server) ServeListener(ln QUICEarlyListener) error {
|
|||
}
|
||||
go func() {
|
||||
if err := s.handleConn(conn); err != nil {
|
||||
s.logger.Debugf(err.Error())
|
||||
s.logger.Debugf("handling connection failed: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -409,10 +426,11 @@ func (s *Server) addListener(l *QUICEarlyListener) error {
|
|||
s.listeners = make(map[*QUICEarlyListener]listenerInfo)
|
||||
}
|
||||
|
||||
if port, err := extractPort((*l).Addr().String()); err == nil {
|
||||
laddr := (*l).Addr()
|
||||
if port, err := extractPort(laddr.String()); err == nil {
|
||||
s.listeners[l] = listenerInfo{port}
|
||||
} else {
|
||||
s.logger.Errorf("Unable to extract port from listener %+v, will not be announced using SetQuicHeaders: %s", err)
|
||||
s.logger.Errorf("Unable to extract port from listener %s, will not be announced using SetQuicHeaders: %s", laddr, err)
|
||||
s.listeners[l] = listenerInfo{}
|
||||
}
|
||||
s.generateAltSvcHeader()
|
||||
|
@ -436,7 +454,11 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
|||
}
|
||||
b := make([]byte, 0, 64)
|
||||
b = quicvarint.Append(b, streamTypeControlStream) // stream type
|
||||
b = (&settingsFrame{Datagram: s.EnableDatagrams, Other: s.AdditionalSettings}).Append(b)
|
||||
b = (&settingsFrame{
|
||||
Datagram: s.EnableDatagrams,
|
||||
ExtendedConnect: true,
|
||||
Other: s.AdditionalSettings,
|
||||
}).Append(b)
|
||||
str.Write(b)
|
||||
|
||||
go s.handleUnidirectionalStreams(conn)
|
||||
|
@ -479,6 +501,8 @@ func (s *Server) handleConn(conn quic.Connection) error {
|
|||
}
|
||||
|
||||
func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
|
||||
var rcvdControlStream atomic.Bool
|
||||
|
||||
for {
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
|
@ -512,6 +536,11 @@ func (s *Server) handleUnidirectionalStreams(conn quic.Connection) {
|
|||
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
|
||||
return
|
||||
}
|
||||
// Only a single control stream is allowed.
|
||||
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
|
||||
return
|
||||
}
|
||||
f, err := parseNextFrame(str, nil)
|
||||
if err != nil {
|
||||
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
|
||||
|
@ -617,8 +646,18 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
|||
ctx := str.Context()
|
||||
ctx = context.WithValue(ctx, ServerContextKey, s)
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, conn.LocalAddr())
|
||||
ctx = context.WithValue(ctx, RemoteAddrContextKey, conn.RemoteAddr())
|
||||
if s.ConnContext != nil {
|
||||
ctx = s.ConnContext(ctx, conn)
|
||||
if ctx == nil {
|
||||
panic("http3: ConnContext returned nil")
|
||||
}
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
r := newResponseWriter(str, conn, s.logger)
|
||||
if req.Method == http.MethodHead {
|
||||
r.isHead = true
|
||||
}
|
||||
handler := s.Handler
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
|
@ -658,6 +697,11 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
|
|||
}
|
||||
// If the EOF was read by the handler, CancelRead() is a no-op.
|
||||
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
|
||||
// abort the stream when there is a panic
|
||||
if panicked {
|
||||
return newStreamError(ErrCodeInternalError, errPanicked)
|
||||
}
|
||||
return requestError{}
|
||||
}
|
||||
|
||||
|
@ -722,7 +766,7 @@ func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) er
|
|||
return server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the given network address for both, TLS and QUIC
|
||||
// ListenAndServe listens on the given network address for both TLS/TCP and QUIC
|
||||
// connections in parallel. It returns if one of the two returns an error.
|
||||
// http.DefaultServeMux is used when handler is nil.
|
||||
// The correct Alt-Svc headers for QUIC are set.
|
||||
|
@ -764,8 +808,8 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error
|
|||
Handler: handler,
|
||||
}
|
||||
|
||||
hErr := make(chan error)
|
||||
qErr := make(chan error)
|
||||
hErr := make(chan error, 1)
|
||||
qErr := make(chan error, 1)
|
||||
go func() {
|
||||
hErr <- http.ListenAndServeTLS(addr, certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
quicServer.SetQuicHeaders(w.Header())
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
quic "github.com/refraction-networking/uquic"
|
||||
mockquic "github.com/refraction-networking/uquic/internal/mocks/quic"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/qerr"
|
||||
"github.com/refraction-networking/uquic/internal/testdata"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
"github.com/refraction-networking/uquic/quicvarint"
|
||||
|
@ -68,11 +69,15 @@ var _ = Describe("Server", func() {
|
|||
s *Server
|
||||
origQuicListenAddr = quicListenAddr
|
||||
)
|
||||
type testConnContextKey string
|
||||
|
||||
BeforeEach(func() {
|
||||
s = &Server{
|
||||
TLSConfig: testdata.GetTLSConfig(),
|
||||
logger: utils.DefaultLogger,
|
||||
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
||||
return context.WithValue(ctx, testConnContextKey("test"), c)
|
||||
},
|
||||
}
|
||||
origQuicListenAddr = quicListenAddr
|
||||
})
|
||||
|
@ -164,6 +169,7 @@ var _ = Describe("Server", func() {
|
|||
Expect(req.Host).To(Equal("www.example.com"))
|
||||
Expect(req.RemoteAddr).To(Equal("127.0.0.1:1337"))
|
||||
Expect(req.Context().Value(ServerContextKey)).To(Equal(s))
|
||||
Expect(req.Context().Value(testConnContextKey("test"))).ToNot(Equal(nil))
|
||||
})
|
||||
|
||||
It("returns 200 with an empty handler", func() {
|
||||
|
@ -222,6 +228,45 @@ var _ = Describe("Server", func() {
|
|||
Expect(hfs).To(HaveLen(3))
|
||||
})
|
||||
|
||||
It("response to HEAD request should not have body", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("foobar"))
|
||||
})
|
||||
|
||||
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
responseBuf := &bytes.Buffer{}
|
||||
setRequest(encodeRequest(headRequest))
|
||||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(gomock.Any())
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
hfs := decodeHeader(responseBuf)
|
||||
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
It("response to HEAD request should also do content sniffing", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("<html></html>"))
|
||||
})
|
||||
|
||||
headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
responseBuf := &bytes.Buffer{}
|
||||
setRequest(encodeRequest(headRequest))
|
||||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(gomock.Any())
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
hfs := decodeHeader(responseBuf)
|
||||
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
|
||||
Expect(hfs).To(HaveKeyWithValue("content-length", []string{"13"}))
|
||||
Expect(hfs).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"}))
|
||||
})
|
||||
|
||||
It("handles a aborting handler", func() {
|
||||
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
panic(http.ErrAbortHandler)
|
||||
|
@ -234,7 +279,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().CancelRead(gomock.Any())
|
||||
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
Expect(serr.err).To(MatchError(errPanicked))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
|
@ -250,7 +295,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().CancelRead(gomock.Any())
|
||||
|
||||
serr := s.handleRequest(conn, str, qpackDecoder, nil)
|
||||
Expect(serr.err).ToNot(HaveOccurred())
|
||||
Expect(serr.err).To(MatchError(errPanicked))
|
||||
Expect(responseBuf.Bytes()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
|
@ -454,7 +499,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Context("control stream handling", func() {
|
||||
var conn *mockquic.MockEarlyConnection
|
||||
testDone := make(chan struct{})
|
||||
testDone := make(chan struct{}, 1)
|
||||
|
||||
BeforeEach(func() {
|
||||
conn = mockquic.NewMockEarlyConnection(mockCtrl)
|
||||
|
@ -485,6 +530,34 @@ var _ = Describe("Server", func() {
|
|||
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to conn.CloseWithError
|
||||
})
|
||||
|
||||
It("rejects duplicate control streams", func() {
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
b = (&settingsFrame{}).Append(b)
|
||||
r1 := bytes.NewReader(b)
|
||||
controlStr1 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr1.EXPECT().Read(gomock.Any()).DoAndReturn(r1.Read).AnyTimes()
|
||||
r2 := bytes.NewReader(b)
|
||||
controlStr2 := mockquic.NewMockStream(mockCtrl)
|
||||
controlStr2.EXPECT().Read(gomock.Any()).DoAndReturn(r2.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream").Do(func(qerr.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr1, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return controlStr2, nil
|
||||
})
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
<-done
|
||||
return nil, errors.New("test done")
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
for _, t := range []uint64{streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream} {
|
||||
streamType := t
|
||||
name := "encoder"
|
||||
|
@ -514,9 +587,7 @@ var _ = Describe("Server", func() {
|
|||
str := mockquic.NewMockStream(mockCtrl)
|
||||
str.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
|
||||
done := make(chan struct{})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(code quic.StreamErrorCode) {
|
||||
close(done)
|
||||
})
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError)).Do(func(quic.StreamErrorCode) { close(done) })
|
||||
|
||||
conn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
|
||||
return str, nil
|
||||
|
@ -543,10 +614,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeMissingSettings))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -566,10 +636,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeFrameError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -589,10 +658,9 @@ var _ = Describe("Server", func() {
|
|||
return nil, errors.New("test done")
|
||||
})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeStreamCreationError))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -614,11 +682,9 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
conn.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false})
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, reason string) {
|
||||
defer GinkgoRecover()
|
||||
Expect(code).To(BeEquivalentTo(ErrCodeSettingsError))
|
||||
Expect(reason).To(Equal("missing QUIC Datagram support"))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support").Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -664,7 +730,7 @@ var _ = Describe("Server", func() {
|
|||
str.EXPECT().Context().Return(reqContext)
|
||||
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
|
||||
str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeNoError))
|
||||
str.EXPECT().Close().Do(func() { close(done) })
|
||||
str.EXPECT().Close().Do(func() error { close(done); return nil })
|
||||
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -739,9 +805,9 @@ var _ = Describe("Server", func() {
|
|||
}).AnyTimes()
|
||||
|
||||
done := make(chan struct{})
|
||||
conn.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ApplicationErrorCode, _ string) {
|
||||
Expect(code).To(Equal(quic.ApplicationErrorCode(ErrCodeFrameUnexpected)))
|
||||
conn.EXPECT().CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), gomock.Any()).Do(func(quic.ApplicationErrorCode, string) error {
|
||||
close(done)
|
||||
return nil
|
||||
})
|
||||
s.handleConn(conn)
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
@ -819,7 +885,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Context("setting http headers", func() {
|
||||
BeforeEach(func() {
|
||||
s.QuicConfig = &quic.Config{Versions: []protocol.VersionNumber{protocol.Version1}}
|
||||
s.QuicConfig = &quic.Config{Versions: []protocol.Version{protocol.Version1}}
|
||||
})
|
||||
|
||||
var ln1 QUICEarlyListener
|
||||
|
@ -880,7 +946,7 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
|
||||
It("works if the quic.Config sets QUIC versions", func() {
|
||||
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version2}
|
||||
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version2}
|
||||
addListener(":443", &ln1)
|
||||
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
|
||||
removeListener(&ln1)
|
||||
|
@ -919,7 +985,7 @@ var _ = Describe("Server", func() {
|
|||
})
|
||||
|
||||
It("doesn't duplicate Alt-Svc values", func() {
|
||||
s.QuicConfig.Versions = []quic.VersionNumber{quic.Version1, quic.Version1}
|
||||
s.QuicConfig.Versions = []quic.Version{quic.Version1, quic.Version1}
|
||||
addListener(":443", &ln1)
|
||||
checkSetHeaders(Equal(http.Header{"Alt-Svc": {`h3=":443"; ma=2592000`}}))
|
||||
removeListener(&ln1)
|
||||
|
@ -960,7 +1026,7 @@ var _ = Describe("Server", func() {
|
|||
Context("ConfigureTLSConfig", func() {
|
||||
It("advertises v1 by default", func() {
|
||||
conf := ConfigureTLSConfig(testdata.GetTLSConfig())
|
||||
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", conf, &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -988,7 +1054,7 @@ var _ = Describe("Server", func() {
|
|||
},
|
||||
}
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1001,7 +1067,7 @@ var _ = Describe("Server", func() {
|
|||
tlsConf := testdata.GetTLSConfig()
|
||||
tlsConf.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return nil, nil }
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1019,7 +1085,7 @@ var _ = Describe("Server", func() {
|
|||
},
|
||||
}
|
||||
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.VersionNumber{quic.Version1}})
|
||||
ln, err := quic.ListenAddr("localhost:0", ConfigureTLSConfig(tlsConf), &quic.Config{Versions: []quic.Version{quic.Version1}})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
c, err := quic.DialAddr(context.Background(), ln.Addr().String(), &tls.Config{InsecureSkipVerify: true, NextProtos: []string{NextProtoH3}}, nil)
|
||||
|
@ -1051,7 +1117,7 @@ var _ = Describe("Server", func() {
|
|||
}
|
||||
|
||||
stopAccept := make(chan struct{})
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1064,7 +1130,7 @@ var _ = Describe("Server", func() {
|
|||
}()
|
||||
|
||||
Consistently(done).ShouldNot(BeClosed())
|
||||
ln.EXPECT().Close().Do(func() { close(stopAccept) })
|
||||
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
@ -1086,13 +1152,13 @@ var _ = Describe("Server", func() {
|
|||
}
|
||||
|
||||
stopAccept1 := make(chan struct{})
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept1
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
ln1.EXPECT().Addr() // generate alt-svc headers
|
||||
stopAccept2 := make(chan struct{})
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept2
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1113,8 +1179,8 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Consistently(done1).ShouldNot(BeClosed())
|
||||
Expect(done2).ToNot(BeClosed())
|
||||
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
|
||||
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
|
||||
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
|
||||
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done1).Should(BeClosed())
|
||||
Eventually(done2).Should(BeClosed())
|
||||
|
@ -1139,7 +1205,7 @@ var _ = Describe("Server", func() {
|
|||
s := &Server{}
|
||||
|
||||
stopAccept := make(chan struct{})
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1153,7 +1219,7 @@ var _ = Describe("Server", func() {
|
|||
|
||||
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
|
||||
Consistently(done).ShouldNot(BeClosed())
|
||||
ln.EXPECT().Close().Do(func() { close(stopAccept) })
|
||||
ln.EXPECT().Close().Do(func() error { close(stopAccept); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
@ -1173,13 +1239,13 @@ var _ = Describe("Server", func() {
|
|||
s := &Server{}
|
||||
|
||||
stopAccept1 := make(chan struct{})
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln1.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept1
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
ln1.EXPECT().Addr() // generate alt-svc headers
|
||||
stopAccept2 := make(chan struct{})
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.Connection, error) {
|
||||
ln2.EXPECT().Accept(gomock.Any()).DoAndReturn(func(context.Context) (quic.EarlyConnection, error) {
|
||||
<-stopAccept2
|
||||
return nil, errors.New("closed")
|
||||
})
|
||||
|
@ -1201,8 +1267,8 @@ var _ = Describe("Server", func() {
|
|||
Consistently(func() int32 { return atomic.LoadInt32(&called) }).Should(Equal(int32(0)))
|
||||
Consistently(done1).ShouldNot(BeClosed())
|
||||
Expect(done2).ToNot(BeClosed())
|
||||
ln1.EXPECT().Close().Do(func() { close(stopAccept1) })
|
||||
ln2.EXPECT().Close().Do(func() { close(stopAccept2) })
|
||||
ln1.EXPECT().Close().Do(func() error { close(stopAccept1); return nil })
|
||||
ln2.EXPECT().Close().Do(func() error { close(stopAccept2); return nil })
|
||||
Expect(s.Close()).To(Succeed())
|
||||
Eventually(done1).Should(BeClosed())
|
||||
Eventually(done2).Should(BeClosed())
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
module test
|
||||
|
||||
go 1.16
|
||||
go 1.21
|
||||
|
||||
// The version doesn't matter here, as we're replacing it with the currently checked out code anyway.
|
||||
require github.com/refraction-networking/uquic v0.21.0
|
||||
require github.com/quic-go/quic-go v0.21.0
|
||||
|
||||
replace github.com/refraction-networking/uquic => ../../
|
||||
require (
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/quic-go/quic-go => ../../
|
||||
|
|
|
@ -1,364 +1,53 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
|
||||
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
|
||||
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
|
||||
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
|
||||
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
|
||||
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
|
||||
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
|
||||
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
|
||||
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
|
||||
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
|
|
@ -31,7 +31,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
server, err = quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var canceledCounter int32
|
||||
var canceledCounter atomic.Int32
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
var wg sync.WaitGroup
|
||||
|
@ -50,18 +50,18 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
ErrorCode: quic.StreamErrorCode(str.StreamID()),
|
||||
Remote: true,
|
||||
}))
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
return
|
||||
}
|
||||
if err := str.Close(); err != nil {
|
||||
Expect(err).To(MatchError(fmt.Sprintf("close called for canceled stream %d", str.StreamID())))
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
numCanceledStreamsChan <- atomic.LoadInt32(&canceledCounter)
|
||||
numCanceledStreamsChan <- canceledCounter.Load()
|
||||
}()
|
||||
return numCanceledStreamsChan
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var canceledCounter int32
|
||||
var canceledCounter atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numStreams)
|
||||
for i := 0; i < numStreams; i++ {
|
||||
|
@ -91,7 +91,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
// cancel around 2/3 of the streams
|
||||
if rand.Int31()%3 != 0 {
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
resetErr := quic.StreamErrorCode(str.StreamID())
|
||||
str.CancelRead(resetErr)
|
||||
_, err := str.Read([]byte{0})
|
||||
|
@ -113,7 +113,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
||||
clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
|
||||
clientCanceledCounter := canceledCounter.Load()
|
||||
// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
|
||||
Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
|
||||
fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
|
||||
|
@ -132,7 +132,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var canceledCounter int32
|
||||
var canceledCounter atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numStreams)
|
||||
for i := 0; i < numStreams; i++ {
|
||||
|
@ -148,7 +148,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
str.CancelRead(quic.StreamErrorCode(str.StreamID()))
|
||||
Expect(data).To(Equal(PRData[:length]))
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
return
|
||||
}
|
||||
data, err := io.ReadAll(str)
|
||||
|
@ -162,7 +162,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Eventually(serverCanceledCounterChan).Should(Receive(&serverCanceledCounter))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
||||
clientCanceledCounter := atomic.LoadInt32(&canceledCounter)
|
||||
clientCanceledCounter := canceledCounter.Load()
|
||||
// The server will only count a stream as being reset if learns about the cancelation before it finished writing all data.
|
||||
Expect(clientCanceledCounter).To(BeNumerically(">=", serverCanceledCounter))
|
||||
fmt.Fprintf(GinkgoWriter, "Canceled reading on %d of %d streams.\n", clientCanceledCounter, numStreams)
|
||||
|
@ -185,7 +185,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numStreams)
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
for i := 0; i < numStreams; i++ {
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -199,7 +199,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
defer close(done)
|
||||
b := make([]byte, 32)
|
||||
if _, err := str.Read(b); err != nil {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
counter.Add(1)
|
||||
Expect(err).To(Equal(&quic.StreamError{
|
||||
StreamID: str.StreamID(),
|
||||
ErrorCode: 1234,
|
||||
|
@ -214,7 +214,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}
|
||||
wg.Wait()
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
numCanceled := atomic.LoadInt32(&counter)
|
||||
numCanceled := counter.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "canceled %d out of %d streams", numCanceled, numStreams)
|
||||
Expect(numCanceled).ToNot(BeZero())
|
||||
Eventually(serverCanceledCounterChan).Should(Receive())
|
||||
|
@ -232,7 +232,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
wg.Add(numStreams)
|
||||
for i := 0; i < numStreams; i++ {
|
||||
go func() {
|
||||
|
@ -242,7 +242,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
data, err := io.ReadAll(str)
|
||||
if err != nil {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
counter.Add(1)
|
||||
Expect(err).To(MatchError(&quic.StreamError{
|
||||
StreamID: str.StreamID(),
|
||||
ErrorCode: quic.StreamErrorCode(str.StreamID()),
|
||||
|
@ -254,7 +254,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}
|
||||
wg.Wait()
|
||||
|
||||
streamCount := atomic.LoadInt32(&counter)
|
||||
streamCount := counter.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Canceled writing on %d of %d streams\n", streamCount, numStreams)
|
||||
Expect(streamCount).To(BeNumerically(">", numStreams/10))
|
||||
Expect(numStreams - streamCount).To(BeNumerically(">", numStreams/10))
|
||||
|
@ -267,7 +267,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var canceledCounter int32
|
||||
var canceledCounter atomic.Int32
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := server.Accept(context.Background())
|
||||
|
@ -280,7 +280,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
// cancel about 2/3 of the streams
|
||||
if rand.Int31()%3 != 0 {
|
||||
str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
return
|
||||
}
|
||||
_, err = str.Write(PRData)
|
||||
|
@ -291,14 +291,14 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}()
|
||||
|
||||
clientCanceledStreams := runClient(server)
|
||||
Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
|
||||
Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
|
||||
})
|
||||
|
||||
It("downloads when the server cancels some streams after sending some data", func() {
|
||||
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var canceledCounter int32
|
||||
var canceledCounter atomic.Int32
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := server.Accept(context.Background())
|
||||
|
@ -314,7 +314,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
_, err = str.Write(PRData[:length])
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str.CancelWrite(quic.StreamErrorCode(str.StreamID()))
|
||||
atomic.AddInt32(&canceledCounter, 1)
|
||||
canceledCounter.Add(1)
|
||||
return
|
||||
}
|
||||
_, err = str.Write(PRData)
|
||||
|
@ -325,7 +325,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}()
|
||||
|
||||
clientCanceledStreams := runClient(server)
|
||||
Expect(clientCanceledStreams).To(Equal(atomic.LoadInt32(&canceledCounter)))
|
||||
Expect(clientCanceledStreams).To(Equal(canceledCounter.Load()))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -378,7 +378,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
wg.Add(numStreams)
|
||||
for i := 0; i < numStreams; i++ {
|
||||
go func() {
|
||||
|
@ -399,13 +399,13 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}))
|
||||
return
|
||||
}
|
||||
atomic.AddInt32(&counter, 1)
|
||||
counter.Add(1)
|
||||
Expect(data).To(Equal(PRData))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
count := atomic.LoadInt32(&counter)
|
||||
count := counter.Load()
|
||||
Expect(count).To(BeNumerically(">", numStreams/15))
|
||||
fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
|
||||
|
||||
|
@ -464,7 +464,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
wg.Add(numStreams)
|
||||
for i := 0; i < numStreams; i++ {
|
||||
go func() {
|
||||
|
@ -495,14 +495,14 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
return
|
||||
}
|
||||
|
||||
atomic.AddInt32(&counter, 1)
|
||||
counter.Add(1)
|
||||
Expect(data).To(Equal(PRData))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
Eventually(done).Should(BeClosed())
|
||||
|
||||
count := atomic.LoadInt32(&counter)
|
||||
count := counter.Load()
|
||||
Expect(count).To(BeNumerically(">", numStreams/15))
|
||||
fmt.Fprintf(GinkgoWriter, "Successfully read from %d of %d streams.\n", count, numStreams)
|
||||
|
||||
|
@ -543,7 +543,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var numToAccept int
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numStreams)
|
||||
for numToAccept < numStreams {
|
||||
|
@ -561,7 +561,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
str, err := conn.AcceptUniStream(ctx)
|
||||
if err != nil {
|
||||
if err.Error() == "context canceled" {
|
||||
atomic.AddInt32(&counter, 1)
|
||||
counter.Add(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -573,7 +573,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}
|
||||
wg.Wait()
|
||||
|
||||
count := atomic.LoadInt32(&counter)
|
||||
count := counter.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Canceled AcceptStream %d times\n", count)
|
||||
Expect(count).To(BeNumerically(">", numStreams/2))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
@ -589,7 +589,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
msg := make(chan struct{}, 1)
|
||||
var numCanceled int32
|
||||
var numCanceled atomic.Int32
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(msg)
|
||||
|
@ -603,7 +603,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
str, err := conn.OpenUniStreamSync(ctx)
|
||||
if err != nil {
|
||||
Expect(err).To(MatchError(context.DeadlineExceeded))
|
||||
atomic.AddInt32(&numCanceled, 1)
|
||||
numCanceled.Add(1)
|
||||
select {
|
||||
case msg <- struct{}{}:
|
||||
default:
|
||||
|
@ -644,7 +644,7 @@ var _ = Describe("Stream Cancellations", func() {
|
|||
}
|
||||
wg.Wait()
|
||||
|
||||
count := atomic.LoadInt32(&numCanceled)
|
||||
count := numCanceled.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Canceled OpenStreamSync %d times\n", count)
|
||||
Expect(count).To(BeNumerically(">=", numStreams-maxIncomingStreams))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
|
|
@ -50,6 +50,7 @@ var _ = Describe("Connection ID lengths tests", func() {
|
|||
ConnectionIDLength: connIDLen,
|
||||
ConnectionIDGenerator: connIDGenerator,
|
||||
}
|
||||
addTracer(tr)
|
||||
ln, err := tr.Listen(getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go func() {
|
||||
|
@ -92,6 +93,7 @@ var _ = Describe("Connection ID lengths tests", func() {
|
|||
ConnectionIDLength: connIDLen,
|
||||
ConnectionIDGenerator: connIDGenerator,
|
||||
}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
cl, err := tr.Dial(
|
||||
context.Background(),
|
||||
|
|
|
@ -19,11 +19,12 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Datagram test", func() {
|
||||
const num = 100
|
||||
const concurrentSends = 100
|
||||
const maxDatagramSize = 250
|
||||
|
||||
var (
|
||||
serverConn, clientConn *net.UDPConn
|
||||
dropped, total int32
|
||||
dropped, total atomic.Int32
|
||||
)
|
||||
|
||||
startServerAndProxy := func(enableDatagram, expectDatagramSupport bool) (port int, closeFn func()) {
|
||||
|
@ -47,19 +48,24 @@ var _ = Describe("Datagram test", func() {
|
|||
|
||||
if expectDatagramSupport {
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
|
||||
if enableDatagram {
|
||||
f := &wire.DatagramFrame{DataLenPresent: true}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(num)
|
||||
for i := 0; i < num; i++ {
|
||||
wg.Add(concurrentSends)
|
||||
for i := 0; i < concurrentSends; i++ {
|
||||
go func(i int) {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(i))
|
||||
Expect(conn.SendMessage(b)).To(Succeed())
|
||||
Expect(conn.SendDatagram(b)).To(Succeed())
|
||||
}(i)
|
||||
}
|
||||
maxDatagramMessageSize := f.MaxDataLen(maxDatagramSize, conn.ConnectionState().Version)
|
||||
b := make([]byte, maxDatagramMessageSize+1)
|
||||
Expect(conn.SendDatagram(b)).To(MatchError(&quic.DatagramTooLargeError{
|
||||
PeerMaxDatagramFrameSize: int64(maxDatagramMessageSize),
|
||||
}))
|
||||
wg.Wait()
|
||||
}
|
||||
} else {
|
||||
|
@ -81,9 +87,9 @@ var _ = Describe("Datagram test", func() {
|
|||
}
|
||||
drop := mrand.Int()%10 == 0
|
||||
if drop {
|
||||
atomic.AddInt32(&dropped, 1)
|
||||
dropped.Add(1)
|
||||
}
|
||||
atomic.AddInt32(&total, 1)
|
||||
total.Add(1)
|
||||
return drop
|
||||
},
|
||||
})
|
||||
|
@ -103,6 +109,8 @@ var _ = Describe("Datagram test", func() {
|
|||
})
|
||||
|
||||
It("sends datagrams", func() {
|
||||
oldMaxDatagramSize := wire.MaxDatagramSize
|
||||
wire.MaxDatagramSize = maxDatagramSize
|
||||
proxyPort, close := startServerAndProxy(true, true)
|
||||
defer close()
|
||||
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
|
||||
|
@ -120,22 +128,23 @@ var _ = Describe("Datagram test", func() {
|
|||
for {
|
||||
// Close the connection if no message is received for 100 ms.
|
||||
timer := time.AfterFunc(scaleDuration(100*time.Millisecond), func() { conn.CloseWithError(0, "") })
|
||||
if _, err := conn.ReceiveMessage(context.Background()); err != nil {
|
||||
if _, err := conn.ReceiveDatagram(context.Background()); err != nil {
|
||||
break
|
||||
}
|
||||
timer.Stop()
|
||||
counter++
|
||||
}
|
||||
|
||||
numDropped := int(atomic.LoadInt32(&dropped))
|
||||
expVal := num - numDropped
|
||||
fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, atomic.LoadInt32(&total))
|
||||
fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, num)
|
||||
numDropped := int(dropped.Load())
|
||||
expVal := concurrentSends - numDropped
|
||||
fmt.Fprintf(GinkgoWriter, "Dropped %d out of %d packets.\n", numDropped, total.Load())
|
||||
fmt.Fprintf(GinkgoWriter, "Received %d out of %d sent datagrams.\n", counter, concurrentSends)
|
||||
Expect(counter).To(And(
|
||||
BeNumerically(">", expVal*9/10),
|
||||
BeNumerically("<", num),
|
||||
BeNumerically("<", concurrentSends),
|
||||
))
|
||||
Eventually(conn.Context().Done).Should(BeClosed())
|
||||
wire.MaxDatagramSize = oldMaxDatagramSize
|
||||
})
|
||||
|
||||
It("server can disable datagram", func() {
|
||||
|
@ -170,7 +179,7 @@ var _ = Describe("Datagram test", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
|
||||
|
||||
Expect(conn.SendMessage([]byte{0})).To(HaveOccurred())
|
||||
Expect(conn.SendDatagram([]byte{0})).To(HaveOccurred())
|
||||
|
||||
close()
|
||||
conn.CloseWithError(0, "")
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Stream deadline tests", func() {
|
||||
setup := func() (*quic.Listener, quic.Stream, quic.Stream) {
|
||||
setup := func() (serverStr, clientStr quic.Stream, close func()) {
|
||||
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
strChan := make(chan quic.SendStream)
|
||||
|
@ -35,19 +35,21 @@ var _ = Describe("Stream deadline tests", func() {
|
|||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
clientStr, err := conn.OpenStream()
|
||||
clientStr, err = conn.OpenStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = clientStr.Write([]byte{0}) // need to write one byte so the server learns about the stream
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
var serverStr quic.Stream
|
||||
Eventually(strChan).Should(Receive(&serverStr))
|
||||
return server, serverStr, clientStr
|
||||
return serverStr, clientStr, func() {
|
||||
Expect(server.Close()).To(Succeed())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
}
|
||||
}
|
||||
|
||||
Context("read deadlines", func() {
|
||||
It("completes a transfer when the deadline is set", func() {
|
||||
server, serverStr, clientStr := setup()
|
||||
defer server.Close()
|
||||
serverStr, clientStr, closeFn := setup()
|
||||
defer closeFn()
|
||||
|
||||
const timeout = time.Millisecond
|
||||
done := make(chan struct{})
|
||||
|
@ -81,8 +83,8 @@ var _ = Describe("Stream deadline tests", func() {
|
|||
})
|
||||
|
||||
It("completes a transfer when the deadline is set concurrently", func() {
|
||||
server, serverStr, clientStr := setup()
|
||||
defer server.Close()
|
||||
serverStr, clientStr, closeFn := setup()
|
||||
defer closeFn()
|
||||
|
||||
const timeout = time.Millisecond
|
||||
go func() {
|
||||
|
@ -131,8 +133,8 @@ var _ = Describe("Stream deadline tests", func() {
|
|||
|
||||
Context("write deadlines", func() {
|
||||
It("completes a transfer when the deadline is set", func() {
|
||||
server, serverStr, clientStr := setup()
|
||||
defer server.Close()
|
||||
serverStr, clientStr, closeFn := setup()
|
||||
defer closeFn()
|
||||
|
||||
const timeout = time.Millisecond
|
||||
done := make(chan struct{})
|
||||
|
@ -164,8 +166,8 @@ var _ = Describe("Stream deadline tests", func() {
|
|||
})
|
||||
|
||||
It("completes a transfer when the deadline is set concurrently", func() {
|
||||
server, serverStr, clientStr := setup()
|
||||
defer server.Close()
|
||||
serverStr, clientStr, closeFn := setup()
|
||||
defer closeFn()
|
||||
|
||||
const timeout = time.Millisecond
|
||||
readDone := make(chan struct{})
|
||||
|
|
|
@ -67,14 +67,14 @@ var _ = Describe("Drop Tests", func() {
|
|||
fmt.Fprintf(GinkgoWriter, "Dropping packets for %s, after a delay of %s\n", dropDuration, dropDelay)
|
||||
startTime := time.Now()
|
||||
|
||||
var numDroppedPackets int32
|
||||
var numDroppedPackets atomic.Int32
|
||||
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
if !d.Is(direction) {
|
||||
return false
|
||||
}
|
||||
drop := time.Now().After(startTime.Add(dropDelay)) && time.Now().Before(startTime.Add(dropDelay).Add(dropDuration))
|
||||
if drop {
|
||||
atomic.AddInt32(&numDroppedPackets, 1)
|
||||
numDroppedPackets.Add(1)
|
||||
}
|
||||
return drop
|
||||
})
|
||||
|
@ -114,7 +114,7 @@ var _ = Describe("Drop Tests", func() {
|
|||
Expect(b[0]).To(Equal(i))
|
||||
}
|
||||
close(done)
|
||||
numDropped := atomic.LoadInt32(&numDroppedPackets)
|
||||
numDropped := numDroppedPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Dropped %d packets.\n", numDropped)
|
||||
Expect(numDropped).To(BeNumerically(">", 0))
|
||||
})
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
//go:build go1.19 && !go1.20
|
||||
|
||||
package self_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const go120 = false
|
||||
|
||||
var errNotSupported = errors.New("not supported")
|
||||
|
||||
func setReadDeadline(w http.ResponseWriter, deadline time.Time) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
func setWriteDeadline(w http.ResponseWriter, deadline time.Time) error {
|
||||
return errNotSupported
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
//go:build go1.20
|
||||
|
||||
package self_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const go120 = true
|
||||
|
||||
func setReadDeadline(w http.ResponseWriter, deadline time.Time) error {
|
||||
rc := http.NewResponseController(w)
|
||||
|
||||
return rc.SetReadDeadline(deadline)
|
||||
}
|
||||
|
||||
func setWriteDeadline(w http.ResponseWriter, deadline time.Time) error {
|
||||
rc := http.NewResponseController(w)
|
||||
|
||||
return rc.SetWriteDeadline(deadline)
|
||||
}
|
|
@ -10,13 +10,12 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
||||
"github.com/refraction-networking/uquic/quicvarint"
|
||||
|
||||
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"
|
||||
|
@ -27,23 +26,17 @@ var directions = []quicproxy.Direction{quicproxy.DirectionIncoming, quicproxy.Di
|
|||
|
||||
type applicationProtocol struct {
|
||||
name string
|
||||
run func()
|
||||
run func(ln *quic.Listener, port int)
|
||||
}
|
||||
|
||||
var _ = Describe("Handshake drop tests", func() {
|
||||
var (
|
||||
proxy *quicproxy.QuicProxy
|
||||
ln *quic.Listener
|
||||
)
|
||||
|
||||
data := GeneratePRData(5000)
|
||||
const timeout = 2 * time.Minute
|
||||
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) {
|
||||
startListenerAndProxy := func(dropCallback quicproxy.DropCallback, doRetry bool, longCertChain bool) (ln *quic.Listener, proxyPort int, closeFn func()) {
|
||||
conf := getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
RequireAddressValidation: func(net.Addr) bool { return doRetry },
|
||||
MaxIdleTimeout: timeout,
|
||||
HandshakeIdleTimeout: timeout,
|
||||
})
|
||||
var tlsConf *tls.Config
|
||||
if longCertChain {
|
||||
|
@ -51,11 +44,18 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
} else {
|
||||
tlsConf = getTLSConfig()
|
||||
}
|
||||
var err error
|
||||
ln, err = quic.ListenAddr("localhost:0", tlsConf, conf)
|
||||
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{
|
||||
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 {
|
||||
|
@ -63,11 +63,18 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
},
|
||||
})
|
||||
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() {
|
||||
run: func(ln *quic.Listener, port int) {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -83,7 +90,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}()
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
fmt.Sprintf("localhost:%d", port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
|
@ -106,7 +113,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
serverSpeaksFirst := &applicationProtocol{
|
||||
name: "server speaks first",
|
||||
run: func() {
|
||||
run: func(ln *quic.Listener, port int) {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -121,7 +128,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}()
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
fmt.Sprintf("localhost:%d", port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
|
@ -144,7 +151,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
nobodySpeaks := &applicationProtocol{
|
||||
name: "nobody speaks",
|
||||
run: func() {
|
||||
run: func(ln *quic.Listener, port int) {
|
||||
serverConnChan := make(chan quic.Connection)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
@ -154,7 +161,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}()
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
fmt.Sprintf("localhost:%d", port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIdleTimeout: timeout,
|
||||
|
@ -170,11 +177,6 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
},
|
||||
}
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(ln.Close()).To(Succeed())
|
||||
Expect(proxy.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
for _, d := range directions {
|
||||
direction := d
|
||||
|
||||
|
@ -195,35 +197,37 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
|
||||
Context(app.name, func() {
|
||||
It(fmt.Sprintf("establishes a connection when the first packet is lost in %s direction", direction), func() {
|
||||
var incoming, outgoing int32
|
||||
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
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 = atomic.AddInt32(&incoming, 1)
|
||||
p = incoming.Add(1)
|
||||
case quicproxy.DirectionOutgoing:
|
||||
p = atomic.AddInt32(&outgoing, 1)
|
||||
p = outgoing.Add(1)
|
||||
}
|
||||
return p == 1 && d.Is(direction)
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
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 int32
|
||||
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
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 = atomic.AddInt32(&incoming, 1)
|
||||
p = incoming.Add(1)
|
||||
case quicproxy.DirectionOutgoing:
|
||||
p = atomic.AddInt32(&outgoing, 1)
|
||||
p = outgoing.Add(1)
|
||||
}
|
||||
return p == 2 && d.Is(direction)
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
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() {
|
||||
|
@ -231,7 +235,7 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
var mx sync.Mutex
|
||||
var incoming, outgoing int
|
||||
|
||||
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
drop := mrand.Int63n(int64(3)) == 0
|
||||
|
||||
mx.Lock()
|
||||
|
@ -261,7 +265,8 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
}
|
||||
return drop
|
||||
}, doRetry, longCertChain)
|
||||
app.run()
|
||||
defer closeFn()
|
||||
app.run(ln, proxyPort)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -282,13 +287,14 @@ var _ = Describe("Handshake drop tests", func() {
|
|||
uint64(27+31*(1000+mrand.Int63()/31)) % quicvarint.Max: b,
|
||||
}
|
||||
|
||||
startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
ln, proxyPort, closeFn := startListenerAndProxy(func(d quicproxy.Direction, _ []byte) bool {
|
||||
if d == quicproxy.DirectionOutgoing {
|
||||
return false
|
||||
}
|
||||
return mrand.Intn(3) == 0
|
||||
}, false, false)
|
||||
clientSpeaksFirst.run()
|
||||
defer closeFn()
|
||||
clientSpeaksFirst.run(ln, proxyPort)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -55,9 +55,19 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
|
||||
// 1 RTT for verifying the source address
|
||||
// 1 RTT for the TLS handshake
|
||||
It("is forward-secure after 2 RTTs", func() {
|
||||
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
|
||||
ln, err := quic.ListenAddr("localhost:0", serverTLSConfig, serverConfig)
|
||||
It("is forward-secure after 2 RTTs with Retry", func() {
|
||||
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
udpConn, err := net.ListenUDP("udp", laddr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer udpConn.Close()
|
||||
tr := &quic.Transport{
|
||||
Conn: udpConn,
|
||||
VerifySourceAddress: func(net.Addr) bool { return true },
|
||||
}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
ln, err := tr.Listen(serverTLSConfig, serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
|
@ -67,7 +77,10 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(nil),
|
||||
getQuicConfig(&quic.Config{GetConfigForClient: func(info *quic.ClientHelloInfo) (*quic.Config, error) {
|
||||
Expect(info.AddrVerified).To(BeTrue())
|
||||
return nil, nil
|
||||
}}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
|
@ -85,7 +98,10 @@ var _ = Describe("Handshake RTT tests", func() {
|
|||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalAddr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(nil),
|
||||
getQuicConfig(&quic.Config{GetConfigForClient: func(info *quic.ClientHelloInfo) (*quic.Config, error) {
|
||||
Expect(info.AddrVerified).To(BeFalse())
|
||||
return nil, nil
|
||||
}}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
|
|
|
@ -80,6 +80,26 @@ var _ = Describe("Handshake tests", func() {
|
|||
}()
|
||||
}
|
||||
|
||||
It("returns the context cancellation error on timeouts", func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), scaleDuration(20*time.Millisecond))
|
||||
defer cancel()
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := quic.DialAddr(
|
||||
ctx,
|
||||
"localhost:1234", // nobody is listening on this port, but we're going to cancel this dial anyway
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
errChan <- err
|
||||
}()
|
||||
|
||||
var err error
|
||||
Eventually(errChan).Should(Receive(&err))
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(context.DeadlineExceeded))
|
||||
})
|
||||
|
||||
It("returns the cancellation reason when a dial is canceled", func() {
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
errChan := make(chan error, 1)
|
||||
|
@ -150,13 +170,14 @@ var _ = Describe("Handshake tests", func() {
|
|||
Context("Certificate validation", func() {
|
||||
It("accepts the certificate", func() {
|
||||
runServer(getTLSConfig())
|
||||
_, err := quic.DialAddr(
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
conn.CloseWithError(0, "")
|
||||
})
|
||||
|
||||
It("has the right local and remote address on the tls.Config.GetConfigForClient ClientHelloInfo.Conn", func() {
|
||||
|
@ -185,6 +206,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
Eventually(done).Should(BeClosed())
|
||||
Expect(server.Addr()).To(Equal(local))
|
||||
Expect(conn.LocalAddr().(*net.UDPAddr).Port).To(Equal(remote.(*net.UDPAddr).Port))
|
||||
|
@ -194,13 +216,14 @@ var _ = Describe("Handshake tests", func() {
|
|||
|
||||
It("works with a long certificate chain", func() {
|
||||
runServer(getTLSConfigWithLongCertChain())
|
||||
_, err := quic.DialAddr(
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
conn.CloseWithError(0, "")
|
||||
})
|
||||
|
||||
It("errors if the server name doesn't match", func() {
|
||||
|
@ -276,7 +299,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("rate limiting", func() {
|
||||
Context("queuening and accepting connections", func() {
|
||||
var (
|
||||
server *quic.Listener
|
||||
pconn net.PacketConn
|
||||
|
@ -301,7 +324,10 @@ var _ = Describe("Handshake tests", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
pconn, err = net.ListenUDP("udp", laddr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
dialer = &quic.Transport{Conn: pconn, ConnectionIDLength: 4}
|
||||
dialer = &quic.Transport{
|
||||
Conn: pconn,
|
||||
ConnectionIDLength: 4,
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -318,8 +344,11 @@ var _ = Describe("Handshake tests", func() {
|
|||
}
|
||||
time.Sleep(25 * time.Millisecond) // wait a bit for the connection to be queued
|
||||
|
||||
_, err := dial()
|
||||
Expect(err).To(HaveOccurred())
|
||||
conn, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err = conn.AcceptStream(ctx)
|
||||
var transportErr *quic.TransportError
|
||||
Expect(errors.As(err, &transportErr)).To(BeTrue())
|
||||
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
|
||||
|
@ -328,18 +357,21 @@ var _ = Describe("Handshake tests", func() {
|
|||
_, err = server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// dial again, and expect that this dial succeeds
|
||||
conn, err := dial()
|
||||
conn2, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
defer conn2.CloseWithError(0, "")
|
||||
time.Sleep(25 * time.Millisecond) // wait a bit for the connection to be queued
|
||||
|
||||
_, err = dial()
|
||||
Expect(err).To(HaveOccurred())
|
||||
conn3, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err = conn3.AcceptStream(ctx)
|
||||
Expect(errors.As(err, &transportErr)).To(BeTrue())
|
||||
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
|
||||
})
|
||||
|
||||
It("removes closed connections from the accept queue", func() {
|
||||
It("also returns closed connections from the accept queue", func() {
|
||||
firstConn, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
@ -350,25 +382,79 @@ var _ = Describe("Handshake tests", func() {
|
|||
}
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
|
||||
|
||||
_, err = dial()
|
||||
Expect(err).To(HaveOccurred())
|
||||
conn, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err = conn.AcceptStream(ctx)
|
||||
var transportErr *quic.TransportError
|
||||
Expect(errors.As(err, &transportErr)).To(BeTrue())
|
||||
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
|
||||
|
||||
// Now close the one of the connection that are waiting to be accepted.
|
||||
// This should free one spot in the queue.
|
||||
Expect(firstConn.CloseWithError(0, ""))
|
||||
const appErrCode quic.ApplicationErrorCode = 12345
|
||||
Expect(firstConn.CloseWithError(appErrCode, ""))
|
||||
Eventually(firstConn.Context().Done()).Should(BeClosed())
|
||||
time.Sleep(scaleDuration(200 * time.Millisecond))
|
||||
|
||||
// dial again, and expect that this dial succeeds
|
||||
_, err = dial()
|
||||
// dial again, and expect that this fails again
|
||||
conn2, err := dial()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err = conn2.AcceptStream(ctx)
|
||||
Expect(errors.As(err, &transportErr)).To(BeTrue())
|
||||
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
|
||||
|
||||
_, err = dial()
|
||||
Expect(err).To(HaveOccurred())
|
||||
// now accept all connections
|
||||
var closedConn quic.Connection
|
||||
for i := 0; i < protocol.MaxAcceptQueueSize; i++ {
|
||||
conn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if conn.Context().Err() != nil {
|
||||
if closedConn != nil {
|
||||
Fail("only expected a single closed connection")
|
||||
}
|
||||
closedConn = conn
|
||||
}
|
||||
}
|
||||
Expect(closedConn).ToNot(BeNil()) // there should be exactly one closed connection
|
||||
_, err = closedConn.AcceptStream(context.Background())
|
||||
var appErr *quic.ApplicationError
|
||||
Expect(errors.As(err, &appErr)).To(BeTrue())
|
||||
Expect(appErr.ErrorCode).To(Equal(appErrCode))
|
||||
})
|
||||
|
||||
It("closes handshaking connections when the server is closed", func() {
|
||||
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
udpConn, err := net.ListenUDP("udp", laddr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
tr := &quic.Transport{Conn: udpConn}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
tlsConf := &tls.Config{}
|
||||
done := make(chan struct{})
|
||||
tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
<-done
|
||||
return nil, errors.New("closed")
|
||||
}
|
||||
ln, err := tr.Listen(tlsConf, getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
_, err := quic.DialAddr(ctx, ln.Addr().String(), getTLSClientConfig(), getQuicConfig(nil))
|
||||
errChan <- err
|
||||
}()
|
||||
time.Sleep(scaleDuration(20 * time.Millisecond)) // wait a bit for the connection to be queued
|
||||
Expect(ln.Close()).To(Succeed())
|
||||
close(done)
|
||||
err = <-errChan
|
||||
var transportErr *quic.TransportError
|
||||
Expect(errors.As(err, &transportErr)).To(BeTrue())
|
||||
Expect(transportErr.ErrorCode).To(Equal(quic.ConnectionRefused))
|
||||
})
|
||||
|
@ -474,14 +560,25 @@ var _ = Describe("Handshake tests", func() {
|
|||
|
||||
It("rejects invalid Retry token with the INVALID_TOKEN error", func() {
|
||||
const rtt = 10 * time.Millisecond
|
||||
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
|
||||
|
||||
// The validity period of the retry token is the handshake timeout,
|
||||
// which is twice the handshake idle timeout.
|
||||
// By setting the handshake timeout shorter than the RTT, the token will have expired by the time
|
||||
// it reaches the server.
|
||||
serverConfig.HandshakeIdleTimeout = rtt / 5
|
||||
|
||||
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), serverConfig)
|
||||
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
udpConn, err := net.ListenUDP("udp", laddr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer udpConn.Close()
|
||||
tr := &quic.Transport{
|
||||
Conn: udpConn,
|
||||
VerifySourceAddress: func(net.Addr) bool { return true },
|
||||
}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
server, err := tr.Listen(getTLSConfig(), serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer server.Close()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
type listenerWrapper struct {
|
||||
http3.QUICEarlyListener
|
||||
listenerClosed bool
|
||||
count int32
|
||||
count atomic.Int32
|
||||
}
|
||||
|
||||
func (ln *listenerWrapper) Close() error {
|
||||
|
@ -29,14 +29,18 @@ func (ln *listenerWrapper) Close() error {
|
|||
}
|
||||
|
||||
func (ln *listenerWrapper) Faker() *fakeClosingListener {
|
||||
atomic.AddInt32(&ln.count, 1)
|
||||
ln.count.Add(1)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &fakeClosingListener{ln, 0, ctx, cancel}
|
||||
return &fakeClosingListener{
|
||||
listenerWrapper: ln,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeClosingListener struct {
|
||||
*listenerWrapper
|
||||
closed int32
|
||||
closed atomic.Bool
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
@ -47,9 +51,9 @@ func (ln *fakeClosingListener) Accept(ctx context.Context) (quic.EarlyConnection
|
|||
}
|
||||
|
||||
func (ln *fakeClosingListener) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&ln.closed, 0, 1) {
|
||||
if ln.closed.CompareAndSwap(false, true) {
|
||||
ln.cancel()
|
||||
if atomic.AddInt32(&ln.listenerWrapper.count, -1) == 0 {
|
||||
if ln.listenerWrapper.count.Add(-1) == 0 {
|
||||
ln.listenerWrapper.Close()
|
||||
}
|
||||
}
|
||||
|
@ -145,8 +149,8 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
// and only the fake listener should be closed
|
||||
Expect(server1.Close()).NotTo(HaveOccurred())
|
||||
Eventually(stoppedServing1).Should(BeClosed())
|
||||
Expect(fake1.closed).To(Equal(int32(1)))
|
||||
Expect(fake2.closed).To(Equal(int32(0)))
|
||||
Expect(fake1.closed.Load()).To(BeTrue())
|
||||
Expect(fake2.closed.Load()).To(BeFalse())
|
||||
Expect(ln.listenerClosed).ToNot(BeTrue())
|
||||
Expect(client.Transport.(*http3.RoundTripper).Close()).NotTo(HaveOccurred())
|
||||
|
||||
|
@ -161,7 +165,7 @@ var _ = Describe("HTTP3 Server hotswap test", func() {
|
|||
// close the other server - both the fake and the actual listeners must close now
|
||||
Expect(server2.Close()).NotTo(HaveOccurred())
|
||||
Eventually(stoppedServing2).Should(BeClosed())
|
||||
Expect(fake2.closed).To(Equal(int32(1)))
|
||||
Expect(fake2.closed.Load()).To(BeTrue())
|
||||
Expect(ln.listenerClosed).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -140,6 +140,26 @@ var _ = Describe("HTTP tests", func() {
|
|||
Expect(resp.Header.Get("Content-Length")).To(Equal(strconv.Itoa(len("foobar"))))
|
||||
})
|
||||
|
||||
It("detects stream errors when server panics when writing response", func() {
|
||||
respChan := make(chan struct{})
|
||||
mux.HandleFunc("/writing_and_panicking", func(w http.ResponseWriter, r *http.Request) {
|
||||
// no recover here as it will interfere with the handler
|
||||
w.Write([]byte("foobar"))
|
||||
w.(http.Flusher).Flush()
|
||||
// wait for the client to receive the response
|
||||
<-respChan
|
||||
panic(http.ErrAbortHandler)
|
||||
})
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/writing_and_panicking", port))
|
||||
close(respChan)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
Expect(err).To(HaveOccurred())
|
||||
// the body will be a prefix of what's written
|
||||
Expect(bytes.HasPrefix([]byte("foobar"), body)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("requests to different servers with the same udpconn", func() {
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remoteAddr", port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -299,6 +319,21 @@ var _ = Describe("HTTP tests", func() {
|
|||
Expect(string(body)).To(Equal("Hello, World!\n"))
|
||||
})
|
||||
|
||||
It("handles context cancellations", func() {
|
||||
mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) {
|
||||
<-r.Context().Done()
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
time.AfterFunc(50*time.Millisecond, cancel)
|
||||
|
||||
_, err = client.Do(req)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(context.Canceled))
|
||||
})
|
||||
|
||||
It("cancels requests", func() {
|
||||
handlerCalled := make(chan struct{})
|
||||
mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -432,55 +467,109 @@ var _ = Describe("HTTP tests", func() {
|
|||
Eventually(done).Should(BeClosed())
|
||||
})
|
||||
|
||||
if go120 {
|
||||
It("supports read deadlines", func() {
|
||||
mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
err := setReadDeadline(w, time.Now().Add(deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
It("supports read deadlines", func() {
|
||||
mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
rc := http.NewResponseController(w)
|
||||
Expect(rc.SetReadDeadline(time.Now().Add(deadlineDelay))).To(Succeed())
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
|
||||
Expect(body).To(ContainSubstring("aa"))
|
||||
body, err := io.ReadAll(r.Body)
|
||||
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
|
||||
Expect(body).To(ContainSubstring("aa"))
|
||||
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
expectedEnd := time.Now().Add(deadlineDelay)
|
||||
resp, err := client.Post(
|
||||
fmt.Sprintf("https://localhost:%d/read-deadline", port),
|
||||
"text/plain",
|
||||
neverEnding('a'),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
|
||||
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(time.Now().After(expectedEnd)).To(BeTrue())
|
||||
Expect(string(body)).To(Equal("ok"))
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
It("supports write deadlines", func() {
|
||||
mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
err := setWriteDeadline(w, time.Now().Add(deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
expectedEnd := time.Now().Add(deadlineDelay)
|
||||
resp, err := client.Post(
|
||||
fmt.Sprintf("https://localhost:%d/read-deadline", port),
|
||||
"text/plain",
|
||||
neverEnding('a'),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
|
||||
_, err = io.Copy(w, neverEnding('a'))
|
||||
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
|
||||
})
|
||||
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(time.Now().After(expectedEnd)).To(BeTrue())
|
||||
Expect(string(body)).To(Equal("ok"))
|
||||
})
|
||||
|
||||
expectedEnd := time.Now().Add(deadlineDelay)
|
||||
It("supports write deadlines", func() {
|
||||
mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
rc := http.NewResponseController(w)
|
||||
Expect(rc.SetWriteDeadline(time.Now().Add(deadlineDelay))).To(Succeed())
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/write-deadline", port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
|
||||
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(time.Now().After(expectedEnd)).To(BeTrue())
|
||||
Expect(string(body)).To(ContainSubstring("aa"))
|
||||
_, err := io.Copy(w, neverEnding('a'))
|
||||
Expect(err).To(MatchError(os.ErrDeadlineExceeded))
|
||||
})
|
||||
}
|
||||
|
||||
expectedEnd := time.Now().Add(deadlineDelay)
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/write-deadline", port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
|
||||
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(time.Now().After(expectedEnd)).To(BeTrue())
|
||||
Expect(string(body)).To(ContainSubstring("aa"))
|
||||
})
|
||||
|
||||
It("sets remote address", func() {
|
||||
mux.HandleFunc("/remote-addr", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
_, ok := r.Context().Value(http3.RemoteAddrContextKey).(net.Addr)
|
||||
Expect(ok).To(BeTrue())
|
||||
})
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remote-addr", port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
})
|
||||
|
||||
It("sets conn context", func() {
|
||||
type ctxKey int
|
||||
server.ConnContext = func(ctx context.Context, c quic.Connection) context.Context {
|
||||
serv, ok := ctx.Value(http3.ServerContextKey).(*http3.Server)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(serv).To(Equal(server))
|
||||
|
||||
ctx = context.WithValue(ctx, ctxKey(0), "Hello")
|
||||
ctx = context.WithValue(ctx, ctxKey(1), c)
|
||||
return ctx
|
||||
}
|
||||
mux.HandleFunc("/conn-context", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer GinkgoRecover()
|
||||
v, ok := r.Context().Value(ctxKey(0)).(string)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(v).To(Equal("Hello"))
|
||||
|
||||
c, ok := r.Context().Value(ctxKey(1)).(quic.Connection)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(c).ToNot(BeNil())
|
||||
|
||||
serv, ok := r.Context().Value(http3.ServerContextKey).(*http3.Server)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(serv).To(Equal(server))
|
||||
})
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://localhost:%d/conn-context", port))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.StatusCode).To(Equal(200))
|
||||
})
|
||||
|
||||
It("checks the server's settings", func() {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/hello", port), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
testErr := errors.New("test error")
|
||||
_, err = rt.RoundTripOpt(req, http3.RoundTripOpt{CheckSettings: func(settings http3.Settings) error {
|
||||
Expect(settings.EnableExtendedConnect).To(BeTrue())
|
||||
Expect(settings.EnableDatagram).To(BeFalse())
|
||||
Expect(settings.Other).To(BeEmpty())
|
||||
return testErr
|
||||
}})
|
||||
Expect(err).To(MatchError(err))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
quic "github.com/refraction-networking/uquic"
|
||||
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/testutils"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
"github.com/refraction-networking/uquic/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -32,7 +32,7 @@ var _ = Describe("MITM test", func() {
|
|||
serverConfig *quic.Config
|
||||
)
|
||||
|
||||
startServerAndProxy := func(delayCb quicproxy.DelayCallback, dropCb quicproxy.DropCallback) (proxyPort int, closeFn func()) {
|
||||
startServerAndProxy := func(delayCb quicproxy.DelayCallback, dropCb quicproxy.DropCallback, forceAddressValidation bool) (proxyPort int, closeFn func()) {
|
||||
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
c, err := net.ListenUDP("udp", addr)
|
||||
|
@ -41,6 +41,10 @@ var _ = Describe("MITM test", func() {
|
|||
Conn: c,
|
||||
ConnectionIDLength: connIDLen,
|
||||
}
|
||||
addTracer(serverTransport)
|
||||
if forceAddressValidation {
|
||||
serverTransport.VerifySourceAddress = func(net.Addr) bool { return true }
|
||||
}
|
||||
ln, err := serverTransport.Listen(getTLSConfig(), serverConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
done := make(chan struct{})
|
||||
|
@ -83,6 +87,7 @@ var _ = Describe("MITM test", func() {
|
|||
Conn: clientUDPConn,
|
||||
ConnectionIDLength: connIDLen,
|
||||
}
|
||||
addTracer(clientTransport)
|
||||
})
|
||||
|
||||
Context("unsuccessful attacks", func() {
|
||||
|
@ -153,7 +158,7 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
|
||||
runTest := func(delayCb quicproxy.DelayCallback) {
|
||||
proxyPort, closeFn := startServerAndProxy(delayCb, nil)
|
||||
proxyPort, closeFn := startServerAndProxy(delayCb, nil, false)
|
||||
defer closeFn()
|
||||
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -196,7 +201,7 @@ var _ = Describe("MITM test", func() {
|
|||
})
|
||||
|
||||
runTest := func(dropCb quicproxy.DropCallback) {
|
||||
proxyPort, closeFn := startServerAndProxy(nil, dropCb)
|
||||
proxyPort, closeFn := startServerAndProxy(nil, dropCb, false)
|
||||
defer closeFn()
|
||||
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -244,17 +249,17 @@ var _ = Describe("MITM test", func() {
|
|||
Context("corrupting packets", func() {
|
||||
const idleTimeout = time.Second
|
||||
|
||||
var numCorrupted, numPackets int32
|
||||
var numCorrupted, numPackets atomic.Int32
|
||||
|
||||
BeforeEach(func() {
|
||||
numCorrupted = 0
|
||||
numPackets = 0
|
||||
numCorrupted.Store(0)
|
||||
numPackets.Store(0)
|
||||
serverConfig.MaxIdleTimeout = idleTimeout
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
num := atomic.LoadInt32(&numCorrupted)
|
||||
fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, atomic.LoadInt32(&numPackets))
|
||||
num := numCorrupted.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Corrupted %d of %d packets.", num, numPackets.Load())
|
||||
Expect(num).To(BeNumerically(">=", 1))
|
||||
// If the packet containing the CONNECTION_CLOSE is corrupted,
|
||||
// we have to wait for the connection to time out.
|
||||
|
@ -266,13 +271,13 @@ var _ = Describe("MITM test", func() {
|
|||
dropCb := func(dir quicproxy.Direction, raw []byte) bool {
|
||||
defer GinkgoRecover()
|
||||
if dir == quicproxy.DirectionIncoming {
|
||||
atomic.AddInt32(&numPackets, 1)
|
||||
numPackets.Add(1)
|
||||
if rand.Intn(interval) == 0 {
|
||||
pos := rand.Intn(len(raw))
|
||||
raw[pos] = byte(rand.Intn(256))
|
||||
_, err := clientTransport.WriteTo(raw, serverTransport.Conn.LocalAddr())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
atomic.AddInt32(&numCorrupted, 1)
|
||||
numCorrupted.Add(1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -286,13 +291,13 @@ var _ = Describe("MITM test", func() {
|
|||
dropCb := func(dir quicproxy.Direction, raw []byte) bool {
|
||||
defer GinkgoRecover()
|
||||
if dir == quicproxy.DirectionOutgoing {
|
||||
atomic.AddInt32(&numPackets, 1)
|
||||
numPackets.Add(1)
|
||||
if rand.Intn(interval) == 0 {
|
||||
pos := rand.Intn(len(raw))
|
||||
raw[pos] = byte(rand.Intn(256))
|
||||
_, err := serverTransport.WriteTo(raw, clientTransport.Conn.LocalAddr())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
atomic.AddInt32(&numCorrupted, 1)
|
||||
numCorrupted.Add(1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -310,17 +315,16 @@ var _ = Describe("MITM test", func() {
|
|||
|
||||
const rtt = 20 * time.Millisecond
|
||||
|
||||
runTest := func(delayCb quicproxy.DelayCallback) (closeFn func(), err error) {
|
||||
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil)
|
||||
runTest := func(proxyPort int) (closeFn func(), err error) {
|
||||
raddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", proxyPort))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = clientTransport.Dial(
|
||||
context.Background(),
|
||||
raddr,
|
||||
getTLSClientConfig(),
|
||||
getQuicConfig(&quic.Config{HandshakeIdleTimeout: 2 * time.Second}),
|
||||
getQuicConfig(&quic.Config{HandshakeIdleTimeout: scaleDuration(200 * time.Millisecond)}),
|
||||
)
|
||||
return func() { clientTransport.Close(); serverCloseFn() }, err
|
||||
return func() { clientTransport.Close() }, err
|
||||
}
|
||||
|
||||
// fails immediately because client connection closes when it can't find compatible version
|
||||
|
@ -338,7 +342,7 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
|
||||
// Create fake version negotiation packet with no supported versions
|
||||
versions := []protocol.VersionNumber{}
|
||||
versions := []protocol.Version{}
|
||||
packet := wire.ComposeVersionNegotiation(
|
||||
protocol.ArbitraryLenConnectionID(hdr.SrcConnectionID.Bytes()),
|
||||
protocol.ArbitraryLenConnectionID(hdr.DestConnectionID.Bytes()),
|
||||
|
@ -352,7 +356,9 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
return rtt / 2
|
||||
}
|
||||
closeFn, err := runTest(delayCb)
|
||||
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
|
||||
defer serverCloseFn()
|
||||
closeFn, err := runTest(proxyPort)
|
||||
defer closeFn()
|
||||
Expect(err).To(HaveOccurred())
|
||||
vnErr := &quic.VersionNegotiationError{}
|
||||
|
@ -363,8 +369,7 @@ var _ = Describe("MITM test", func() {
|
|||
// times out, because client doesn't accept subsequent real retry packets from server
|
||||
// as it has already accepted a retry.
|
||||
// TODO: determine behavior when server does not send Retry packets
|
||||
It("fails when a forged Retry packet with modified srcConnID is sent to client", func() {
|
||||
serverConfig.RequireAddressValidation = func(net.Addr) bool { return true }
|
||||
It("fails when a forged Retry packet with modified Source Connection ID is sent to client", func() {
|
||||
var initialPacketIntercepted bool
|
||||
done := make(chan struct{})
|
||||
delayCb := func(dir quicproxy.Direction, raw []byte) time.Duration {
|
||||
|
@ -388,7 +393,9 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
return rtt / 2
|
||||
}
|
||||
closeFn, err := runTest(delayCb)
|
||||
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, true)
|
||||
defer serverCloseFn()
|
||||
closeFn, err := runTest(proxyPort)
|
||||
defer closeFn()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
|
@ -412,13 +419,15 @@ var _ = Describe("MITM test", func() {
|
|||
}
|
||||
defer close(done)
|
||||
injected = true
|
||||
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.Version, hdr.DestConnectionID, nil)
|
||||
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.DestConnectionID, nil, protocol.PerspectiveServer, hdr.Version)
|
||||
_, err = serverTransport.WriteTo(initialPacket, clientTransport.Conn.LocalAddr())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
return rtt
|
||||
}
|
||||
closeFn, err := runTest(delayCb)
|
||||
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
|
||||
defer serverCloseFn()
|
||||
closeFn, err := runTest(proxyPort)
|
||||
defer closeFn()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
|
@ -442,13 +451,15 @@ var _ = Describe("MITM test", func() {
|
|||
injected = true
|
||||
// Fake Initial with ACK for packet 2 (unsent)
|
||||
ack := &wire.AckFrame{AckRanges: []wire.AckRange{{Smallest: 2, Largest: 2}}}
|
||||
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.Version, hdr.DestConnectionID, []wire.Frame{ack})
|
||||
initialPacket := testutils.ComposeInitialPacket(hdr.DestConnectionID, hdr.SrcConnectionID, hdr.DestConnectionID, []wire.Frame{ack}, protocol.PerspectiveServer, hdr.Version)
|
||||
_, err = serverTransport.WriteTo(initialPacket, clientTransport.Conn.LocalAddr())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
return rtt
|
||||
}
|
||||
closeFn, err := runTest(delayCb)
|
||||
proxyPort, serverCloseFn := startServerAndProxy(delayCb, nil, false)
|
||||
defer serverCloseFn()
|
||||
closeFn, err := runTest(proxyPort)
|
||||
defer closeFn()
|
||||
Expect(err).To(HaveOccurred())
|
||||
var transportErr *quic.TransportError
|
||||
|
|
|
@ -73,6 +73,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.Close()
|
||||
tr := &quic.Transport{Conn: conn}
|
||||
addTracer(tr)
|
||||
|
||||
done1 := make(chan struct{})
|
||||
done2 := make(chan struct{})
|
||||
|
@ -108,6 +109,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.Close()
|
||||
tr := &quic.Transport{Conn: conn}
|
||||
addTracer(tr)
|
||||
|
||||
done1 := make(chan struct{})
|
||||
done2 := make(chan struct{})
|
||||
|
@ -138,6 +140,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.Close()
|
||||
tr := &quic.Transport{Conn: conn}
|
||||
addTracer(tr)
|
||||
server, err := tr.Listen(
|
||||
getTLSConfig(),
|
||||
getQuicConfig(nil),
|
||||
|
@ -166,6 +169,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn1.Close()
|
||||
tr1 := &quic.Transport{Conn: conn1}
|
||||
addTracer(tr1)
|
||||
|
||||
addr2, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -173,6 +177,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn2.Close()
|
||||
tr2 := &quic.Transport{Conn: conn2}
|
||||
addTracer(tr2)
|
||||
|
||||
server1, err := tr1.Listen(
|
||||
getTLSConfig(),
|
||||
|
@ -219,6 +224,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn1.Close()
|
||||
tr1 := &quic.Transport{Conn: conn1}
|
||||
addTracer(tr1)
|
||||
|
||||
addr2, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -226,6 +232,7 @@ var _ = Describe("Multiplexing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn2.Close()
|
||||
tr2 := &quic.Transport{Conn: conn2}
|
||||
addTracer(tr2)
|
||||
|
||||
server, err := tr1.Listen(getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -250,6 +257,9 @@ var _ = Describe("Multiplexing", func() {
|
|||
b := make([]byte, packetLen)
|
||||
rand.Read(b[1:]) // keep the first byte set to 0, so it's not classified as a QUIC packet
|
||||
_, err := tr1.WriteTo(b, tr2.Conn.LocalAddr())
|
||||
if ctx.Err() != nil { // ctx canceled while Read was executing
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
sentPackets.Add(1)
|
||||
}
|
||||
|
|
90
integrationtests/self/qlog_dir_test.go
Normal file
90
integrationtests/self/qlog_dir_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package self_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/qlog"
|
||||
)
|
||||
|
||||
var _ = Describe("qlog dir tests", Serial, func() {
|
||||
var originalQlogDirValue string
|
||||
var tempTestDirPath string
|
||||
|
||||
BeforeEach(func() {
|
||||
originalQlogDirValue = os.Getenv("QLOGDIR")
|
||||
var err error
|
||||
tempTestDirPath, err = os.MkdirTemp("", "temp_test_dir")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
err := os.Setenv("QLOGDIR", originalQlogDirValue)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = os.RemoveAll(tempTestDirPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
handshake := func() {
|
||||
serverStopped := make(chan struct{})
|
||||
server, err := quic.ListenAddr(
|
||||
"localhost:0",
|
||||
getTLSConfig(),
|
||||
&quic.Config{
|
||||
Tracer: qlog.DefaultTracer,
|
||||
},
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(serverStopped)
|
||||
for {
|
||||
if _, err := server.Accept(context.Background()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
server.Addr().String(),
|
||||
getTLSClientConfig(),
|
||||
&quic.Config{
|
||||
Tracer: qlog.DefaultTracer,
|
||||
},
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
conn.CloseWithError(0, "")
|
||||
server.Close()
|
||||
<-serverStopped
|
||||
}
|
||||
|
||||
It("environment variable is set", func() {
|
||||
qlogDir := path.Join(tempTestDirPath, "qlogs")
|
||||
err := os.Setenv("QLOGDIR", qlogDir)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
handshake()
|
||||
_, err = os.Stat(tempTestDirPath)
|
||||
qlogDirCreated := !os.IsNotExist(err)
|
||||
Expect(qlogDirCreated).To(BeTrue())
|
||||
childs, err := os.ReadDir(qlogDir)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(childs)).To(Equal(2))
|
||||
odcids := make([]string, 0)
|
||||
vantagePoints := make([]string, 0)
|
||||
qlogFileNameRegexp := regexp.MustCompile(`^([0-f]+)_(client|server).qlog$`)
|
||||
for _, child := range childs {
|
||||
matches := qlogFileNameRegexp.FindStringSubmatch(child.Name())
|
||||
odcids = append(odcids, matches[1])
|
||||
vantagePoints = append(vantagePoints, matches[2])
|
||||
}
|
||||
Expect(odcids[0]).To(Equal(odcids[1]))
|
||||
Expect(vantagePoints).To(ContainElements("client", "server"))
|
||||
})
|
||||
})
|
|
@ -52,22 +52,23 @@ var _ = Describe("TLS session resumption", func() {
|
|||
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
|
||||
tlsConf := getTLSClientConfig()
|
||||
tlsConf.ClientSessionCache = cache
|
||||
conn, err := quic.DialAddr(
|
||||
conn1, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn1.CloseWithError(0, "")
|
||||
var sessionKey string
|
||||
Eventually(puts).Should(Receive(&sessionKey))
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
serverConn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
conn, err = quic.DialAddr(
|
||||
conn2, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
|
@ -75,11 +76,12 @@ var _ = Describe("TLS session resumption", func() {
|
|||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(gets).To(Receive(Equal(sessionKey)))
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
Expect(conn2.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
|
||||
serverConn, err = server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
conn2.CloseWithError(0, "")
|
||||
})
|
||||
|
||||
It("doesn't use session resumption, if the config disables it", func() {
|
||||
|
@ -94,15 +96,16 @@ var _ = Describe("TLS session resumption", func() {
|
|||
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
|
||||
tlsConf := getTLSClientConfig()
|
||||
tlsConf.ClientSessionCache = cache
|
||||
conn, err := quic.DialAddr(
|
||||
conn1, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn1.CloseWithError(0, "")
|
||||
Consistently(puts).ShouldNot(Receive())
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
@ -110,14 +113,15 @@ var _ = Describe("TLS session resumption", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
conn, err = quic.DialAddr(
|
||||
conn2, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
Expect(conn2.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
defer conn2.CloseWithError(0, "")
|
||||
|
||||
serverConn, err = server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -142,7 +146,7 @@ var _ = Describe("TLS session resumption", func() {
|
|||
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
|
||||
tlsConf := getTLSClientConfig()
|
||||
tlsConf.ClientSessionCache = cache
|
||||
conn, err := quic.DialAddr(
|
||||
conn1, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
|
@ -150,7 +154,8 @@ var _ = Describe("TLS session resumption", func() {
|
|||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Consistently(puts).ShouldNot(Receive())
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
defer conn1.CloseWithError(0, "")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
@ -158,14 +163,15 @@ var _ = Describe("TLS session resumption", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
conn, err = quic.DialAddr(
|
||||
conn2, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
Expect(conn2.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
defer conn2.CloseWithError(0, "")
|
||||
|
||||
serverConn, err = server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
|
|
@ -87,10 +87,9 @@ var (
|
|||
logBuf *syncedBuffer
|
||||
versionParam string
|
||||
|
||||
qlogTracer func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer
|
||||
enableQlog bool
|
||||
|
||||
version quic.VersionNumber
|
||||
version quic.Version
|
||||
tlsConfig *tls.Config
|
||||
tlsConfigLongChain *tls.Config
|
||||
tlsClientConfig *tls.Config
|
||||
|
@ -139,9 +138,6 @@ func init() {
|
|||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
if enableQlog {
|
||||
qlogTracer = tools.NewQlogger(GinkgoWriter)
|
||||
}
|
||||
switch versionParam {
|
||||
case "1":
|
||||
version = quic.Version1
|
||||
|
@ -151,7 +147,7 @@ var _ = BeforeSuite(func() {
|
|||
Fail(fmt.Sprintf("unknown QUIC version: %s", versionParam))
|
||||
}
|
||||
fmt.Printf("Using QUIC version: %s\n", version)
|
||||
protocol.SupportedVersions = []quic.VersionNumber{version}
|
||||
protocol.SupportedVersions = []quic.Version{version}
|
||||
})
|
||||
|
||||
func getTLSConfig() *tls.Config {
|
||||
|
@ -176,28 +172,48 @@ func getQuicConfig(conf *quic.Config) *quic.Config {
|
|||
} else {
|
||||
conf = conf.Clone()
|
||||
}
|
||||
if enableQlog {
|
||||
if conf.Tracer == nil {
|
||||
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return logging.NewMultiplexedConnectionTracer(
|
||||
qlogTracer(ctx, p, connID),
|
||||
// multiplex it with an empty tracer to check that we're correctly ignoring unset callbacks everywhere
|
||||
&logging.ConnectionTracer{},
|
||||
)
|
||||
}
|
||||
} else if qlogTracer != nil {
|
||||
origTracer := conf.Tracer
|
||||
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return logging.NewMultiplexedConnectionTracer(
|
||||
qlogTracer(ctx, p, connID),
|
||||
origTracer(ctx, p, connID),
|
||||
)
|
||||
}
|
||||
if !enableQlog {
|
||||
return conf
|
||||
}
|
||||
if conf.Tracer == nil {
|
||||
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return logging.NewMultiplexedConnectionTracer(
|
||||
tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID),
|
||||
// multiplex it with an empty tracer to check that we're correctly ignoring unset callbacks everywhere
|
||||
&logging.ConnectionTracer{},
|
||||
)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
origTracer := conf.Tracer
|
||||
conf.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return logging.NewMultiplexedConnectionTracer(
|
||||
tools.NewQlogConnectionTracer(GinkgoWriter)(ctx, p, connID),
|
||||
origTracer(ctx, p, connID),
|
||||
)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func addTracer(tr *quic.Transport) {
|
||||
if !enableQlog {
|
||||
return
|
||||
}
|
||||
if tr.Tracer == nil {
|
||||
tr.Tracer = logging.NewMultiplexedTracer(
|
||||
tools.QlogTracer(GinkgoWriter),
|
||||
// multiplex it with an empty tracer to check that we're correctly ignoring unset callbacks everywhere
|
||||
&logging.Tracer{},
|
||||
)
|
||||
return
|
||||
}
|
||||
origTracer := tr.Tracer
|
||||
tr.Tracer = logging.NewMultiplexedTracer(
|
||||
tools.QlogTracer(GinkgoWriter),
|
||||
origTracer,
|
||||
)
|
||||
}
|
||||
|
||||
var _ = BeforeEach(func() {
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
|
||||
|
||||
|
|
|
@ -22,12 +22,12 @@ type faultyConn struct {
|
|||
net.PacketConn
|
||||
|
||||
MaxPackets int32
|
||||
counter int32
|
||||
counter atomic.Int32
|
||||
}
|
||||
|
||||
func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(p)
|
||||
counter := atomic.AddInt32(&c.counter, 1)
|
||||
counter := c.counter.Add(1)
|
||||
if counter <= c.MaxPackets {
|
||||
return n, addr, err
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func (c *faultyConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
|||
}
|
||||
|
||||
func (c *faultyConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
counter := atomic.AddInt32(&c.counter, 1)
|
||||
counter := c.counter.Add(1)
|
||||
if counter <= c.MaxPackets {
|
||||
return c.PacketConn.WriteTo(p, addr)
|
||||
}
|
||||
|
@ -185,11 +185,13 @@ var _ = Describe("Timeout tests", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer server.Close()
|
||||
|
||||
serverConnChan := make(chan quic.Connection, 1)
|
||||
serverConnClosed := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverConnChan <- conn
|
||||
conn.AcceptStream(context.Background()) // blocks until the connection is closed
|
||||
close(serverConnClosed)
|
||||
}()
|
||||
|
@ -240,7 +242,7 @@ var _ = Describe("Timeout tests", func() {
|
|||
Consistently(serverConnClosed).ShouldNot(BeClosed())
|
||||
|
||||
// make the go routine return
|
||||
Expect(server.Close()).To(Succeed())
|
||||
(<-serverConnChan).CloseWithError(0, "")
|
||||
Eventually(serverConnClosed).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
@ -266,11 +268,13 @@ var _ = Describe("Timeout tests", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer proxy.Close()
|
||||
|
||||
serverConnChan := make(chan quic.Connection, 1)
|
||||
serverConnClosed := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverConnChan <- conn
|
||||
<-conn.Context().Done() // block until the connection is closed
|
||||
close(serverConnClosed)
|
||||
}()
|
||||
|
@ -309,7 +313,7 @@ var _ = Describe("Timeout tests", func() {
|
|||
Consistently(serverConnClosed).ShouldNot(BeClosed())
|
||||
|
||||
// make the go routine return
|
||||
Expect(server.Close()).To(Succeed())
|
||||
(<-serverConnChan).CloseWithError(0, "")
|
||||
Eventually(serverConnClosed).Should(BeClosed())
|
||||
})
|
||||
})
|
||||
|
@ -325,11 +329,13 @@ var _ = Describe("Timeout tests", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer server.Close()
|
||||
|
||||
serverConnChan := make(chan quic.Connection, 1)
|
||||
serverConnClosed := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
serverConnChan <- conn
|
||||
conn.AcceptStream(context.Background()) // blocks until the connection is closed
|
||||
close(serverConnClosed)
|
||||
}()
|
||||
|
@ -370,7 +376,7 @@ var _ = Describe("Timeout tests", func() {
|
|||
_, err = str.Write([]byte("foobar"))
|
||||
checkTimeoutError(err)
|
||||
|
||||
Expect(server.Close()).To(Succeed())
|
||||
(<-serverConnChan).CloseWithError(0, "")
|
||||
Eventually(serverConnClosed).Should(BeClosed())
|
||||
})
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Handshake tests", func() {
|
||||
var _ = Describe("Tracer tests", func() {
|
||||
addTracers := func(pers protocol.Perspective, conf *quic.Config) *quic.Config {
|
||||
enableQlog := mrand.Int()%3 != 0
|
||||
enableCustomTracer := mrand.Int()%3 != 0
|
||||
|
@ -30,10 +30,10 @@ var _ = Describe("Handshake tests", func() {
|
|||
if enableQlog {
|
||||
tracerConstructors = append(tracerConstructors, func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
if mrand.Int()%2 == 0 { // simulate that a qlog collector might only want to log some connections
|
||||
fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %x\n", p, connID)
|
||||
fmt.Fprintf(GinkgoWriter, "%s qlog tracer deciding to not trace connection %s\n", p, connID)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %x\n", p, connID)
|
||||
fmt.Fprintf(GinkgoWriter, "%s qlog tracing connection %s\n", p, connID)
|
||||
return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(&bytes.Buffer{}), io.NopCloser(nil)), p, connID)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -142,5 +142,6 @@ var _ = Describe("Unidirectional Streams", func() {
|
|||
runReceivingPeer(client)
|
||||
<-done1
|
||||
<-done2
|
||||
client.CloseWithError(0, "")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,916 +0,0 @@
|
|||
//go:build !go1.21
|
||||
|
||||
package self_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
||||
quicproxy "github.com/refraction-networking/uquic/integrationtests/tools/proxy"
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
"github.com/refraction-networking/uquic/logging"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("0-RTT", func() {
|
||||
rtt := scaleDuration(5 * time.Millisecond)
|
||||
|
||||
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
|
||||
var num0RTTPackets uint32 // to be used as an atomic
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
|
||||
for len(data) > 0 {
|
||||
if !wire.IsLongHeaderPacket(data[0]) {
|
||||
break
|
||||
}
|
||||
hdr, _, rest, err := wire.ParsePacket(data)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
atomic.AddUint32(&num0RTTPackets, 1)
|
||||
break
|
||||
}
|
||||
data = rest
|
||||
}
|
||||
return rtt / 2
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return proxy, &num0RTTPackets
|
||||
}
|
||||
|
||||
dialAndReceiveSessionTicket := func(serverConf *quic.Config) (*tls.Config, *tls.Config) {
|
||||
tlsConf := getTLSConfig()
|
||||
if serverConf == nil {
|
||||
serverConf = getQuicConfig(nil)
|
||||
}
|
||||
serverConf.Allow0RTT = true
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
serverConf,
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { return rtt / 2 },
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer proxy.Close()
|
||||
|
||||
// dial the first connection in order to receive a session ticket
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(done)
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
<-conn.Context().Done()
|
||||
}()
|
||||
|
||||
clientConf := getTLSClientConfig()
|
||||
gets := make(chan string, 100)
|
||||
puts := make(chan string, 100)
|
||||
clientConf.ClientSessionCache = newClientSessionCache(tls.NewLRUClientSessionCache(100), gets, puts)
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Eventually(puts).Should(Receive())
|
||||
// received the session ticket. We're done here.
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
Eventually(done).Should(BeClosed())
|
||||
return tlsConf, clientConf
|
||||
}
|
||||
|
||||
transfer0RTTData := func(
|
||||
ln *quic.EarlyListener,
|
||||
proxyPort int,
|
||||
connIDLen int,
|
||||
clientTLSConf *tls.Config,
|
||||
clientConf *quic.Config,
|
||||
testdata []byte, // data to transfer
|
||||
) {
|
||||
// accept the second connection, and receive the data sent in 0-RTT
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptStream(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
data, err := io.ReadAll(str)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(data).To(Equal(testdata))
|
||||
Expect(str.Close()).To(Succeed())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
<-conn.Context().Done()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
if clientConf == nil {
|
||||
clientConf = getQuicConfig(nil)
|
||||
}
|
||||
var conn quic.EarlyConnection
|
||||
if connIDLen == 0 {
|
||||
var err error
|
||||
conn, err = quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
clientTLSConf,
|
||||
clientConf,
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
} else {
|
||||
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
udpConn, err := net.ListenUDP("udp", addr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer udpConn.Close()
|
||||
tr := &quic.Transport{
|
||||
Conn: udpConn,
|
||||
ConnectionIDLength: connIDLen,
|
||||
}
|
||||
defer tr.Close()
|
||||
conn, err = tr.DialEarly(
|
||||
context.Background(),
|
||||
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: proxyPort},
|
||||
clientTLSConf,
|
||||
clientConf,
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
defer conn.CloseWithError(0, "")
|
||||
str, err := conn.OpenStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = str.Write(testdata)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
<-conn.HandshakeComplete()
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
io.ReadAll(str) // wait for the EOF from the server to arrive before closing the conn
|
||||
conn.CloseWithError(0, "")
|
||||
Eventually(done).Should(BeClosed())
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
}
|
||||
|
||||
check0RTTRejected := func(
|
||||
ln *quic.EarlyListener,
|
||||
proxyPort int,
|
||||
clientConf *tls.Config,
|
||||
) {
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxyPort),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = str.Write(make([]byte, 3000))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
|
||||
// make sure the server doesn't process the data
|
||||
ctx, cancel := context.WithTimeout(context.Background(), scaleDuration(50*time.Millisecond))
|
||||
defer cancel()
|
||||
serverConn, err := ln.Accept(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
_, err = serverConn.AcceptUniStream(ctx)
|
||||
Expect(err).To(Equal(context.DeadlineExceeded))
|
||||
Expect(serverConn.CloseWithError(0, "")).To(Succeed())
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
}
|
||||
|
||||
// can be used to extract 0-RTT from a packetCounter
|
||||
get0RTTPackets := func(packets []packet) []protocol.PacketNumber {
|
||||
var zeroRTTPackets []protocol.PacketNumber
|
||||
for _, p := range packets {
|
||||
if p.hdr.Type == protocol.PacketType0RTT {
|
||||
zeroRTTPackets = append(zeroRTTPackets, p.hdr.PacketNumber)
|
||||
}
|
||||
}
|
||||
return zeroRTTPackets
|
||||
}
|
||||
|
||||
for _, l := range []int{0, 15} {
|
||||
connIDLen := l
|
||||
|
||||
It(fmt.Sprintf("transfers 0-RTT data, with %d byte connection IDs", connIDLen), func() {
|
||||
tlsConf, clientTLSConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
transfer0RTTData(
|
||||
ln,
|
||||
proxy.LocalPort(),
|
||||
connIDLen,
|
||||
clientTLSConf,
|
||||
getQuicConfig(nil),
|
||||
PRData,
|
||||
)
|
||||
|
||||
var numNewConnIDs int
|
||||
for _, p := range counter.getRcvdLongHeaderPackets() {
|
||||
for _, f := range p.frames {
|
||||
if _, ok := f.(*logging.NewConnectionIDFrame); ok {
|
||||
numNewConnIDs++
|
||||
}
|
||||
}
|
||||
}
|
||||
if connIDLen == 0 {
|
||||
Expect(numNewConnIDs).To(BeZero())
|
||||
} else {
|
||||
Expect(numNewConnIDs).ToNot(BeZero())
|
||||
}
|
||||
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
|
||||
Expect(zeroRTTPackets).To(ContainElement(protocol.PacketNumber(0)))
|
||||
})
|
||||
}
|
||||
|
||||
// Test that data intended to be sent with 1-RTT protection is not sent in 0-RTT packets.
|
||||
It("waits for a connection until the handshake is done", func() {
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
zeroRTTData := GeneratePRData(5 << 10)
|
||||
oneRTTData := PRData
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
// now accept the second connection, and receive the 0-RTT data
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.AcceptUniStream(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
data, err := io.ReadAll(str)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(data).To(Equal(zeroRTTData))
|
||||
str, err = conn.AcceptUniStream(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
data, err = io.ReadAll(str)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(data).To(Equal(oneRTTData))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
}()
|
||||
|
||||
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
firstStr, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = firstStr.Write(zeroRTTData)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(firstStr.Close()).To(Succeed())
|
||||
|
||||
// wait for the handshake to complete
|
||||
Eventually(conn.HandshakeComplete()).Should(BeClosed())
|
||||
str, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = str.Write(PRData)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
<-conn.Context().Done()
|
||||
|
||||
// check that 0-RTT packets only contain STREAM frames for the first stream
|
||||
var num0RTT int
|
||||
for _, p := range counter.getRcvdLongHeaderPackets() {
|
||||
if p.hdr.Header.Type != protocol.PacketType0RTT {
|
||||
continue
|
||||
}
|
||||
for _, f := range p.frames {
|
||||
sf, ok := f.(*logging.StreamFrame)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
num0RTT++
|
||||
Expect(sf.StreamID).To(Equal(firstStr.StreamID()))
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(GinkgoWriter, "received %d STREAM frames in 0-RTT packets\n", 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
|
||||
)
|
||||
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
|
||||
if wire.IsLongHeaderPacket(data[0]) {
|
||||
hdr, _, _, err := wire.ParsePacket(data)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
atomic.AddUint32(&num0RTTPackets, 1)
|
||||
}
|
||||
}
|
||||
return rtt / 2
|
||||
},
|
||||
DropPacket: func(_ quicproxy.Direction, data []byte) bool {
|
||||
if !wire.IsLongHeaderPacket(data[0]) {
|
||||
return false
|
||||
}
|
||||
hdr, _, _, err := wire.ParsePacket(data)
|
||||
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()
|
||||
|
||||
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, 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())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
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 protocol.ByteCount
|
||||
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
countZeroRTTBytes := func(data []byte) (n protocol.ByteCount) {
|
||||
for len(data) > 0 {
|
||||
hdr, _, rest, err := wire.ParsePacket(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = rest
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
n += hdr.Length - 16 /* AEAD tag */
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
RequireAddressValidation: func(net.Addr) bool { return true },
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DelayPacket: func(dir quicproxy.Direction, data []byte) time.Duration {
|
||||
connID, err := wire.ParseConnectionID(data, 0)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if zeroRTTBytes := countZeroRTTBytes(data); zeroRTTBytes > 0 {
|
||||
if firstConnID == nil {
|
||||
firstConnID = &connID
|
||||
firstCounter += zeroRTTBytes
|
||||
} else if firstConnID != nil && *firstConnID == connID {
|
||||
Expect(secondConnID).To(BeNil())
|
||||
firstCounter += zeroRTTBytes
|
||||
} else if secondConnID == nil {
|
||||
secondConnID = &connID
|
||||
secondCounter += zeroRTTBytes
|
||||
} else if secondConnID != nil && *secondConnID == connID {
|
||||
secondCounter += zeroRTTBytes
|
||||
} else {
|
||||
Fail("received 3 connection IDs on 0-RTT packets")
|
||||
}
|
||||
}
|
||||
return rtt / 2
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer proxy.Close()
|
||||
|
||||
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, GeneratePRData(5000)) // ~5 packets
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
Expect(firstCounter).To(BeNumerically("~", 5000+100 /* framing overhead */, 100)) // the FIN bit might be sent extra
|
||||
Expect(secondCounter).To(BeNumerically("~", firstCounter, 20))
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
Expect(len(zeroRTTPackets)).To(BeNumerically(">=", 5))
|
||||
Expect(zeroRTTPackets[0]).To(BeNumerically(">=", protocol.PacketNumber(5)))
|
||||
})
|
||||
|
||||
It("doesn't reject 0-RTT when the server's transport stream limit increased", func() {
|
||||
const maxStreams = 1
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
|
||||
MaxIncomingUniStreams: maxStreams,
|
||||
}))
|
||||
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIncomingUniStreams: maxStreams + 1,
|
||||
Allow0RTT: true,
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = str.Write([]byte("foobar"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
// The client remembers the old limit and refuses to open a new stream.
|
||||
_, err = conn.OpenUniStream()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("too many open streams"))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
_, err = conn.OpenUniStreamSync(ctx)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
})
|
||||
|
||||
It("rejects 0-RTT when the server's stream limit decreased", func() {
|
||||
const maxStreams = 42
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
|
||||
MaxIncomingStreams: maxStreams,
|
||||
}))
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIncomingStreams: maxStreams - 1,
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("rejects 0-RTT when the ALPN changed", func() {
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
// now close the listener and dial new connection with a different ALPN
|
||||
// clientConf.NextProtos = []string{"new-alpn"}
|
||||
clientConf.NextProtos = append(clientConf.NextProtos, "new-alpn")
|
||||
tlsConf.NextProtos = []string{"new-alpn"}
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("rejects 0-RTT when the application doesn't allow it", func() {
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
// now close the listener and dial new connection with a different ALPN
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: false, // application rejects 0-RTT
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
|
||||
DescribeTable("flow control limits",
|
||||
func(addFlowControlLimit func(*quic.Config, uint64)) {
|
||||
counter, tracer := newPacketTracer()
|
||||
firstConf := getQuicConfig(&quic.Config{Allow0RTT: true})
|
||||
addFlowControlLimit(firstConf, 3)
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(firstConf)
|
||||
|
||||
secondConf := getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
})
|
||||
addFlowControlLimit(secondConf, 100)
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
secondConf,
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, _ := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
str, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
written := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(written)
|
||||
_, err := str.Write([]byte("foobar"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
}()
|
||||
|
||||
Eventually(written).Should(BeClosed())
|
||||
|
||||
serverConn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
rstr, err := serverConn.AcceptUniStream(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
data, err := io.ReadAll(rstr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(data).To(Equal([]byte("foobar")))
|
||||
Expect(serverConn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
Expect(serverConn.CloseWithError(0, "")).To(Succeed())
|
||||
Eventually(conn.Context().Done()).Should(BeClosed())
|
||||
|
||||
var processedFirst bool
|
||||
for _, p := range counter.getRcvdLongHeaderPackets() {
|
||||
for _, f := range p.frames {
|
||||
if sf, ok := f.(*logging.StreamFrame); ok {
|
||||
if !processedFirst {
|
||||
// The first STREAM should have been sent in a 0-RTT packet.
|
||||
// Due to the flow control limit, the STREAM frame was limit to the first 3 bytes.
|
||||
Expect(p.hdr.Type).To(Equal(protocol.PacketType0RTT))
|
||||
Expect(sf.Length).To(BeEquivalentTo(3))
|
||||
processedFirst = true
|
||||
} else {
|
||||
Fail("STREAM was shouldn't have been sent in 0-RTT")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Entry("doesn't reject 0-RTT when the server's transport stream flow control limit increased", func(c *quic.Config, limit uint64) { c.InitialStreamReceiveWindow = limit }),
|
||||
Entry("doesn't reject 0-RTT when the server's transport connection flow control limit increased", func(c *quic.Config, limit uint64) { c.InitialConnectionReceiveWindow = limit }),
|
||||
)
|
||||
|
||||
for _, l := range []int{0, 15} {
|
||||
connIDLen := l
|
||||
|
||||
It(fmt.Sprintf("correctly deals with 0-RTT rejections, for %d byte connection IDs", connIDLen), func() {
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
// now dial new connection with different transport parameters
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
MaxIncomingUniStreams: 1,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// The client remembers that it was allowed to open 2 uni-directional streams.
|
||||
firstStr, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
written := make(chan struct{}, 2)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer func() { written <- struct{}{} }()
|
||||
_, err := firstStr.Write([]byte("first flight"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
secondStr, err := conn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer func() { written <- struct{}{} }()
|
||||
_, err := secondStr.Write([]byte("first flight"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
_, err = conn.AcceptStream(ctx)
|
||||
Expect(err).To(MatchError(quic.Err0RTTRejected))
|
||||
Eventually(written).Should(Receive())
|
||||
Eventually(written).Should(Receive())
|
||||
_, err = firstStr.Write([]byte("foobar"))
|
||||
Expect(err).To(MatchError(quic.Err0RTTRejected))
|
||||
_, err = conn.OpenUniStream()
|
||||
Expect(err).To(MatchError(quic.Err0RTTRejected))
|
||||
|
||||
_, err = conn.AcceptStream(ctx)
|
||||
Expect(err).To(Equal(quic.Err0RTTRejected))
|
||||
|
||||
newConn := conn.NextConnection()
|
||||
str, err := newConn.OpenUniStream()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = newConn.OpenUniStream()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("too many open streams"))
|
||||
_, err = str.Write([]byte("second flight"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str.Close()).To(Succeed())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
}
|
||||
|
||||
It("queues 0-RTT packets, if the Initial is delayed", func() {
|
||||
tlsConf, clientConf := dialAndReceiveSessionTicket(nil)
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: ln.Addr().String(),
|
||||
DelayPacket: func(dir quicproxy.Direction, data []byte) time.Duration {
|
||||
if dir == quicproxy.DirectionIncoming && wire.IsLongHeaderPacket(data[0]) && data[0]&0x30>>4 == 0 { // Initial packet from client
|
||||
return rtt/2 + rtt
|
||||
}
|
||||
return rtt / 2
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer proxy.Close()
|
||||
|
||||
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, PRData)
|
||||
|
||||
Expect(counter.getRcvdLongHeaderPackets()[0].hdr.Type).To(Equal(protocol.PacketTypeInitial))
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
Expect(len(zeroRTTPackets)).To(BeNumerically(">", 10))
|
||||
Expect(zeroRTTPackets[0]).To(Equal(protocol.PacketNumber(0)))
|
||||
})
|
||||
|
||||
It("sends 0-RTT datagrams", func() {
|
||||
tlsConf, clientTLSConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
}))
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
EnableDatagrams: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
// second connection
|
||||
sentMessage := GeneratePRData(100)
|
||||
var receivedMessage []byte
|
||||
received := make(chan struct{})
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer close(received)
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
receivedMessage, err = conn.ReceiveMessage(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
}()
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientTLSConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
Expect(conn.SendMessage(sentMessage)).To(Succeed())
|
||||
<-conn.HandshakeComplete()
|
||||
<-received
|
||||
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
Expect(receivedMessage).To(Equal(sentMessage))
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
Expect(zeroRTTPackets).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("rejects 0-RTT datagrams when the server doesn't support datagrams anymore", func() {
|
||||
tlsConf, clientTLSConf := dialAndReceiveSessionTicket(getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
}))
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
Allow0RTT: true,
|
||||
EnableDatagrams: false,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
// second connection
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = conn.ReceiveMessage(context.Background())
|
||||
Expect(err.Error()).To(Equal("datagram support disabled"))
|
||||
<-conn.HandshakeComplete()
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
}()
|
||||
conn, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientTLSConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
EnableDatagrams: true,
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// the client can temporarily send datagrams but the server doesn't process them.
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
Expect(conn.SendMessage(make([]byte, 100))).To(Succeed())
|
||||
<-conn.HandshakeComplete()
|
||||
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
})
|
|
@ -1,12 +1,9 @@
|
|||
//go:build go1.21
|
||||
|
||||
package self_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -57,8 +54,8 @@ func (m metadataClientSessionCache) Put(key string, session *tls.ClientSessionSt
|
|||
var _ = Describe("0-RTT", func() {
|
||||
rtt := scaleDuration(5 * time.Millisecond)
|
||||
|
||||
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *uint32) {
|
||||
var num0RTTPackets uint32 // to be used as an atomic
|
||||
runCountingProxy := func(serverPort int) (*quicproxy.QuicProxy, *atomic.Uint32) {
|
||||
var num0RTTPackets atomic.Uint32
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", serverPort),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
|
||||
|
@ -69,7 +66,7 @@ var _ = Describe("0-RTT", func() {
|
|||
hdr, _, rest, err := wire.ParsePacket(data)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
atomic.AddUint32(&num0RTTPackets, 1)
|
||||
num0RTTPackets.Add(1)
|
||||
break
|
||||
}
|
||||
data = rest
|
||||
|
@ -179,6 +176,7 @@ var _ = Describe("0-RTT", func() {
|
|||
Conn: udpConn,
|
||||
ConnectionIDLength: connIDLen,
|
||||
}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
conn, err = tr.DialEarly(
|
||||
context.Background(),
|
||||
|
@ -290,7 +288,7 @@ var _ = Describe("0-RTT", func() {
|
|||
Expect(numNewConnIDs).ToNot(BeZero())
|
||||
}
|
||||
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
|
@ -383,10 +381,7 @@ var _ = Describe("0-RTT", func() {
|
|||
})
|
||||
|
||||
It("transfers 0-RTT data, when 0-RTT packets are lost", func() {
|
||||
var (
|
||||
num0RTTPackets uint32 // to be used as an atomic
|
||||
num0RTTDropped uint32
|
||||
)
|
||||
var num0RTTPackets, numDropped atomic.Uint32
|
||||
|
||||
tlsConf := getTLSConfig()
|
||||
clientConf := getTLSClientConfig()
|
||||
|
@ -405,17 +400,8 @@ var _ = Describe("0-RTT", func() {
|
|||
defer ln.Close()
|
||||
|
||||
proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration {
|
||||
if wire.IsLongHeaderPacket(data[0]) {
|
||||
hdr, _, _, err := wire.ParsePacket(data)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
atomic.AddUint32(&num0RTTPackets, 1)
|
||||
}
|
||||
}
|
||||
return rtt / 2
|
||||
},
|
||||
RemoteAddr: fmt.Sprintf("localhost:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { return rtt / 2 },
|
||||
DropPacket: func(_ quicproxy.Direction, data []byte) bool {
|
||||
if !wire.IsLongHeaderPacket(data[0]) {
|
||||
return false
|
||||
|
@ -423,10 +409,11 @@ var _ = Describe("0-RTT", func() {
|
|||
hdr, _, _, err := wire.ParsePacket(data)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
if hdr.Type == protocol.PacketType0RTT {
|
||||
count := num0RTTPackets.Add(1)
|
||||
// drop 25% of the 0-RTT packets
|
||||
drop := mrand.Intn(4) == 0
|
||||
drop := count%4 == 0
|
||||
if drop {
|
||||
atomic.AddUint32(&num0RTTDropped, 1)
|
||||
numDropped.Add(1)
|
||||
}
|
||||
return drop
|
||||
}
|
||||
|
@ -438,10 +425,9 @@ var _ = Describe("0-RTT", func() {
|
|||
|
||||
transfer0RTTData(ln, proxy.LocalPort(), protocol.DefaultConnectionIDLength, clientConf, nil, 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())
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets. Dropped %d of those.", num0RTT, numDropped.Load())
|
||||
Expect(numDropped.Load()).ToNot(BeZero())
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).ToNot(BeEmpty())
|
||||
})
|
||||
|
@ -470,14 +456,20 @@ var _ = Describe("0-RTT", func() {
|
|||
}
|
||||
|
||||
counter, tracer := newPacketTracer()
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
laddr, err := net.ResolveUDPAddr("udp", "localhost:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
udpConn, err := net.ListenUDP("udp", laddr)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer udpConn.Close()
|
||||
tr := &quic.Transport{
|
||||
Conn: udpConn,
|
||||
VerifySourceAddress: func(net.Addr) bool { return true },
|
||||
}
|
||||
addTracer(tr)
|
||||
defer tr.Close()
|
||||
ln, err := tr.ListenEarly(
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{
|
||||
RequireAddressValidation: func(net.Addr) bool { return true },
|
||||
Allow0RTT: true,
|
||||
Tracer: newTracer(tracer),
|
||||
}),
|
||||
getQuicConfig(&quic.Config{Allow0RTT: true, Tracer: newTracer(tracer)}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
@ -524,6 +516,39 @@ var _ = Describe("0-RTT", func() {
|
|||
Expect(zeroRTTPackets[0]).To(BeNumerically(">=", protocol.PacketNumber(5)))
|
||||
})
|
||||
|
||||
It("doesn't use 0-RTT when Dial is used for the resumed connection", func() {
|
||||
tlsConf := getTLSConfig()
|
||||
clientConf := getTLSClientConfig()
|
||||
dialAndReceiveSessionTicket(tlsConf, getQuicConfig(nil), clientConf)
|
||||
|
||||
ln, err := quic.ListenAddrEarly(
|
||||
"localhost:0",
|
||||
tlsConf,
|
||||
getQuicConfig(&quic.Config{Allow0RTT: true}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
proxy, num0RTTPackets := runCountingProxy(ln.Addr().(*net.UDPAddr).Port)
|
||||
defer proxy.Close()
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", proxy.LocalPort()),
|
||||
clientConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn.CloseWithError(0, "")
|
||||
Expect(conn.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
Expect(num0RTTPackets.Load()).To(BeZero())
|
||||
|
||||
serverConn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
})
|
||||
|
||||
It("doesn't reject 0-RTT when the server's transport stream limit increased", func() {
|
||||
const maxStreams = 1
|
||||
tlsConf := getTLSConfig()
|
||||
|
@ -595,7 +620,7 @@ var _ = Describe("0-RTT", func() {
|
|||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
|
@ -628,7 +653,7 @@ var _ = Describe("0-RTT", func() {
|
|||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
|
@ -657,12 +682,55 @@ var _ = Describe("0-RTT", func() {
|
|||
check0RTTRejected(ln, proxy.LocalPort(), clientConf)
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("doesn't use 0-RTT, if the server didn't enable it", func() {
|
||||
server, err := quic.ListenAddr("localhost:0", getTLSConfig(), getQuicConfig(nil))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer server.Close()
|
||||
|
||||
gets := make(chan string, 100)
|
||||
puts := make(chan string, 100)
|
||||
cache := newClientSessionCache(tls.NewLRUClientSessionCache(10), gets, puts)
|
||||
tlsConf := getTLSClientConfig()
|
||||
tlsConf.ClientSessionCache = cache
|
||||
conn1, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer conn1.CloseWithError(0, "")
|
||||
var sessionKey string
|
||||
Eventually(puts).Should(Receive(&sessionKey))
|
||||
Expect(conn1.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
serverConn, err := server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeFalse())
|
||||
|
||||
conn2, err := quic.DialAddrEarly(
|
||||
context.Background(),
|
||||
fmt.Sprintf("localhost:%d", server.Addr().(*net.UDPAddr).Port),
|
||||
tlsConf,
|
||||
getQuicConfig(nil),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(gets).To(Receive(Equal(sessionKey)))
|
||||
Expect(conn2.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
|
||||
serverConn, err = server.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(serverConn.ConnectionState().TLS.DidResume).To(BeTrue())
|
||||
Expect(serverConn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
conn2.CloseWithError(0, "")
|
||||
})
|
||||
|
||||
DescribeTable("flow control limits",
|
||||
func(addFlowControlLimit func(*quic.Config, uint64)) {
|
||||
counter, tracer := newPacketTracer()
|
||||
|
@ -813,7 +881,7 @@ var _ = Describe("0-RTT", func() {
|
|||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
|
||||
// The client should send 0-RTT packets, but the server doesn't process them.
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
|
@ -961,7 +1029,7 @@ var _ = Describe("0-RTT", func() {
|
|||
defer close(received)
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
receivedMessage, err = conn.ReceiveMessage(context.Background())
|
||||
receivedMessage, err = conn.ReceiveDatagram(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
}()
|
||||
|
@ -975,14 +1043,14 @@ var _ = Describe("0-RTT", func() {
|
|||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
Expect(conn.SendMessage(sentMessage)).To(Succeed())
|
||||
Expect(conn.SendDatagram(sentMessage)).To(Succeed())
|
||||
<-conn.HandshakeComplete()
|
||||
<-received
|
||||
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeTrue())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
Expect(receivedMessage).To(Equal(sentMessage))
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
zeroRTTPackets := get0RTTPackets(counter.getRcvdLongHeaderPackets())
|
||||
|
@ -1017,7 +1085,7 @@ var _ = Describe("0-RTT", func() {
|
|||
defer GinkgoRecover()
|
||||
conn, err := ln.Accept(context.Background())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = conn.ReceiveMessage(context.Background())
|
||||
_, err = conn.ReceiveDatagram(context.Background())
|
||||
Expect(err.Error()).To(Equal("datagram support disabled"))
|
||||
<-conn.HandshakeComplete()
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
|
@ -1033,13 +1101,13 @@ var _ = Describe("0-RTT", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
// the client can temporarily send datagrams but the server doesn't process them.
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeTrue())
|
||||
Expect(conn.SendMessage(make([]byte, 100))).To(Succeed())
|
||||
Expect(conn.SendDatagram(make([]byte, 100))).To(Succeed())
|
||||
<-conn.HandshakeComplete()
|
||||
|
||||
Expect(conn.ConnectionState().SupportsDatagrams).To(BeFalse())
|
||||
Expect(conn.ConnectionState().Used0RTT).To(BeFalse())
|
||||
Expect(conn.CloseWithError(0, "")).To(Succeed())
|
||||
num0RTT := atomic.LoadUint32(num0RTTPackets)
|
||||
num0RTT := num0RTTPackets.Load()
|
||||
fmt.Fprintf(GinkgoWriter, "Sent %d 0-RTT packets.", num0RTT)
|
||||
Expect(num0RTT).ToNot(BeZero())
|
||||
Expect(get0RTTPackets(counter.getRcvdLongHeaderPackets())).To(BeEmpty())
|
||||
|
|
|
@ -141,7 +141,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
Context("Proxy tests", func() {
|
||||
var (
|
||||
serverConn *net.UDPConn
|
||||
serverNumPacketsSent int32
|
||||
serverNumPacketsSent atomic.Int32
|
||||
serverReceivedPackets chan packetData
|
||||
clientConn *net.UDPConn
|
||||
proxy *QuicProxy
|
||||
|
@ -159,9 +159,9 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
BeforeEach(func() {
|
||||
stoppedReading = make(chan struct{})
|
||||
serverReceivedPackets = make(chan packetData, 100)
|
||||
atomic.StoreInt32(&serverNumPacketsSent, 0)
|
||||
serverNumPacketsSent.Store(0)
|
||||
|
||||
// setup a dump UDP server
|
||||
// set up a dump UDP server
|
||||
// in production this would be a QUIC server
|
||||
raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -181,7 +181,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
data := buf[0:n]
|
||||
serverReceivedPackets <- packetData(data)
|
||||
// echo the packet
|
||||
atomic.AddInt32(&serverNumPacketsSent, 1)
|
||||
serverNumPacketsSent.Add(1)
|
||||
serverConn.WriteToUDP(data, addr)
|
||||
}
|
||||
}()
|
||||
|
@ -236,7 +236,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
}()
|
||||
|
||||
Eventually(serverReceivedPackets).Should(HaveLen(2))
|
||||
Expect(atomic.LoadInt32(&serverNumPacketsSent)).To(BeEquivalentTo(2))
|
||||
Expect(serverNumPacketsSent.Load()).To(BeEquivalentTo(2))
|
||||
Eventually(clientReceivedPackets).Should(HaveLen(2))
|
||||
Expect(string(<-clientReceivedPackets)).To(ContainSubstring("foobar"))
|
||||
Expect(string(<-clientReceivedPackets)).To(ContainSubstring("decafbad"))
|
||||
|
@ -245,14 +245,14 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
|
||||
Context("Drop Callbacks", func() {
|
||||
It("drops incoming packets", func() {
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
opts := &Opts{
|
||||
RemoteAddr: serverConn.LocalAddr().String(),
|
||||
DropPacket: func(d Direction, _ []byte) bool {
|
||||
if d != DirectionIncoming {
|
||||
return false
|
||||
}
|
||||
return atomic.AddInt32(&counter, 1)%2 == 1
|
||||
return counter.Add(1)%2 == 1
|
||||
},
|
||||
}
|
||||
startProxy(opts)
|
||||
|
@ -267,14 +267,14 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
|
||||
It("drops outgoing packets", func() {
|
||||
const numPackets = 6
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
opts := &Opts{
|
||||
RemoteAddr: serverConn.LocalAddr().String(),
|
||||
DropPacket: func(d Direction, _ []byte) bool {
|
||||
if d != DirectionOutgoing {
|
||||
return false
|
||||
}
|
||||
return atomic.AddInt32(&counter, 1)%2 == 1
|
||||
return counter.Add(1)%2 == 1
|
||||
},
|
||||
}
|
||||
startProxy(opts)
|
||||
|
@ -315,7 +315,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
}
|
||||
|
||||
It("delays incoming packets", func() {
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
opts := &Opts{
|
||||
RemoteAddr: serverConn.LocalAddr().String(),
|
||||
// delay packet 1 by 200 ms
|
||||
|
@ -325,7 +325,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
if d == DirectionOutgoing {
|
||||
return 0
|
||||
}
|
||||
p := atomic.AddInt32(&counter, 1)
|
||||
p := counter.Add(1)
|
||||
return time.Duration(p) * delay
|
||||
},
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
})
|
||||
|
||||
It("handles reordered packets", func() {
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
opts := &Opts{
|
||||
RemoteAddr: serverConn.LocalAddr().String(),
|
||||
// delay packet 1 by 600 ms
|
||||
|
@ -359,7 +359,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
if d == DirectionOutgoing {
|
||||
return 0
|
||||
}
|
||||
p := atomic.AddInt32(&counter, 1)
|
||||
p := counter.Add(1)
|
||||
return 600*time.Millisecond - time.Duration(p-1)*delay
|
||||
},
|
||||
}
|
||||
|
@ -407,7 +407,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
|
||||
It("delays outgoing packets", func() {
|
||||
const numPackets = 3
|
||||
var counter int32
|
||||
var counter atomic.Int32
|
||||
opts := &Opts{
|
||||
RemoteAddr: serverConn.LocalAddr().String(),
|
||||
// delay packet 1 by 200 ms
|
||||
|
@ -417,7 +417,7 @@ var _ = Describe("QUIC Proxy", func() {
|
|||
if d == DirectionIncoming {
|
||||
return 0
|
||||
}
|
||||
p := atomic.AddInt32(&counter, 1)
|
||||
p := counter.Add(1)
|
||||
return time.Duration(p) * delay
|
||||
},
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
quic "github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
|
@ -14,13 +15,21 @@ import (
|
|||
"github.com/refraction-networking/uquic/qlog"
|
||||
)
|
||||
|
||||
func NewQlogger(logger io.Writer) func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
|
||||
func QlogTracer(logger io.Writer) *logging.Tracer {
|
||||
filename := fmt.Sprintf("log_%s_transport.qlog", time.Now().Format("2006-01-02T15:04:05"))
|
||||
fmt.Fprintf(logger, "Creating %s.\n", filename)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create qlog file: %s", err)
|
||||
return nil
|
||||
}
|
||||
bw := bufio.NewWriter(f)
|
||||
return qlog.NewTracer(utils.NewBufferedWriteCloser(bw, f))
|
||||
}
|
||||
|
||||
func NewQlogConnectionTracer(logger io.Writer) func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
|
||||
role := "server"
|
||||
if p == logging.PerspectiveClient {
|
||||
role = "client"
|
||||
}
|
||||
filename := fmt.Sprintf("log_%x_%s.qlog", connID.Bytes(), role)
|
||||
filename := fmt.Sprintf("log_%s_%s.qlog", connID, p.String())
|
||||
fmt.Fprintf(logger, "Creating %s.\n", filename)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
type versioner interface {
|
||||
GetVersion() protocol.VersionNumber
|
||||
GetVersion() protocol.Version
|
||||
}
|
||||
|
||||
type result struct {
|
||||
|
@ -69,11 +69,11 @@ var _ = Describe("Handshake tests", func() {
|
|||
}
|
||||
}
|
||||
|
||||
var supportedVersions []protocol.VersionNumber
|
||||
var supportedVersions []protocol.Version
|
||||
|
||||
BeforeEach(func() {
|
||||
supportedVersions = append([]quic.VersionNumber{}, protocol.SupportedVersions...)
|
||||
protocol.SupportedVersions = append(protocol.SupportedVersions, []protocol.VersionNumber{7, 8, 9, 10}...)
|
||||
supportedVersions = append([]quic.Version{}, protocol.SupportedVersions...)
|
||||
protocol.SupportedVersions = append(protocol.SupportedVersions, []protocol.Version{7, 8, 9, 10}...)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
@ -86,7 +86,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
// the server doesn't support the highest supported version, which is the first one the client will try
|
||||
// but it supports a bunch of versions that the client doesn't speak
|
||||
serverConfig := &quic.Config{}
|
||||
serverConfig.Versions = []protocol.VersionNumber{7, 8, protocol.SupportedVersions[0], 9}
|
||||
serverConfig.Versions = []protocol.Version{7, 8, protocol.SupportedVersions[0], 9}
|
||||
serverResult, serverTracer := newVersionNegotiationTracer()
|
||||
serverConfig.Tracer = func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
|
||||
return serverTracer
|
||||
|
@ -126,7 +126,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
}
|
||||
server, cl := startServer(getTLSConfig(), serverConfig)
|
||||
defer cl()
|
||||
clientVersions := []protocol.VersionNumber{7, 8, 9, protocol.SupportedVersions[0], 10}
|
||||
clientVersions := []protocol.Version{7, 8, 9, protocol.SupportedVersions[0], 10}
|
||||
clientResult, clientTracer := newVersionNegotiationTracer()
|
||||
conn, err := quic.DialAddr(
|
||||
context.Background(),
|
||||
|
@ -170,7 +170,7 @@ var _ = Describe("Handshake tests", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
defer ln.Close()
|
||||
|
||||
clientVersions := []protocol.VersionNumber{7, 8, 9, protocol.SupportedVersions[0], 10}
|
||||
clientVersions := []protocol.Version{7, 8, 9, protocol.SupportedVersions[0], 10}
|
||||
clientResult, clientTracer := newVersionNegotiationTracer()
|
||||
_, err = quic.DialAddr(
|
||||
context.Background(),
|
||||
|
|
|
@ -65,7 +65,7 @@ func maybeAddQLOGTracer(c *quic.Config) *quic.Config {
|
|||
if !enableQlog {
|
||||
return c
|
||||
}
|
||||
qlogger := tools.NewQlogger(GinkgoWriter)
|
||||
qlogger := tools.NewQlogConnectionTracer(GinkgoWriter)
|
||||
if c.Tracer == nil {
|
||||
c.Tracer = qlogger
|
||||
} else if qlogger != nil {
|
||||
|
|
39
interface.go
39
interface.go
|
@ -17,8 +17,12 @@ import (
|
|||
// The StreamID is the ID of a QUIC stream.
|
||||
type StreamID = protocol.StreamID
|
||||
|
||||
// A Version is a QUIC version number.
|
||||
type Version = protocol.Version
|
||||
|
||||
// A VersionNumber is a QUIC version number.
|
||||
type VersionNumber = protocol.VersionNumber
|
||||
// Deprecated: VersionNumber was renamed to Version.
|
||||
type VersionNumber = Version
|
||||
|
||||
const (
|
||||
// Version1 is RFC 9000
|
||||
|
@ -160,6 +164,9 @@ type Connection interface {
|
|||
OpenStream() (Stream, error)
|
||||
// OpenStreamSync opens a new bidirectional QUIC stream.
|
||||
// It blocks until a new stream can be opened.
|
||||
// There is no signaling to the peer about new streams:
|
||||
// The peer can only accept the stream after data has been sent on the stream,
|
||||
// or the stream has been reset or closed.
|
||||
// If the error is non-nil, it satisfies the net.Error interface.
|
||||
// If the connection was closed due to a timeout, Timeout() will be true.
|
||||
OpenStreamSync(context.Context) (Stream, error)
|
||||
|
@ -188,10 +195,14 @@ type Connection interface {
|
|||
// Warning: This API should not be considered stable and might change soon.
|
||||
ConnectionState() ConnectionState
|
||||
|
||||
// SendMessage sends a message as a datagram, as specified in RFC 9221.
|
||||
SendMessage([]byte) error
|
||||
// ReceiveMessage gets a message received in a datagram, as specified in RFC 9221.
|
||||
ReceiveMessage(context.Context) ([]byte, error)
|
||||
// SendDatagram sends a message using a QUIC datagram, as specified in RFC 9221.
|
||||
// There is no delivery guarantee for DATAGRAM frames, they are not retransmitted if lost.
|
||||
// The payload of the datagram needs to fit into a single QUIC packet.
|
||||
// In addition, a datagram may be dropped before being sent out if the available packet size suddenly decreases.
|
||||
// If the payload is too large to be sent at the current time, a DatagramTooLargeError is returned.
|
||||
SendDatagram(payload []byte) error
|
||||
// ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221.
|
||||
ReceiveDatagram(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// An EarlyConnection is a connection that is handshaking.
|
||||
|
@ -252,7 +263,7 @@ type Config struct {
|
|||
GetConfigForClient func(info *ClientHelloInfo) (*Config, error)
|
||||
// The QUIC versions that can be negotiated.
|
||||
// If not set, it uses all versions available.
|
||||
Versions []VersionNumber
|
||||
Versions []Version
|
||||
// HandshakeIdleTimeout is the idle timeout before completion of the handshake.
|
||||
// If we don't receive any packet from the peer within this time, the connection attempt is aborted.
|
||||
// Additionally, if the handshake doesn't complete in twice this time, the connection attempt is also aborted.
|
||||
|
@ -264,11 +275,6 @@ type Config struct {
|
|||
// If the timeout is exceeded, the connection is closed.
|
||||
// If this value is zero, the timeout is set to 30 seconds.
|
||||
MaxIdleTimeout time.Duration
|
||||
// RequireAddressValidation determines if a QUIC Retry packet is sent.
|
||||
// This allows the server to verify the client's address, at the cost of increasing the handshake latency by 1 RTT.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc9000#section-8 for details.
|
||||
// If not set, every client is forced to prove its remote address.
|
||||
RequireAddressValidation func(net.Addr) bool
|
||||
// The TokenStore stores tokens received from the server.
|
||||
// Tokens are used to skip address validation on future connection attempts.
|
||||
// The key used to store tokens is the ServerName from the tls.Config, if set
|
||||
|
@ -328,8 +334,15 @@ type Config struct {
|
|||
Tracer func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer
|
||||
}
|
||||
|
||||
// ClientHelloInfo contains information about an incoming connection attempt.
|
||||
type ClientHelloInfo struct {
|
||||
// RemoteAddr is the remote address on the Initial packet.
|
||||
// Unless AddrVerified is set, the address is not yet verified, and could be a spoofed IP address.
|
||||
RemoteAddr net.Addr
|
||||
// AddrVerified says if the remote address was verified using QUIC's Retry mechanism.
|
||||
// Note that the Retry mechanism costs one network roundtrip,
|
||||
// and is not performed unless Transport.MaxUnvalidatedHandshakes is surpassed.
|
||||
AddrVerified bool
|
||||
}
|
||||
|
||||
// ConnectionState records basic details about a QUIC connection
|
||||
|
@ -339,12 +352,12 @@ type ConnectionState struct {
|
|||
// SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated.
|
||||
// This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams).
|
||||
// If datagram support was negotiated, datagrams can be sent and received using the
|
||||
// SendMessage and ReceiveMessage methods on the Connection.
|
||||
// SendDatagram and ReceiveDatagram methods on the Connection.
|
||||
SupportsDatagrams bool
|
||||
// Used0RTT says if 0-RTT resumption was used.
|
||||
Used0RTT bool
|
||||
// Version is the QUIC version of the QUIC connection.
|
||||
Version VersionNumber
|
||||
Version Version
|
||||
// GSO says if generic segmentation offload is used
|
||||
GSO bool
|
||||
}
|
||||
|
|
|
@ -20,5 +20,5 @@ func NewAckHandler(
|
|||
logger utils.Logger,
|
||||
) (SentPacketHandler, ReceivedPacketHandler) {
|
||||
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, clientAddressValidated, enableECN, pers, tracer, logger)
|
||||
return sph, newReceivedPacketHandler(sph, rttStats, logger)
|
||||
return sph, newReceivedPacketHandler(sph, logger)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//
|
||||
// mockgen -build_flags=-tags=gomock -package ackhandler -destination mock_ecn_handler_test.go github.com/refraction-networking/uquic/internal/ackhandler ECNHandler
|
||||
//
|
||||
|
||||
// Package ackhandler is a generated GoMock package.
|
||||
package ackhandler
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -build_flags=-tags=gomock -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/refraction-networking/uquic/internal/ackhandler SentPacketTracker
|
||||
// mockgen -typed -build_flags=-tags=gomock -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker
|
||||
//
|
||||
|
||||
// Package ackhandler is a generated GoMock package.
|
||||
package ackhandler
|
||||
|
||||
|
@ -47,9 +48,33 @@ func (m *MockSentPacketTracker) GetLowestPacketNotConfirmedAcked() protocol.Pack
|
|||
}
|
||||
|
||||
// GetLowestPacketNotConfirmedAcked indicates an expected call of GetLowestPacketNotConfirmedAcked.
|
||||
func (mr *MockSentPacketTrackerMockRecorder) GetLowestPacketNotConfirmedAcked() *gomock.Call {
|
||||
func (mr *MockSentPacketTrackerMockRecorder) GetLowestPacketNotConfirmedAcked() *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLowestPacketNotConfirmedAcked", reflect.TypeOf((*MockSentPacketTracker)(nil).GetLowestPacketNotConfirmedAcked))
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLowestPacketNotConfirmedAcked", reflect.TypeOf((*MockSentPacketTracker)(nil).GetLowestPacketNotConfirmedAcked))
|
||||
return &MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall wrap *gomock.Call
|
||||
type MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall) Return(arg0 protocol.PacketNumber) *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall) Do(f func() protocol.PacketNumber) *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall) DoAndReturn(f func() protocol.PacketNumber) *MockSentPacketTrackerGetLowestPacketNotConfirmedAckedCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ReceivedPacket mocks base method.
|
||||
|
@ -59,7 +84,31 @@ func (m *MockSentPacketTracker) ReceivedPacket(arg0 protocol.EncryptionLevel) {
|
|||
}
|
||||
|
||||
// ReceivedPacket indicates an expected call of ReceivedPacket.
|
||||
func (mr *MockSentPacketTrackerMockRecorder) ReceivedPacket(arg0 any) *gomock.Call {
|
||||
func (mr *MockSentPacketTrackerMockRecorder) ReceivedPacket(arg0 any) *MockSentPacketTrackerReceivedPacketCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedPacket", reflect.TypeOf((*MockSentPacketTracker)(nil).ReceivedPacket), arg0)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedPacket", reflect.TypeOf((*MockSentPacketTracker)(nil).ReceivedPacket), arg0)
|
||||
return &MockSentPacketTrackerReceivedPacketCall{Call: call}
|
||||
}
|
||||
|
||||
// MockSentPacketTrackerReceivedPacketCall wrap *gomock.Call
|
||||
type MockSentPacketTrackerReceivedPacketCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockSentPacketTrackerReceivedPacketCall) Return() *MockSentPacketTrackerReceivedPacketCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockSentPacketTrackerReceivedPacketCall) Do(f func(protocol.EncryptionLevel)) *MockSentPacketTrackerReceivedPacketCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockSentPacketTrackerReceivedPacketCall) DoAndReturn(f func(protocol.EncryptionLevel)) *MockSentPacketTrackerReceivedPacketCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package ackhandler
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/refraction-networking/uquic/internal/ackhandler SentPacketTracker"
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_sent_packet_tracker_test.go github.com/quic-go/quic-go/internal/ackhandler SentPacketTracker"
|
||||
type SentPacketTracker = sentPacketTracker
|
||||
|
||||
//go:generate sh -c "go run go.uber.org/mock/mockgen -build_flags=\"-tags=gomock\" -package ackhandler -destination mock_ecn_handler_test.go github.com/refraction-networking/uquic/internal/ackhandler ECNHandler"
|
||||
|
|
|
@ -80,5 +80,5 @@ func (p *skippingPacketNumberGenerator) Pop() (bool, protocol.PacketNumber) {
|
|||
func (p *skippingPacketNumberGenerator) generateNewSkip() {
|
||||
// make sure that there are never two consecutive packet numbers that are skipped
|
||||
p.nextToSkip = p.next + 3 + protocol.PacketNumber(p.rng.Int31n(int32(2*p.period)))
|
||||
p.period = utils.Min(2*p.period, p.maxPeriod)
|
||||
p.period = min(2*p.period, p.maxPeriod)
|
||||
}
|
||||
|
|
|
@ -14,23 +14,19 @@ type receivedPacketHandler struct {
|
|||
|
||||
initialPackets *receivedPacketTracker
|
||||
handshakePackets *receivedPacketTracker
|
||||
appDataPackets *receivedPacketTracker
|
||||
appDataPackets appDataReceivedPacketTracker
|
||||
|
||||
lowest1RTTPacket protocol.PacketNumber
|
||||
}
|
||||
|
||||
var _ ReceivedPacketHandler = &receivedPacketHandler{}
|
||||
|
||||
func newReceivedPacketHandler(
|
||||
sentPackets sentPacketTracker,
|
||||
rttStats *utils.RTTStats,
|
||||
logger utils.Logger,
|
||||
) ReceivedPacketHandler {
|
||||
func newReceivedPacketHandler(sentPackets sentPacketTracker, logger utils.Logger) ReceivedPacketHandler {
|
||||
return &receivedPacketHandler{
|
||||
sentPackets: sentPackets,
|
||||
initialPackets: newReceivedPacketTracker(rttStats, logger),
|
||||
handshakePackets: newReceivedPacketTracker(rttStats, logger),
|
||||
appDataPackets: newReceivedPacketTracker(rttStats, logger),
|
||||
initialPackets: newReceivedPacketTracker(),
|
||||
handshakePackets: newReceivedPacketTracker(),
|
||||
appDataPackets: *newAppDataReceivedPacketTracker(logger),
|
||||
lowest1RTTPacket: protocol.InvalidPacketNumber,
|
||||
}
|
||||
}
|
||||
|
@ -88,41 +84,28 @@ func (h *receivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) {
|
|||
}
|
||||
|
||||
func (h *receivedPacketHandler) GetAlarmTimeout() time.Time {
|
||||
var initialAlarm, handshakeAlarm time.Time
|
||||
if h.initialPackets != nil {
|
||||
initialAlarm = h.initialPackets.GetAlarmTimeout()
|
||||
}
|
||||
if h.handshakePackets != nil {
|
||||
handshakeAlarm = h.handshakePackets.GetAlarmTimeout()
|
||||
}
|
||||
oneRTTAlarm := h.appDataPackets.GetAlarmTimeout()
|
||||
return utils.MinNonZeroTime(utils.MinNonZeroTime(initialAlarm, handshakeAlarm), oneRTTAlarm)
|
||||
return h.appDataPackets.GetAlarmTimeout()
|
||||
}
|
||||
|
||||
func (h *receivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, onlyIfQueued bool) *wire.AckFrame {
|
||||
var ack *wire.AckFrame
|
||||
//nolint:exhaustive // 0-RTT packets can't contain ACK frames.
|
||||
switch encLevel {
|
||||
case protocol.EncryptionInitial:
|
||||
if h.initialPackets != nil {
|
||||
ack = h.initialPackets.GetAckFrame(onlyIfQueued)
|
||||
return h.initialPackets.GetAckFrame()
|
||||
}
|
||||
return nil
|
||||
case protocol.EncryptionHandshake:
|
||||
if h.handshakePackets != nil {
|
||||
ack = h.handshakePackets.GetAckFrame(onlyIfQueued)
|
||||
return h.handshakePackets.GetAckFrame()
|
||||
}
|
||||
return nil
|
||||
case protocol.Encryption1RTT:
|
||||
// 0-RTT packets can't contain ACK frames
|
||||
return h.appDataPackets.GetAckFrame(onlyIfQueued)
|
||||
default:
|
||||
// 0-RTT packets can't contain ACK frames
|
||||
return nil
|
||||
}
|
||||
// For Initial and Handshake ACKs, the delay time is ignored by the receiver.
|
||||
// Set it to 0 in order to save bytes.
|
||||
if ack != nil {
|
||||
ack.DelayTime = 0
|
||||
}
|
||||
return ack
|
||||
}
|
||||
|
||||
func (h *receivedPacketHandler) IsPotentiallyDuplicate(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel) bool {
|
||||
|
|
|
@ -18,11 +18,7 @@ var _ = Describe("Received Packet Handler", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
sentPackets = NewMockSentPacketTracker(mockCtrl)
|
||||
handler = newReceivedPacketHandler(
|
||||
sentPackets,
|
||||
&utils.RTTStats{},
|
||||
utils.DefaultLogger,
|
||||
)
|
||||
handler = newReceivedPacketHandler(sentPackets, utils.DefaultLogger)
|
||||
})
|
||||
|
||||
It("generates ACKs for different packet number spaces", func() {
|
||||
|
|
|
@ -9,40 +9,19 @@ import (
|
|||
"github.com/refraction-networking/uquic/internal/wire"
|
||||
)
|
||||
|
||||
// number of ack-eliciting packets received before sending an ack.
|
||||
const packetsBeforeAck = 2
|
||||
|
||||
// The receivedPacketTracker tracks packets for the Initial and Handshake packet number space.
|
||||
// Every received packet is acknowledged immediately.
|
||||
type receivedPacketTracker struct {
|
||||
largestObserved protocol.PacketNumber
|
||||
ignoreBelow protocol.PacketNumber
|
||||
largestObservedRcvdTime time.Time
|
||||
ect0, ect1, ecnce uint64
|
||||
ect0, ect1, ecnce uint64
|
||||
|
||||
packetHistory *receivedPacketHistory
|
||||
|
||||
maxAckDelay time.Duration
|
||||
rttStats *utils.RTTStats
|
||||
packetHistory receivedPacketHistory
|
||||
|
||||
lastAck *wire.AckFrame
|
||||
hasNewAck bool // true as soon as we received an ack-eliciting new packet
|
||||
ackQueued bool // true once we received more than 2 (or later in the connection 10) ack-eliciting packets
|
||||
|
||||
ackElicitingPacketsReceivedSinceLastAck int
|
||||
ackAlarm time.Time
|
||||
lastAck *wire.AckFrame
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
func newReceivedPacketTracker(
|
||||
rttStats *utils.RTTStats,
|
||||
logger utils.Logger,
|
||||
) *receivedPacketTracker {
|
||||
return &receivedPacketTracker{
|
||||
packetHistory: newReceivedPacketHistory(),
|
||||
maxAckDelay: protocol.MaxAckDelay,
|
||||
rttStats: rttStats,
|
||||
logger: logger,
|
||||
}
|
||||
func newReceivedPacketTracker() *receivedPacketTracker {
|
||||
return &receivedPacketTracker{packetHistory: *newReceivedPacketHistory()}
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
|
||||
|
@ -50,19 +29,7 @@ func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn pro
|
|||
return fmt.Errorf("recevedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", pn)
|
||||
}
|
||||
|
||||
isMissing := h.isMissing(pn)
|
||||
if pn >= h.largestObserved {
|
||||
h.largestObserved = pn
|
||||
h.largestObservedRcvdTime = rcvTime
|
||||
}
|
||||
|
||||
if ackEliciting {
|
||||
h.hasNewAck = true
|
||||
}
|
||||
if ackEliciting {
|
||||
h.maybeQueueACK(pn, rcvTime, isMissing)
|
||||
}
|
||||
//nolint:exhaustive // Only need to count ECT(0), ECT(1) and ECNCE.
|
||||
//nolint:exhaustive // Only need to count ECT(0), ECT(1) and ECN-CE.
|
||||
switch ecn {
|
||||
case protocol.ECT0:
|
||||
h.ect0++
|
||||
|
@ -71,12 +38,99 @@ func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn pro
|
|||
case protocol.ECNCE:
|
||||
h.ecnce++
|
||||
}
|
||||
if !ackEliciting {
|
||||
return nil
|
||||
}
|
||||
h.hasNewAck = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) GetAckFrame() *wire.AckFrame {
|
||||
if !h.hasNewAck {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function always returns the same ACK frame struct, filled with the most recent values.
|
||||
ack := h.lastAck
|
||||
if ack == nil {
|
||||
ack = &wire.AckFrame{}
|
||||
}
|
||||
ack.Reset()
|
||||
ack.ECT0 = h.ect0
|
||||
ack.ECT1 = h.ect1
|
||||
ack.ECNCE = h.ecnce
|
||||
ack.AckRanges = h.packetHistory.AppendAckRanges(ack.AckRanges)
|
||||
|
||||
h.lastAck = ack
|
||||
h.hasNewAck = false
|
||||
return ack
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) IsPotentiallyDuplicate(pn protocol.PacketNumber) bool {
|
||||
return h.packetHistory.IsPotentiallyDuplicate(pn)
|
||||
}
|
||||
|
||||
// number of ack-eliciting packets received before sending an ACK
|
||||
const packetsBeforeAck = 2
|
||||
|
||||
// The appDataReceivedPacketTracker tracks packets received in the Application Data packet number space.
|
||||
// It waits until at least 2 packets were received before queueing an ACK, or until the max_ack_delay was reached.
|
||||
type appDataReceivedPacketTracker struct {
|
||||
receivedPacketTracker
|
||||
|
||||
largestObservedRcvdTime time.Time
|
||||
|
||||
largestObserved protocol.PacketNumber
|
||||
ignoreBelow protocol.PacketNumber
|
||||
|
||||
maxAckDelay time.Duration
|
||||
ackQueued bool // true if we need send a new ACK
|
||||
|
||||
ackElicitingPacketsReceivedSinceLastAck int
|
||||
ackAlarm time.Time
|
||||
|
||||
logger utils.Logger
|
||||
}
|
||||
|
||||
func newAppDataReceivedPacketTracker(logger utils.Logger) *appDataReceivedPacketTracker {
|
||||
h := &appDataReceivedPacketTracker{
|
||||
receivedPacketTracker: *newReceivedPacketTracker(),
|
||||
maxAckDelay: protocol.MaxAckDelay,
|
||||
logger: logger,
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *appDataReceivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
|
||||
if err := h.receivedPacketTracker.ReceivedPacket(pn, ecn, rcvTime, ackEliciting); err != nil {
|
||||
return err
|
||||
}
|
||||
if pn >= h.largestObserved {
|
||||
h.largestObserved = pn
|
||||
h.largestObservedRcvdTime = rcvTime
|
||||
}
|
||||
if !ackEliciting {
|
||||
return nil
|
||||
}
|
||||
h.ackElicitingPacketsReceivedSinceLastAck++
|
||||
isMissing := h.isMissing(pn)
|
||||
if !h.ackQueued && h.shouldQueueACK(pn, ecn, isMissing) {
|
||||
h.ackQueued = true
|
||||
h.ackAlarm = time.Time{} // cancel the ack alarm
|
||||
}
|
||||
if !h.ackQueued {
|
||||
// No ACK queued, but we'll need to acknowledge the packet after max_ack_delay.
|
||||
h.ackAlarm = rcvTime.Add(h.maxAckDelay)
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tSetting ACK timer to max ack delay: %s", h.maxAckDelay)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IgnoreBelow sets a lower limit for acknowledging packets.
|
||||
// Packets with packet numbers smaller than p will not be acked.
|
||||
func (h *receivedPacketTracker) IgnoreBelow(pn protocol.PacketNumber) {
|
||||
func (h *appDataReceivedPacketTracker) IgnoreBelow(pn protocol.PacketNumber) {
|
||||
if pn <= h.ignoreBelow {
|
||||
return
|
||||
}
|
||||
|
@ -88,14 +142,14 @@ func (h *receivedPacketTracker) IgnoreBelow(pn protocol.PacketNumber) {
|
|||
}
|
||||
|
||||
// isMissing says if a packet was reported missing in the last ACK.
|
||||
func (h *receivedPacketTracker) isMissing(p protocol.PacketNumber) bool {
|
||||
func (h *appDataReceivedPacketTracker) isMissing(p protocol.PacketNumber) bool {
|
||||
if h.lastAck == nil || p < h.ignoreBelow {
|
||||
return false
|
||||
}
|
||||
return p < h.lastAck.LargestAcked() && !h.lastAck.AcksPacket(p)
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) hasNewMissingPackets() bool {
|
||||
func (h *appDataReceivedPacketTracker) hasNewMissingPackets() bool {
|
||||
if h.lastAck == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -103,31 +157,21 @@ func (h *receivedPacketTracker) hasNewMissingPackets() bool {
|
|||
return highestRange.Smallest > h.lastAck.LargestAcked()+1 && highestRange.Len() == 1
|
||||
}
|
||||
|
||||
// maybeQueueACK queues an ACK, if necessary.
|
||||
func (h *receivedPacketTracker) maybeQueueACK(pn protocol.PacketNumber, rcvTime time.Time, wasMissing bool) {
|
||||
func (h *appDataReceivedPacketTracker) shouldQueueACK(pn protocol.PacketNumber, ecn protocol.ECN, wasMissing bool) bool {
|
||||
// always acknowledge the first packet
|
||||
if h.lastAck == nil {
|
||||
if !h.ackQueued {
|
||||
h.logger.Debugf("\tQueueing ACK because the first packet should be acknowledged.")
|
||||
}
|
||||
h.ackQueued = true
|
||||
return
|
||||
h.logger.Debugf("\tQueueing ACK because the first packet should be acknowledged.")
|
||||
return true
|
||||
}
|
||||
|
||||
if h.ackQueued {
|
||||
return
|
||||
}
|
||||
|
||||
h.ackElicitingPacketsReceivedSinceLastAck++
|
||||
|
||||
// Send an ACK if this packet was reported missing in an ACK sent before.
|
||||
// Ack decimation with reordering relies on the timer to send an ACK, but if
|
||||
// missing packets we reported in the previous ack, send an ACK immediately.
|
||||
// missing packets we reported in the previous ACK, send an ACK immediately.
|
||||
if wasMissing {
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tQueueing ACK because packet %d was missing before.", pn)
|
||||
}
|
||||
h.ackQueued = true
|
||||
return true
|
||||
}
|
||||
|
||||
// send an ACK every 2 ack-eliciting packets
|
||||
|
@ -135,62 +179,42 @@ func (h *receivedPacketTracker) maybeQueueACK(pn protocol.PacketNumber, rcvTime
|
|||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tQueueing ACK because packet %d packets were received after the last ACK (using initial threshold: %d).", h.ackElicitingPacketsReceivedSinceLastAck, packetsBeforeAck)
|
||||
}
|
||||
h.ackQueued = true
|
||||
} else if h.ackAlarm.IsZero() {
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tSetting ACK timer to max ack delay: %s", h.maxAckDelay)
|
||||
}
|
||||
h.ackAlarm = rcvTime.Add(h.maxAckDelay)
|
||||
return true
|
||||
}
|
||||
|
||||
// Queue an ACK if there are new missing packets to report.
|
||||
// queue an ACK if there are new missing packets to report
|
||||
if h.hasNewMissingPackets() {
|
||||
h.logger.Debugf("\tQueuing ACK because there's a new missing packet to report.")
|
||||
h.ackQueued = true
|
||||
return true
|
||||
}
|
||||
|
||||
if h.ackQueued {
|
||||
// cancel the ack alarm
|
||||
h.ackAlarm = time.Time{}
|
||||
// queue an ACK if the packet was ECN-CE marked
|
||||
if ecn == protocol.ECNCE {
|
||||
h.logger.Debugf("\tQueuing ACK because the packet was ECN-CE marked.")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) GetAckFrame(onlyIfQueued bool) *wire.AckFrame {
|
||||
if !h.hasNewAck {
|
||||
return nil
|
||||
}
|
||||
func (h *appDataReceivedPacketTracker) GetAckFrame(onlyIfQueued bool) *wire.AckFrame {
|
||||
now := time.Now()
|
||||
if onlyIfQueued {
|
||||
if !h.ackQueued && (h.ackAlarm.IsZero() || h.ackAlarm.After(now)) {
|
||||
if onlyIfQueued && !h.ackQueued {
|
||||
if h.ackAlarm.IsZero() || h.ackAlarm.After(now) {
|
||||
return nil
|
||||
}
|
||||
if h.logger.Debug() && !h.ackQueued && !h.ackAlarm.IsZero() {
|
||||
if h.logger.Debug() && !h.ackAlarm.IsZero() {
|
||||
h.logger.Debugf("Sending ACK because the ACK timer expired.")
|
||||
}
|
||||
}
|
||||
|
||||
// This function always returns the same ACK frame struct, filled with the most recent values.
|
||||
ack := h.lastAck
|
||||
ack := h.receivedPacketTracker.GetAckFrame()
|
||||
if ack == nil {
|
||||
ack = &wire.AckFrame{}
|
||||
return nil
|
||||
}
|
||||
ack.Reset()
|
||||
ack.DelayTime = utils.Max(0, now.Sub(h.largestObservedRcvdTime))
|
||||
ack.ECT0 = h.ect0
|
||||
ack.ECT1 = h.ect1
|
||||
ack.ECNCE = h.ecnce
|
||||
ack.AckRanges = h.packetHistory.AppendAckRanges(ack.AckRanges)
|
||||
|
||||
h.lastAck = ack
|
||||
h.ackAlarm = time.Time{}
|
||||
ack.DelayTime = max(0, now.Sub(h.largestObservedRcvdTime))
|
||||
h.ackQueued = false
|
||||
h.hasNewAck = false
|
||||
h.ackAlarm = time.Time{}
|
||||
h.ackElicitingPacketsReceivedSinceLastAck = 0
|
||||
return ack
|
||||
}
|
||||
|
||||
func (h *receivedPacketTracker) GetAlarmTimeout() time.Time { return h.ackAlarm }
|
||||
|
||||
func (h *receivedPacketTracker) IsPotentiallyDuplicate(pn protocol.PacketNumber) bool {
|
||||
return h.packetHistory.IsPotentiallyDuplicate(pn)
|
||||
}
|
||||
func (h *appDataReceivedPacketTracker) GetAlarmTimeout() time.Time { return h.ackAlarm }
|
||||
|
|
|
@ -12,20 +12,70 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Received Packet Tracker", func() {
|
||||
var (
|
||||
tracker *receivedPacketTracker
|
||||
rttStats *utils.RTTStats
|
||||
)
|
||||
var tracker *receivedPacketTracker
|
||||
|
||||
BeforeEach(func() {
|
||||
rttStats = &utils.RTTStats{}
|
||||
tracker = newReceivedPacketTracker(rttStats, utils.DefaultLogger)
|
||||
tracker = newReceivedPacketTracker()
|
||||
})
|
||||
|
||||
It("acknowledges packets", func() {
|
||||
t := time.Now().Add(-10 * time.Second)
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(3), protocol.ECNNon, t, true)).To(Succeed())
|
||||
ack := tracker.GetAckFrame()
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.AckRanges).To(Equal([]wire.AckRange{{Smallest: 3, Largest: 3}}))
|
||||
Expect(ack.DelayTime).To(BeZero())
|
||||
// now receive another packet
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(4), protocol.ECNNon, t.Add(time.Second), true)).To(Succeed())
|
||||
ack = tracker.GetAckFrame()
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.AckRanges).To(Equal([]wire.AckRange{{Smallest: 3, Largest: 4}}))
|
||||
Expect(ack.DelayTime).To(BeZero())
|
||||
})
|
||||
|
||||
It("also acknowledges delayed packets", func() {
|
||||
t := time.Now().Add(-10 * time.Second)
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(3), protocol.ECNNon, t, true)).To(Succeed())
|
||||
ack := tracker.GetAckFrame()
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.LargestAcked()).To(Equal(protocol.PacketNumber(3)))
|
||||
Expect(ack.LowestAcked()).To(Equal(protocol.PacketNumber(3)))
|
||||
Expect(ack.DelayTime).To(BeZero())
|
||||
// now receive another packet
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(1), protocol.ECNNon, t.Add(time.Second), true)).To(Succeed())
|
||||
ack = tracker.GetAckFrame()
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.AckRanges).To(HaveLen(2))
|
||||
Expect(ack.AckRanges).To(ContainElement(wire.AckRange{Smallest: 1, Largest: 1}))
|
||||
Expect(ack.AckRanges).To(ContainElement(wire.AckRange{Smallest: 3, Largest: 3}))
|
||||
Expect(ack.DelayTime).To(BeZero())
|
||||
})
|
||||
|
||||
It("doesn't trigger ACKs for non-ack-eliciting packets", func() {
|
||||
t := time.Now().Add(-10 * time.Second)
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(3), protocol.ECNNon, t, false)).To(Succeed())
|
||||
Expect(tracker.GetAckFrame()).To(BeNil())
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(4), protocol.ECNNon, t.Add(5*time.Second), false)).To(Succeed())
|
||||
Expect(tracker.GetAckFrame()).To(BeNil())
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(5), protocol.ECNNon, t.Add(10*time.Second), true)).To(Succeed())
|
||||
ack := tracker.GetAckFrame()
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.AckRanges).To(Equal([]wire.AckRange{{Smallest: 3, Largest: 5}}))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Application Data Received Packet Tracker", func() {
|
||||
var tracker *appDataReceivedPacketTracker
|
||||
|
||||
BeforeEach(func() {
|
||||
tracker = newAppDataReceivedPacketTracker(utils.DefaultLogger)
|
||||
})
|
||||
|
||||
Context("accepting packets", func() {
|
||||
It("saves the time when each packet arrived", func() {
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(3), protocol.ECNNon, time.Now(), true)).To(Succeed())
|
||||
Expect(tracker.largestObservedRcvdTime).To(BeTemporally("~", time.Now(), 10*time.Millisecond))
|
||||
t := time.Now()
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(3), protocol.ECNNon, t, true)).To(Succeed())
|
||||
Expect(tracker.largestObservedRcvdTime).To(Equal(t))
|
||||
})
|
||||
|
||||
It("updates the largestObserved and the largestObservedRcvdTime", func() {
|
||||
|
@ -50,6 +100,7 @@ var _ = Describe("Received Packet Tracker", func() {
|
|||
|
||||
Context("ACKs", func() {
|
||||
Context("queueing ACKs", func() {
|
||||
// receives and gets ACKs for packet numbers 1 to 10 (including)
|
||||
receiveAndAck10Packets := func() {
|
||||
for i := 1; i <= 10; i++ {
|
||||
Expect(tracker.ReceivedPacket(protocol.PacketNumber(i), protocol.ECNNon, time.Time{}, true)).To(Succeed())
|
||||
|
@ -126,6 +177,16 @@ var _ = Describe("Received Packet Tracker", func() {
|
|||
Expect(tracker.GetAlarmTimeout()).To(Equal(rcvTime.Add(protocol.MaxAckDelay)))
|
||||
})
|
||||
|
||||
It("queues an ACK if the packet was ECN-CE marked", func() {
|
||||
receiveAndAck10Packets()
|
||||
Expect(tracker.ReceivedPacket(11, protocol.ECNCE, time.Now(), true)).To(Succeed())
|
||||
ack := tracker.GetAckFrame(true)
|
||||
Expect(ack).ToNot(BeNil())
|
||||
Expect(ack.AckRanges).To(HaveLen(1))
|
||||
Expect(ack.AckRanges[0].Largest).To(Equal(protocol.PacketNumber(11)))
|
||||
Expect(ack.ECNCE).To(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("queues an ACK if it was reported missing before", func() {
|
||||
receiveAndAck10Packets()
|
||||
Expect(tracker.ReceivedPacket(11, protocol.ECNNon, time.Now(), true)).To(Succeed())
|
||||
|
|
|
@ -245,7 +245,7 @@ func (h *sentPacketHandler) SentPacket(
|
|||
|
||||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
if h.logger.Debug() && pnSpace.history.HasOutstandingPackets() {
|
||||
for p := utils.Max(0, pnSpace.largestSent+1); p < pn; p++ {
|
||||
for p := max(0, pnSpace.largestSent+1); p < pn; p++ {
|
||||
h.logger.Debugf("Skipping packet number %d", p)
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
|
|||
// don't use the ack delay for Initial and Handshake packets
|
||||
var ackDelay time.Duration
|
||||
if encLevel == protocol.Encryption1RTT {
|
||||
ackDelay = utils.Min(ack.DelayTime, h.rttStats.MaxAckDelay())
|
||||
ackDelay = min(ack.DelayTime, h.rttStats.MaxAckDelay())
|
||||
}
|
||||
h.rttStats.UpdateRTT(rcvTime.Sub(p.SendTime), ackDelay, rcvTime)
|
||||
if h.logger.Debug() {
|
||||
|
@ -354,7 +354,7 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
|
|||
}
|
||||
}
|
||||
|
||||
pnSpace.largestAcked = utils.Max(pnSpace.largestAcked, largestAcked)
|
||||
pnSpace.largestAcked = max(pnSpace.largestAcked, largestAcked)
|
||||
|
||||
if err := h.detectLostPackets(rcvTime, encLevel); err != nil {
|
||||
return false, err
|
||||
|
@ -446,7 +446,7 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL
|
|||
|
||||
for _, p := range h.ackedPackets {
|
||||
if p.LargestAcked != protocol.InvalidPacketNumber && encLevel == protocol.Encryption1RTT {
|
||||
h.lowestNotConfirmedAcked = utils.Max(h.lowestNotConfirmedAcked, p.LargestAcked+1)
|
||||
h.lowestNotConfirmedAcked = max(h.lowestNotConfirmedAcked, p.LargestAcked+1)
|
||||
}
|
||||
|
||||
for _, f := range p.Frames {
|
||||
|
@ -607,11 +607,11 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
|
|||
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||
pnSpace.lossTime = time.Time{}
|
||||
|
||||
maxRTT := float64(utils.Max(h.rttStats.LatestRTT(), h.rttStats.SmoothedRTT()))
|
||||
maxRTT := float64(max(h.rttStats.LatestRTT(), h.rttStats.SmoothedRTT()))
|
||||
lossDelay := time.Duration(timeThreshold * maxRTT)
|
||||
|
||||
// Minimum time of granularity before packets are deemed lost.
|
||||
lossDelay = utils.Max(lossDelay, protocol.TimerGranularity)
|
||||
lossDelay = max(lossDelay, protocol.TimerGranularity)
|
||||
|
||||
// Packets sent before this time are deemed lost.
|
||||
lostSendTime := now.Add(-lossDelay)
|
||||
|
@ -891,7 +891,7 @@ func (h *sentPacketHandler) ResetForRetry(now time.Time) error {
|
|||
// Otherwise, we don't know which Initial the Retry was sent in response to.
|
||||
if h.ptoCount == 0 {
|
||||
// Don't set the RTT to a value lower than 5ms here.
|
||||
h.rttStats.UpdateRTT(utils.Max(minRTTAfterRetry, now.Sub(firstPacketSendTime)), 0, now)
|
||||
h.rttStats.UpdateRTT(max(minRTTAfterRetry, now.Sub(firstPacketSendTime)), 0, now)
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation())
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
// This cubic implementation is based on the one found in Chromiums's QUIC
|
||||
|
@ -187,7 +186,7 @@ func (c *Cubic) CongestionWindowAfterAck(
|
|||
targetCongestionWindow = c.originPointCongestionWindow - deltaCongestionWindow
|
||||
}
|
||||
// Limit the CWND increase to half the acked bytes.
|
||||
targetCongestionWindow = utils.Min(targetCongestionWindow, currentCongestionWindow+c.ackedBytesCount/2)
|
||||
targetCongestionWindow = min(targetCongestionWindow, currentCongestionWindow+c.ackedBytesCount/2)
|
||||
|
||||
// Increase the window by approximately Alpha * 1 MSS of bytes every
|
||||
// time we ack an estimated tcp window of bytes. For small
|
||||
|
|
|
@ -178,7 +178,7 @@ func (c *cubicSender) OnPacketAcked(
|
|||
priorInFlight protocol.ByteCount,
|
||||
eventTime time.Time,
|
||||
) {
|
||||
c.largestAckedPacketNumber = utils.Max(ackedPacketNumber, c.largestAckedPacketNumber)
|
||||
c.largestAckedPacketNumber = max(ackedPacketNumber, c.largestAckedPacketNumber)
|
||||
if c.InRecovery() {
|
||||
return
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ func (c *cubicSender) maybeIncreaseCwnd(
|
|||
c.numAckedPackets = 0
|
||||
}
|
||||
} else {
|
||||
c.congestionWindow = utils.Min(c.maxCongestionWindow(), c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime))
|
||||
c.congestionWindow = min(c.maxCongestionWindow(), c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
// Note(pwestin): the magic clamping numbers come from the original code in
|
||||
|
@ -75,8 +74,8 @@ func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT ti
|
|||
// Divide minRTT by 8 to get a rtt increase threshold for exiting.
|
||||
minRTTincreaseThresholdUs := int64(minRTT / time.Microsecond >> hybridStartDelayFactorExp)
|
||||
// Ensure the rtt threshold is never less than 2ms or more than 16ms.
|
||||
minRTTincreaseThresholdUs = utils.Min(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs)
|
||||
minRTTincreaseThreshold := time.Duration(utils.Max(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond
|
||||
minRTTincreaseThresholdUs = min(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs)
|
||||
minRTTincreaseThreshold := time.Duration(max(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond
|
||||
|
||||
if s.currentMinRTT > (minRTT + minRTTincreaseThreshold) {
|
||||
s.hystartFound = true
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package congestion
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
const maxBurstSizePackets = 10
|
||||
|
@ -26,7 +24,7 @@ func newPacer(getBandwidth func() Bandwidth) *pacer {
|
|||
bw := uint64(getBandwidth() / BytesPerSecond)
|
||||
// Use a slightly higher value than the actual measured bandwidth.
|
||||
// RTT variations then won't result in under-utilization of the congestion window.
|
||||
// Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire,
|
||||
// Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire,
|
||||
// provided the congestion window is fully utilized and acknowledgments arrive at regular intervals.
|
||||
return bw * 5 / 4
|
||||
},
|
||||
|
@ -37,7 +35,7 @@ func newPacer(getBandwidth func() Bandwidth) *pacer {
|
|||
|
||||
func (p *pacer) SentPacket(sendTime time.Time, size protocol.ByteCount) {
|
||||
budget := p.Budget(sendTime)
|
||||
if size > budget {
|
||||
if size >= budget {
|
||||
p.budgetAtLastSent = 0
|
||||
} else {
|
||||
p.budgetAtLastSent = budget - size
|
||||
|
@ -53,11 +51,11 @@ func (p *pacer) Budget(now time.Time) protocol.ByteCount {
|
|||
if budget < 0 { // protect against overflows
|
||||
budget = protocol.MaxByteCount
|
||||
}
|
||||
return utils.Min(p.maxBurstSize(), budget)
|
||||
return min(p.maxBurstSize(), budget)
|
||||
}
|
||||
|
||||
func (p *pacer) maxBurstSize() protocol.ByteCount {
|
||||
return utils.Max(
|
||||
return max(
|
||||
protocol.ByteCount(uint64((protocol.MinPacingDelay+protocol.TimerGranularity).Nanoseconds())*p.adjustedBandwidth())/1e9,
|
||||
maxBurstSizePackets*p.maxDatagramSize,
|
||||
)
|
||||
|
@ -69,10 +67,16 @@ func (p *pacer) TimeUntilSend() time.Time {
|
|||
if p.budgetAtLastSent >= p.maxDatagramSize {
|
||||
return time.Time{}
|
||||
}
|
||||
return p.lastSentTime.Add(utils.Max(
|
||||
protocol.MinPacingDelay,
|
||||
time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/float64(p.adjustedBandwidth())))*time.Nanosecond,
|
||||
))
|
||||
diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
|
||||
bw := p.adjustedBandwidth()
|
||||
// We might need to round up this value.
|
||||
// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.
|
||||
d := diff / bw
|
||||
// this is effectively a math.Ceil, but using only integer math
|
||||
if diff%bw > 0 {
|
||||
d++
|
||||
}
|
||||
return p.lastSentTime.Add(max(protocol.MinPacingDelay, time.Duration(d)*time.Nanosecond))
|
||||
}
|
||||
|
||||
func (p *pacer) SetMaxDatagramSize(s protocol.ByteCount) {
|
||||
|
|
|
@ -101,6 +101,17 @@ var _ = Describe("Pacer", func() {
|
|||
Expect(p.Budget(t.Add(5 * t2.Sub(t)))).To(BeEquivalentTo(5 * packetSize))
|
||||
})
|
||||
|
||||
It("has enough budget for at least one packet when the timer expires", func() {
|
||||
t := time.Now()
|
||||
sendBurst(t)
|
||||
for bw := uint64(100); bw < uint64(5*initialMaxDatagramSize); bw++ {
|
||||
bandwidth = bw // reduce the bandwidth to 5 packet per second
|
||||
t2 := p.TimeUntilSend()
|
||||
Expect(t2).To(BeTemporally(">", t))
|
||||
Expect(p.Budget(t2)).To(BeNumerically(">=", initialMaxDatagramSize))
|
||||
}
|
||||
})
|
||||
|
||||
It("never allows bursts larger than the maximum burst size", func() {
|
||||
t := time.Now()
|
||||
sendBurst(t)
|
||||
|
|
|
@ -48,10 +48,12 @@ func (c *baseFlowController) AddBytesSent(n protocol.ByteCount) {
|
|||
}
|
||||
|
||||
// UpdateSendWindow is called after receiving a MAX_{STREAM_}DATA frame.
|
||||
func (c *baseFlowController) UpdateSendWindow(offset protocol.ByteCount) {
|
||||
func (c *baseFlowController) UpdateSendWindow(offset protocol.ByteCount) (updated bool) {
|
||||
if offset > c.sendWindow {
|
||||
c.sendWindow = offset
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *baseFlowController) sendWindowSize() protocol.ByteCount {
|
||||
|
@ -107,7 +109,7 @@ func (c *baseFlowController) maybeAdjustWindowSize() {
|
|||
now := time.Now()
|
||||
if now.Sub(c.epochStartTime) < time.Duration(4*fraction*float64(rtt)) {
|
||||
// window is consumed too fast, try to increase the window size
|
||||
newSize := utils.Min(2*c.receiveWindowSize, c.maxReceiveWindowSize)
|
||||
newSize := min(2*c.receiveWindowSize, c.maxReceiveWindowSize)
|
||||
if newSize > c.receiveWindowSize && (c.allowWindowIncrease == nil || c.allowWindowIncrease(newSize-c.receiveWindowSize)) {
|
||||
c.receiveWindowSize = newSize
|
||||
}
|
||||
|
|
|
@ -59,9 +59,9 @@ var _ = Describe("Base Flow controller", func() {
|
|||
})
|
||||
|
||||
It("does not decrease the flow control window", func() {
|
||||
controller.UpdateSendWindow(20)
|
||||
Expect(controller.UpdateSendWindow(20)).To(BeTrue())
|
||||
Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(20)))
|
||||
controller.UpdateSendWindow(10)
|
||||
Expect(controller.UpdateSendWindow(10)).To(BeFalse())
|
||||
Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(20)))
|
||||
})
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCoun
|
|||
c.mutex.Lock()
|
||||
if inc > c.receiveWindowSize {
|
||||
c.logger.Debugf("Increasing receive flow control window for the connection to %d kB, in response to stream flow control window increase", c.receiveWindowSize/(1<<10))
|
||||
newSize := utils.Min(inc, c.maxReceiveWindowSize)
|
||||
newSize := min(inc, c.maxReceiveWindowSize)
|
||||
if delta := newSize - c.receiveWindowSize; delta > 0 && c.allowWindowIncrease(delta) {
|
||||
c.receiveWindowSize = newSize
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import "github.com/refraction-networking/uquic/internal/protocol"
|
|||
type flowController interface {
|
||||
// for sending
|
||||
SendWindowSize() protocol.ByteCount
|
||||
UpdateSendWindow(protocol.ByteCount)
|
||||
UpdateSendWindow(protocol.ByteCount) (updated bool)
|
||||
AddBytesSent(protocol.ByteCount)
|
||||
// for receiving
|
||||
AddBytesRead(protocol.ByteCount)
|
||||
|
@ -16,12 +16,11 @@ type flowController interface {
|
|||
// A StreamFlowController is a flow controller for a QUIC stream.
|
||||
type StreamFlowController interface {
|
||||
flowController
|
||||
// for receiving
|
||||
// UpdateHighestReceived should be called when a new highest offset is received
|
||||
// UpdateHighestReceived is called when a new highest offset is received
|
||||
// final has to be to true if this is the final offset of the stream,
|
||||
// as contained in a STREAM frame with FIN bit, and the RESET_STREAM frame
|
||||
UpdateHighestReceived(offset protocol.ByteCount, final bool) error
|
||||
// Abandon should be called when reading from the stream is aborted early,
|
||||
// Abandon is called when reading from the stream is aborted early,
|
||||
// and there won't be any further calls to AddBytesRead.
|
||||
Abandon()
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ func (c *streamFlowController) AddBytesSent(n protocol.ByteCount) {
|
|||
}
|
||||
|
||||
func (c *streamFlowController) SendWindowSize() protocol.ByteCount {
|
||||
return utils.Min(c.baseFlowController.sendWindowSize(), c.connection.SendWindowSize())
|
||||
return min(c.baseFlowController.sendWindowSize(), c.connection.SendWindowSize())
|
||||
}
|
||||
|
||||
func (c *streamFlowController) shouldQueueWindowUpdate() bool {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package handshake
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/refraction-networking/uquic/internal/protocol"
|
||||
"github.com/refraction-networking/uquic/internal/utils"
|
||||
)
|
||||
|
||||
func createAEAD(suite *cipherSuite, trafficSecret []byte, v protocol.VersionNumber) cipher.AEAD {
|
||||
func createAEAD(suite *cipherSuite, trafficSecret []byte, v protocol.Version) *xorNonceAEAD {
|
||||
keyLabel := hkdfLabelKeyV1
|
||||
ivLabel := hkdfLabelIVV1
|
||||
if v == protocol.Version2 {
|
||||
|
@ -21,28 +19,26 @@ func createAEAD(suite *cipherSuite, trafficSecret []byte, v protocol.VersionNumb
|
|||
}
|
||||
|
||||
type longHeaderSealer struct {
|
||||
aead cipher.AEAD
|
||||
aead *xorNonceAEAD
|
||||
headerProtector headerProtector
|
||||
|
||||
// use a single slice to avoid allocations
|
||||
nonceBuf []byte
|
||||
nonceBuf [8]byte
|
||||
}
|
||||
|
||||
var _ LongHeaderSealer = &longHeaderSealer{}
|
||||
|
||||
func newLongHeaderSealer(aead cipher.AEAD, headerProtector headerProtector) LongHeaderSealer {
|
||||
func newLongHeaderSealer(aead *xorNonceAEAD, headerProtector headerProtector) LongHeaderSealer {
|
||||
if aead.NonceSize() != 8 {
|
||||
panic("unexpected nonce size")
|
||||
}
|
||||
return &longHeaderSealer{
|
||||
aead: aead,
|
||||
headerProtector: headerProtector,
|
||||
nonceBuf: make([]byte, aead.NonceSize()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *longHeaderSealer) Seal(dst, src []byte, pn protocol.PacketNumber, ad []byte) []byte {
|
||||
binary.BigEndian.PutUint64(s.nonceBuf[len(s.nonceBuf)-8:], uint64(pn))
|
||||
// The AEAD we're using here will be the qtls.aeadAESGCM13.
|
||||
// It uses the nonce provided here and XOR it with the IV.
|
||||
return s.aead.Seal(dst, s.nonceBuf, src, ad)
|
||||
binary.BigEndian.PutUint64(s.nonceBuf[:], uint64(pn))
|
||||
return s.aead.Seal(dst, s.nonceBuf[:], src, ad)
|
||||
}
|
||||
|
||||
func (s *longHeaderSealer) EncryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
|
||||
|
@ -54,21 +50,23 @@ func (s *longHeaderSealer) Overhead() int {
|
|||
}
|
||||
|
||||
type longHeaderOpener struct {
|
||||
aead cipher.AEAD
|
||||
aead *xorNonceAEAD
|
||||
headerProtector headerProtector
|
||||
highestRcvdPN protocol.PacketNumber // highest packet number received (which could be successfully unprotected)
|
||||
|
||||
// use a single slice to avoid allocations
|
||||
nonceBuf []byte
|
||||
// use a single array to avoid allocations
|
||||
nonceBuf [8]byte
|
||||
}
|
||||
|
||||
var _ LongHeaderOpener = &longHeaderOpener{}
|
||||
|
||||
func newLongHeaderOpener(aead cipher.AEAD, headerProtector headerProtector) LongHeaderOpener {
|
||||
func newLongHeaderOpener(aead *xorNonceAEAD, headerProtector headerProtector) LongHeaderOpener {
|
||||
if aead.NonceSize() != 8 {
|
||||
panic("unexpected nonce size")
|
||||
}
|
||||
return &longHeaderOpener{
|
||||
aead: aead,
|
||||
headerProtector: headerProtector,
|
||||
nonceBuf: make([]byte, aead.NonceSize()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,12 +75,10 @@ func (o *longHeaderOpener) DecodePacketNumber(wirePN protocol.PacketNumber, wire
|
|||
}
|
||||
|
||||
func (o *longHeaderOpener) Open(dst, src []byte, pn protocol.PacketNumber, ad []byte) ([]byte, error) {
|
||||
binary.BigEndian.PutUint64(o.nonceBuf[len(o.nonceBuf)-8:], uint64(pn))
|
||||
// The AEAD we're using here will be the qtls.aeadAESGCM13.
|
||||
// It uses the nonce provided here and XOR it with the IV.
|
||||
dec, err := o.aead.Open(dst, o.nonceBuf, src, ad)
|
||||
binary.BigEndian.PutUint64(o.nonceBuf[:], uint64(pn))
|
||||
dec, err := o.aead.Open(dst, o.nonceBuf[:], src, ad)
|
||||
if err == nil {
|
||||
o.highestRcvdPN = utils.Max(o.highestRcvdPN, pn)
|
||||
o.highestRcvdPN = max(o.highestRcvdPN, pn)
|
||||
} else {
|
||||
err = ErrDecryptionFailed
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Long Header AEAD", func() {
|
||||
for _, ver := range []protocol.VersionNumber{protocol.Version1, protocol.Version2} {
|
||||
for _, ver := range []protocol.Version{protocol.Version1, protocol.Version2} {
|
||||
v := ver
|
||||
|
||||
Context(fmt.Sprintf("using version %s", v), func() {
|
||||
|
@ -34,8 +34,8 @@ var _ = Describe("Long Header AEAD", func() {
|
|||
aead, err := cipher.NewGCM(block)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
return newLongHeaderSealer(aead, newHeaderProtector(cs, hpKey, true, v)),
|
||||
newLongHeaderOpener(aead, newHeaderProtector(cs, hpKey, true, v))
|
||||
return newLongHeaderSealer(&xorNonceAEAD{aead: aead}, newHeaderProtector(cs, hpKey, true, v)),
|
||||
newLongHeaderOpener(&xorNonceAEAD{aead: aead}, newHeaderProtector(cs, hpKey, true, v))
|
||||
}
|
||||
|
||||
Context("message encryption", func() {
|
||||
|
|
|
@ -19,7 +19,7 @@ type cipherSuite struct {
|
|||
ID uint16
|
||||
Hash crypto.Hash
|
||||
KeyLen int
|
||||
AEAD func(key, nonceMask []byte) cipher.AEAD
|
||||
AEAD func(key, nonceMask []byte) *xorNonceAEAD
|
||||
}
|
||||
|
||||
func (s cipherSuite) IVLen() int { return aeadNonceLength }
|
||||
|
@ -37,7 +37,7 @@ func getCipherSuite(id uint16) *cipherSuite {
|
|||
}
|
||||
}
|
||||
|
||||
func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {
|
||||
func aeadAESGCMTLS13(key, nonceMask []byte) *xorNonceAEAD {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {
|
|||
return ret
|
||||
}
|
||||
|
||||
func aeadChaCha20Poly1305(key, nonceMask []byte) cipher.AEAD {
|
||||
func aeadChaCha20Poly1305(key, nonceMask []byte) *xorNonceAEAD {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
@ -26,15 +25,15 @@ type quicVersionContextKey struct{}
|
|||
|
||||
var QUICVersionContextKey = &quicVersionContextKey{}
|
||||
|
||||
const clientSessionStateRevision = 3
|
||||
const clientSessionStateRevision = 4
|
||||
|
||||
type cryptoSetup struct {
|
||||
tlsConf *tls.Config
|
||||
conn *qtls.QUICConn
|
||||
conn *tls.QUICConn
|
||||
|
||||
events []Event
|
||||
|
||||
version protocol.VersionNumber
|
||||
version protocol.Version
|
||||
|
||||
ourParams *wire.TransportParameters
|
||||
peerParams *wire.TransportParameters
|
||||
|
@ -49,8 +48,6 @@ type cryptoSetup struct {
|
|||
|
||||
perspective protocol.Perspective
|
||||
|
||||
mutex sync.Mutex // protects all members below
|
||||
|
||||
handshakeCompleteTime time.Time
|
||||
|
||||
zeroRTTOpener LongHeaderOpener // only set for the server
|
||||
|
@ -80,7 +77,7 @@ func NewCryptoSetupClient(
|
|||
rttStats *utils.RTTStats,
|
||||
tracer *logging.ConnectionTracer,
|
||||
logger utils.Logger,
|
||||
version protocol.VersionNumber,
|
||||
version protocol.Version,
|
||||
) CryptoSetup {
|
||||
cs := newCryptoSetup(
|
||||
connID,
|
||||
|
@ -94,11 +91,12 @@ func NewCryptoSetupClient(
|
|||
|
||||
tlsConf = tlsConf.Clone()
|
||||
tlsConf.MinVersion = tls.VersionTLS13
|
||||
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
|
||||
quicConf := &tls.QUICConfig{TLSConfig: tlsConf}
|
||||
qtls.SetupConfigForClient(quicConf, cs.marshalDataForSessionState, cs.handleDataFromSessionState)
|
||||
cs.tlsConf = tlsConf
|
||||
cs.allow0RTT = enable0RTT
|
||||
|
||||
cs.conn = qtls.QUICClient(quicConf)
|
||||
cs.conn = tls.QUICClient(quicConf)
|
||||
cs.conn.SetTransportParameters(cs.ourParams.Marshal(protocol.PerspectiveClient))
|
||||
|
||||
return cs
|
||||
|
@ -114,7 +112,7 @@ func NewCryptoSetupServer(
|
|||
rttStats *utils.RTTStats,
|
||||
tracer *logging.ConnectionTracer,
|
||||
logger utils.Logger,
|
||||
version protocol.VersionNumber,
|
||||
version protocol.Version,
|
||||
) CryptoSetup {
|
||||
cs := newCryptoSetup(
|
||||
connID,
|
||||
|
@ -127,12 +125,12 @@ func NewCryptoSetupServer(
|
|||
)
|
||||
cs.allow0RTT = allow0RTT
|
||||
|
||||
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
|
||||
quicConf := &tls.QUICConfig{TLSConfig: tlsConf}
|
||||
qtls.SetupConfigForServer(quicConf, cs.allow0RTT, cs.getDataForSessionTicket, cs.handleSessionTicket)
|
||||
addConnToClientHelloInfo(quicConf.TLSConfig, localAddr, remoteAddr)
|
||||
|
||||
cs.tlsConf = quicConf.TLSConfig
|
||||
cs.conn = qtls.QUICServer(quicConf)
|
||||
cs.conn = tls.QUICServer(quicConf)
|
||||
|
||||
return cs
|
||||
}
|
||||
|
@ -147,6 +145,9 @@ func addConnToClientHelloInfo(conf *tls.Config, localAddr, remoteAddr net.Addr)
|
|||
info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
|
||||
c, err := gcfc(info)
|
||||
if c != nil {
|
||||
c = c.Clone()
|
||||
// This won't be necessary anymore once https://github.com/golang/go/issues/63722 is accepted.
|
||||
c.MinVersion = tls.VersionTLS13
|
||||
// We're returning a tls.Config here, so we need to apply this recursively.
|
||||
addConnToClientHelloInfo(c, localAddr, remoteAddr)
|
||||
}
|
||||
|
@ -169,7 +170,7 @@ func newCryptoSetup(
|
|||
tracer *logging.ConnectionTracer,
|
||||
logger utils.Logger,
|
||||
perspective protocol.Perspective,
|
||||
version protocol.VersionNumber,
|
||||
version protocol.Version,
|
||||
) *cryptoSetup {
|
||||
initialSealer, initialOpener := NewInitialAEAD(connID, perspective, version)
|
||||
if tracer != nil && tracer.UpdatedKeyFromTLS != nil {
|
||||
|
@ -261,29 +262,29 @@ func (h *cryptoSetup) handleMessage(data []byte, encLevel protocol.EncryptionLev
|
|||
}
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) handleEvent(ev qtls.QUICEvent) (done bool, err error) {
|
||||
func (h *cryptoSetup) handleEvent(ev tls.QUICEvent) (done bool, err error) {
|
||||
switch ev.Kind {
|
||||
case qtls.QUICNoEvent:
|
||||
case tls.QUICNoEvent:
|
||||
return true, nil
|
||||
case qtls.QUICSetReadSecret:
|
||||
h.SetReadKey(ev.Level, ev.Suite, ev.Data)
|
||||
case tls.QUICSetReadSecret:
|
||||
h.setReadKey(ev.Level, ev.Suite, ev.Data)
|
||||
return false, nil
|
||||
case qtls.QUICSetWriteSecret:
|
||||
h.SetWriteKey(ev.Level, ev.Suite, ev.Data)
|
||||
case tls.QUICSetWriteSecret:
|
||||
h.setWriteKey(ev.Level, ev.Suite, ev.Data)
|
||||
return false, nil
|
||||
case qtls.QUICTransportParameters:
|
||||
case tls.QUICTransportParameters:
|
||||
return false, h.handleTransportParameters(ev.Data)
|
||||
case qtls.QUICTransportParametersRequired:
|
||||
case tls.QUICTransportParametersRequired:
|
||||
h.conn.SetTransportParameters(h.ourParams.Marshal(h.perspective))
|
||||
// [UQUIC] doesn't expect this and may fail
|
||||
return false, nil
|
||||
case qtls.QUICRejectedEarlyData:
|
||||
case tls.QUICRejectedEarlyData:
|
||||
h.rejected0RTT()
|
||||
return false, nil
|
||||
case qtls.QUICWriteData:
|
||||
h.WriteRecord(ev.Level, ev.Data)
|
||||
case tls.QUICWriteData:
|
||||
h.writeRecord(ev.Level, ev.Data)
|
||||
return false, nil
|
||||
case qtls.QUICHandshakeDone:
|
||||
case tls.QUICHandshakeDone:
|
||||
h.handshakeComplete()
|
||||
return false, nil
|
||||
default:
|
||||
|
@ -311,41 +312,56 @@ func (h *cryptoSetup) handleTransportParameters(data []byte) error {
|
|||
}
|
||||
|
||||
// must be called after receiving the transport parameters
|
||||
func (h *cryptoSetup) marshalDataForSessionState() []byte {
|
||||
func (h *cryptoSetup) marshalDataForSessionState(earlyData bool) []byte {
|
||||
b := make([]byte, 0, 256)
|
||||
b = quicvarint.Append(b, clientSessionStateRevision)
|
||||
b = quicvarint.Append(b, uint64(h.rttStats.SmoothedRTT().Microseconds()))
|
||||
return h.peerParams.MarshalForSessionTicket(b)
|
||||
if earlyData {
|
||||
// only save the transport parameters for 0-RTT enabled session tickets
|
||||
return h.peerParams.MarshalForSessionTicket(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) handleDataFromSessionState(data []byte) {
|
||||
tp, err := h.handleDataFromSessionStateImpl(data)
|
||||
func (h *cryptoSetup) handleDataFromSessionState(data []byte, earlyData bool) (allowEarlyData bool) {
|
||||
rtt, tp, err := decodeDataFromSessionState(data, earlyData)
|
||||
if err != nil {
|
||||
h.logger.Debugf("Restoring of transport parameters from session ticket failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
h.zeroRTTParameters = tp
|
||||
h.rttStats.SetInitialRTT(rtt)
|
||||
// The session ticket might have been saved from a connection that allowed 0-RTT,
|
||||
// and therefore contain transport parameters.
|
||||
// Only use them if 0-RTT is actually used on the new connection.
|
||||
if tp != nil && h.allow0RTT {
|
||||
h.zeroRTTParameters = tp
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) handleDataFromSessionStateImpl(data []byte) (*wire.TransportParameters, error) {
|
||||
func decodeDataFromSessionState(data []byte, earlyData bool) (time.Duration, *wire.TransportParameters, error) {
|
||||
r := bytes.NewReader(data)
|
||||
ver, err := quicvarint.Read(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
if ver != clientSessionStateRevision {
|
||||
return nil, fmt.Errorf("mismatching version. Got %d, expected %d", ver, clientSessionStateRevision)
|
||||
return 0, nil, fmt.Errorf("mismatching version. Got %d, expected %d", ver, clientSessionStateRevision)
|
||||
}
|
||||
rtt, err := quicvarint.Read(r)
|
||||
rttEncoded, err := quicvarint.Read(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
rtt := time.Duration(rttEncoded) * time.Microsecond
|
||||
if !earlyData {
|
||||
return rtt, nil, nil
|
||||
}
|
||||
h.rttStats.SetInitialRTT(time.Duration(rtt) * time.Microsecond)
|
||||
var tp wire.TransportParameters
|
||||
if err := tp.UnmarshalFromSessionTicket(r); err != nil {
|
||||
return nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
return &tp, nil
|
||||
return rtt, &tp, nil
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) getDataForSessionTicket() []byte {
|
||||
|
@ -362,7 +378,9 @@ func (h *cryptoSetup) getDataForSessionTicket() []byte {
|
|||
// Due to limitations in crypto/tls, it's only possible to generate a single session ticket per connection.
|
||||
// It is only valid for the server.
|
||||
func (h *cryptoSetup) GetSessionTicket() ([]byte, error) {
|
||||
if err := qtls.SendSessionTicket(h.conn, h.allow0RTT); err != nil {
|
||||
if err := h.conn.SendSessionTicket(tls.QUICSessionTicketOptions{
|
||||
EarlyData: h.allow0RTT,
|
||||
}); err != nil {
|
||||
// Session tickets might be disabled by tls.Config.SessionTicketsDisabled.
|
||||
// We can't check h.tlsConfig here, since the actual config might have been obtained from
|
||||
// the GetConfigForClient callback.
|
||||
|
@ -374,18 +392,20 @@ func (h *cryptoSetup) GetSessionTicket() ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
ev := h.conn.NextEvent()
|
||||
if ev.Kind != qtls.QUICWriteData || ev.Level != qtls.QUICEncryptionLevelApplication {
|
||||
if ev.Kind != tls.QUICWriteData || ev.Level != tls.QUICEncryptionLevelApplication {
|
||||
panic("crypto/tls bug: where's my session ticket?")
|
||||
}
|
||||
ticket := ev.Data
|
||||
if ev := h.conn.NextEvent(); ev.Kind != qtls.QUICNoEvent {
|
||||
if ev := h.conn.NextEvent(); ev.Kind != tls.QUICNoEvent {
|
||||
panic("crypto/tls bug: why more than one ticket?")
|
||||
}
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
// handleSessionTicket is called for the server when receiving the client's session ticket.
|
||||
// It reads parameters from the session ticket and decides whether to accept 0-RTT when the session ticket is used for 0-RTT.
|
||||
// It reads parameters from the session ticket and checks whether to accept 0-RTT if the session ticket enabled 0-RTT.
|
||||
// Note that the fact that the session ticket allows 0-RTT doesn't mean that the actual TLS handshake enables 0-RTT:
|
||||
// A client may use a 0-RTT enabled session to resume a TLS session without using 0-RTT.
|
||||
func (h *cryptoSetup) handleSessionTicket(sessionTicketData []byte, using0RTT bool) bool {
|
||||
var t sessionTicket
|
||||
if err := t.Unmarshal(sessionTicketData, using0RTT); err != nil {
|
||||
|
@ -413,22 +433,19 @@ func (h *cryptoSetup) handleSessionTicket(sessionTicketData []byte, using0RTT bo
|
|||
func (h *cryptoSetup) rejected0RTT() {
|
||||
h.logger.Debugf("0-RTT was rejected. Dropping 0-RTT keys.")
|
||||
|
||||
h.mutex.Lock()
|
||||
had0RTTKeys := h.zeroRTTSealer != nil
|
||||
h.zeroRTTSealer = nil
|
||||
h.mutex.Unlock()
|
||||
|
||||
if had0RTTKeys {
|
||||
h.events = append(h.events, Event{Kind: EventDiscard0RTTKeys})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) SetReadKey(el qtls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
|
||||
func (h *cryptoSetup) setReadKey(el tls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
|
||||
suite := getCipherSuite(suiteID)
|
||||
h.mutex.Lock()
|
||||
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
|
||||
switch el {
|
||||
case qtls.QUICEncryptionLevelEarly:
|
||||
case tls.QUICEncryptionLevelEarly:
|
||||
if h.perspective == protocol.PerspectiveClient {
|
||||
panic("Received 0-RTT read key for the client")
|
||||
}
|
||||
|
@ -440,7 +457,7 @@ func (h *cryptoSetup) SetReadKey(el qtls.QUICEncryptionLevel, suiteID uint16, tr
|
|||
if h.logger.Debug() {
|
||||
h.logger.Debugf("Installed 0-RTT Read keys (using %s)", tls.CipherSuiteName(suite.ID))
|
||||
}
|
||||
case qtls.QUICEncryptionLevelHandshake:
|
||||
case tls.QUICEncryptionLevelHandshake:
|
||||
h.handshakeOpener = newLongHeaderOpener(
|
||||
createAEAD(suite, trafficSecret, h.version),
|
||||
newHeaderProtector(suite, trafficSecret, true, h.version),
|
||||
|
@ -448,7 +465,7 @@ func (h *cryptoSetup) SetReadKey(el qtls.QUICEncryptionLevel, suiteID uint16, tr
|
|||
if h.logger.Debug() {
|
||||
h.logger.Debugf("Installed Handshake Read keys (using %s)", tls.CipherSuiteName(suite.ID))
|
||||
}
|
||||
case qtls.QUICEncryptionLevelApplication:
|
||||
case tls.QUICEncryptionLevelApplication:
|
||||
h.aead.SetReadKey(suite, trafficSecret)
|
||||
h.has1RTTOpener = true
|
||||
if h.logger.Debug() {
|
||||
|
@ -457,19 +474,17 @@ func (h *cryptoSetup) SetReadKey(el qtls.QUICEncryptionLevel, suiteID uint16, tr
|
|||
default:
|
||||
panic("unexpected read encryption level")
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
h.events = append(h.events, Event{Kind: EventReceivedReadKeys})
|
||||
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
|
||||
h.tracer.UpdatedKeyFromTLS(qtls.FromTLSEncryptionLevel(el), h.perspective.Opposite())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
|
||||
func (h *cryptoSetup) setWriteKey(el tls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
|
||||
suite := getCipherSuite(suiteID)
|
||||
h.mutex.Lock()
|
||||
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
|
||||
switch el {
|
||||
case qtls.QUICEncryptionLevelEarly:
|
||||
case tls.QUICEncryptionLevelEarly:
|
||||
if h.perspective == protocol.PerspectiveServer {
|
||||
panic("Received 0-RTT write key for the server")
|
||||
}
|
||||
|
@ -477,7 +492,6 @@ func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, t
|
|||
createAEAD(suite, trafficSecret, h.version),
|
||||
newHeaderProtector(suite, trafficSecret, true, h.version),
|
||||
)
|
||||
h.mutex.Unlock()
|
||||
if h.logger.Debug() {
|
||||
h.logger.Debugf("Installed 0-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID))
|
||||
}
|
||||
|
@ -486,7 +500,7 @@ func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, t
|
|||
}
|
||||
// don't set used0RTT here. 0-RTT might still get rejected.
|
||||
return
|
||||
case qtls.QUICEncryptionLevelHandshake:
|
||||
case tls.QUICEncryptionLevelHandshake:
|
||||
h.handshakeSealer = newLongHeaderSealer(
|
||||
createAEAD(suite, trafficSecret, h.version),
|
||||
newHeaderProtector(suite, trafficSecret, true, h.version),
|
||||
|
@ -494,7 +508,7 @@ func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, t
|
|||
if h.logger.Debug() {
|
||||
h.logger.Debugf("Installed Handshake Write keys (using %s)", tls.CipherSuiteName(suite.ID))
|
||||
}
|
||||
case qtls.QUICEncryptionLevelApplication:
|
||||
case tls.QUICEncryptionLevelApplication:
|
||||
h.aead.SetWriteKey(suite, trafficSecret)
|
||||
h.has1RTTSealer = true
|
||||
if h.logger.Debug() {
|
||||
|
@ -512,21 +526,20 @@ func (h *cryptoSetup) SetWriteKey(el qtls.QUICEncryptionLevel, suiteID uint16, t
|
|||
default:
|
||||
panic("unexpected write encryption level")
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
if h.tracer != nil && h.tracer.UpdatedKeyFromTLS != nil {
|
||||
h.tracer.UpdatedKeyFromTLS(qtls.FromTLSEncryptionLevel(el), h.perspective)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteRecord is called when TLS writes data
|
||||
func (h *cryptoSetup) WriteRecord(encLevel qtls.QUICEncryptionLevel, p []byte) {
|
||||
// writeRecord is called when TLS writes data
|
||||
func (h *cryptoSetup) writeRecord(encLevel tls.QUICEncryptionLevel, p []byte) {
|
||||
//nolint:exhaustive // handshake records can only be written for Initial and Handshake.
|
||||
switch encLevel {
|
||||
case qtls.QUICEncryptionLevelInitial:
|
||||
case tls.QUICEncryptionLevelInitial:
|
||||
h.events = append(h.events, Event{Kind: EventWriteInitialData, Data: p})
|
||||
case qtls.QUICEncryptionLevelHandshake:
|
||||
case tls.QUICEncryptionLevelHandshake:
|
||||
h.events = append(h.events, Event{Kind: EventWriteHandshakeData, Data: p})
|
||||
case qtls.QUICEncryptionLevelApplication:
|
||||
case tls.QUICEncryptionLevelApplication:
|
||||
panic("unexpected write")
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected write encryption level: %s", encLevel))
|
||||
|
@ -534,11 +547,9 @@ func (h *cryptoSetup) WriteRecord(encLevel qtls.QUICEncryptionLevel, p []byte) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) DiscardInitialKeys() {
|
||||
h.mutex.Lock()
|
||||
dropped := h.initialOpener != nil
|
||||
h.initialOpener = nil
|
||||
h.initialSealer = nil
|
||||
h.mutex.Unlock()
|
||||
if dropped {
|
||||
h.logger.Debugf("Dropping Initial keys.")
|
||||
}
|
||||
|
@ -553,22 +564,17 @@ func (h *cryptoSetup) SetHandshakeConfirmed() {
|
|||
h.aead.SetHandshakeConfirmed()
|
||||
// drop Handshake keys
|
||||
var dropped bool
|
||||
h.mutex.Lock()
|
||||
if h.handshakeOpener != nil {
|
||||
h.handshakeOpener = nil
|
||||
h.handshakeSealer = nil
|
||||
dropped = true
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
if dropped {
|
||||
h.logger.Debugf("Dropping Handshake keys.")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *cryptoSetup) GetInitialSealer() (LongHeaderSealer, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.initialSealer == nil {
|
||||
return nil, ErrKeysDropped
|
||||
}
|
||||
|
@ -576,9 +582,6 @@ func (h *cryptoSetup) GetInitialSealer() (LongHeaderSealer, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) Get0RTTSealer() (LongHeaderSealer, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.zeroRTTSealer == nil {
|
||||
return nil, ErrKeysDropped
|
||||
}
|
||||
|
@ -586,9 +589,6 @@ func (h *cryptoSetup) Get0RTTSealer() (LongHeaderSealer, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) GetHandshakeSealer() (LongHeaderSealer, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.handshakeSealer == nil {
|
||||
if h.initialSealer == nil {
|
||||
return nil, ErrKeysDropped
|
||||
|
@ -599,9 +599,6 @@ func (h *cryptoSetup) GetHandshakeSealer() (LongHeaderSealer, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) Get1RTTSealer() (ShortHeaderSealer, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if !h.has1RTTSealer {
|
||||
return nil, ErrKeysNotYetAvailable
|
||||
}
|
||||
|
@ -609,9 +606,6 @@ func (h *cryptoSetup) Get1RTTSealer() (ShortHeaderSealer, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) GetInitialOpener() (LongHeaderOpener, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.initialOpener == nil {
|
||||
return nil, ErrKeysDropped
|
||||
}
|
||||
|
@ -619,9 +613,6 @@ func (h *cryptoSetup) GetInitialOpener() (LongHeaderOpener, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) Get0RTTOpener() (LongHeaderOpener, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.zeroRTTOpener == nil {
|
||||
if h.initialOpener != nil {
|
||||
return nil, ErrKeysNotYetAvailable
|
||||
|
@ -633,9 +624,6 @@ func (h *cryptoSetup) Get0RTTOpener() (LongHeaderOpener, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) GetHandshakeOpener() (LongHeaderOpener, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.handshakeOpener == nil {
|
||||
if h.initialOpener != nil {
|
||||
return nil, ErrKeysNotYetAvailable
|
||||
|
@ -647,9 +635,6 @@ func (h *cryptoSetup) GetHandshakeOpener() (LongHeaderOpener, error) {
|
|||
}
|
||||
|
||||
func (h *cryptoSetup) Get1RTTOpener() (ShortHeaderOpener, error) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.zeroRTTOpener != nil && time.Since(h.handshakeCompleteTime) > 3*h.rttStats.PTO(true) {
|
||||
h.zeroRTTOpener = nil
|
||||
h.logger.Debugf("Dropping 0-RTT keys.")
|
||||
|
@ -673,7 +658,7 @@ func (h *cryptoSetup) ConnectionState() ConnectionState {
|
|||
|
||||
func wrapError(err error) error {
|
||||
// alert 80 is an internal error
|
||||
if alertErr := qtls.AlertError(0); errors.As(err, &alertErr) && alertErr != 80 {
|
||||
if alertErr := tls.AlertError(0); errors.As(err, &alertErr) && alertErr != 80 {
|
||||
return qerr.NewLocalCryptoError(uint8(alertErr), err)
|
||||
}
|
||||
return &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: err.Error()}
|
||||
|
|
|
@ -7,8 +7,7 @@ import (
|
|||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
|
@ -140,32 +139,43 @@ var _ = Describe("Crypto Setup TLS", func() {
|
|||
},
|
||||
}
|
||||
addConnToClientHelloInfo(tlsConf, local, remote)
|
||||
_, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
|
||||
conf, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(localAddr).To(Equal(local))
|
||||
Expect(remoteAddr).To(Equal(remote))
|
||||
Expect(conf).ToNot(BeNil())
|
||||
Expect(conf.MinVersion).To(BeEquivalentTo(tls.VersionTLS13))
|
||||
})
|
||||
|
||||
It("wraps GetConfigForClient, recursively", func() {
|
||||
var localAddr, remoteAddr net.Addr
|
||||
tlsConf := &tls.Config{}
|
||||
var innerConf *tls.Config
|
||||
getCert := func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { //nolint:unparam
|
||||
localAddr = info.Conn.LocalAddr()
|
||||
remoteAddr = info.Conn.RemoteAddr()
|
||||
cert := generateCert()
|
||||
return &cert, nil
|
||||
}
|
||||
tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
conf := tlsConf.Clone()
|
||||
conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
localAddr = info.Conn.LocalAddr()
|
||||
remoteAddr = info.Conn.RemoteAddr()
|
||||
cert := generateCert()
|
||||
return &cert, nil
|
||||
}
|
||||
return conf, nil
|
||||
innerConf = tlsConf.Clone()
|
||||
// set the MaxVersion, so we can check that quic-go doesn't overwrite the user's config
|
||||
innerConf.MaxVersion = tls.VersionTLS12
|
||||
innerConf.GetCertificate = getCert
|
||||
return innerConf, nil
|
||||
}
|
||||
addConnToClientHelloInfo(tlsConf, local, remote)
|
||||
conf, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(conf).ToNot(BeNil())
|
||||
Expect(conf.MinVersion).To(BeEquivalentTo(tls.VersionTLS13))
|
||||
_, err = conf.GetCertificate(&tls.ClientHelloInfo{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(localAddr).To(Equal(local))
|
||||
Expect(remoteAddr).To(Equal(remote))
|
||||
// make sure that the tls.Config returned by GetConfigForClient isn't modified
|
||||
Expect(reflect.ValueOf(innerConf.GetCertificate).Pointer() == reflect.ValueOf(getCert).Pointer()).To(BeTrue())
|
||||
Expect(innerConf.MaxVersion).To(BeEquivalentTo(tls.VersionTLS12))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -452,9 +462,7 @@ var _ = Describe("Crypto Setup TLS", func() {
|
|||
Expect(server.ConnectionState().DidResume).To(BeTrue())
|
||||
Expect(client.ConnectionState().DidResume).To(BeTrue())
|
||||
Expect(clientRTTStats.SmoothedRTT()).To(Equal(clientRTT))
|
||||
if !strings.Contains(runtime.Version(), "go1.20") {
|
||||
Expect(serverRTTStats.SmoothedRTT()).To(Equal(serverRTT))
|
||||
}
|
||||
Expect(serverRTTStats.SmoothedRTT()).To(Equal(serverRTT))
|
||||
})
|
||||
|
||||
It("doesn't use session resumption if the server disabled it", func() {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue