package qpack
import (
"errors"
"fmt"
"io"
"golang.org/x/net/http2/hpack"
)
// An invalidIndexError is returned when decoding encounters an invalid index
// (e.g., an index that is out of bounds for the static table).
type invalidIndexError int
func (e invalidIndexError) Error() string {
return fmt.Sprintf("invalid indexed representation index %d", int(e))
}
var errNoDynamicTable = errors.New("no dynamic table")
// A Decoder decodes QPACK header blocks.
// A Decoder can be reused to decode multiple header blocks on different streams
// on the same connection (e.g., headers then trailers).
// This will be useful when dynamic table support is added.
type Decoder struct{}
// DecodeFunc is a function that decodes the next header field from a header block.
// It should be called repeatedly until it returns io.EOF.
// It returns io.EOF when all header fields have been decoded.
// Any error other than io.EOF indicates a decoding error.
type DecodeFunc func() (HeaderField, error)
// NewDecoder returns a new Decoder.
func NewDecoder() *Decoder {
return &Decoder{}
}
// Decode returns a function that decodes header fields from the given header block.
// It does not copy the slice; the caller must ensure it remains valid during decoding.
func (d *Decoder) Decode(p []byte) DecodeFunc {
var readRequiredInsertCount bool
var readDeltaBase bool
return func() (HeaderField, error) {
if !readRequiredInsertCount {
requiredInsertCount, rest, err := readVarInt(8, p)
if err != nil {
return HeaderField{}, err
}
p = rest
readRequiredInsertCount = true
if requiredInsertCount != 0 {
return HeaderField{}, errors.New("expected Required Insert Count to be zero")
}
}
if !readDeltaBase {
base, rest, err := readVarInt(7, p)
if err != nil {
return HeaderField{}, err
}
p = rest
readDeltaBase = true
if base != 0 {
return HeaderField{}, errors.New("expected Base to be zero")
}
}
if len(p) == 0 {
return HeaderField{}, io.EOF
}
b := p[0]
var hf HeaderField
var rest []byte
var err error
switch {
case (b & 0x80) > 0: // 1xxxxxxx
hf, rest, err = d.parseIndexedHeaderField(p)
case (b & 0xc0) == 0x40: // 01xxxxxx
hf, rest, err = d.parseLiteralHeaderField(p)
case (b & 0xe0) == 0x20: // 001xxxxx
hf, rest, err = d.parseLiteralHeaderFieldWithoutNameReference(p)
default:
err = fmt.Errorf("unexpected type byte: %#x", b)
}
p = rest
if err != nil {
return HeaderField{}, err
}
return hf, nil
}
}
func (d *Decoder) parseIndexedHeaderField(buf []byte) (_ HeaderField, rest []byte, _ error) {
if buf[0]&0x40 == 0 {
return HeaderField{}, buf, errNoDynamicTable
}
index, rest, err := readVarInt(6, buf)
if err != nil {
return HeaderField{}, buf, err
}
hf, ok := d.at(index)
if !ok {
return HeaderField{}, buf, invalidIndexError(index)
}
return hf, rest, nil
}
func (d *Decoder) parseLiteralHeaderField(buf []byte) (_ HeaderField, rest []byte, _ error) {
if buf[0]&0x10 == 0 {
return HeaderField{}, buf, errNoDynamicTable
}
// We don't need to check the value of the N-bit here.
// It's only relevant when re-encoding header fields,
// and determines whether the header field can be added to the dynamic table.
// Since we don't support the dynamic table, we can ignore it.
index, rest, err := readVarInt(4, buf)
if err != nil {
return HeaderField{}, buf, err
}
hf, ok := d.at(index)
if !ok {
return HeaderField{}, buf, invalidIndexError(index)
}
buf = rest
if len(buf) == 0 {
return HeaderField{}, buf, io.ErrUnexpectedEOF
}
usesHuffman := buf[0]&0x80 > 0
val, rest, err := d.readString(rest, 7, usesHuffman)
if err != nil {
return HeaderField{}, rest, err
}
hf.Value = val
return hf, rest, nil
}
func (d *Decoder) parseLiteralHeaderFieldWithoutNameReference(buf []byte) (_ HeaderField, rest []byte, _ error) {
usesHuffmanForName := buf[0]&0x8 > 0
name, rest, err := d.readString(buf, 3, usesHuffmanForName)
if err != nil {
return HeaderField{}, rest, err
}
buf = rest
if len(buf) == 0 {
return HeaderField{}, rest, io.ErrUnexpectedEOF
}
usesHuffmanForVal := buf[0]&0x80 > 0
val, rest, err := d.readString(buf, 7, usesHuffmanForVal)
if err != nil {
return HeaderField{}, rest, err
}
return HeaderField{Name: name, Value: val}, rest, nil
}
func (d *Decoder) readString(buf []byte, n uint8, usesHuffman bool) (string, []byte, error) {
l, buf, err := readVarInt(n, buf)
if err != nil {
return "", nil, err
}
if uint64(len(buf)) < l {
return "", nil, io.ErrUnexpectedEOF
}
var val string
if usesHuffman {
val, err = hpack.HuffmanDecodeToString(buf[:l])
if err != nil {
return "", nil, err
}
} else {
val = string(buf[:l])
}
buf = buf[l:]
return val, buf, nil
}
func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
if i >= uint64(len(staticTableEntries)) {
return
}
return staticTableEntries[i], true
}
package qpack
import (
"io"
"golang.org/x/net/http2/hpack"
)
// An Encoder performs QPACK encoding.
type Encoder struct {
wrotePrefix bool
w io.Writer
buf []byte
}
// NewEncoder returns a new Encoder which performs QPACK encoding. An
// encoded data is written to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
// WriteField encodes f into a single Write to e's underlying Writer.
// This function may also produce bytes for the Header Block Prefix
// if necessary. If produced, it is done before encoding f.
func (e *Encoder) WriteField(f HeaderField) error {
// write the Header Block Prefix
if !e.wrotePrefix {
e.buf = appendVarInt(e.buf, 8, 0)
e.buf = appendVarInt(e.buf, 7, 0)
e.wrotePrefix = true
}
idxAndVals, nameFound := encoderMap[f.Name]
if nameFound {
if idxAndVals.values == nil {
if len(f.Value) == 0 {
e.writeIndexedField(idxAndVals.idx)
} else {
e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx)
}
} else {
valIdx, valueFound := idxAndVals.values[f.Value]
if valueFound {
e.writeIndexedField(valIdx)
} else {
e.writeLiteralFieldWithNameReference(&f, idxAndVals.idx)
}
}
} else {
e.writeLiteralFieldWithoutNameReference(f)
}
_, err := e.w.Write(e.buf)
e.buf = e.buf[:0]
return err
}
// Close declares that the encoding is complete and resets the Encoder
// to be reused again for a new header block.
func (e *Encoder) Close() error {
e.wrotePrefix = false
return nil
}
func (e *Encoder) writeLiteralFieldWithoutNameReference(f HeaderField) {
offset := len(e.buf)
e.buf = appendVarInt(e.buf, 3, hpack.HuffmanEncodeLength(f.Name))
e.buf[offset] ^= 0x20 ^ 0x8
e.buf = hpack.AppendHuffmanString(e.buf, f.Name)
offset = len(e.buf)
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
e.buf[offset] ^= 0x80
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
}
// Encodes a header field whose name is present in one of the tables.
func (e *Encoder) writeLiteralFieldWithNameReference(f *HeaderField, id uint8) {
offset := len(e.buf)
e.buf = appendVarInt(e.buf, 4, uint64(id))
// Set the 01NTxxxx pattern, forcing N to 0 and T to 1
e.buf[offset] ^= 0x50
offset = len(e.buf)
e.buf = appendVarInt(e.buf, 7, hpack.HuffmanEncodeLength(f.Value))
e.buf[offset] ^= 0x80
e.buf = hpack.AppendHuffmanString(e.buf, f.Value)
}
// Encodes an indexed field, meaning it's entirely defined in one of the tables.
func (e *Encoder) writeIndexedField(id uint8) {
offset := len(e.buf)
e.buf = appendVarInt(e.buf, 6, uint64(id))
// Set the 1Txxxxxx pattern, forcing T to 1
e.buf[offset] ^= 0xc0
}
package qpack
// A HeaderField is a name-value pair. Both the name and value are
// treated as opaque sequences of octets.
type HeaderField struct {
Name string
Value string
}
// IsPseudo reports whether the header field is an HTTP3 pseudo header.
// That is, it reports whether it starts with a colon.
// It is not otherwise guaranteed to be a valid pseudo header field,
// though.
func (hf HeaderField) IsPseudo() bool {
return len(hf.Name) != 0 && hf.Name[0] == ':'
}
package qpack
// copied from the Go standard library HPACK implementation
import (
"errors"
"io"
)
var errVarintOverflow = errors.New("varint integer overflow")
// appendVarInt appends i, as encoded in variable integer form using n
// bit prefix, to dst and returns the extended buffer.
//
// See
// http://http2.github.io/http2-spec/compression.html#integer.representation
func appendVarInt(dst []byte, n byte, i uint64) []byte {
k := uint64((1 << n) - 1)
if i < k {
return append(dst, byte(i))
}
dst = append(dst, byte(k))
i -= k
for ; i >= 128; i >>= 7 {
dst = append(dst, byte(0x80|(i&0x7f)))
}
return append(dst, byte(i))
}
// readVarInt reads an unsigned variable length integer off the
// beginning of p. n is the parameter as described in
// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1.
//
// n must always be between 1 and 8.
//
// The returned remain buffer is either a smaller suffix of p, or err != nil.
// The error is io.ErrUnexpectedEOF if p doesn't contain a complete integer.
func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) {
if n < 1 || n > 8 {
panic("bad n")
}
if len(p) == 0 {
return 0, p, io.ErrUnexpectedEOF
}
i = uint64(p[0])
if n < 8 {
i &= (1 << uint64(n)) - 1
}
if i < (1<<uint64(n))-1 {
return i, p[1:], nil
}
origP := p
p = p[1:]
var m uint64
for len(p) > 0 {
b := p[0]
p = p[1:]
i += uint64(b&127) << m
if b&128 == 0 {
return i, p, nil
}
m += 7
if m >= 63 { // TODO: proper overflow check. making this up.
return 0, origP, errVarintOverflow
}
}
return 0, origP, io.ErrUnexpectedEOF
}
package quic
import (
"sync"
"github.com/quic-go/quic-go/internal/protocol"
)
type packetBuffer struct {
Data []byte
// refCount counts how many packets Data is used in.
// It doesn't support concurrent use.
// It is > 1 when used for coalesced packet.
refCount int
}
// Split increases the refCount.
// It must be called when a packet buffer is used for more than one packet,
// e.g. when splitting coalesced packets.
func (b *packetBuffer) Split() {
b.refCount++
}
// Decrement decrements the reference counter.
// It doesn't put the buffer back into the pool.
func (b *packetBuffer) Decrement() {
b.refCount--
if b.refCount < 0 {
panic("negative packetBuffer refCount")
}
}
// MaybeRelease puts the packet buffer back into the pool,
// if the reference counter already reached 0.
func (b *packetBuffer) MaybeRelease() {
// only put the packetBuffer back if it's not used any more
if b.refCount == 0 {
b.putBack()
}
}
// Release puts back the packet buffer into the pool.
// It should be called when processing is definitely finished.
func (b *packetBuffer) Release() {
b.Decrement()
if b.refCount != 0 {
panic("packetBuffer refCount not zero")
}
b.putBack()
}
// Len returns the length of Data
func (b *packetBuffer) Len() protocol.ByteCount { return protocol.ByteCount(len(b.Data)) }
func (b *packetBuffer) Cap() protocol.ByteCount { return protocol.ByteCount(cap(b.Data)) }
func (b *packetBuffer) putBack() {
if cap(b.Data) == protocol.MaxPacketBufferSize {
bufferPool.Put(b)
return
}
if cap(b.Data) == protocol.MaxLargePacketBufferSize {
largeBufferPool.Put(b)
return
}
panic("putPacketBuffer called with packet of wrong size!")
}
var bufferPool, largeBufferPool sync.Pool
func getPacketBuffer() *packetBuffer {
buf := bufferPool.Get().(*packetBuffer)
buf.refCount = 1
buf.Data = buf.Data[:0]
return buf
}
func getLargePacketBuffer() *packetBuffer {
buf := largeBufferPool.Get().(*packetBuffer)
buf.refCount = 1
buf.Data = buf.Data[:0]
return buf
}
func init() {
bufferPool.New = func() any {
return &packetBuffer{Data: make([]byte, 0, protocol.MaxPacketBufferSize)}
}
largeBufferPool.New = func() any {
return &packetBuffer{Data: make([]byte, 0, protocol.MaxLargePacketBufferSize)}
}
}
package quic
import (
"context"
"crypto/tls"
"errors"
"net"
"github.com/quic-go/quic-go/internal/protocol"
)
// make it possible to mock connection ID for initial generation in the tests
var generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
// DialAddr establishes a new QUIC connection to a server.
// It resolves the address, and then creates a new UDP connection to dial the QUIC server.
// When the QUIC connection is closed, this UDP connection is closed.
// See [Dial] for more details.
func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (*Conn, error) {
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return nil, err
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, false)
if err != nil {
tr.Close()
return nil, err
}
return conn, nil
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
// See [DialAddr] for more details.
func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (*Conn, error) {
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return nil, err
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
tr, err := setupTransport(udpConn, tlsConf, true)
if err != nil {
return nil, err
}
conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, true)
if err != nil {
tr.Close()
return nil, err
}
return conn, nil
}
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
// See [Dial] for more details.
func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (*Conn, error) {
dl, err := setupTransport(c, tlsConf, false)
if err != nil {
return nil, err
}
conn, err := dl.DialEarly(ctx, addr, tlsConf, conf)
if err != nil {
dl.Close()
return nil, err
}
return conn, nil
}
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
// If the PacketConn satisfies the [OOBCapablePacketConn] interface (as a [net.UDPConn] does),
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
// will be used instead of ReadFrom and WriteTo to read/write packets.
// The [tls.Config] must define an application protocol (using tls.Config.NextProtos).
//
// This is a convenience function. More advanced use cases should instantiate a [Transport],
// which offers configuration options for a more fine-grained control of the connection establishment,
// including reusing the underlying UDP socket for multiple QUIC connections.
func Dial(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (*Conn, error) {
dl, err := setupTransport(c, tlsConf, false)
if err != nil {
return nil, err
}
conn, err := dl.Dial(ctx, addr, tlsConf, conf)
if err != nil {
dl.Close()
return nil, err
}
return conn, nil
}
func setupTransport(c net.PacketConn, tlsConf *tls.Config, createdPacketConn bool) (*Transport, error) {
if tlsConf == nil {
return nil, errors.New("quic: tls.Config not set")
}
return &Transport{
Conn: c,
createdConn: createdPacketConn,
isSingleUse: true,
}, nil
}
package quic
import (
"math/bits"
"net"
"sync/atomic"
"github.com/quic-go/quic-go/internal/utils"
)
// A closedLocalConn is a connection that we closed locally.
// 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 atomic.Uint32
logger utils.Logger
sendPacket func(net.Addr, packetInfo)
}
var _ packetHandler = &closedLocalConn{}
// newClosedLocalConn creates a new closedLocalConn and runs it.
func newClosedLocalConn(sendPacket func(net.Addr, packetInfo), logger utils.Logger) packetHandler {
return &closedLocalConn{
sendPacket: sendPacket,
logger: logger,
}
}
func (c *closedLocalConn) handlePacket(p receivedPacket) {
n := c.counter.Add(1)
// exponential backoff
// only send a CONNECTION_CLOSE for the 1st, 2nd, 4th, 8th, 16th, ... packet arriving
if bits.OnesCount32(n) != 1 {
return
}
c.logger.Debugf("Received %d packets after sending CONNECTION_CLOSE. Retransmitting.", n)
c.sendPacket(p.remoteAddr, p.info)
}
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{}
var _ packetHandler = &closedRemoteConn{}
func newClosedRemoteConn() packetHandler {
return &closedRemoteConn{}
}
func (c *closedRemoteConn) handlePacket(receivedPacket) {}
func (c *closedRemoteConn) destroy(error) {}
func (c *closedRemoteConn) closeWithTransportError(TransportErrorCode) {}
package quic
import (
"fmt"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// Clone clones a Config.
func (c *Config) Clone() *Config {
copy := *c
return ©
}
func (c *Config) handshakeTimeout() time.Duration {
return 2 * c.HandshakeIdleTimeout
}
func (c *Config) maxRetryTokenAge() time.Duration {
return c.handshakeTimeout()
}
func validateConfig(config *Config) error {
if config == nil {
return nil
}
const maxStreams = 1 << 60
if config.MaxIncomingStreams > maxStreams {
config.MaxIncomingStreams = maxStreams
}
if config.MaxIncomingUniStreams > maxStreams {
config.MaxIncomingUniStreams = maxStreams
}
if config.MaxStreamReceiveWindow > quicvarint.Max {
config.MaxStreamReceiveWindow = quicvarint.Max
}
if config.MaxConnectionReceiveWindow > quicvarint.Max {
config.MaxConnectionReceiveWindow = quicvarint.Max
}
if config.InitialPacketSize > 0 && config.InitialPacketSize < protocol.MinInitialPacketSize {
config.InitialPacketSize = protocol.MinInitialPacketSize
}
if config.InitialPacketSize > protocol.MaxPacketBufferSize {
config.InitialPacketSize = protocol.MaxPacketBufferSize
}
// check that all QUIC versions are actually supported
for _, v := range config.Versions {
if !protocol.IsValidVersion(v) {
return fmt.Errorf("invalid QUIC version: %s", v)
}
}
return nil
}
// 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 {
if config == nil {
config = &Config{}
}
versions := config.Versions
if len(versions) == 0 {
versions = protocol.SupportedVersions
}
handshakeIdleTimeout := protocol.DefaultHandshakeIdleTimeout
if config.HandshakeIdleTimeout != 0 {
handshakeIdleTimeout = config.HandshakeIdleTimeout
}
idleTimeout := protocol.DefaultIdleTimeout
if config.MaxIdleTimeout != 0 {
idleTimeout = config.MaxIdleTimeout
}
initialStreamReceiveWindow := config.InitialStreamReceiveWindow
if initialStreamReceiveWindow == 0 {
initialStreamReceiveWindow = protocol.DefaultInitialMaxStreamData
}
maxStreamReceiveWindow := config.MaxStreamReceiveWindow
if maxStreamReceiveWindow == 0 {
maxStreamReceiveWindow = protocol.DefaultMaxReceiveStreamFlowControlWindow
}
initialConnectionReceiveWindow := config.InitialConnectionReceiveWindow
if initialConnectionReceiveWindow == 0 {
initialConnectionReceiveWindow = protocol.DefaultInitialMaxData
}
maxConnectionReceiveWindow := config.MaxConnectionReceiveWindow
if maxConnectionReceiveWindow == 0 {
maxConnectionReceiveWindow = protocol.DefaultMaxReceiveConnectionFlowControlWindow
}
maxIncomingStreams := config.MaxIncomingStreams
if maxIncomingStreams == 0 {
maxIncomingStreams = protocol.DefaultMaxIncomingStreams
} else if maxIncomingStreams < 0 {
maxIncomingStreams = 0
}
maxIncomingUniStreams := config.MaxIncomingUniStreams
if maxIncomingUniStreams == 0 {
maxIncomingUniStreams = protocol.DefaultMaxIncomingUniStreams
} else if maxIncomingUniStreams < 0 {
maxIncomingUniStreams = 0
}
initialPacketSize := config.InitialPacketSize
if initialPacketSize == 0 {
initialPacketSize = protocol.InitialPacketSize
}
return &Config{
GetConfigForClient: config.GetConfigForClient,
Versions: versions,
HandshakeIdleTimeout: handshakeIdleTimeout,
MaxIdleTimeout: idleTimeout,
KeepAlivePeriod: config.KeepAlivePeriod,
InitialStreamReceiveWindow: initialStreamReceiveWindow,
MaxStreamReceiveWindow: maxStreamReceiveWindow,
InitialConnectionReceiveWindow: initialConnectionReceiveWindow,
MaxConnectionReceiveWindow: maxConnectionReceiveWindow,
AllowConnectionWindowIncrease: config.AllowConnectionWindowIncrease,
MaxIncomingStreams: maxIncomingStreams,
MaxIncomingUniStreams: maxIncomingUniStreams,
TokenStore: config.TokenStore,
EnableDatagrams: config.EnableDatagrams,
InitialPacketSize: initialPacketSize,
DisablePathMTUDiscovery: config.DisablePathMTUDiscovery,
EnableStreamResetPartialDelivery: config.EnableStreamResetPartialDelivery,
Allow0RTT: config.Allow0RTT,
Tracer: config.Tracer,
}
}
package quic
import (
"fmt"
"slices"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
type connRunnerCallbacks struct {
AddConnectionID func(protocol.ConnectionID)
RemoveConnectionID func(protocol.ConnectionID)
ReplaceWithClosed func([]protocol.ConnectionID, []byte, time.Duration)
}
// The memory address of the Transport is used as the key.
type connRunners map[connRunner]connRunnerCallbacks
func (cr connRunners) AddConnectionID(id protocol.ConnectionID) {
for _, c := range cr {
c.AddConnectionID(id)
}
}
func (cr connRunners) RemoveConnectionID(id protocol.ConnectionID) {
for _, c := range cr {
c.RemoveConnectionID(id)
}
}
func (cr connRunners) ReplaceWithClosed(ids []protocol.ConnectionID, b []byte, expiry time.Duration) {
for _, c := range cr {
c.ReplaceWithClosed(ids, b, expiry)
}
}
type connIDToRetire struct {
t monotime.Time
connID protocol.ConnectionID
}
type connIDGenerator struct {
generator ConnectionIDGenerator
highestSeq uint64
connRunners connRunners
activeSrcConnIDs map[uint64]protocol.ConnectionID
connIDsToRetire []connIDToRetire // sorted by t
initialClientDestConnID *protocol.ConnectionID // nil for the client
statelessResetter *statelessResetter
queueControlFrame func(wire.Frame)
}
func newConnIDGenerator(
runner connRunner,
initialConnectionID protocol.ConnectionID,
initialClientDestConnID *protocol.ConnectionID, // nil for the client
statelessResetter *statelessResetter,
callbacks connRunnerCallbacks,
queueControlFrame func(wire.Frame),
generator ConnectionIDGenerator,
) *connIDGenerator {
m := &connIDGenerator{
generator: generator,
activeSrcConnIDs: make(map[uint64]protocol.ConnectionID),
statelessResetter: statelessResetter,
connRunners: map[connRunner]connRunnerCallbacks{runner: callbacks},
queueControlFrame: queueControlFrame,
}
m.activeSrcConnIDs[0] = initialConnectionID
m.initialClientDestConnID = initialClientDestConnID
return m
}
func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error {
if m.generator.ConnectionIDLen() == 0 {
return nil
}
// The active_connection_id_limit transport parameter is the number of
// connection IDs the peer will store. This limit includes the connection ID
// used during the handshake, and the one sent in the preferred_address
// 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 < min(limit, protocol.MaxIssuedConnectionIDs); i++ {
if err := m.issueNewConnID(); err != nil {
return err
}
}
return nil
}
func (m *connIDGenerator) Retire(seq uint64, sentWithDestConnID protocol.ConnectionID, expiry monotime.Time) error {
if seq > m.highestSeq {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("retired connection ID %d (highest issued: %d)", seq, m.highestSeq),
}
}
connID, ok := m.activeSrcConnIDs[seq]
// We might already have deleted this connection ID, if this is a duplicate frame.
if !ok {
return nil
}
if connID == sentWithDestConnID {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("retired connection ID %d (%s), which was used as the Destination Connection ID on this packet", seq, connID),
}
}
m.queueConnIDForRetiring(connID, expiry)
delete(m.activeSrcConnIDs, seq)
// Don't issue a replacement for the initial connection ID.
if seq == 0 {
return nil
}
return m.issueNewConnID()
}
func (m *connIDGenerator) queueConnIDForRetiring(connID protocol.ConnectionID, expiry monotime.Time) {
idx := slices.IndexFunc(m.connIDsToRetire, func(c connIDToRetire) bool {
return c.t.After(expiry)
})
if idx == -1 {
idx = len(m.connIDsToRetire)
}
m.connIDsToRetire = slices.Insert(m.connIDsToRetire, idx, connIDToRetire{t: expiry, connID: connID})
}
func (m *connIDGenerator) issueNewConnID() error {
connID, err := m.generator.GenerateConnectionID()
if err != nil {
return err
}
m.activeSrcConnIDs[m.highestSeq+1] = connID
m.connRunners.AddConnectionID(connID)
m.queueControlFrame(&wire.NewConnectionIDFrame{
SequenceNumber: m.highestSeq + 1,
ConnectionID: connID,
StatelessResetToken: m.statelessResetter.GetStatelessResetToken(connID),
})
m.highestSeq++
return nil
}
func (m *connIDGenerator) SetHandshakeComplete(connIDExpiry monotime.Time) {
if m.initialClientDestConnID != nil {
m.queueConnIDForRetiring(*m.initialClientDestConnID, connIDExpiry)
m.initialClientDestConnID = nil
}
}
func (m *connIDGenerator) RemoveRetiredConnIDs(now monotime.Time) {
if len(m.connIDsToRetire) == 0 {
return
}
for _, c := range m.connIDsToRetire {
if c.t.After(now) {
break
}
m.connRunners.RemoveConnectionID(c.connID)
m.connIDsToRetire = m.connIDsToRetire[1:]
}
}
func (m *connIDGenerator) RemoveAll() {
if m.initialClientDestConnID != nil {
m.connRunners.RemoveConnectionID(*m.initialClientDestConnID)
}
for _, connID := range m.activeSrcConnIDs {
m.connRunners.RemoveConnectionID(connID)
}
for _, c := range m.connIDsToRetire {
m.connRunners.RemoveConnectionID(c.connID)
}
}
func (m *connIDGenerator) ReplaceWithClosed(connClose []byte, expiry time.Duration) {
connIDs := make([]protocol.ConnectionID, 0, len(m.activeSrcConnIDs)+len(m.connIDsToRetire)+1)
if m.initialClientDestConnID != nil {
connIDs = append(connIDs, *m.initialClientDestConnID)
}
for _, connID := range m.activeSrcConnIDs {
connIDs = append(connIDs, connID)
}
for _, c := range m.connIDsToRetire {
connIDs = append(connIDs, c.connID)
}
m.connRunners.ReplaceWithClosed(connIDs, connClose, expiry)
}
func (m *connIDGenerator) AddConnRunner(runner connRunner, r connRunnerCallbacks) {
// The transport might have already been added earlier.
// This happens if the application migrates back to and old path.
if _, ok := m.connRunners[runner]; ok {
return
}
m.connRunners[runner] = r
if m.initialClientDestConnID != nil {
r.AddConnectionID(*m.initialClientDestConnID)
}
for _, connID := range m.activeSrcConnIDs {
r.AddConnectionID(connID)
}
}
package quic
import (
"fmt"
"slices"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
type newConnID struct {
SequenceNumber uint64
ConnectionID protocol.ConnectionID
StatelessResetToken protocol.StatelessResetToken
}
type connIDManager struct {
queue []newConnID
highestProbingID uint64
pathProbing map[pathID]newConnID // initialized lazily
handshakeComplete bool
activeSequenceNumber uint64
highestRetired uint64
activeConnectionID protocol.ConnectionID
activeStatelessResetToken *protocol.StatelessResetToken
// We change the connection ID after sending on average
// protocol.PacketsPerConnectionID packets. The actual value is randomized
// hide the packet loss rate from on-path observers.
rand utils.Rand
packetsSinceLastChange uint32
packetsPerConnectionID uint32
addStatelessResetToken func(protocol.StatelessResetToken)
removeStatelessResetToken func(protocol.StatelessResetToken)
queueControlFrame func(wire.Frame)
closed bool
}
func newConnIDManager(
initialDestConnID protocol.ConnectionID,
addStatelessResetToken func(protocol.StatelessResetToken),
removeStatelessResetToken func(protocol.StatelessResetToken),
queueControlFrame func(wire.Frame),
) *connIDManager {
return &connIDManager{
activeConnectionID: initialDestConnID,
addStatelessResetToken: addStatelessResetToken,
removeStatelessResetToken: removeStatelessResetToken,
queueControlFrame: queueControlFrame,
queue: make([]newConnID, 0, protocol.MaxActiveConnectionIDs),
}
}
func (h *connIDManager) AddFromPreferredAddress(connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
return h.addConnectionID(1, connID, resetToken)
}
func (h *connIDManager) Add(f *wire.NewConnectionIDFrame) error {
if err := h.add(f); err != nil {
return err
}
if len(h.queue) >= protocol.MaxActiveConnectionIDs {
return &qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}
}
return nil
}
func (h *connIDManager) add(f *wire.NewConnectionIDFrame) error {
if h.activeConnectionID.Len() == 0 {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received NEW_CONNECTION_ID frame but zero-length connection IDs are in use",
}
}
// If the NEW_CONNECTION_ID frame is reordered, such that its sequence number is smaller than the currently active
// connection ID or if it was already retired, send the RETIRE_CONNECTION_ID frame immediately.
if f.SequenceNumber < max(h.activeSequenceNumber, h.highestProbingID) || f.SequenceNumber < h.highestRetired {
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: f.SequenceNumber,
})
return nil
}
if f.RetirePriorTo != 0 && h.pathProbing != nil {
for id, entry := range h.pathProbing {
if entry.SequenceNumber < f.RetirePriorTo {
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: entry.SequenceNumber,
})
h.removeStatelessResetToken(entry.StatelessResetToken)
delete(h.pathProbing, id)
}
}
}
// Retire elements in the queue.
// Doesn't retire the active connection ID.
if f.RetirePriorTo > h.highestRetired {
var newQueue []newConnID
for _, entry := range h.queue {
if entry.SequenceNumber >= f.RetirePriorTo {
newQueue = append(newQueue, entry)
} else {
h.queueControlFrame(&wire.RetireConnectionIDFrame{SequenceNumber: entry.SequenceNumber})
}
}
h.queue = newQueue
h.highestRetired = f.RetirePriorTo
}
if f.SequenceNumber == h.activeSequenceNumber {
return nil
}
if err := h.addConnectionID(f.SequenceNumber, f.ConnectionID, f.StatelessResetToken); err != nil {
return err
}
// Retire the active connection ID, if necessary.
if h.activeSequenceNumber < f.RetirePriorTo {
// The queue is guaranteed to have at least one element at this point.
h.updateConnectionID()
}
return nil
}
func (h *connIDManager) addConnectionID(seq uint64, connID protocol.ConnectionID, resetToken protocol.StatelessResetToken) error {
// fast path: add to the end of the queue
if len(h.queue) == 0 || h.queue[len(h.queue)-1].SequenceNumber < seq {
h.queue = append(h.queue, newConnID{
SequenceNumber: seq,
ConnectionID: connID,
StatelessResetToken: resetToken,
})
return nil
}
// slow path: insert in the middle
for i, entry := range h.queue {
if entry.SequenceNumber == seq {
if entry.ConnectionID != connID {
return fmt.Errorf("received conflicting connection IDs for sequence number %d", seq)
}
if entry.StatelessResetToken != resetToken {
return fmt.Errorf("received conflicting stateless reset tokens for sequence number %d", seq)
}
return nil
}
// insert at the correct position to maintain sorted order
if entry.SequenceNumber > seq {
h.queue = slices.Insert(h.queue, i, newConnID{
SequenceNumber: seq,
ConnectionID: connID,
StatelessResetToken: resetToken,
})
return nil
}
}
return nil // unreachable
}
func (h *connIDManager) updateConnectionID() {
h.assertNotClosed()
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: h.activeSequenceNumber,
})
h.highestRetired = max(h.highestRetired, h.activeSequenceNumber)
if h.activeStatelessResetToken != nil {
h.removeStatelessResetToken(*h.activeStatelessResetToken)
}
front := h.queue[0]
h.queue = h.queue[1:]
h.activeSequenceNumber = front.SequenceNumber
h.activeConnectionID = front.ConnectionID
h.activeStatelessResetToken = &front.StatelessResetToken
h.packetsSinceLastChange = 0
h.packetsPerConnectionID = protocol.PacketsPerConnectionID/2 + uint32(h.rand.Int31n(protocol.PacketsPerConnectionID))
h.addStatelessResetToken(*h.activeStatelessResetToken)
}
func (h *connIDManager) Close() {
h.closed = true
if h.activeStatelessResetToken != nil {
h.removeStatelessResetToken(*h.activeStatelessResetToken)
}
if h.pathProbing != nil {
for _, entry := range h.pathProbing {
h.removeStatelessResetToken(entry.StatelessResetToken)
}
}
}
// is called when the server performs a Retry
// and when the server changes the connection ID in the first Initial sent
func (h *connIDManager) ChangeInitialConnID(newConnID protocol.ConnectionID) {
if h.activeSequenceNumber != 0 {
panic("expected first connection ID to have sequence number 0")
}
h.activeConnectionID = newConnID
}
// is called when the server provides a stateless reset token in the transport parameters
func (h *connIDManager) SetStatelessResetToken(token protocol.StatelessResetToken) {
h.assertNotClosed()
if h.activeSequenceNumber != 0 {
panic("expected first connection ID to have sequence number 0")
}
h.activeStatelessResetToken = &token
h.addStatelessResetToken(token)
}
func (h *connIDManager) SentPacket() {
h.packetsSinceLastChange++
}
func (h *connIDManager) shouldUpdateConnID() bool {
if !h.handshakeComplete {
return false
}
// initiate the first change as early as possible (after handshake completion)
if len(h.queue) > 0 && h.activeSequenceNumber == 0 {
return true
}
// For later changes, only change if
// 1. The queue of connection IDs is filled more than 50%.
// 2. We sent at least PacketsPerConnectionID packets
return 2*len(h.queue) >= protocol.MaxActiveConnectionIDs &&
h.packetsSinceLastChange >= h.packetsPerConnectionID
}
func (h *connIDManager) Get() protocol.ConnectionID {
h.assertNotClosed()
if h.shouldUpdateConnID() {
h.updateConnectionID()
}
return h.activeConnectionID
}
func (h *connIDManager) SetHandshakeComplete() {
h.handshakeComplete = true
}
// GetConnIDForPath retrieves a connection ID for a new path (i.e. not the active one).
// Once a connection ID is allocated for a path, it cannot be used for a different path.
// When called with the same pathID, it will return the same connection ID,
// unless the peer requested that this connection ID be retired.
func (h *connIDManager) GetConnIDForPath(id pathID) (protocol.ConnectionID, bool) {
h.assertNotClosed()
// if we're using zero-length connection IDs, we don't need to change the connection ID
if h.activeConnectionID.Len() == 0 {
return protocol.ConnectionID{}, true
}
if h.pathProbing == nil {
h.pathProbing = make(map[pathID]newConnID)
}
entry, ok := h.pathProbing[id]
if ok {
return entry.ConnectionID, true
}
if len(h.queue) == 0 {
return protocol.ConnectionID{}, false
}
front := h.queue[0]
h.queue = h.queue[1:]
h.pathProbing[id] = front
h.highestProbingID = front.SequenceNumber
h.addStatelessResetToken(front.StatelessResetToken)
return front.ConnectionID, true
}
func (h *connIDManager) RetireConnIDForPath(pathID pathID) {
h.assertNotClosed()
// if we're using zero-length connection IDs, we don't need to change the connection ID
if h.activeConnectionID.Len() == 0 {
return
}
entry, ok := h.pathProbing[pathID]
if !ok {
return
}
h.queueControlFrame(&wire.RetireConnectionIDFrame{
SequenceNumber: entry.SequenceNumber,
})
h.removeStatelessResetToken(entry.StatelessResetToken)
delete(h.pathProbing, pathID)
}
func (h *connIDManager) IsActiveStatelessResetToken(token protocol.StatelessResetToken) bool {
if h.activeStatelessResetToken != nil {
if *h.activeStatelessResetToken == token {
return true
}
}
if h.pathProbing != nil {
for _, entry := range h.pathProbing {
if entry.StatelessResetToken == token {
return true
}
}
}
return false
}
// Using the connIDManager after it has been closed can have disastrous effects:
// If the connection ID is rotated, a new entry would be inserted into the packet handler map,
// leading to a memory leak of the connection struct.
// See https://github.com/quic-go/quic-go/pull/4852 for more details.
func (h *connIDManager) assertNotClosed() {
if h.closed {
panic("connection ID manager is closed")
}
}
package quic
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"reflect"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/utils/ringbuffer"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
type unpacker interface {
UnpackLongHeader(hdr *wire.Header, data []byte) (*unpackedPacket, error)
UnpackShortHeader(rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error)
}
type cryptoStreamHandler interface {
StartHandshake(context.Context) error
ChangeConnectionID(protocol.ConnectionID)
SetLargest1RTTAcked(protocol.PacketNumber) error
SetHandshakeConfirmed()
GetSessionTicket() ([]byte, error)
NextEvent() handshake.Event
DiscardInitialKeys()
HandleMessage([]byte, protocol.EncryptionLevel) error
io.Closer
ConnectionState() handshake.ConnectionState
}
type receivedPacket struct {
buffer *packetBuffer
remoteAddr net.Addr
rcvTime monotime.Time
data []byte
ecn protocol.ECN
info packetInfo // only valid if the contained IP address is valid
}
type receivedPacketWithDatagramID struct {
receivedPacket
datagramID qlog.DatagramID
}
func (p *receivedPacket) Size() protocol.ByteCount { return protocol.ByteCount(len(p.data)) }
func (p *receivedPacket) Clone() *receivedPacket {
return &receivedPacket{
remoteAddr: p.remoteAddr,
rcvTime: p.rcvTime,
data: p.data,
buffer: p.buffer,
ecn: p.ecn,
info: p.info,
}
}
type connRunner interface {
Add(protocol.ConnectionID, packetHandler) bool
Remove(protocol.ConnectionID)
ReplaceWithClosed([]protocol.ConnectionID, []byte, time.Duration)
AddResetToken(protocol.StatelessResetToken, packetHandler)
RemoveResetToken(protocol.StatelessResetToken)
}
type closeError struct {
err error
immediate bool
}
type errCloseForRecreating struct {
nextPacketNumber protocol.PacketNumber
nextVersion protocol.Version
}
func (e *errCloseForRecreating) Error() string {
return "closing connection in order to recreate it"
}
var deadlineSendImmediately = monotime.Time(42 * time.Millisecond) // any value > time.Time{} and before time.Now() is fine
type blockMode uint8
const (
// blockModeNone means that the connection is not blocked.
blockModeNone blockMode = iota
// blockModeCongestionLimited means that the connection is congestion limited.
// In that case, we can still send acknowledgments and PTO probe packets.
blockModeCongestionLimited
// blockModeHardBlocked means that no packet can be sent, under no circumstances. This can happen when:
// * the send queue is full
// * the SentPacketHandler returns SendNone, e.g. when we are tracking the maximum number of packets
// In that case, the timer will be set to the idle timeout.
blockModeHardBlocked
)
// A Conn is a QUIC connection between two peers.
// Calls to the connection (and to streams) can return the following types of errors:
// - [ApplicationError]: for errors triggered by the application running on top of QUIC
// - [TransportError]: for errors triggered by the QUIC transport (in many cases a misbehaving peer)
// - [IdleTimeoutError]: when the peer goes away unexpectedly (this is a [net.Error] timeout error)
// - [HandshakeTimeoutError]: when the cryptographic handshake takes too long (this is a [net.Error] timeout error)
// - [StatelessResetError]: when we receive a stateless reset
// - [VersionNegotiationError]: returned by the client, when there's no version overlap between the peers
type Conn struct {
// Destination connection ID used during the handshake.
// Used to check source connection ID on incoming packets.
handshakeDestConnID protocol.ConnectionID
// Set for the client. Destination connection ID used on the first Initial sent.
origDestConnID protocol.ConnectionID
retrySrcConnID *protocol.ConnectionID // only set for the client (and if a Retry was performed)
srcConnIDLen int
perspective protocol.Perspective
version protocol.Version
config *Config
conn sendConn
sendQueue sender
// lazily initialzed: most connections never migrate
pathManager *pathManager
largestRcvdAppData protocol.PacketNumber
pathManagerOutgoing atomic.Pointer[pathManagerOutgoing]
streamsMap *streamsMap
connIDManager *connIDManager
connIDGenerator *connIDGenerator
rttStats *utils.RTTStats
connStats utils.ConnectionStats
cryptoStreamManager *cryptoStreamManager
sentPacketHandler ackhandler.SentPacketHandler
receivedPacketHandler ackhandler.ReceivedPacketHandler
retransmissionQueue *retransmissionQueue
framer *framer
connFlowController *connectionFlowController
tokenStoreKey string // only set for the client
tokenGenerator *handshake.TokenGenerator // only set for the server
unpacker unpacker
frameParser wire.FrameParser
packer packer
mtuDiscoverer mtuDiscoverer // initialized when the transport parameters are received
maxPayloadSizeEstimate atomic.Uint32
initialStream *initialCryptoStream
handshakeStream *cryptoStream
oneRTTStream *cryptoStream // only set for the server
cryptoStreamHandler cryptoStreamHandler
notifyReceivedPacket chan struct{}
sendingScheduled chan struct{}
receivedPacketMx sync.Mutex
receivedPackets ringbuffer.RingBuffer[receivedPacket]
// closeChan is used to notify the run loop that it should terminate
closeChan chan struct{}
closeErr atomic.Pointer[closeError]
ctx context.Context
ctxCancel context.CancelCauseFunc
handshakeCompleteChan chan struct{}
undecryptablePackets []receivedPacketWithDatagramID // undecryptable packets, waiting for a change in encryption level
undecryptablePacketsToProcess []receivedPacketWithDatagramID
earlyConnReadyChan chan struct{}
sentFirstPacket bool
droppedInitialKeys bool
handshakeComplete bool
handshakeConfirmed bool
receivedRetry bool
versionNegotiated bool
receivedFirstPacket bool
blocked blockMode
// the minimum of the max_idle_timeout values advertised by both endpoints
idleTimeout time.Duration
creationTime monotime.Time
// The idle timeout is set based on the max of the time we received the last packet...
lastPacketReceivedTime monotime.Time
// ... and the time we sent a new ack-eliciting packet after receiving a packet.
firstAckElicitingPacketAfterIdleSentTime monotime.Time
// pacingDeadline is the time when the next packet should be sent
pacingDeadline monotime.Time
peerParams *wire.TransportParameters
timer *time.Timer
// keepAlivePingSent stores whether a keep alive PING is in flight.
// It is reset as soon as we receive a packet from the peer.
keepAlivePingSent bool
keepAliveInterval time.Duration
datagramQueue *datagramQueue
connStateMutex sync.Mutex
connState ConnectionState
logID string
qlogTrace qlogwriter.Trace
qlogger qlogwriter.Recorder
logger utils.Logger
}
var _ streamSender = &Conn{}
type connTestHooks struct {
run func() error
earlyConnReady func() <-chan struct{}
context func() context.Context
handshakeComplete func() <-chan struct{}
closeWithTransportError func(TransportErrorCode)
destroy func(error)
handlePacket func(receivedPacket)
}
type wrappedConn struct {
testHooks *connTestHooks
*Conn
}
var newConnection = func(
ctx context.Context,
ctxCancel context.CancelCauseFunc,
conn sendConn,
runner connRunner,
origDestConnID protocol.ConnectionID,
retrySrcConnID *protocol.ConnectionID,
clientDestConnID protocol.ConnectionID,
destConnID protocol.ConnectionID,
srcConnID protocol.ConnectionID,
connIDGenerator ConnectionIDGenerator,
statelessResetter *statelessResetter,
conf *Config,
tlsConf *tls.Config,
tokenGenerator *handshake.TokenGenerator,
clientAddressValidated bool,
rtt time.Duration,
qlogTrace qlogwriter.Trace,
logger utils.Logger,
v protocol.Version,
) *wrappedConn {
s := &Conn{
ctx: ctx,
ctxCancel: ctxCancel,
conn: conn,
config: conf,
handshakeDestConnID: destConnID,
srcConnIDLen: srcConnID.Len(),
tokenGenerator: tokenGenerator,
oneRTTStream: newCryptoStream(),
perspective: protocol.PerspectiveServer,
qlogTrace: qlogTrace,
logger: logger,
version: v,
}
if qlogTrace != nil {
s.qlogger = qlogTrace.AddProducer()
}
if origDestConnID.Len() > 0 {
s.logID = origDestConnID.String()
} else {
s.logID = destConnID.String()
}
s.connIDManager = newConnIDManager(
destConnID,
func(token protocol.StatelessResetToken) { runner.AddResetToken(token, s) },
runner.RemoveResetToken,
s.queueControlFrame,
)
s.connIDGenerator = newConnIDGenerator(
runner,
srcConnID,
&clientDestConnID,
statelessResetter,
connRunnerCallbacks{
AddConnectionID: func(connID protocol.ConnectionID) { runner.Add(connID, s) },
RemoveConnectionID: runner.Remove,
ReplaceWithClosed: runner.ReplaceWithClosed,
},
s.queueControlFrame,
connIDGenerator,
)
s.preSetup()
s.rttStats.SetInitialRTT(rtt)
s.sentPacketHandler = ackhandler.NewSentPacketHandler(
0,
protocol.ByteCount(s.config.InitialPacketSize),
s.rttStats,
&s.connStats,
clientAddressValidated,
s.conn.capabilities().ECN,
s.receivedPacketHandler.IgnorePacketsBelow,
s.perspective,
s.qlogger,
s.logger,
)
s.maxPayloadSizeEstimate.Store(uint32(estimateMaxPayloadSize(protocol.ByteCount(s.config.InitialPacketSize))))
statelessResetToken := statelessResetter.GetStatelessResetToken(srcConnID)
params := &wire.TransportParameters{
InitialMaxStreamDataBidiLocal: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxStreamDataBidiRemote: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxStreamDataUni: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxData: protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
MaxIdleTimeout: s.config.MaxIdleTimeout,
MaxBidiStreamNum: protocol.StreamNum(s.config.MaxIncomingStreams),
MaxUniStreamNum: protocol.StreamNum(s.config.MaxIncomingUniStreams),
MaxAckDelay: protocol.MaxAckDelayInclGranularity,
AckDelayExponent: protocol.AckDelayExponent,
MaxUDPPayloadSize: protocol.MaxPacketBufferSize,
StatelessResetToken: &statelessResetToken,
OriginalDestinationConnectionID: origDestConnID,
// For interoperability with quic-go versions before May 2023, this value must be set to a value
// different from protocol.DefaultActiveConnectionIDLimit.
// If set to the default value, it will be omitted from the transport parameters, which will make
// old quic-go versions interpret it as 0, instead of the default value of 2.
// See https://github.com/quic-go/quic-go/pull/3806.
ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
InitialSourceConnectionID: srcConnID,
RetrySourceConnectionID: retrySrcConnID,
EnableResetStreamAt: conf.EnableStreamResetPartialDelivery,
}
if s.config.EnableDatagrams {
params.MaxDatagramFrameSize = wire.MaxDatagramSize
} else {
params.MaxDatagramFrameSize = protocol.InvalidByteCount
}
if s.qlogger != nil {
s.qlogTransportParameters(params, protocol.PerspectiveServer, false)
}
cs := handshake.NewCryptoSetupServer(
clientDestConnID,
conn.LocalAddr(),
conn.RemoteAddr(),
params,
tlsConf,
conf.Allow0RTT,
s.rttStats,
s.qlogger,
logger,
s.version,
)
s.cryptoStreamHandler = cs
s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, &s.receivedPacketHandler, s.datagramQueue, s.perspective)
s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen)
s.cryptoStreamManager = newCryptoStreamManager(s.initialStream, s.handshakeStream, s.oneRTTStream)
return &wrappedConn{Conn: s}
}
// declare this as a variable, such that we can it mock it in the tests
var newClientConnection = func(
ctx context.Context,
conn sendConn,
runner connRunner,
destConnID protocol.ConnectionID,
srcConnID protocol.ConnectionID,
connIDGenerator ConnectionIDGenerator,
statelessResetter *statelessResetter,
conf *Config,
tlsConf *tls.Config,
initialPacketNumber protocol.PacketNumber,
enable0RTT bool,
hasNegotiatedVersion bool,
qlogTrace qlogwriter.Trace,
logger utils.Logger,
v protocol.Version,
) *wrappedConn {
s := &Conn{
conn: conn,
config: conf,
origDestConnID: destConnID,
handshakeDestConnID: destConnID,
srcConnIDLen: srcConnID.Len(),
perspective: protocol.PerspectiveClient,
logID: destConnID.String(),
logger: logger,
qlogTrace: qlogTrace,
versionNegotiated: hasNegotiatedVersion,
version: v,
}
if qlogTrace != nil {
s.qlogger = qlogTrace.AddProducer()
}
if s.qlogger != nil {
var srcAddr, destAddr *net.UDPAddr
if addr, ok := conn.LocalAddr().(*net.UDPAddr); ok {
srcAddr = addr
}
if addr, ok := conn.RemoteAddr().(*net.UDPAddr); ok {
destAddr = addr
}
s.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr))
}
s.connIDManager = newConnIDManager(
destConnID,
func(token protocol.StatelessResetToken) { runner.AddResetToken(token, s) },
runner.RemoveResetToken,
s.queueControlFrame,
)
s.connIDGenerator = newConnIDGenerator(
runner,
srcConnID,
nil,
statelessResetter,
connRunnerCallbacks{
AddConnectionID: func(connID protocol.ConnectionID) { runner.Add(connID, s) },
RemoveConnectionID: runner.Remove,
ReplaceWithClosed: runner.ReplaceWithClosed,
},
s.queueControlFrame,
connIDGenerator,
)
s.ctx, s.ctxCancel = context.WithCancelCause(ctx)
s.preSetup()
s.sentPacketHandler = ackhandler.NewSentPacketHandler(
initialPacketNumber,
protocol.ByteCount(s.config.InitialPacketSize),
s.rttStats,
&s.connStats,
false, // has no effect
s.conn.capabilities().ECN,
s.receivedPacketHandler.IgnorePacketsBelow,
s.perspective,
s.qlogger,
s.logger,
)
s.maxPayloadSizeEstimate.Store(uint32(estimateMaxPayloadSize(protocol.ByteCount(s.config.InitialPacketSize))))
oneRTTStream := newCryptoStream()
params := &wire.TransportParameters{
InitialMaxStreamDataBidiRemote: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxStreamDataBidiLocal: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxStreamDataUni: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
InitialMaxData: protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
MaxIdleTimeout: s.config.MaxIdleTimeout,
MaxBidiStreamNum: protocol.StreamNum(s.config.MaxIncomingStreams),
MaxUniStreamNum: protocol.StreamNum(s.config.MaxIncomingUniStreams),
MaxAckDelay: protocol.MaxAckDelayInclGranularity,
MaxUDPPayloadSize: protocol.MaxPacketBufferSize,
AckDelayExponent: protocol.AckDelayExponent,
// For interoperability with quic-go versions before May 2023, this value must be set to a value
// different from protocol.DefaultActiveConnectionIDLimit.
// If set to the default value, it will be omitted from the transport parameters, which will make
// old quic-go versions interpret it as 0, instead of the default value of 2.
// See https://github.com/quic-go/quic-go/pull/3806.
ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
InitialSourceConnectionID: srcConnID,
EnableResetStreamAt: conf.EnableStreamResetPartialDelivery,
}
if s.config.EnableDatagrams {
params.MaxDatagramFrameSize = wire.MaxDatagramSize
} else {
params.MaxDatagramFrameSize = protocol.InvalidByteCount
}
if s.qlogger != nil {
s.qlogTransportParameters(params, protocol.PerspectiveClient, false)
}
cs := handshake.NewCryptoSetupClient(
destConnID,
params,
tlsConf,
enable0RTT,
s.rttStats,
s.qlogger,
logger,
s.version,
)
s.cryptoStreamHandler = cs
s.cryptoStreamManager = newCryptoStreamManager(s.initialStream, s.handshakeStream, oneRTTStream)
s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen)
s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, &s.receivedPacketHandler, s.datagramQueue, s.perspective)
if len(tlsConf.ServerName) > 0 {
s.tokenStoreKey = tlsConf.ServerName
} else {
s.tokenStoreKey = conn.RemoteAddr().String()
}
if s.config.TokenStore != nil {
if token := s.config.TokenStore.Pop(s.tokenStoreKey); token != nil {
s.packer.SetToken(token.data)
s.rttStats.SetInitialRTT(token.rtt)
}
}
return &wrappedConn{Conn: s}
}
func (c *Conn) preSetup() {
c.largestRcvdAppData = protocol.InvalidPacketNumber
c.initialStream = newInitialCryptoStream(c.perspective == protocol.PerspectiveClient)
c.handshakeStream = newCryptoStream()
c.sendQueue = newSendQueue(c.conn)
c.retransmissionQueue = newRetransmissionQueue()
c.frameParser = *wire.NewFrameParser(
c.config.EnableDatagrams,
c.config.EnableStreamResetPartialDelivery,
false, // ACK_FREQUENCY is not supported yet
)
c.rttStats = utils.NewRTTStats()
c.connFlowController = newConnectionFlowController(
protocol.ByteCount(c.config.InitialConnectionReceiveWindow),
protocol.ByteCount(c.config.MaxConnectionReceiveWindow),
func(size protocol.ByteCount) bool {
if c.config.AllowConnectionWindowIncrease == nil {
return true
}
return c.config.AllowConnectionWindowIncrease(c, uint64(size))
},
c.rttStats,
c.logger,
)
c.earlyConnReadyChan = make(chan struct{})
c.streamsMap = newStreamsMap(
c.ctx,
c,
c.queueControlFrame,
c.newFlowController,
uint64(c.config.MaxIncomingStreams),
uint64(c.config.MaxIncomingUniStreams),
c.perspective,
)
c.framer = newFramer(c.connFlowController)
c.receivedPackets.Init(8)
c.notifyReceivedPacket = make(chan struct{}, 1)
c.closeChan = make(chan struct{}, 1)
c.sendingScheduled = make(chan struct{}, 1)
c.handshakeCompleteChan = make(chan struct{})
now := monotime.Now()
c.lastPacketReceivedTime = now
c.creationTime = now
c.receivedPacketHandler = *ackhandler.NewReceivedPacketHandler(c.logger)
c.datagramQueue = newDatagramQueue(c.scheduleSending, c.logger)
c.connState.Version = c.version
}
// run the connection main loop
func (c *Conn) run() (err error) {
defer func() { c.ctxCancel(err) }()
defer func() {
// drain queued packets that will never be processed
c.receivedPacketMx.Lock()
defer c.receivedPacketMx.Unlock()
for !c.receivedPackets.Empty() {
p := c.receivedPackets.PopFront()
p.buffer.Decrement()
p.buffer.MaybeRelease()
}
}()
c.timer = time.NewTimer(monotime.Until(c.idleTimeoutStartTime().Add(c.config.HandshakeIdleTimeout)))
if err := c.cryptoStreamHandler.StartHandshake(c.ctx); err != nil {
return err
}
if err := c.handleHandshakeEvents(monotime.Now()); err != nil {
return err
}
go func() {
if err := c.sendQueue.Run(); err != nil {
c.destroyImpl(err)
}
}()
if c.perspective == protocol.PerspectiveClient {
c.scheduleSending() // so the ClientHello actually gets sent
}
var sendQueueAvailable <-chan struct{}
runLoop:
for {
if c.framer.QueuedTooManyControlFrames() {
c.setCloseError(&closeError{err: &qerr.TransportError{ErrorCode: InternalError}})
break runLoop
}
// Close immediately if requested
select {
case <-c.closeChan:
break runLoop
default:
}
// no need to set a timer if we can send packets immediately
if c.pacingDeadline != deadlineSendImmediately {
c.maybeResetTimer()
}
// 1st: handle undecryptable packets, if any.
// This can only occur before completion of the handshake.
if len(c.undecryptablePacketsToProcess) > 0 {
var processedUndecryptablePacket bool
queue := c.undecryptablePacketsToProcess
c.undecryptablePacketsToProcess = nil
for _, p := range queue {
processed, err := c.handleOnePacket(p.receivedPacket, p.datagramID)
if err != nil {
c.setCloseError(&closeError{err: err})
break runLoop
}
if processed {
processedUndecryptablePacket = true
}
}
if processedUndecryptablePacket {
// if we processed any undecryptable packets, jump to the resetting of the timers directly
continue
}
}
// 2nd: receive packets.
processed, err := c.handlePackets() // don't check receivedPackets.Len() in the run loop to avoid locking the mutex
if err != nil {
c.setCloseError(&closeError{err: err})
break runLoop
}
// We don't need to wait for new events if:
// * we processed packets: we probably need to send an ACK, and potentially more data
// * the pacer allows us to send more packets immediately
shouldProceedImmediately := sendQueueAvailable == nil && (processed || c.pacingDeadline.Equal(deadlineSendImmediately))
if !shouldProceedImmediately {
// 3rd: wait for something to happen:
// * closing of the connection
// * timer firing
// * sending scheduled
// * send queue available
// * received packets
select {
case <-c.closeChan:
break runLoop
case <-c.timer.C:
case <-c.sendingScheduled:
case <-sendQueueAvailable:
case <-c.notifyReceivedPacket:
wasProcessed, err := c.handlePackets()
if err != nil {
c.setCloseError(&closeError{err: err})
break runLoop
}
// if we processed any undecryptable packets, jump to the resetting of the timers directly
if !wasProcessed {
continue
}
}
}
// Check for loss detection timeout.
// This could cause packets to be declared lost, and retransmissions to be enqueued.
now := monotime.Now()
if timeout := c.sentPacketHandler.GetLossDetectionTimeout(); !timeout.IsZero() && !timeout.After(now) {
if err := c.sentPacketHandler.OnLossDetectionTimeout(now); err != nil {
c.setCloseError(&closeError{err: err})
break runLoop
}
}
if keepAliveTime := c.nextKeepAliveTime(); !keepAliveTime.IsZero() && !now.Before(keepAliveTime) {
// send a PING frame since there is no activity in the connection
c.logger.Debugf("Sending a keep-alive PING to keep the connection alive.")
c.framer.QueueControlFrame(&wire.PingFrame{})
c.keepAlivePingSent = true
} else if !c.handshakeComplete && now.Sub(c.creationTime) >= c.config.handshakeTimeout() {
c.destroyImpl(qerr.ErrHandshakeTimeout)
break runLoop
} else {
idleTimeoutStartTime := c.idleTimeoutStartTime()
if (!c.handshakeComplete && now.Sub(idleTimeoutStartTime) >= c.config.HandshakeIdleTimeout) ||
(c.handshakeComplete && !now.Before(c.nextIdleTimeoutTime())) {
c.destroyImpl(qerr.ErrIdleTimeout)
break runLoop
}
}
c.connIDGenerator.RemoveRetiredConnIDs(now)
if c.perspective == protocol.PerspectiveClient {
pm := c.pathManagerOutgoing.Load()
if pm != nil {
tr, ok := pm.ShouldSwitchPath()
if ok {
c.switchToNewPath(tr, now)
}
}
}
if c.sendQueue.WouldBlock() {
// The send queue is still busy sending out packets. Wait until there's space to enqueue new packets.
sendQueueAvailable = c.sendQueue.Available()
// Cancel the pacing timer, as we can't send any more packets until the send queue is available again.
c.pacingDeadline = 0
c.blocked = blockModeHardBlocked
continue
}
if c.closeErr.Load() != nil {
break runLoop
}
c.blocked = blockModeNone // sending might set it back to true if we're congestion limited
if err := c.triggerSending(now); err != nil {
c.setCloseError(&closeError{err: err})
break runLoop
}
if c.sendQueue.WouldBlock() {
// The send queue is still busy sending out packets. Wait until there's space to enqueue new packets.
sendQueueAvailable = c.sendQueue.Available()
// Cancel the pacing timer, as we can't send any more packets until the send queue is available again.
c.pacingDeadline = 0
c.blocked = blockModeHardBlocked
} else {
sendQueueAvailable = nil
}
}
closeErr := c.closeErr.Load()
c.cryptoStreamHandler.Close()
c.sendQueue.Close() // close the send queue before sending the CONNECTION_CLOSE
c.handleCloseError(closeErr)
if c.qlogger != nil {
if e := (&errCloseForRecreating{}); !errors.As(closeErr.err, &e) {
c.qlogger.Close()
}
}
c.logger.Infof("Connection %s closed.", c.logID)
c.timer.Stop()
return closeErr.err
}
// blocks until the early connection can be used
func (c *Conn) earlyConnReady() <-chan struct{} {
return c.earlyConnReadyChan
}
// Context returns a context that is cancelled when the connection is closed.
// The cancellation cause is set to the error that caused the connection to close.
func (c *Conn) Context() context.Context {
return c.ctx
}
func (c *Conn) supportsDatagrams() bool {
return c.peerParams.MaxDatagramFrameSize > 0
}
// ConnectionState returns basic details about the QUIC connection.
func (c *Conn) ConnectionState() ConnectionState {
c.connStateMutex.Lock()
defer c.connStateMutex.Unlock()
cs := c.cryptoStreamHandler.ConnectionState()
c.connState.TLS = cs.ConnectionState
c.connState.Used0RTT = cs.Used0RTT
if c.peerParams != nil {
c.connState.SupportsDatagrams.Remote = c.supportsDatagrams()
c.connState.SupportsStreamResetPartialDelivery.Remote = c.peerParams.EnableResetStreamAt
}
c.connState.SupportsDatagrams.Local = c.config.EnableDatagrams
c.connState.SupportsStreamResetPartialDelivery.Local = c.config.EnableStreamResetPartialDelivery
c.connState.GSO = c.conn.capabilities().GSO
return c.connState
}
// ConnectionStats contains statistics about the QUIC connection
type ConnectionStats struct {
// MinRTT is the estimate of the minimum RTT observed on the active network
// path.
MinRTT time.Duration
// LatestRTT is the last RTT sample observed on the active network path.
LatestRTT time.Duration
// SmoothedRTT is an exponentially weighted moving average of an endpoint's
// RTT samples. See https://www.rfc-editor.org/rfc/rfc9002#section-5.3
SmoothedRTT time.Duration
// MeanDeviation estimates the variation in the RTT samples using a mean
// variation. See https://www.rfc-editor.org/rfc/rfc9002#section-5.3
MeanDeviation time.Duration
// BytesSent is the number of bytes sent on the underlying connection,
// including retransmissions. Does not include UDP or any other outer
// framing.
BytesSent uint64
// PacketsSent is the number of packets sent on the underlying connection,
// including those that are determined to have been lost.
PacketsSent uint64
// BytesReceived is the number of total bytes received on the underlying
// connection, including duplicate data for streams. Does not include UDP or
// any other outer framing.
BytesReceived uint64
// PacketsReceived is the number of total packets received on the underlying
// connection, including packets that were not processable.
PacketsReceived uint64
// BytesLost is the number of bytes lost on the underlying connection (does
// not monotonically increase, because packets that are declared lost can
// subsequently be received). Does not include UDP or any other outer
// framing.
BytesLost uint64
// PacketsLost is the number of packets lost on the underlying connection
// (does not monotonically increase, because packets that are declared lost
// can subsequently be received).
PacketsLost uint64
}
func (c *Conn) ConnectionStats() ConnectionStats {
return ConnectionStats{
MinRTT: c.rttStats.MinRTT(),
LatestRTT: c.rttStats.LatestRTT(),
SmoothedRTT: c.rttStats.SmoothedRTT(),
MeanDeviation: c.rttStats.MeanDeviation(),
BytesSent: c.connStats.BytesSent.Load(),
PacketsSent: c.connStats.PacketsSent.Load(),
BytesReceived: c.connStats.BytesReceived.Load(),
PacketsReceived: c.connStats.PacketsReceived.Load(),
BytesLost: c.connStats.BytesLost.Load(),
PacketsLost: c.connStats.PacketsLost.Load(),
}
}
// Time when the connection should time out
func (c *Conn) nextIdleTimeoutTime() monotime.Time {
idleTimeout := max(c.idleTimeout, c.rttStats.PTO(true)*3)
return c.idleTimeoutStartTime().Add(idleTimeout)
}
// Time when the next keep-alive packet should be sent.
// It returns a zero time if no keep-alive should be sent.
func (c *Conn) nextKeepAliveTime() monotime.Time {
if c.config.KeepAlivePeriod == 0 || c.keepAlivePingSent {
return 0
}
keepAliveInterval := max(c.keepAliveInterval, c.rttStats.PTO(true)*3/2)
return c.lastPacketReceivedTime.Add(keepAliveInterval)
}
func (c *Conn) maybeResetTimer() {
var deadline monotime.Time
if !c.handshakeComplete {
deadline = c.creationTime.Add(c.config.handshakeTimeout())
if t := c.idleTimeoutStartTime().Add(c.config.HandshakeIdleTimeout); t.Before(deadline) {
deadline = t
}
} else {
// A keep-alive packet is ack-eliciting, so it can only be sent if the connection is
// neither congestion limited nor hard-blocked.
if c.blocked != blockModeNone {
deadline = c.nextIdleTimeoutTime()
} else {
if keepAliveTime := c.nextKeepAliveTime(); !keepAliveTime.IsZero() {
deadline = keepAliveTime
} else {
deadline = c.nextIdleTimeoutTime()
}
}
}
// If the connection is hard-blocked, we can't even send acknowledgments,
// nor can we send PTO probe packets.
if c.blocked == blockModeHardBlocked {
c.timer.Reset(monotime.Until(deadline))
return
}
if t := c.receivedPacketHandler.GetAlarmTimeout(); !t.IsZero() && t.Before(deadline) {
deadline = t
}
if t := c.sentPacketHandler.GetLossDetectionTimeout(); !t.IsZero() && t.Before(deadline) {
deadline = t
}
if c.blocked == blockModeCongestionLimited {
c.timer.Reset(monotime.Until(deadline))
return
}
if !c.pacingDeadline.IsZero() && c.pacingDeadline.Before(deadline) {
deadline = c.pacingDeadline
}
c.timer.Reset(monotime.Until(deadline))
}
func (c *Conn) idleTimeoutStartTime() monotime.Time {
startTime := c.lastPacketReceivedTime
if t := c.firstAckElicitingPacketAfterIdleSentTime; !t.IsZero() && t.After(startTime) {
startTime = t
}
return startTime
}
func (c *Conn) switchToNewPath(tr *Transport, now monotime.Time) {
initialPacketSize := protocol.ByteCount(c.config.InitialPacketSize)
c.sentPacketHandler.MigratedPath(now, initialPacketSize)
maxPacketSize := protocol.ByteCount(protocol.MaxPacketBufferSize)
if c.peerParams.MaxUDPPayloadSize > 0 && c.peerParams.MaxUDPPayloadSize < maxPacketSize {
maxPacketSize = c.peerParams.MaxUDPPayloadSize
}
c.mtuDiscoverer.Reset(now, initialPacketSize, maxPacketSize)
c.conn = newSendConn(tr.conn, c.conn.RemoteAddr(), packetInfo{}, utils.DefaultLogger) // TODO: find a better way
c.sendQueue.Close()
c.sendQueue = newSendQueue(c.conn)
go func() {
if err := c.sendQueue.Run(); err != nil {
c.destroyImpl(err)
}
}()
}
func (c *Conn) handleHandshakeComplete(now monotime.Time) error {
defer close(c.handshakeCompleteChan)
// Once the handshake completes, we have derived 1-RTT keys.
// There's no point in queueing undecryptable packets for later decryption anymore.
c.undecryptablePackets = nil
c.connIDManager.SetHandshakeComplete()
c.connIDGenerator.SetHandshakeComplete(now.Add(3 * c.rttStats.PTO(false)))
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.ALPNInformation{
ChosenALPN: c.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 c.perspective == protocol.PerspectiveClient {
c.applyTransportParameters()
return nil
}
// All these only apply to the server side.
if err := c.handleHandshakeConfirmed(now); err != nil {
return err
}
ticket, err := c.cryptoStreamHandler.GetSessionTicket()
if err != nil {
return err
}
if ticket != nil { // may be nil if session tickets are disabled via tls.Config.SessionTicketsDisabled
c.oneRTTStream.Write(ticket)
for c.oneRTTStream.HasData() {
if cf := c.oneRTTStream.PopCryptoFrame(protocol.MaxPostHandshakeCryptoFrameSize); cf != nil {
c.queueControlFrame(cf)
}
}
}
token, err := c.tokenGenerator.NewToken(c.conn.RemoteAddr(), c.rttStats.SmoothedRTT())
if err != nil {
return err
}
c.queueControlFrame(&wire.NewTokenFrame{Token: token})
c.queueControlFrame(&wire.HandshakeDoneFrame{})
return nil
}
func (c *Conn) handleHandshakeConfirmed(now monotime.Time) error {
// Drop initial keys.
// On the client side, this should have happened when sending the first Handshake packet,
// but this is not guaranteed if the server misbehaves.
// See CVE-2025-59530 for more details.
if err := c.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil {
return err
}
if err := c.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil {
return err
}
c.handshakeConfirmed = true
c.cryptoStreamHandler.SetHandshakeConfirmed()
if !c.config.DisablePathMTUDiscovery && c.conn.capabilities().DF {
c.mtuDiscoverer.Start(now)
}
return nil
}
const maxPacketsToProcess = 32
func (c *Conn) handlePackets() (wasProcessed bool, _ error) {
// Process packets from the receivedPackets queue.
// Limit the number of packets to process to maxPacketsToProcess,
// so we eventually get a chance to send out an ACK when receiving a lot of packets.
c.receivedPacketMx.Lock()
if c.receivedPackets.Empty() {
c.receivedPacketMx.Unlock()
return false, nil
}
var hasMorePackets bool
for range maxPacketsToProcess {
p := c.receivedPackets.PopFront()
c.receivedPacketMx.Unlock()
var datagramID qlog.DatagramID
if c.qlogger != nil && wire.IsLongHeaderPacket(p.data[0]) {
datagramID = qlog.CalculateDatagramID(p.data)
}
processed, err := c.handleOnePacket(p, datagramID)
if err != nil {
return false, err
}
if processed {
wasProcessed = true
}
c.receivedPacketMx.Lock()
hasMorePackets = !c.receivedPackets.Empty()
if !hasMorePackets {
break
}
// Prioritize sending of new CRYPTO data.
// This is especially relevant when processing 0-RTT packets.
if !c.handshakeComplete && (c.initialStream.HasData() || c.handshakeStream.HasData()) {
break
}
}
c.receivedPacketMx.Unlock()
if hasMorePackets {
select {
case c.notifyReceivedPacket <- struct{}{}:
default:
}
}
return wasProcessed, nil
}
func (c *Conn) handleOnePacket(rp receivedPacket, datagramID qlog.DatagramID) (wasProcessed bool, _ error) {
c.sentPacketHandler.ReceivedBytes(rp.Size(), rp.rcvTime)
if wire.IsVersionNegotiationPacket(rp.data) {
return false, c.handleVersionNegotiationPacket(rp)
}
var counter uint8
var lastConnID protocol.ConnectionID
data := rp.data
p := rp
for len(data) > 0 {
if counter > 0 {
p = *(p.Clone())
p.data = data
destConnID, err := wire.ParseConnectionID(p.data, c.srcConnIDLen)
if err != nil {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: len(data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropHeaderParseError,
})
}
c.logger.Debugf("error parsing packet, couldn't parse connection ID: %s", err)
break
}
if destConnID != lastConnID {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{DestConnectionID: destConnID},
Raw: qlog.RawInfo{Length: len(data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropUnknownConnectionID,
})
}
c.logger.Debugf("coalesced packet has different destination connection ID: %s, expected %s", destConnID, lastConnID)
break
}
}
if wire.IsLongHeaderPacket(p.data[0]) {
hdr, packetData, rest, err := wire.ParsePacket(p.data)
if err != nil {
if c.qlogger != nil {
if err == wire.ErrUnsupportedVersion {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{Version: hdr.Version},
Raw: qlog.RawInfo{Length: len(data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropUnsupportedVersion,
})
} else {
c.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: len(data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropHeaderParseError,
})
}
}
c.logger.Debugf("error parsing packet: %s", err)
break
}
lastConnID = hdr.DestConnectionID
if hdr.Version != c.version {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: len(data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropUnexpectedVersion,
})
}
c.logger.Debugf("Dropping packet with version %x. Expected %x.", hdr.Version, c.version)
break
}
if counter > 0 {
p.buffer.Split()
}
counter++
// only log if this actually a coalesced packet
if c.logger.Debug() && (counter > 1 || len(rest) > 0) {
c.logger.Debugf("Parsed a coalesced packet. Part %d: %d bytes. Remaining: %d bytes.", counter, len(packetData), len(rest))
}
p.data = packetData
processed, err := c.handleLongHeaderPacket(p, hdr, datagramID)
if err != nil {
return false, err
}
if processed {
wasProcessed = true
}
data = rest
} else {
if counter > 0 {
p.buffer.Split()
}
processed, err := c.handleShortHeaderPacket(p, counter > 0, datagramID)
if err != nil {
return false, err
}
if processed {
wasProcessed = true
}
break
}
}
p.buffer.MaybeRelease()
c.blocked = blockModeNone
return wasProcessed, nil
}
func (c *Conn) handleShortHeaderPacket(
p receivedPacket,
isCoalesced bool,
datagramID qlog.DatagramID, // only for logging
) (wasProcessed bool, _ error) {
var wasQueued bool
defer func() {
// Put back the packet buffer if the packet wasn't queued for later decryption.
if !wasQueued {
p.buffer.Decrement()
}
}()
destConnID, err := wire.ParseConnectionID(p.data, c.srcConnIDLen)
if err != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType1RTT,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: len(p.data)},
DatagramID: datagramID,
Trigger: qlog.PacketDropHeaderParseError,
})
return false, nil
}
pn, pnLen, keyPhase, data, err := c.unpacker.UnpackShortHeader(p.rcvTime, p.data)
if err != nil {
// Stateless reset packets (see RFC 9000, section 10.3):
// * fill the entire UDP datagram (i.e. they cannot be part of a coalesced packet)
// * are short header packets (first bit is 0)
// * have the QUIC bit set (second bit is 1)
// * are at least 21 bytes long
if !isCoalesced && len(p.data) >= protocol.MinReceivedStatelessResetSize && p.data[0]&0b11000000 == 0b01000000 {
token := protocol.StatelessResetToken(p.data[len(p.data)-16:])
if c.connIDManager.IsActiveStatelessResetToken(token) {
return false, &StatelessResetError{}
}
}
wasQueued, err = c.handleUnpackError(err, p, qlog.PacketType1RTT, datagramID)
return false, err
}
c.largestRcvdAppData = max(c.largestRcvdAppData, pn)
if c.logger.Debug() {
c.logger.Debugf("<- Reading packet %d (%d bytes) for connection %s, 1-RTT", pn, p.Size(), destConnID)
wire.LogShortHeader(c.logger, destConnID, pn, pnLen, keyPhase)
}
if c.receivedPacketHandler.IsPotentiallyDuplicate(pn, protocol.Encryption1RTT) {
c.logger.Debugf("Dropping (potentially) duplicate packet.")
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType1RTT,
PacketNumber: pn,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropDuplicate,
})
}
return false, nil
}
var log func([]qlog.Frame)
if c.qlogger != nil {
log = func(frames []qlog.Frame) {
c.qlogger.RecordEvent(qlog.PacketReceived{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType1RTT,
DestConnectionID: destConnID,
PacketNumber: pn,
KeyPhaseBit: keyPhase,
},
Raw: qlog.RawInfo{
Length: int(p.Size()),
PayloadLength: int(p.Size() - wire.ShortHeaderLen(destConnID, pnLen)),
},
DatagramID: datagramID,
Frames: frames,
ECN: toQlogECN(p.ecn),
})
}
}
isNonProbing, pathChallenge, err := c.handleUnpackedShortHeaderPacket(destConnID, pn, data, p.ecn, p.rcvTime, log)
if err != nil {
return false, err
}
// In RFC 9000, only the client can migrate between paths.
if c.perspective == protocol.PerspectiveClient {
return true, nil
}
if addrsEqual(p.remoteAddr, c.RemoteAddr()) {
return true, nil
}
var shouldSwitchPath bool
if c.pathManager == nil {
c.pathManager = newPathManager(
c.connIDManager.GetConnIDForPath,
c.connIDManager.RetireConnIDForPath,
c.logger,
)
}
destConnID, frames, shouldSwitchPath := c.pathManager.HandlePacket(p.remoteAddr, p.rcvTime, pathChallenge, isNonProbing)
if len(frames) > 0 {
probe, buf, err := c.packer.PackPathProbePacket(destConnID, frames, c.version)
if err != nil {
return true, err
}
c.logger.Debugf("sending path probe packet to %s", p.remoteAddr)
c.logShortHeaderPacketWithDatagramID(probe, protocol.ECNNon, buf.Len(), false, datagramID)
c.registerPackedShortHeaderPacket(probe, protocol.ECNNon, p.rcvTime)
c.sendQueue.SendProbe(buf, p.remoteAddr, p.info)
}
// We only switch paths in response to the highest-numbered non-probing packet,
// see section 9.3 of RFC 9000.
if !shouldSwitchPath || pn != c.largestRcvdAppData {
return true, nil
}
c.pathManager.SwitchToPath(p.remoteAddr)
c.sentPacketHandler.MigratedPath(p.rcvTime, protocol.ByteCount(c.config.InitialPacketSize))
maxPacketSize := protocol.ByteCount(protocol.MaxPacketBufferSize)
if c.peerParams.MaxUDPPayloadSize > 0 && c.peerParams.MaxUDPPayloadSize < maxPacketSize {
maxPacketSize = c.peerParams.MaxUDPPayloadSize
}
c.mtuDiscoverer.Reset(
p.rcvTime,
protocol.ByteCount(c.config.InitialPacketSize),
maxPacketSize,
)
c.conn.ChangeRemoteAddr(p.remoteAddr, p.info)
return true, nil
}
func (c *Conn) handleLongHeaderPacket(p receivedPacket, hdr *wire.Header, datagramID qlog.DatagramID) (wasProcessed bool, _ error) {
var wasQueued bool
defer func() {
// Put back the packet buffer if the packet wasn't queued for later decryption.
if !wasQueued {
p.buffer.Decrement()
}
}()
if hdr.Type == protocol.PacketTypeRetry {
return c.handleRetryPacket(hdr, p.data, p.rcvTime), nil
}
// The server can change the source connection ID with the first Handshake packet.
// After this, all packets with a different source connection have to be ignored.
if c.receivedFirstPacket && hdr.Type == protocol.PacketTypeInitial && hdr.SrcConnectionID != c.handshakeDestConnID {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropUnknownConnectionID,
})
}
c.logger.Debugf("Dropping Initial packet (%d bytes) with unexpected source connection ID: %s (expected %s)", p.Size(), hdr.SrcConnectionID, c.handshakeDestConnID)
return false, nil
}
// drop 0-RTT packets, if we are a client
if c.perspective == protocol.PerspectiveClient && hdr.Type == protocol.PacketType0RTT {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false, nil
}
packet, err := c.unpacker.UnpackLongHeader(hdr, p.data)
if err != nil {
wasQueued, err = c.handleUnpackError(err, p, toQlogPacketType(hdr.Type), datagramID)
return false, err
}
if c.logger.Debug() {
c.logger.Debugf("<- Reading packet %d (%d bytes) for connection %s, %s", packet.hdr.PacketNumber, p.Size(), hdr.DestConnectionID, packet.encryptionLevel)
packet.hdr.Log(c.logger)
}
if pn := packet.hdr.PacketNumber; c.receivedPacketHandler.IsPotentiallyDuplicate(pn, packet.encryptionLevel) {
c.logger.Debugf("Dropping (potentially) duplicate packet.")
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: toQlogPacketType(packet.hdr.Type),
DestConnectionID: hdr.DestConnectionID,
SrcConnectionID: hdr.SrcConnectionID,
PacketNumber: pn,
Version: packet.hdr.Version,
},
Raw: qlog.RawInfo{Length: int(p.Size()), PayloadLength: int(packet.hdr.Length)},
DatagramID: datagramID,
Trigger: qlog.PacketDropDuplicate,
})
}
return false, nil
}
if err := c.handleUnpackedLongHeaderPacket(packet, p.ecn, p.rcvTime, datagramID, p.Size()); err != nil {
return false, err
}
return true, nil
}
func (c *Conn) handleUnpackError(err error, p receivedPacket, pt qlog.PacketType, datagramID qlog.DatagramID) (wasQueued bool, _ error) {
switch err {
case handshake.ErrKeysDropped:
if c.qlogger != nil {
connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen)
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: pt,
DestConnectionID: connID,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropKeyUnavailable,
})
}
c.logger.Debugf("Dropping %s packet (%d bytes) because we already dropped the keys.", pt, p.Size())
return false, nil
case handshake.ErrKeysNotYetAvailable:
// Sealer for this encryption level not yet available.
// Try again later.
c.tryQueueingUndecryptablePacket(p, pt, datagramID)
return true, nil
case wire.ErrInvalidReservedBits:
return false, &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: err.Error(),
}
case handshake.ErrDecryptionFailed:
// This might be a packet injected by an attacker. Drop it.
if c.qlogger != nil {
connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen)
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: pt,
DestConnectionID: connID,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropPayloadDecryptError,
})
}
c.logger.Debugf("Dropping %s packet (%d bytes) that could not be unpacked. Error: %s", pt, p.Size(), err)
return false, nil
default:
var headerErr *headerParseError
if errors.As(err, &headerErr) {
// This might be a packet injected by an attacker. Drop it.
if c.qlogger != nil {
connID, _ := wire.ParseConnectionID(p.data, c.srcConnIDLen)
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: pt,
DestConnectionID: connID,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropHeaderParseError,
})
}
c.logger.Debugf("Dropping %s packet (%d bytes) for which we couldn't unpack the header. Error: %s", pt, p.Size(), err)
return false, nil
}
// This is an error returned by the AEAD (other than ErrDecryptionFailed).
// For example, a PROTOCOL_VIOLATION due to key updates.
return false, err
}
}
func (c *Conn) handleRetryPacket(hdr *wire.Header, data []byte, rcvTime monotime.Time) bool /* was this a valid Retry */ {
if c.perspective == protocol.PerspectiveServer {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
SrcConnectionID: hdr.SrcConnectionID,
DestConnectionID: hdr.DestConnectionID,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: len(data)},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
c.logger.Debugf("Ignoring Retry.")
return false
}
if c.receivedFirstPacket {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
SrcConnectionID: hdr.SrcConnectionID,
DestConnectionID: hdr.DestConnectionID,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: len(data)},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
c.logger.Debugf("Ignoring Retry, since we already received a packet.")
return false
}
destConnID := c.connIDManager.Get()
if hdr.SrcConnectionID == destConnID {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
SrcConnectionID: hdr.SrcConnectionID,
DestConnectionID: hdr.DestConnectionID,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: len(data)},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
c.logger.Debugf("Ignoring Retry, since the server didn't change the Source Connection ID.")
return false
}
// If a token is already set, this means that we already received a Retry from the server.
// Ignore this Retry packet.
if c.receivedRetry {
c.logger.Debugf("Ignoring Retry, since a Retry was already received.")
return false
}
tag := handshake.GetRetryIntegrityTag(data[:len(data)-16], destConnID, hdr.Version)
if !bytes.Equal(data[len(data)-16:], tag[:]) {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
SrcConnectionID: hdr.SrcConnectionID,
DestConnectionID: hdr.DestConnectionID,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: len(data)},
Trigger: qlog.PacketDropPayloadDecryptError,
})
}
c.logger.Debugf("Ignoring spoofed Retry. Integrity Tag doesn't match.")
return false
}
newDestConnID := hdr.SrcConnectionID
c.receivedRetry = true
c.sentPacketHandler.ResetForRetry(rcvTime)
c.handshakeDestConnID = newDestConnID
c.retrySrcConnID = &newDestConnID
c.cryptoStreamHandler.ChangeConnectionID(newDestConnID)
c.packer.SetToken(hdr.Token)
c.connIDManager.ChangeInitialConnID(newDestConnID)
if c.logger.Debug() {
c.logger.Debugf("<- Received Retry:")
(&wire.ExtendedHeader{Header: *hdr}).Log(c.logger)
c.logger.Debugf("Switching destination connection ID to: %s", hdr.SrcConnectionID)
}
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketReceived{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
DestConnectionID: destConnID,
SrcConnectionID: newDestConnID,
Version: hdr.Version,
Token: &qlog.Token{Raw: hdr.Token},
},
Raw: qlog.RawInfo{Length: len(data)},
})
}
c.scheduleSending()
return true
}
func (c *Conn) handleVersionNegotiationPacket(p receivedPacket) error {
if c.perspective == protocol.PerspectiveServer || // servers never receive version negotiation packets
c.receivedFirstPacket || c.versionNegotiated { // ignore delayed / duplicated version negotiation packets
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return nil
}
src, dest, supportedVersions, err := wire.ParseVersionNegotiationPacket(p.data)
if err != nil {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropHeaderParseError,
})
}
c.logger.Debugf("Error parsing Version Negotiation packet: %s", err)
return nil
}
if slices.Contains(supportedVersions, c.version) {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedVersion,
})
}
// The Version Negotiation packet contains the version that we offered.
// This might be a packet sent by an attacker, or it was corrupted.
return nil
}
c.logger.Infof("Received a Version Negotiation packet. Supported Versions: %s", supportedVersions)
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.VersionNegotiationReceived{
Header: qlog.PacketHeaderVersionNegotiation{
DestConnectionID: dest,
SrcConnectionID: src,
},
SupportedVersions: supportedVersions,
})
}
newVersion, ok := protocol.ChooseSupportedVersion(c.config.Versions, supportedVersions)
if !ok {
c.destroyImpl(&VersionNegotiationError{
Ours: c.config.Versions,
Theirs: supportedVersions,
})
c.logger.Infof("No compatible QUIC version found.")
return nil
}
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.VersionInformation{
ChosenVersion: newVersion,
ClientVersions: c.config.Versions,
ServerVersions: supportedVersions,
})
}
c.logger.Infof("Switching to QUIC version %s.", newVersion)
nextPN, _ := c.sentPacketHandler.PeekPacketNumber(protocol.EncryptionInitial)
return &errCloseForRecreating{
nextPacketNumber: nextPN,
nextVersion: newVersion,
}
}
func (c *Conn) handleUnpackedLongHeaderPacket(
packet *unpackedPacket,
ecn protocol.ECN,
rcvTime monotime.Time,
datagramID qlog.DatagramID, // only for logging
packetSize protocol.ByteCount, // only for logging
) error {
if !c.receivedFirstPacket {
c.receivedFirstPacket = true
if !c.versionNegotiated && c.qlogger != nil {
var clientVersions, serverVersions []Version
switch c.perspective {
case protocol.PerspectiveClient:
clientVersions = c.config.Versions
case protocol.PerspectiveServer:
serverVersions = c.config.Versions
}
c.qlogger.RecordEvent(qlog.VersionInformation{
ChosenVersion: c.version,
ClientVersions: clientVersions,
ServerVersions: serverVersions,
})
}
// The server can change the source connection ID with the first Handshake packet.
if c.perspective == protocol.PerspectiveClient && packet.hdr.SrcConnectionID != c.handshakeDestConnID {
cid := packet.hdr.SrcConnectionID
c.logger.Debugf("Received first packet. Switching destination connection ID to: %s", cid)
c.handshakeDestConnID = cid
c.connIDManager.ChangeInitialConnID(cid)
}
// We create the connection as soon as we receive the first packet from the client.
// We do that before authenticating the packet.
// That means that if the source connection ID was corrupted,
// we might have created a connection with an incorrect source connection ID.
// Once we authenticate the first packet, we need to update it.
if c.perspective == protocol.PerspectiveServer {
if packet.hdr.SrcConnectionID != c.handshakeDestConnID {
c.handshakeDestConnID = packet.hdr.SrcConnectionID
c.connIDManager.ChangeInitialConnID(packet.hdr.SrcConnectionID)
}
if c.qlogger != nil {
var srcAddr, destAddr *net.UDPAddr
if addr, ok := c.conn.LocalAddr().(*net.UDPAddr); ok {
srcAddr = addr
}
if addr, ok := c.conn.RemoteAddr().(*net.UDPAddr); ok {
destAddr = addr
}
c.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr))
}
}
}
if c.perspective == protocol.PerspectiveServer && packet.encryptionLevel == protocol.EncryptionHandshake &&
!c.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 := c.dropEncryptionLevel(protocol.EncryptionInitial, rcvTime); err != nil {
return err
}
}
c.lastPacketReceivedTime = rcvTime
c.firstAckElicitingPacketAfterIdleSentTime = 0
c.keepAlivePingSent = false
if packet.hdr.Type == protocol.PacketType0RTT {
c.largestRcvdAppData = max(c.largestRcvdAppData, packet.hdr.PacketNumber)
}
var log func([]qlog.Frame)
if c.qlogger != nil {
log = func(frames []qlog.Frame) {
var token *qlog.Token
if len(packet.hdr.Token) > 0 {
token = &qlog.Token{Raw: packet.hdr.Token}
}
c.qlogger.RecordEvent(qlog.PacketReceived{
Header: qlog.PacketHeader{
PacketType: toQlogPacketType(packet.hdr.Type),
DestConnectionID: packet.hdr.DestConnectionID,
SrcConnectionID: packet.hdr.SrcConnectionID,
PacketNumber: packet.hdr.PacketNumber,
Version: packet.hdr.Version,
Token: token,
},
Raw: qlog.RawInfo{
Length: int(packetSize),
PayloadLength: int(packet.hdr.Length),
},
DatagramID: datagramID,
Frames: frames,
ECN: toQlogECN(ecn),
})
}
}
isAckEliciting, _, _, err := c.handleFrames(packet.data, packet.hdr.DestConnectionID, packet.encryptionLevel, log, rcvTime)
if err != nil {
return err
}
c.sentPacketHandler.ReceivedPacket(packet.encryptionLevel, rcvTime)
return c.receivedPacketHandler.ReceivedPacket(packet.hdr.PacketNumber, ecn, packet.encryptionLevel, rcvTime, isAckEliciting)
}
func (c *Conn) handleUnpackedShortHeaderPacket(
destConnID protocol.ConnectionID,
pn protocol.PacketNumber,
data []byte,
ecn protocol.ECN,
rcvTime monotime.Time,
log func([]qlog.Frame),
) (isNonProbing bool, pathChallenge *wire.PathChallengeFrame, _ error) {
c.lastPacketReceivedTime = rcvTime
c.firstAckElicitingPacketAfterIdleSentTime = 0
c.keepAlivePingSent = false
isAckEliciting, isNonProbing, pathChallenge, err := c.handleFrames(data, destConnID, protocol.Encryption1RTT, log, rcvTime)
if err != nil {
return false, nil, err
}
c.sentPacketHandler.ReceivedPacket(protocol.Encryption1RTT, rcvTime)
if err := c.receivedPacketHandler.ReceivedPacket(pn, ecn, protocol.Encryption1RTT, rcvTime, isAckEliciting); err != nil {
return false, nil, err
}
return isNonProbing, pathChallenge, nil
}
// handleFrames parses the frames, one after the other, and handles them.
// It returns the last PATH_CHALLENGE frame contained in the packet, if any.
func (c *Conn) handleFrames(
data []byte,
destConnID protocol.ConnectionID,
encLevel protocol.EncryptionLevel,
log func([]qlog.Frame),
rcvTime monotime.Time,
) (isAckEliciting, isNonProbing bool, pathChallenge *wire.PathChallengeFrame, _ error) {
// Only used for tracing.
// If we're not tracing, this slice will always remain empty.
var frames []qlog.Frame
if log != nil {
frames = make([]qlog.Frame, 0, 4)
}
handshakeWasComplete := c.handshakeComplete
var handleErr error
var skipHandling bool
for len(data) > 0 {
frameType, l, err := c.frameParser.ParseType(data, encLevel)
if err != nil {
// The frame parser skips over PADDING frames, and returns an io.EOF if the PADDING
// frames were the last frames in this packet.
if err == io.EOF {
break
}
return false, false, nil, err
}
data = data[l:]
if ackhandler.IsFrameTypeAckEliciting(frameType) {
isAckEliciting = true
}
if !wire.IsProbingFrameType(frameType) {
isNonProbing = true
}
// We're inlining common cases, to avoid using interfaces
// Fast path: STREAM, DATAGRAM and ACK
if frameType.IsStreamFrameType() {
streamFrame, l, err := c.frameParser.ParseStreamFrame(frameType, data, c.version)
if err != nil {
return false, false, nil, err
}
data = data[l:]
if log != nil {
frames = append(frames, toQlogFrame(streamFrame))
}
// an error occurred handling a previous frame, don't handle the current frame
if skipHandling {
continue
}
wire.LogFrame(c.logger, streamFrame, false)
handleErr = c.streamsMap.HandleStreamFrame(streamFrame, rcvTime)
} else if frameType.IsAckFrameType() {
ackFrame, l, err := c.frameParser.ParseAckFrame(frameType, data, encLevel, c.version)
if err != nil {
return false, false, nil, err
}
data = data[l:]
if log != nil {
frames = append(frames, toQlogFrame(ackFrame))
}
// an error occurred handling a previous frame, don't handle the current frame
if skipHandling {
continue
}
wire.LogFrame(c.logger, ackFrame, false)
handleErr = c.handleAckFrame(ackFrame, encLevel, rcvTime)
} else if frameType.IsDatagramFrameType() {
datagramFrame, l, err := c.frameParser.ParseDatagramFrame(frameType, data, c.version)
if err != nil {
return false, false, nil, err
}
data = data[l:]
if log != nil {
frames = append(frames, toQlogFrame(datagramFrame))
}
// an error occurred handling a previous frame, don't handle the current frame
if skipHandling {
continue
}
wire.LogFrame(c.logger, datagramFrame, false)
handleErr = c.handleDatagramFrame(datagramFrame)
} else {
frame, l, err := c.frameParser.ParseLessCommonFrame(frameType, data, c.version)
if err != nil {
return false, false, nil, err
}
data = data[l:]
if log != nil {
frames = append(frames, toQlogFrame(frame))
}
// an error occurred handling a previous frame, don't handle the current frame
if skipHandling {
continue
}
pc, err := c.handleFrame(frame, encLevel, destConnID, rcvTime)
if pc != nil {
pathChallenge = pc
}
handleErr = err
}
if handleErr != nil {
// if we're logging, we need to keep parsing (but not handling) all frames
skipHandling = true
if log == nil {
return false, false, nil, handleErr
}
}
}
if log != nil {
log(frames)
if handleErr != nil {
return false, false, nil, handleErr
}
}
// Handle completion of the handshake after processing all the frames.
// This ensures that we correctly handle the following case on the server side:
// We receive a Handshake packet that contains the CRYPTO frame that allows us to complete the handshake,
// and an ACK serialized after that CRYPTO frame. In this case, we still want to process the ACK frame.
if !handshakeWasComplete && c.handshakeComplete {
if err := c.handleHandshakeComplete(rcvTime); err != nil {
return false, false, nil, err
}
}
return
}
func (c *Conn) handleFrame(
f wire.Frame,
encLevel protocol.EncryptionLevel,
destConnID protocol.ConnectionID,
rcvTime monotime.Time,
) (pathChallenge *wire.PathChallengeFrame, _ error) {
var err error
wire.LogFrame(c.logger, f, false)
switch frame := f.(type) {
case *wire.CryptoFrame:
err = c.handleCryptoFrame(frame, encLevel, rcvTime)
case *wire.ConnectionCloseFrame:
err = c.handleConnectionCloseFrame(frame)
case *wire.ResetStreamFrame:
err = c.streamsMap.HandleResetStreamFrame(frame, rcvTime)
case *wire.MaxDataFrame:
c.connFlowController.UpdateSendWindow(frame.MaximumData)
case *wire.MaxStreamDataFrame:
err = c.streamsMap.HandleMaxStreamDataFrame(frame)
case *wire.MaxStreamsFrame:
c.streamsMap.HandleMaxStreamsFrame(frame)
case *wire.DataBlockedFrame:
case *wire.StreamDataBlockedFrame:
err = c.streamsMap.HandleStreamDataBlockedFrame(frame)
case *wire.StreamsBlockedFrame:
case *wire.StopSendingFrame:
err = c.streamsMap.HandleStopSendingFrame(frame)
case *wire.PingFrame:
case *wire.PathChallengeFrame:
c.handlePathChallengeFrame(frame)
pathChallenge = frame
case *wire.PathResponseFrame:
err = c.handlePathResponseFrame(frame)
case *wire.NewTokenFrame:
err = c.handleNewTokenFrame(frame)
case *wire.NewConnectionIDFrame:
err = c.connIDManager.Add(frame)
case *wire.RetireConnectionIDFrame:
err = c.connIDGenerator.Retire(frame.SequenceNumber, destConnID, rcvTime.Add(3*c.rttStats.PTO(false)))
case *wire.HandshakeDoneFrame:
err = c.handleHandshakeDoneFrame(rcvTime)
default:
err = fmt.Errorf("unexpected frame type: %s", reflect.ValueOf(&frame).Elem().Type().Name())
}
return pathChallenge, err
}
// handlePacket is called by the server with a new packet
func (c *Conn) handlePacket(p receivedPacket) {
c.receivedPacketMx.Lock()
// Discard packets once the amount of queued packets is larger than
// the channel size, protocol.MaxConnUnprocessedPackets
if c.receivedPackets.Len() >= protocol.MaxConnUnprocessedPackets {
if c.qlogger != nil {
var datagramID qlog.DatagramID
if wire.IsLongHeaderPacket(p.data[0]) {
datagramID = qlog.CalculateDatagramID(p.data)
}
c.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropDOSPrevention,
})
}
c.receivedPacketMx.Unlock()
return
}
c.receivedPackets.PushBack(p)
c.receivedPacketMx.Unlock()
select {
case c.notifyReceivedPacket <- struct{}{}:
default:
}
}
func (c *Conn) handleConnectionCloseFrame(frame *wire.ConnectionCloseFrame) error {
if frame.IsApplicationError {
return &qerr.ApplicationError{
Remote: true,
ErrorCode: qerr.ApplicationErrorCode(frame.ErrorCode),
ErrorMessage: frame.ReasonPhrase,
}
}
return &qerr.TransportError{
Remote: true,
ErrorCode: qerr.TransportErrorCode(frame.ErrorCode),
FrameType: frame.FrameType,
ErrorMessage: frame.ReasonPhrase,
}
}
func (c *Conn) handleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) error {
if err := c.cryptoStreamManager.HandleCryptoFrame(frame, encLevel); err != nil {
return err
}
for {
data := c.cryptoStreamManager.GetCryptoData(encLevel)
if data == nil {
break
}
if err := c.cryptoStreamHandler.HandleMessage(data, encLevel); err != nil {
return err
}
}
return c.handleHandshakeEvents(rcvTime)
}
func (c *Conn) handleHandshakeEvents(now monotime.Time) error {
for {
ev := c.cryptoStreamHandler.NextEvent()
var err error
switch ev.Kind {
case handshake.EventNoEvent:
return nil
case handshake.EventHandshakeComplete:
// Don't call handleHandshakeComplete yet.
// It's advantageous to process ACK frames that might be serialized after the CRYPTO frame first.
c.handshakeComplete = true
case handshake.EventReceivedTransportParameters:
err = c.handleTransportParameters(ev.TransportParameters)
case handshake.EventRestoredTransportParameters:
c.restoreTransportParameters(ev.TransportParameters)
close(c.earlyConnReadyChan)
case handshake.EventReceivedReadKeys:
// queue all previously undecryptable packets
c.undecryptablePacketsToProcess = append(c.undecryptablePacketsToProcess, c.undecryptablePackets...)
c.undecryptablePackets = nil
case handshake.EventDiscard0RTTKeys:
err = c.dropEncryptionLevel(protocol.Encryption0RTT, now)
case handshake.EventWriteInitialData:
_, err = c.initialStream.Write(ev.Data)
case handshake.EventWriteHandshakeData:
_, err = c.handshakeStream.Write(ev.Data)
}
if err != nil {
return err
}
}
}
func (c *Conn) handlePathChallengeFrame(f *wire.PathChallengeFrame) {
if c.perspective == protocol.PerspectiveClient {
c.queueControlFrame(&wire.PathResponseFrame{Data: f.Data})
}
}
func (c *Conn) handlePathResponseFrame(f *wire.PathResponseFrame) error {
switch c.perspective {
case protocol.PerspectiveClient:
return c.handlePathResponseFrameClient(f)
case protocol.PerspectiveServer:
return c.handlePathResponseFrameServer(f)
default:
panic("unreachable")
}
}
func (c *Conn) handlePathResponseFrameClient(f *wire.PathResponseFrame) error {
pm := c.pathManagerOutgoing.Load()
if pm == nil {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "unexpected PATH_RESPONSE frame",
}
}
pm.HandlePathResponseFrame(f)
return nil
}
func (c *Conn) handlePathResponseFrameServer(f *wire.PathResponseFrame) error {
if c.pathManager == nil {
// since we didn't send PATH_CHALLENGEs yet, we don't expect PATH_RESPONSEs
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "unexpected PATH_RESPONSE frame",
}
}
c.pathManager.HandlePathResponseFrame(f)
return nil
}
func (c *Conn) handleNewTokenFrame(frame *wire.NewTokenFrame) error {
if c.perspective == protocol.PerspectiveServer {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received NEW_TOKEN frame from the client",
}
}
if c.config.TokenStore != nil {
c.config.TokenStore.Put(c.tokenStoreKey, &ClientToken{data: frame.Token, rtt: c.rttStats.SmoothedRTT()})
}
return nil
}
func (c *Conn) handleHandshakeDoneFrame(rcvTime monotime.Time) error {
if c.perspective == protocol.PerspectiveServer {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received a HANDSHAKE_DONE frame",
}
}
if !c.handshakeConfirmed {
return c.handleHandshakeConfirmed(rcvTime)
}
return nil
}
func (c *Conn) handleAckFrame(frame *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) error {
acked1RTTPacket, err := c.sentPacketHandler.ReceivedAck(frame, encLevel, c.lastPacketReceivedTime)
if err != nil {
return err
}
if !acked1RTTPacket {
return nil
}
// On the client side: If the packet acknowledged a 1-RTT packet, this confirms the handshake.
// This is only possible if the ACK was sent in a 1-RTT packet.
// This is an optimization over simply waiting for a HANDSHAKE_DONE frame, see section 4.1.2 of RFC 9001.
if c.perspective == protocol.PerspectiveClient && !c.handshakeConfirmed {
if err := c.handleHandshakeConfirmed(rcvTime); err != nil {
return err
}
}
// If one of the acknowledged packets was a Path MTU probe packet, this might have increased the Path MTU estimate.
if c.mtuDiscoverer != nil {
mtu := c.mtuDiscoverer.CurrentSize()
maxPayloadSize := estimateMaxPayloadSize(mtu)
if maxPayloadSize > protocol.ByteCount(c.maxPayloadSizeEstimate.Load()) {
c.maxPayloadSizeEstimate.Store(uint32(maxPayloadSize))
c.sentPacketHandler.SetMaxDatagramSize(mtu)
}
}
return c.cryptoStreamHandler.SetLargest1RTTAcked(frame.LargestAcked())
}
func (c *Conn) handleDatagramFrame(f *wire.DatagramFrame) error {
if f.Length(c.version) > wire.MaxDatagramSize {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "DATAGRAM frame too large",
}
}
c.datagramQueue.HandleDatagramFrame(f)
return nil
}
func (c *Conn) setCloseError(e *closeError) {
c.closeErr.CompareAndSwap(nil, e)
select {
case c.closeChan <- struct{}{}:
default:
}
}
// closeLocal closes the connection and send a CONNECTION_CLOSE containing the error
func (c *Conn) closeLocal(e error) {
c.setCloseError(&closeError{err: e, immediate: false})
}
// destroy closes the connection without sending the error on the wire
func (c *Conn) destroy(e error) {
c.destroyImpl(e)
<-c.ctx.Done()
}
func (c *Conn) destroyImpl(e error) {
c.setCloseError(&closeError{err: e, immediate: true})
}
// CloseWithError closes the connection with an error.
// The error string will be sent to the peer.
func (c *Conn) CloseWithError(code ApplicationErrorCode, desc string) error {
c.closeLocal(&qerr.ApplicationError{
ErrorCode: code,
ErrorMessage: desc,
})
<-c.ctx.Done()
return nil
}
func (c *Conn) closeWithTransportError(code TransportErrorCode) {
c.closeLocal(&qerr.TransportError{ErrorCode: code})
<-c.ctx.Done()
}
func (c *Conn) handleCloseError(closeErr *closeError) {
if closeErr.immediate {
if nerr, ok := closeErr.err.(net.Error); ok && nerr.Timeout() {
c.logger.Errorf("Destroying connection: %s", closeErr.err)
} else {
c.logger.Errorf("Destroying connection with error: %s", closeErr.err)
}
} else {
if closeErr.err == nil {
c.logger.Infof("Closing connection.")
} else {
c.logger.Errorf("Closing connection with error: %s", closeErr.err)
}
}
e := closeErr.err
if e == nil {
e = &qerr.ApplicationError{}
} else {
defer func() { closeErr.err = e }()
}
var (
statelessResetErr *StatelessResetError
versionNegotiationErr *VersionNegotiationError
recreateErr *errCloseForRecreating
applicationErr *ApplicationError
transportErr *TransportError
)
var isRemoteClose bool
var trigger qlog.ConnectionCloseTrigger
var reason string
var transportErrorCode *qlog.TransportErrorCode
var applicationErrorCode *qlog.ApplicationErrorCode
switch {
case errors.Is(e, qerr.ErrIdleTimeout),
errors.Is(e, qerr.ErrHandshakeTimeout):
trigger = qlog.ConnectionCloseTriggerIdleTimeout
case errors.As(e, &statelessResetErr):
trigger = qlog.ConnectionCloseTriggerStatelessReset
case errors.As(e, &versionNegotiationErr):
trigger = qlog.ConnectionCloseTriggerVersionMismatch
case errors.As(e, &recreateErr):
case errors.As(e, &applicationErr):
isRemoteClose = applicationErr.Remote
reason = applicationErr.ErrorMessage
applicationErrorCode = &applicationErr.ErrorCode
case errors.As(e, &transportErr):
isRemoteClose = transportErr.Remote
reason = transportErr.ErrorMessage
transportErrorCode = &transportErr.ErrorCode
case closeErr.immediate:
e = closeErr.err
default:
te := &qerr.TransportError{
ErrorCode: qerr.InternalError,
ErrorMessage: e.Error(),
}
e = te
reason = te.ErrorMessage
code := te.ErrorCode
transportErrorCode = &code
}
c.streamsMap.CloseWithError(e)
if c.datagramQueue != nil {
c.datagramQueue.CloseWithError(e)
}
// In rare instances, the connection ID manager might switch to a new connection ID
// when sending the CONNECTION_CLOSE frame.
// The connection ID manager removes the active stateless reset token from the packet
// handler map when it is closed, so we need to make sure that this happens last.
defer c.connIDManager.Close()
if c.qlogger != nil && !errors.As(e, &recreateErr) {
initiator := qlog.InitiatorLocal
if isRemoteClose {
initiator = qlog.InitiatorRemote
}
c.qlogger.RecordEvent(qlog.ConnectionClosed{
Initiator: initiator,
ConnectionError: transportErrorCode,
ApplicationError: applicationErrorCode,
Trigger: trigger,
Reason: reason,
})
}
// If this is a remote close we're done here
if isRemoteClose {
c.connIDGenerator.ReplaceWithClosed(nil, 3*c.rttStats.PTO(false))
return
}
if closeErr.immediate {
c.connIDGenerator.RemoveAll()
return
}
// Don't send out any CONNECTION_CLOSE if this is an error that occurred
// before we even sent out the first packet.
if c.perspective == protocol.PerspectiveClient && !c.sentFirstPacket {
c.connIDGenerator.RemoveAll()
return
}
connClosePacket, err := c.sendConnectionClose(e)
if err != nil {
c.logger.Debugf("Error sending CONNECTION_CLOSE: %s", err)
}
c.connIDGenerator.ReplaceWithClosed(connClosePacket, 3*c.rttStats.PTO(false))
}
func (c *Conn) dropEncryptionLevel(encLevel protocol.EncryptionLevel, now monotime.Time) error {
c.sentPacketHandler.DropPackets(encLevel, now)
c.receivedPacketHandler.DropPackets(encLevel)
//nolint:exhaustive // only Initial and 0-RTT need special treatment
switch encLevel {
case protocol.EncryptionInitial:
c.droppedInitialKeys = true
c.cryptoStreamHandler.DiscardInitialKeys()
case protocol.Encryption0RTT:
c.streamsMap.ResetFor0RTT()
c.framer.Handle0RTTRejection()
return c.connFlowController.Reset()
}
return c.cryptoStreamManager.Drop(encLevel)
}
// is called for the client, when restoring transport parameters saved for 0-RTT
func (c *Conn) restoreTransportParameters(params *wire.TransportParameters) {
if c.logger.Debug() {
c.logger.Debugf("Restoring Transport Parameters: %s", params)
}
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.ParametersSet{
Restore: true,
Initiator: qlog.InitiatorRemote,
SentBy: c.perspective,
OriginalDestinationConnectionID: params.OriginalDestinationConnectionID,
InitialSourceConnectionID: params.InitialSourceConnectionID,
RetrySourceConnectionID: params.RetrySourceConnectionID,
StatelessResetToken: params.StatelessResetToken,
DisableActiveMigration: params.DisableActiveMigration,
MaxIdleTimeout: params.MaxIdleTimeout,
MaxUDPPayloadSize: params.MaxUDPPayloadSize,
AckDelayExponent: params.AckDelayExponent,
MaxAckDelay: params.MaxAckDelay,
ActiveConnectionIDLimit: params.ActiveConnectionIDLimit,
InitialMaxData: params.InitialMaxData,
InitialMaxStreamDataBidiLocal: params.InitialMaxStreamDataBidiLocal,
InitialMaxStreamDataBidiRemote: params.InitialMaxStreamDataBidiRemote,
InitialMaxStreamDataUni: params.InitialMaxStreamDataUni,
InitialMaxStreamsBidi: int64(params.MaxBidiStreamNum),
InitialMaxStreamsUni: int64(params.MaxUniStreamNum),
MaxDatagramFrameSize: params.MaxDatagramFrameSize,
EnableResetStreamAt: params.EnableResetStreamAt,
})
}
c.peerParams = params
c.connIDGenerator.SetMaxActiveConnIDs(params.ActiveConnectionIDLimit)
c.connFlowController.UpdateSendWindow(params.InitialMaxData)
c.streamsMap.HandleTransportParameters(params)
}
func (c *Conn) handleTransportParameters(params *wire.TransportParameters) error {
if c.qlogger != nil {
c.qlogTransportParameters(params, c.perspective.Opposite(), false)
}
if err := c.checkTransportParameters(params); err != nil {
return &qerr.TransportError{
ErrorCode: qerr.TransportParameterError,
ErrorMessage: err.Error(),
}
}
if c.perspective == protocol.PerspectiveClient && c.peerParams != nil && c.ConnectionState().Used0RTT && !params.ValidForUpdate(c.peerParams) {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "server sent reduced limits after accepting 0-RTT data",
}
}
c.peerParams = params
// On the client side we have to wait for handshake completion.
// During a 0-RTT connection, we are only allowed to use the new transport parameters for 1-RTT packets.
if c.perspective == protocol.PerspectiveServer {
c.applyTransportParameters()
// On the server side, the early connection is ready as soon as we processed
// the client's transport parameters.
close(c.earlyConnReadyChan)
}
return nil
}
func (c *Conn) checkTransportParameters(params *wire.TransportParameters) error {
if c.logger.Debug() {
c.logger.Debugf("Processed Transport Parameters: %s", params)
}
// check the initial_source_connection_id
if params.InitialSourceConnectionID != c.handshakeDestConnID {
return fmt.Errorf("expected initial_source_connection_id to equal %s, is %s", c.handshakeDestConnID, params.InitialSourceConnectionID)
}
if c.perspective == protocol.PerspectiveServer {
return nil
}
// check the original_destination_connection_id
if params.OriginalDestinationConnectionID != c.origDestConnID {
return fmt.Errorf("expected original_destination_connection_id to equal %s, is %s", c.origDestConnID, params.OriginalDestinationConnectionID)
}
if c.retrySrcConnID != nil { // a Retry was performed
if params.RetrySourceConnectionID == nil {
return errors.New("missing retry_source_connection_id")
}
if *params.RetrySourceConnectionID != *c.retrySrcConnID {
return fmt.Errorf("expected retry_source_connection_id to equal %s, is %s", c.retrySrcConnID, *params.RetrySourceConnectionID)
}
} else if params.RetrySourceConnectionID != nil {
return errors.New("received retry_source_connection_id, although no Retry was performed")
}
return nil
}
func (c *Conn) applyTransportParameters() {
params := c.peerParams
// Our local idle timeout will always be > 0.
c.idleTimeout = c.config.MaxIdleTimeout
// If the peer advertised an idle timeout, take the minimum of the values.
if params.MaxIdleTimeout > 0 {
c.idleTimeout = min(c.idleTimeout, params.MaxIdleTimeout)
}
c.keepAliveInterval = min(c.config.KeepAlivePeriod, c.idleTimeout/2)
c.streamsMap.HandleTransportParameters(params)
c.frameParser.SetAckDelayExponent(params.AckDelayExponent)
c.connFlowController.UpdateSendWindow(params.InitialMaxData)
c.rttStats.SetMaxAckDelay(params.MaxAckDelay)
c.connIDGenerator.SetMaxActiveConnIDs(params.ActiveConnectionIDLimit)
if params.StatelessResetToken != nil {
c.connIDManager.SetStatelessResetToken(*params.StatelessResetToken)
}
// We don't support connection migration yet, so we don't have any use for the preferred_address.
if params.PreferredAddress != nil {
// Retire the connection ID.
c.connIDManager.AddFromPreferredAddress(params.PreferredAddress.ConnectionID, params.PreferredAddress.StatelessResetToken)
}
maxPacketSize := protocol.ByteCount(protocol.MaxPacketBufferSize)
if params.MaxUDPPayloadSize > 0 && params.MaxUDPPayloadSize < maxPacketSize {
maxPacketSize = params.MaxUDPPayloadSize
}
c.mtuDiscoverer = newMTUDiscoverer(
c.rttStats,
protocol.ByteCount(c.config.InitialPacketSize),
maxPacketSize,
c.qlogger,
)
}
func (c *Conn) triggerSending(now monotime.Time) error {
c.pacingDeadline = 0
sendMode := c.sentPacketHandler.SendMode(now)
switch sendMode {
case ackhandler.SendAny:
return c.sendPackets(now)
case ackhandler.SendNone:
c.blocked = blockModeHardBlocked
return nil
case ackhandler.SendPacingLimited:
deadline := c.sentPacketHandler.TimeUntilSend()
if deadline.IsZero() {
deadline = deadlineSendImmediately
}
c.pacingDeadline = deadline
// Allow sending of an ACK if we're pacing limit.
// This makes sure that a peer that is mostly receiving data (and thus has an inaccurate cwnd estimate)
// sends enough ACKs to allow its peer to utilize the bandwidth.
return c.maybeSendAckOnlyPacket(now)
case ackhandler.SendAck:
// We can at most send a single ACK only packet.
// There will only be a new ACK after receiving new packets.
// SendAck is only returned when we're congestion limited, so we don't need to set the pacing timer.
c.blocked = blockModeCongestionLimited
return c.maybeSendAckOnlyPacket(now)
case ackhandler.SendPTOInitial, ackhandler.SendPTOHandshake, ackhandler.SendPTOAppData:
if err := c.sendProbePacket(sendMode, now); err != nil {
return err
}
if c.sendQueue.WouldBlock() {
c.scheduleSending()
return nil
}
return c.triggerSending(now)
default:
return fmt.Errorf("BUG: invalid send mode %d", sendMode)
}
}
func (c *Conn) sendPackets(now monotime.Time) error {
if c.perspective == protocol.PerspectiveClient && c.handshakeConfirmed {
if pm := c.pathManagerOutgoing.Load(); pm != nil {
connID, frame, tr, ok := pm.NextPathToProbe()
if ok {
probe, buf, err := c.packer.PackPathProbePacket(connID, []ackhandler.Frame{frame}, c.version)
if err != nil {
return err
}
c.logger.Debugf("sending path probe packet from %s", c.LocalAddr())
c.logShortHeaderPacket(probe, protocol.ECNNon, buf.Len())
c.registerPackedShortHeaderPacket(probe, protocol.ECNNon, now)
tr.WriteTo(buf.Data, c.conn.RemoteAddr())
// There's (likely) more data to send. Loop around again.
c.scheduleSending()
return nil
}
}
}
// Path MTU Discovery
// Can't use GSO, since we need to send a single packet that's larger than our current maximum size.
// Performance-wise, this doesn't matter, since we only send a very small (<10) number of
// MTU probe packets per connection.
if c.handshakeConfirmed && c.mtuDiscoverer != nil && c.mtuDiscoverer.ShouldSendProbe(now) {
ping, size := c.mtuDiscoverer.GetPing(now)
p, buf, err := c.packer.PackMTUProbePacket(ping, size, c.version)
if err != nil {
return err
}
ecn := c.sentPacketHandler.ECNMode(true)
c.logShortHeaderPacket(p, ecn, buf.Len())
c.registerPackedShortHeaderPacket(p, ecn, now)
c.sendQueue.Send(buf, 0, ecn)
// There's (likely) more data to send. Loop around again.
c.scheduleSending()
return nil
}
if offset := c.connFlowController.GetWindowUpdate(now); offset > 0 {
c.framer.QueueControlFrame(&wire.MaxDataFrame{MaximumData: offset})
}
if cf := c.cryptoStreamManager.GetPostHandshakeData(protocol.MaxPostHandshakeCryptoFrameSize); cf != nil {
c.queueControlFrame(cf)
}
if !c.handshakeConfirmed {
packet, err := c.packer.PackCoalescedPacket(false, c.maxPacketSize(), now, c.version)
if err != nil || packet == nil {
return err
}
c.sentFirstPacket = true
if err := c.sendPackedCoalescedPacket(packet, c.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket()), now); err != nil {
return err
}
//nolint:exhaustive // only need to handle pacing-related events here
switch c.sentPacketHandler.SendMode(now) {
case ackhandler.SendPacingLimited:
c.resetPacingDeadline()
case ackhandler.SendAny:
c.pacingDeadline = deadlineSendImmediately
}
return nil
}
if c.conn.capabilities().GSO {
return c.sendPacketsWithGSO(now)
}
return c.sendPacketsWithoutGSO(now)
}
func (c *Conn) sendPacketsWithoutGSO(now monotime.Time) error {
for {
buf := getPacketBuffer()
ecn := c.sentPacketHandler.ECNMode(true)
if _, err := c.appendOneShortHeaderPacket(buf, c.maxPacketSize(), ecn, now); err != nil {
if err == errNothingToPack {
buf.Release()
return nil
}
return err
}
c.sendQueue.Send(buf, 0, ecn)
if c.sendQueue.WouldBlock() {
return nil
}
sendMode := c.sentPacketHandler.SendMode(now)
if sendMode == ackhandler.SendPacingLimited {
c.resetPacingDeadline()
return nil
}
if sendMode != ackhandler.SendAny {
return nil
}
// Prioritize receiving of packets over sending out more packets.
c.receivedPacketMx.Lock()
hasPackets := !c.receivedPackets.Empty()
c.receivedPacketMx.Unlock()
if hasPackets {
c.pacingDeadline = deadlineSendImmediately
return nil
}
}
}
func (c *Conn) sendPacketsWithGSO(now monotime.Time) error {
buf := getLargePacketBuffer()
maxSize := c.maxPacketSize()
ecn := c.sentPacketHandler.ECNMode(true)
for {
var dontSendMore bool
size, err := c.appendOneShortHeaderPacket(buf, maxSize, ecn, now)
if err != nil {
if err != errNothingToPack {
return err
}
if buf.Len() == 0 {
buf.Release()
return nil
}
dontSendMore = true
}
if !dontSendMore {
sendMode := c.sentPacketHandler.SendMode(now)
if sendMode == ackhandler.SendPacingLimited {
c.resetPacingDeadline()
}
if sendMode != ackhandler.SendAny {
dontSendMore = true
}
}
// Don't send more packets in this batch if they require a different ECN marking than the previous ones.
nextECN := c.sentPacketHandler.ECNMode(true)
// Append another packet if
// 1. The congestion controller and pacer allow sending more
// 2. The last packet appended was a full-size packet
// 3. The next packet will have the same ECN marking
// 4. We still have enough space for another full-size packet in the buffer
if !dontSendMore && size == maxSize && nextECN == ecn && buf.Len()+maxSize <= buf.Cap() {
continue
}
c.sendQueue.Send(buf, uint16(maxSize), ecn)
if dontSendMore {
return nil
}
if c.sendQueue.WouldBlock() {
return nil
}
// Prioritize receiving of packets over sending out more packets.
c.receivedPacketMx.Lock()
hasPackets := !c.receivedPackets.Empty()
c.receivedPacketMx.Unlock()
if hasPackets {
c.pacingDeadline = deadlineSendImmediately
return nil
}
ecn = nextECN
buf = getLargePacketBuffer()
}
}
func (c *Conn) resetPacingDeadline() {
deadline := c.sentPacketHandler.TimeUntilSend()
if deadline.IsZero() {
deadline = deadlineSendImmediately
}
c.pacingDeadline = deadline
}
func (c *Conn) maybeSendAckOnlyPacket(now monotime.Time) error {
if !c.handshakeConfirmed {
ecn := c.sentPacketHandler.ECNMode(false)
packet, err := c.packer.PackCoalescedPacket(true, c.maxPacketSize(), now, c.version)
if err != nil {
return err
}
if packet == nil {
return nil
}
return c.sendPackedCoalescedPacket(packet, ecn, now)
}
ecn := c.sentPacketHandler.ECNMode(true)
p, buf, err := c.packer.PackAckOnlyPacket(c.maxPacketSize(), now, c.version)
if err != nil {
if err == errNothingToPack {
return nil
}
return err
}
c.logShortHeaderPacket(p, ecn, buf.Len())
c.registerPackedShortHeaderPacket(p, ecn, now)
c.sendQueue.Send(buf, 0, ecn)
return nil
}
func (c *Conn) sendProbePacket(sendMode ackhandler.SendMode, now monotime.Time) error {
var encLevel protocol.EncryptionLevel
//nolint:exhaustive // We only need to handle the PTO send modes here.
switch sendMode {
case ackhandler.SendPTOInitial:
encLevel = protocol.EncryptionInitial
case ackhandler.SendPTOHandshake:
encLevel = protocol.EncryptionHandshake
case ackhandler.SendPTOAppData:
encLevel = protocol.Encryption1RTT
default:
return fmt.Errorf("connection BUG: unexpected send mode: %d", sendMode)
}
// Queue probe packets until we actually send out a packet,
// or until there are no more packets to queue.
var packet *coalescedPacket
for packet == nil {
if wasQueued := c.sentPacketHandler.QueueProbePacket(encLevel); !wasQueued {
break
}
var err error
packet, err = c.packer.PackPTOProbePacket(encLevel, c.maxPacketSize(), false, now, c.version)
if err != nil {
return err
}
}
if packet == nil {
var err error
packet, err = c.packer.PackPTOProbePacket(encLevel, c.maxPacketSize(), true, now, c.version)
if err != nil {
return err
}
}
if packet == nil || (len(packet.longHdrPackets) == 0 && packet.shortHdrPacket == nil) {
return fmt.Errorf("connection BUG: couldn't pack %s probe packet: %v", encLevel, packet)
}
return c.sendPackedCoalescedPacket(packet, c.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket()), now)
}
// appendOneShortHeaderPacket appends a new packet to the given packetBuffer.
// If there was nothing to pack, the returned size is 0.
func (c *Conn) appendOneShortHeaderPacket(buf *packetBuffer, maxSize protocol.ByteCount, ecn protocol.ECN, now monotime.Time) (protocol.ByteCount, error) {
startLen := buf.Len()
p, err := c.packer.AppendPacket(buf, maxSize, now, c.version)
if err != nil {
return 0, err
}
size := buf.Len() - startLen
c.logShortHeaderPacket(p, ecn, size)
c.registerPackedShortHeaderPacket(p, ecn, now)
return size, nil
}
func (c *Conn) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, now monotime.Time) {
if p.IsPathProbePacket {
c.sentPacketHandler.SentPacket(
now,
p.PacketNumber,
protocol.InvalidPacketNumber,
p.StreamFrames,
p.Frames,
protocol.Encryption1RTT,
ecn,
p.Length,
p.IsPathMTUProbePacket,
true,
)
return
}
if c.firstAckElicitingPacketAfterIdleSentTime.IsZero() && (len(p.StreamFrames) > 0 || ackhandler.HasAckElicitingFrames(p.Frames)) {
c.firstAckElicitingPacketAfterIdleSentTime = now
}
largestAcked := protocol.InvalidPacketNumber
if p.Ack != nil {
largestAcked = p.Ack.LargestAcked()
}
c.sentPacketHandler.SentPacket(
now,
p.PacketNumber,
largestAcked,
p.StreamFrames,
p.Frames,
protocol.Encryption1RTT,
ecn,
p.Length,
p.IsPathMTUProbePacket,
false,
)
c.connIDManager.SentPacket()
}
func (c *Conn) sendPackedCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN, now monotime.Time) error {
c.logCoalescedPacket(packet, ecn)
for _, p := range packet.longHdrPackets {
if c.firstAckElicitingPacketAfterIdleSentTime.IsZero() && p.IsAckEliciting() {
c.firstAckElicitingPacketAfterIdleSentTime = now
}
largestAcked := protocol.InvalidPacketNumber
if p.ack != nil {
largestAcked = p.ack.LargestAcked()
}
c.sentPacketHandler.SentPacket(
now,
p.header.PacketNumber,
largestAcked,
p.streamFrames,
p.frames,
p.EncryptionLevel(),
ecn,
p.length,
false,
false,
)
if c.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake &&
!c.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 := c.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil {
return err
}
}
}
if p := packet.shortHdrPacket; p != nil {
if c.firstAckElicitingPacketAfterIdleSentTime.IsZero() && p.IsAckEliciting() {
c.firstAckElicitingPacketAfterIdleSentTime = now
}
largestAcked := protocol.InvalidPacketNumber
if p.Ack != nil {
largestAcked = p.Ack.LargestAcked()
}
c.sentPacketHandler.SentPacket(
now,
p.PacketNumber,
largestAcked,
p.StreamFrames,
p.Frames,
protocol.Encryption1RTT,
ecn,
p.Length,
p.IsPathMTUProbePacket,
false,
)
}
c.connIDManager.SentPacket()
c.sendQueue.Send(packet.buffer, 0, ecn)
return nil
}
func (c *Conn) sendConnectionClose(e error) ([]byte, error) {
var packet *coalescedPacket
var err error
var transportErr *qerr.TransportError
var applicationErr *qerr.ApplicationError
if errors.As(e, &transportErr) {
packet, err = c.packer.PackConnectionClose(transportErr, c.maxPacketSize(), c.version)
} else if errors.As(e, &applicationErr) {
packet, err = c.packer.PackApplicationClose(applicationErr, c.maxPacketSize(), c.version)
} else {
packet, err = c.packer.PackConnectionClose(&qerr.TransportError{
ErrorCode: qerr.InternalError,
ErrorMessage: fmt.Sprintf("connection BUG: unspecified error type (msg: %s)", e.Error()),
}, c.maxPacketSize(), c.version)
}
if err != nil {
return nil, err
}
ecn := c.sentPacketHandler.ECNMode(packet.IsOnlyShortHeaderPacket())
c.logCoalescedPacket(packet, ecn)
return packet.buffer.Data, c.conn.Write(packet.buffer.Data, 0, ecn)
}
func (c *Conn) maxPacketSize() protocol.ByteCount {
if c.mtuDiscoverer == nil {
// Use the configured packet size on the client side.
// If the server sends a max_udp_payload_size that's smaller than this size, we can ignore this:
// Apparently the server still processed the (fully padded) Initial packet anyway.
if c.perspective == protocol.PerspectiveClient {
return protocol.ByteCount(c.config.InitialPacketSize)
}
// On the server side, there's no downside to using 1200 bytes until we received the client's transport
// parameters:
// * If the first packet didn't contain the entire ClientHello, all we can do is ACK that packet. We don't
// need a lot of bytes for that.
// * If it did, we will have processed the transport parameters and initialized the MTU discoverer.
return protocol.MinInitialPacketSize
}
return c.mtuDiscoverer.CurrentSize()
}
// AcceptStream returns the next stream opened by the peer, blocking until one is available.
func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error) {
return c.streamsMap.AcceptStream(ctx)
}
// AcceptUniStream returns the next unidirectional stream opened by the peer, blocking until one is available.
func (c *Conn) AcceptUniStream(ctx context.Context) (*ReceiveStream, error) {
return c.streamsMap.AcceptUniStream(ctx)
}
// OpenStream opens a new bidirectional QUIC stream.
// 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.
// When reaching the peer's stream limit, it is not possible to open a new stream until the
// peer raises the stream limit. In that case, a [StreamLimitReachedError] is returned.
func (c *Conn) OpenStream() (*Stream, error) {
return c.streamsMap.OpenStream()
}
// 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.
func (c *Conn) OpenStreamSync(ctx context.Context) (*Stream, error) {
return c.streamsMap.OpenStreamSync(ctx)
}
// OpenUniStream opens a new outgoing unidirectional QUIC stream.
// 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.
// When reaching the peer's stream limit, it is not possible to open a new stream until the
// peer raises the stream limit. In that case, a [StreamLimitReachedError] is returned.
func (c *Conn) OpenUniStream() (*SendStream, error) {
return c.streamsMap.OpenUniStream()
}
// OpenUniStreamSync opens a new outgoing unidirectional 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.
func (c *Conn) OpenUniStreamSync(ctx context.Context) (*SendStream, error) {
return c.streamsMap.OpenUniStreamSync(ctx)
}
func (c *Conn) newFlowController(id protocol.StreamID) *streamFlowController {
initialSendWindow := c.peerParams.InitialMaxStreamDataUni
if protocol.StreamTypeOf(id) == protocol.StreamTypeBidi {
if protocol.StreamInitiator(id) == c.perspective {
initialSendWindow = c.peerParams.InitialMaxStreamDataBidiRemote
} else {
initialSendWindow = c.peerParams.InitialMaxStreamDataBidiLocal
}
}
return newStreamFlowController(
id,
c.connFlowController,
protocol.ByteCount(c.config.InitialStreamReceiveWindow),
protocol.ByteCount(c.config.MaxStreamReceiveWindow),
initialSendWindow,
c.rttStats,
c.logger,
)
}
// scheduleSending signals that we have data for sending
func (c *Conn) scheduleSending() {
select {
case c.sendingScheduled <- struct{}{}:
default:
}
}
// tryQueueingUndecryptablePacket queues a packet for which we're missing the decryption keys.
// The qlogevents.PacketType is only used for logging purposes.
func (c *Conn) tryQueueingUndecryptablePacket(p receivedPacket, pt qlog.PacketType, datagramID qlog.DatagramID) {
if c.handshakeComplete {
panic("shouldn't queue undecryptable packets after handshake completion")
}
if len(c.undecryptablePackets)+1 > protocol.MaxUndecryptablePackets {
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: pt,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
Trigger: qlog.PacketDropDOSPrevention,
})
}
c.logger.Infof("Dropping undecryptable packet (%d bytes). Undecryptable packet queue full.", p.Size())
return
}
c.logger.Infof("Queueing packet (%d bytes) for later decryption", p.Size())
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.PacketBuffered{
Header: qlog.PacketHeader{
PacketType: pt,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
DatagramID: datagramID,
})
}
c.undecryptablePackets = append(c.undecryptablePackets, receivedPacketWithDatagramID{receivedPacket: p, datagramID: datagramID})
}
func (c *Conn) queueControlFrame(f wire.Frame) {
c.framer.QueueControlFrame(f)
c.scheduleSending()
}
func (c *Conn) onHasConnectionData() { c.scheduleSending() }
func (c *Conn) onHasStreamData(id protocol.StreamID, str *SendStream) {
c.framer.AddActiveStream(id, str)
c.scheduleSending()
}
func (c *Conn) onHasStreamControlFrame(id protocol.StreamID, str streamControlFrameGetter) {
c.framer.AddStreamWithControlFrames(id, str)
c.scheduleSending()
}
func (c *Conn) onStreamCompleted(id protocol.StreamID) {
if err := c.streamsMap.DeleteStream(id); err != nil {
c.closeLocal(err)
}
c.framer.RemoveActiveStream(id)
}
// SendDatagram sends a message using a QUIC datagram, as specified in RFC 9221,
// if the peer enabled datagram support.
// 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.
func (c *Conn) SendDatagram(p []byte) error {
if !c.supportsDatagrams() {
return errors.New("datagram support disabled")
}
f := &wire.DatagramFrame{DataLenPresent: true}
// The payload size estimate is conservative.
// Under many circumstances we could send a few more bytes.
maxDataLen := min(
f.MaxDataLen(c.peerParams.MaxDatagramFrameSize, c.version),
protocol.ByteCount(c.maxPayloadSizeEstimate.Load()),
)
if protocol.ByteCount(len(p)) > maxDataLen {
return &DatagramTooLargeError{MaxDatagramPayloadSize: int64(maxDataLen)}
}
f.Data = make([]byte, len(p))
copy(f.Data, p)
return c.datagramQueue.Add(f)
}
// ReceiveDatagram gets a message received in a QUIC datagram, as specified in RFC 9221.
func (c *Conn) ReceiveDatagram(ctx context.Context) ([]byte, error) {
if !c.config.EnableDatagrams {
return nil, errors.New("datagram support disabled")
}
return c.datagramQueue.Receive(ctx)
}
// LocalAddr returns the local address of the QUIC connection.
func (c *Conn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
// RemoteAddr returns the remote address of the QUIC connection.
func (c *Conn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
// getPathManager lazily initializes the Conn's pathManagerOutgoing.
// May create multiple pathManagerOutgoing objects if called concurrently.
func (c *Conn) getPathManager() *pathManagerOutgoing {
old := c.pathManagerOutgoing.Load()
if old != nil {
// Path manager is already initialized
return old
}
// Initialize the path manager
new := newPathManagerOutgoing(
c.connIDManager.GetConnIDForPath,
c.connIDManager.RetireConnIDForPath,
c.scheduleSending,
)
if c.pathManagerOutgoing.CompareAndSwap(old, new) {
return new
}
// Swap failed. A concurrent writer wrote first, use their value.
return c.pathManagerOutgoing.Load()
}
func (c *Conn) AddPath(t *Transport) (*Path, error) {
if c.perspective == protocol.PerspectiveServer {
return nil, errors.New("server cannot initiate connection migration")
}
if c.peerParams.DisableActiveMigration {
return nil, errors.New("server disabled connection migration")
}
if err := t.init(false); err != nil {
return nil, err
}
return c.getPathManager().NewPath(
t,
200*time.Millisecond, // initial RTT estimate
func() {
runner := (*packetHandlerMap)(t)
c.connIDGenerator.AddConnRunner(
runner,
connRunnerCallbacks{
AddConnectionID: func(connID protocol.ConnectionID) { runner.Add(connID, c) },
RemoveConnectionID: runner.Remove,
ReplaceWithClosed: runner.ReplaceWithClosed,
},
)
},
), nil
}
// HandshakeComplete blocks until the handshake completes (or fails).
// For the client, data sent before completion of the handshake is encrypted with 0-RTT keys.
// For the server, data sent before completion of the handshake is encrypted with 1-RTT keys,
// however the client's identity is only verified once the handshake completes.
func (c *Conn) HandshakeComplete() <-chan struct{} {
return c.handshakeCompleteChan
}
// QlogTrace returns the qlog trace of the QUIC connection.
// It is nil if qlog is not enabled.
func (c *Conn) QlogTrace() qlogwriter.Trace {
return c.qlogTrace
}
// NextConnection transitions a connection to be usable after a 0-RTT rejection.
// It waits for the handshake to complete and then enables the connection for normal use.
// This should be called when the server rejects 0-RTT and the application receives
// [Err0RTTRejected] errors.
//
// Note that 0-RTT rejection invalidates all data sent in 0-RTT packets. It is the
// application's responsibility to handle this (for example by resending the data).
func (c *Conn) NextConnection(ctx context.Context) (*Conn, error) {
// The handshake might fail after the server rejected 0-RTT.
// This could happen if the Finished message is malformed or never received.
select {
case <-ctx.Done():
return nil, context.Cause(ctx)
case <-c.Context().Done():
case <-c.HandshakeComplete():
c.streamsMap.UseResetMaps()
}
return c, nil
}
// estimateMaxPayloadSize estimates the maximum payload size for short header packets.
// It is not very sophisticated: it just subtracts the size of header (assuming the maximum
// connection ID length), and the size of the encryption tag.
func estimateMaxPayloadSize(mtu protocol.ByteCount) protocol.ByteCount {
return mtu - 1 /* type byte */ - 20 /* maximum connection ID length */ - 16 /* tag size */
}
package quic
import (
"net"
"net/netip"
"slices"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
)
// ConvertFrame converts a wire.Frame into a logging.Frame.
// This makes it possible for external packages to access the frames.
// Furthermore, it removes the data slices from CRYPTO and STREAM frames.
func toQlogFrame(frame wire.Frame) qlog.Frame {
switch f := frame.(type) {
case *wire.AckFrame:
// We use a pool for ACK frames.
// Implementations of the tracer interface may hold on to frames, so we need to make a copy here.
return qlog.Frame{Frame: toQlogAckFrame(f)}
case *wire.CryptoFrame:
return qlog.Frame{
Frame: &qlog.CryptoFrame{
Offset: int64(f.Offset),
Length: int64(len(f.Data)),
},
}
case *wire.StreamFrame:
return qlog.Frame{
Frame: &qlog.StreamFrame{
StreamID: f.StreamID,
Offset: int64(f.Offset),
Length: int64(f.DataLen()),
Fin: f.Fin,
},
}
case *wire.DatagramFrame:
return qlog.Frame{
Frame: &qlog.DatagramFrame{
Length: int64(len(f.Data)),
},
}
default:
return qlog.Frame{Frame: frame}
}
}
func toQlogAckFrame(f *wire.AckFrame) *qlog.AckFrame {
ack := &qlog.AckFrame{
AckRanges: slices.Clone(f.AckRanges),
DelayTime: f.DelayTime,
ECNCE: f.ECNCE,
ECT0: f.ECT0,
ECT1: f.ECT1,
}
return ack
}
func (c *Conn) logLongHeaderPacket(p *longHeaderPacket, ecn protocol.ECN, datagramID qlog.DatagramID) {
// quic-go logging
if c.logger.Debug() {
p.header.Log(c.logger)
if p.ack != nil {
wire.LogFrame(c.logger, p.ack, true)
}
for _, frame := range p.frames {
wire.LogFrame(c.logger, frame.Frame, true)
}
for _, frame := range p.streamFrames {
wire.LogFrame(c.logger, frame.Frame, true)
}
}
// tracing
if c.qlogger != nil {
numFrames := len(p.frames) + len(p.streamFrames)
if p.ack != nil {
numFrames++
}
frames := make([]qlog.Frame, 0, numFrames)
if p.ack != nil {
frames = append(frames, toQlogFrame(p.ack))
}
for _, f := range p.frames {
frames = append(frames, toQlogFrame(f.Frame))
}
for _, f := range p.streamFrames {
frames = append(frames, toQlogFrame(f.Frame))
}
c.qlogger.RecordEvent(qlog.PacketSent{
Header: qlog.PacketHeader{
PacketType: toQlogPacketType(p.header.Type),
KeyPhaseBit: p.header.KeyPhase,
PacketNumber: p.header.PacketNumber,
Version: p.header.Version,
SrcConnectionID: p.header.SrcConnectionID,
DestConnectionID: p.header.DestConnectionID,
},
Raw: qlog.RawInfo{
Length: int(p.length),
PayloadLength: int(p.header.Length),
},
DatagramID: datagramID,
Frames: frames,
ECN: toQlogECN(ecn),
})
}
}
func (c *Conn) logShortHeaderPacket(p shortHeaderPacket, ecn protocol.ECN, size protocol.ByteCount) {
c.logShortHeaderPacketWithDatagramID(p, ecn, size, false, 0)
}
func (c *Conn) logShortHeaderPacketWithDatagramID(p shortHeaderPacket, ecn protocol.ECN, size protocol.ByteCount, isCoalesced bool, datagramID qlog.DatagramID) {
if c.logger.Debug() && !isCoalesced {
c.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, 1-RTT (ECN: %s)", p.PacketNumber, size, c.logID, ecn)
}
// quic-go logging
if c.logger.Debug() {
wire.LogShortHeader(c.logger, p.DestConnID, p.PacketNumber, p.PacketNumberLen, p.KeyPhase)
if p.Ack != nil {
wire.LogFrame(c.logger, p.Ack, true)
}
for _, f := range p.Frames {
wire.LogFrame(c.logger, f.Frame, true)
}
for _, f := range p.StreamFrames {
wire.LogFrame(c.logger, f.Frame, true)
}
}
// tracing
if c.qlogger != nil {
numFrames := len(p.Frames) + len(p.StreamFrames)
if p.Ack != nil {
numFrames++
}
fs := make([]qlog.Frame, 0, numFrames)
if p.Ack != nil {
fs = append(fs, toQlogFrame(p.Ack))
}
for _, f := range p.Frames {
fs = append(fs, toQlogFrame(f.Frame))
}
for _, f := range p.StreamFrames {
fs = append(fs, toQlogFrame(f.Frame))
}
c.qlogger.RecordEvent(qlog.PacketSent{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType1RTT,
KeyPhaseBit: p.KeyPhase,
PacketNumber: p.PacketNumber,
Version: c.version,
DestConnectionID: p.DestConnID,
},
Raw: qlog.RawInfo{
Length: int(size),
PayloadLength: int(size - wire.ShortHeaderLen(p.DestConnID, p.PacketNumberLen)),
},
DatagramID: datagramID,
Frames: fs,
ECN: toQlogECN(ecn),
})
}
}
func (c *Conn) logCoalescedPacket(packet *coalescedPacket, ecn protocol.ECN) {
var datagramID qlog.DatagramID
if c.qlogger != nil {
datagramID = qlog.CalculateDatagramID(packet.buffer.Data)
}
if c.logger.Debug() {
// There's a short period between dropping both Initial and Handshake keys and completion of the handshake,
// during which we might call PackCoalescedPacket but just pack a short header packet.
if len(packet.longHdrPackets) == 0 && packet.shortHdrPacket != nil {
c.logShortHeaderPacketWithDatagramID(
*packet.shortHdrPacket,
ecn,
packet.shortHdrPacket.Length,
false,
datagramID,
)
return
}
if len(packet.longHdrPackets) > 1 {
c.logger.Debugf("-> Sending coalesced packet (%d parts, %d bytes) for connection %s", len(packet.longHdrPackets), packet.buffer.Len(), c.logID)
} else {
c.logger.Debugf("-> Sending packet %d (%d bytes) for connection %s, %s", packet.longHdrPackets[0].header.PacketNumber, packet.buffer.Len(), c.logID, packet.longHdrPackets[0].EncryptionLevel())
}
}
for _, p := range packet.longHdrPackets {
c.logLongHeaderPacket(p, ecn, datagramID)
}
if p := packet.shortHdrPacket; p != nil {
c.logShortHeaderPacketWithDatagramID(*p, ecn, p.Length, true, datagramID)
}
}
func (c *Conn) qlogTransportParameters(tp *wire.TransportParameters, sentBy protocol.Perspective, restore bool) {
ev := qlog.ParametersSet{
Restore: restore,
OriginalDestinationConnectionID: tp.OriginalDestinationConnectionID,
InitialSourceConnectionID: tp.InitialSourceConnectionID,
RetrySourceConnectionID: tp.RetrySourceConnectionID,
StatelessResetToken: tp.StatelessResetToken,
DisableActiveMigration: tp.DisableActiveMigration,
MaxIdleTimeout: tp.MaxIdleTimeout,
MaxUDPPayloadSize: tp.MaxUDPPayloadSize,
AckDelayExponent: tp.AckDelayExponent,
MaxAckDelay: tp.MaxAckDelay,
ActiveConnectionIDLimit: tp.ActiveConnectionIDLimit,
InitialMaxData: tp.InitialMaxData,
InitialMaxStreamDataBidiLocal: tp.InitialMaxStreamDataBidiLocal,
InitialMaxStreamDataBidiRemote: tp.InitialMaxStreamDataBidiRemote,
InitialMaxStreamDataUni: tp.InitialMaxStreamDataUni,
InitialMaxStreamsBidi: int64(tp.MaxBidiStreamNum),
InitialMaxStreamsUni: int64(tp.MaxUniStreamNum),
MaxDatagramFrameSize: tp.MaxDatagramFrameSize,
EnableResetStreamAt: tp.EnableResetStreamAt,
}
if sentBy == c.perspective {
ev.Initiator = qlog.InitiatorLocal
} else {
ev.Initiator = qlog.InitiatorRemote
}
if tp.PreferredAddress != nil {
ev.PreferredAddress = &qlog.PreferredAddress{
IPv4: tp.PreferredAddress.IPv4,
IPv6: tp.PreferredAddress.IPv6,
ConnectionID: tp.PreferredAddress.ConnectionID,
StatelessResetToken: tp.PreferredAddress.StatelessResetToken,
}
}
c.qlogger.RecordEvent(ev)
}
func toQlogECN(ecn protocol.ECN) qlog.ECN {
//nolint:exhaustive // only need to handle the 3 valid values
switch ecn {
case protocol.ECT0:
return qlog.ECT0
case protocol.ECT1:
return qlog.ECT1
case protocol.ECNCE:
return qlog.ECNCE
default:
return qlog.ECNUnsupported
}
}
func toQlogPacketType(pt protocol.PacketType) qlog.PacketType {
var qpt qlog.PacketType
switch pt {
case protocol.PacketTypeInitial:
qpt = qlog.PacketTypeInitial
case protocol.PacketTypeHandshake:
qpt = qlog.PacketTypeHandshake
case protocol.PacketType0RTT:
qpt = qlog.PacketType0RTT
case protocol.PacketTypeRetry:
qpt = qlog.PacketTypeRetry
}
return qpt
}
func toPathEndpointInfo(addr *net.UDPAddr) qlog.PathEndpointInfo {
if addr == nil {
return qlog.PathEndpointInfo{}
}
var info qlog.PathEndpointInfo
if addr.IP == nil || addr.IP.To4() != nil {
addrPort := netip.AddrPortFrom(netip.AddrFrom4([4]byte(addr.IP.To4())), uint16(addr.Port))
if addrPort.IsValid() {
info.IPv4 = addrPort
}
} else {
addrPort := netip.AddrPortFrom(netip.AddrFrom16([16]byte(addr.IP.To16())), uint16(addr.Port))
if addrPort.IsValid() {
info.IPv6 = addrPort
}
}
return info
}
// startedConnectionEvent builds a StartedConnection event using consistent logic
// for both endpoints. If the local address is unspecified (e.g., dual-stack
// listener), it selects the family based on the remote address and uses the
// unspecified address of that family with the local port.
func startedConnectionEvent(local, remote *net.UDPAddr) qlog.StartedConnection {
var localInfo, remoteInfo qlog.PathEndpointInfo
if remote != nil {
remoteInfo = toPathEndpointInfo(remote)
}
if local != nil {
if local.IP == nil || local.IP.IsUnspecified() {
// Choose local family based on the remote address family.
if remote != nil && remote.IP.To4() != nil {
ap := netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), uint16(local.Port))
if ap.IsValid() {
localInfo.IPv4 = ap
}
} else if remote != nil && remote.IP.To16() != nil && remote.IP.To4() == nil {
ap := netip.AddrPortFrom(netip.AddrFrom16([16]byte{}), uint16(local.Port))
if ap.IsValid() {
localInfo.IPv6 = ap
}
}
} else {
localInfo = toPathEndpointInfo(local)
}
}
return qlog.StartedConnection{Local: localInfo, Remote: remoteInfo}
}
package quic
import (
"errors"
"fmt"
"io"
"os"
"slices"
"strconv"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
const disableClientHelloScramblingEnv = "QUIC_GO_DISABLE_CLIENTHELLO_SCRAMBLING"
// The baseCryptoStream is used by the cryptoStream and the initialCryptoStream.
// This allows us to implement different logic for PopCryptoFrame for the two streams.
type baseCryptoStream struct {
queue frameSorter
highestOffset protocol.ByteCount
finished bool
writeOffset protocol.ByteCount
writeBuf []byte
}
func newCryptoStream() *cryptoStream {
return &cryptoStream{baseCryptoStream{queue: *newFrameSorter()}}
}
func (s *baseCryptoStream) HandleCryptoFrame(f *wire.CryptoFrame) error {
highestOffset := f.Offset + protocol.ByteCount(len(f.Data))
if maxOffset := highestOffset; maxOffset > protocol.MaxCryptoStreamOffset {
return &qerr.TransportError{
ErrorCode: qerr.CryptoBufferExceeded,
ErrorMessage: fmt.Sprintf("received invalid offset %d on crypto stream, maximum allowed %d", maxOffset, protocol.MaxCryptoStreamOffset),
}
}
if s.finished {
if highestOffset > s.highestOffset {
// reject crypto data received after this stream was already finished
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received crypto data after change of encryption level",
}
}
// ignore data with a smaller offset than the highest received
// could e.g. be a retransmission
return nil
}
s.highestOffset = max(s.highestOffset, highestOffset)
return s.queue.Push(f.Data, f.Offset, nil)
}
// GetCryptoData retrieves data that was received in CRYPTO frames
func (s *baseCryptoStream) GetCryptoData() []byte {
_, data, _ := s.queue.Pop()
return data
}
func (s *baseCryptoStream) Finish() error {
if s.queue.HasMoreData() {
return &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "encryption level changed, but crypto stream has more data to read",
}
}
s.finished = true
return nil
}
// Writes writes data that should be sent out in CRYPTO frames
func (s *baseCryptoStream) Write(p []byte) (int, error) {
s.writeBuf = append(s.writeBuf, p...)
return len(p), nil
}
func (s *baseCryptoStream) HasData() bool {
return len(s.writeBuf) > 0
}
func (s *baseCryptoStream) PopCryptoFrame(maxLen protocol.ByteCount) *wire.CryptoFrame {
f := &wire.CryptoFrame{Offset: s.writeOffset}
n := min(f.MaxDataLen(maxLen), protocol.ByteCount(len(s.writeBuf)))
if n <= 0 {
return nil
}
f.Data = s.writeBuf[:n]
s.writeBuf = s.writeBuf[n:]
s.writeOffset += n
return f
}
type cryptoStream struct {
baseCryptoStream
}
type clientHelloCut struct {
start protocol.ByteCount
end protocol.ByteCount
}
type initialCryptoStream struct {
baseCryptoStream
scramble bool
end protocol.ByteCount
cuts [2]clientHelloCut
}
func newInitialCryptoStream(isClient bool) *initialCryptoStream {
var scramble bool
if isClient {
disabled, err := strconv.ParseBool(os.Getenv(disableClientHelloScramblingEnv))
scramble = err != nil || !disabled
}
s := &initialCryptoStream{
baseCryptoStream: baseCryptoStream{queue: *newFrameSorter()},
scramble: scramble,
}
for i := range len(s.cuts) {
s.cuts[i].start = protocol.InvalidByteCount
s.cuts[i].end = protocol.InvalidByteCount
}
return s
}
func (s *initialCryptoStream) HasData() bool {
// The ClientHello might be written in multiple parts.
// In order to correctly split the ClientHello, we need the entire ClientHello has been queued.
if s.scramble && s.writeOffset == 0 && s.cuts[0].start == protocol.InvalidByteCount {
return false
}
return s.baseCryptoStream.HasData()
}
func (s *initialCryptoStream) Write(p []byte) (int, error) {
s.writeBuf = append(s.writeBuf, p...)
if !s.scramble {
return len(p), nil
}
if s.cuts[0].start == protocol.InvalidByteCount {
sniPos, sniLen, echPos, err := findSNIAndECH(s.writeBuf)
if errors.Is(err, io.ErrUnexpectedEOF) {
return len(p), nil
}
if err != nil {
return len(p), err
}
if sniPos == -1 && echPos == -1 {
// Neither SNI nor ECH found.
// There's nothing to scramble.
s.scramble = false
return len(p), nil
}
s.end = protocol.ByteCount(len(s.writeBuf))
s.cuts[0].start = protocol.ByteCount(sniPos + sniLen/2) // right in the middle
s.cuts[0].end = protocol.ByteCount(sniPos + sniLen)
if echPos > 0 {
// ECH extension found, cut the ECH extension type value (a uint16) in half
start := protocol.ByteCount(echPos + 1)
s.cuts[1].start = start
// cut somewhere (16 bytes), most likely in the ECH extension value
s.cuts[1].end = min(start+16, s.end)
}
slices.SortFunc(s.cuts[:], func(a, b clientHelloCut) int {
if a.start == protocol.InvalidByteCount {
return 1
}
if a.start > b.start {
return 1
}
return -1
})
}
return len(p), nil
}
func (s *initialCryptoStream) PopCryptoFrame(maxLen protocol.ByteCount) *wire.CryptoFrame {
if !s.scramble {
return s.baseCryptoStream.PopCryptoFrame(maxLen)
}
// send out the skipped parts
if s.writeOffset == s.end {
var foundCuts bool
var f *wire.CryptoFrame
for i, c := range s.cuts {
if c.start == protocol.InvalidByteCount {
continue
}
foundCuts = true
if f != nil {
break
}
f = &wire.CryptoFrame{Offset: c.start}
n := min(f.MaxDataLen(maxLen), c.end-c.start)
if n <= 0 {
return nil
}
f.Data = s.writeBuf[c.start : c.start+n]
s.cuts[i].start += n
if s.cuts[i].start == c.end {
s.cuts[i].start = protocol.InvalidByteCount
s.cuts[i].end = protocol.InvalidByteCount
foundCuts = false
}
}
if !foundCuts {
// no more cuts found, we're done sending out everything up until s.end
s.writeBuf = s.writeBuf[s.end:]
s.end = protocol.InvalidByteCount
s.scramble = false
}
return f
}
nextCut := clientHelloCut{start: protocol.InvalidByteCount, end: protocol.InvalidByteCount}
for _, c := range s.cuts {
if c.start == protocol.InvalidByteCount {
continue
}
if c.start > s.writeOffset {
nextCut = c
break
}
}
f := &wire.CryptoFrame{Offset: s.writeOffset}
maxOffset := nextCut.start
if maxOffset == protocol.InvalidByteCount {
maxOffset = s.end
}
n := min(f.MaxDataLen(maxLen), maxOffset-s.writeOffset)
if n <= 0 {
return nil
}
f.Data = s.writeBuf[s.writeOffset : s.writeOffset+n]
// Don't reslice the writeBuf yet.
// This is done once all parts have been sent out.
s.writeOffset += n
if s.writeOffset == nextCut.start {
s.writeOffset = nextCut.end
}
return f
}
package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
type cryptoStreamManager struct {
initialStream *initialCryptoStream
handshakeStream *cryptoStream
oneRTTStream *cryptoStream
}
func newCryptoStreamManager(
initialStream *initialCryptoStream,
handshakeStream *cryptoStream,
oneRTTStream *cryptoStream,
) *cryptoStreamManager {
return &cryptoStreamManager{
initialStream: initialStream,
handshakeStream: handshakeStream,
oneRTTStream: oneRTTStream,
}
}
func (m *cryptoStreamManager) HandleCryptoFrame(frame *wire.CryptoFrame, encLevel protocol.EncryptionLevel) error {
//nolint:exhaustive // CRYPTO frames cannot be sent in 0-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
return m.initialStream.HandleCryptoFrame(frame)
case protocol.EncryptionHandshake:
return m.handshakeStream.HandleCryptoFrame(frame)
case protocol.Encryption1RTT:
return m.oneRTTStream.HandleCryptoFrame(frame)
default:
return fmt.Errorf("received CRYPTO frame with unexpected encryption level: %s", encLevel)
}
}
func (m *cryptoStreamManager) GetCryptoData(encLevel protocol.EncryptionLevel) []byte {
//nolint:exhaustive // CRYPTO frames cannot be sent in 0-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
return m.initialStream.GetCryptoData()
case protocol.EncryptionHandshake:
return m.handshakeStream.GetCryptoData()
case protocol.Encryption1RTT:
return m.oneRTTStream.GetCryptoData()
default:
panic(fmt.Sprintf("received CRYPTO frame with unexpected encryption level: %s", encLevel))
}
}
func (m *cryptoStreamManager) GetPostHandshakeData(maxSize protocol.ByteCount) *wire.CryptoFrame {
if !m.oneRTTStream.HasData() {
return nil
}
return m.oneRTTStream.PopCryptoFrame(maxSize)
}
func (m *cryptoStreamManager) Drop(encLevel protocol.EncryptionLevel) error {
//nolint:exhaustive // 1-RTT keys should never get dropped.
switch encLevel {
case protocol.EncryptionInitial:
return m.initialStream.Finish()
case protocol.EncryptionHandshake:
return m.handshakeStream.Finish()
default:
panic(fmt.Sprintf("dropped unexpected encryption level: %s", encLevel))
}
}
package quic
import (
"context"
"sync"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/utils/ringbuffer"
"github.com/quic-go/quic-go/internal/wire"
)
const (
maxDatagramSendQueueLen = 32
maxDatagramRcvQueueLen = 128
)
type datagramQueue struct {
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
rcvd chan struct{} // used to notify Receive that a new datagram was received
closeErr error
closed chan struct{}
hasData func()
logger utils.Logger
}
func newDatagramQueue(hasData func(), logger utils.Logger) *datagramQueue {
return &datagramQueue{
hasData: hasData,
rcvd: make(chan struct{}, 1),
sent: make(chan struct{}, 1),
closed: make(chan struct{}),
logger: logger,
}
}
// 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()
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 {
h.sendMx.Lock()
defer h.sendMx.Unlock()
if h.sendQueue.Empty() {
return nil
}
return h.sendQueue.PeekFront()
}
func (h *datagramQueue) Pop() {
h.sendMx.Lock()
defer h.sendMx.Unlock()
_ = h.sendQueue.PopFront()
select {
case h.sent <- struct{}{}:
default:
}
}
// HandleDatagramFrame handles a received DATAGRAM frame.
func (h *datagramQueue) HandleDatagramFrame(f *wire.DatagramFrame) {
data := make([]byte, len(f.Data))
copy(data, f.Data)
var queued bool
h.rcvMx.Lock()
if len(h.rcvQueue) < maxDatagramRcvQueueLen {
h.rcvQueue = append(h.rcvQueue, data)
queued = true
select {
case h.rcvd <- struct{}{}:
default:
}
}
h.rcvMx.Unlock()
if !queued && h.logger.Debug() {
h.logger.Debugf("Discarding received DATAGRAM frame (%d bytes payload)", len(f.Data))
}
}
// Receive gets a received DATAGRAM frame.
func (h *datagramQueue) Receive(ctx context.Context) ([]byte, error) {
for {
h.rcvMx.Lock()
if len(h.rcvQueue) > 0 {
data := h.rcvQueue[0]
h.rcvQueue = h.rcvQueue[1:]
h.rcvMx.Unlock()
return data, nil
}
h.rcvMx.Unlock()
select {
case <-h.rcvd:
continue
case <-h.closed:
return nil, h.closeErr
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
func (h *datagramQueue) CloseWithError(e error) {
h.closeErr = e
close(h.closed)
}
package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/qerr"
)
type (
// TransportError indicates an error that occurred on the QUIC transport layer.
// Every transport error other than CONNECTION_REFUSED and APPLICATION_ERROR is
// likely a bug in the implementation.
TransportError = qerr.TransportError
// ApplicationError is an application-defined error.
ApplicationError = qerr.ApplicationError
// VersionNegotiationError indicates a failure to negotiate a QUIC version.
VersionNegotiationError = qerr.VersionNegotiationError
// StatelessResetError indicates a stateless reset was received.
// This can happen when the peer reboots, or when packets are misrouted.
// See section 10.3 of RFC 9000 for details.
StatelessResetError = qerr.StatelessResetError
// IdleTimeoutError indicates that the connection timed out because it was inactive for too long.
IdleTimeoutError = qerr.IdleTimeoutError
// HandshakeTimeoutError indicates that the connection timed out before completing the handshake.
HandshakeTimeoutError = qerr.HandshakeTimeoutError
)
type (
// TransportErrorCode is a QUIC transport error code, see section 20 of RFC 9000.
TransportErrorCode = qerr.TransportErrorCode
// ApplicationErrorCode is an QUIC application error code.
ApplicationErrorCode = qerr.ApplicationErrorCode
// StreamErrorCode is a QUIC stream error code. The meaning of the value is defined by the application.
StreamErrorCode = qerr.StreamErrorCode
)
const (
// NoError is the NO_ERROR transport error code.
NoError = qerr.NoError
// InternalError is the INTERNAL_ERROR transport error code.
InternalError = qerr.InternalError
// ConnectionRefused is the CONNECTION_REFUSED transport error code.
ConnectionRefused = qerr.ConnectionRefused
// FlowControlError is the FLOW_CONTROL_ERROR transport error code.
FlowControlError = qerr.FlowControlError
// StreamLimitError is the STREAM_LIMIT_ERROR transport error code.
StreamLimitError = qerr.StreamLimitError
// StreamStateError is the STREAM_STATE_ERROR transport error code.
StreamStateError = qerr.StreamStateError
// FinalSizeError is the FINAL_SIZE_ERROR transport error code.
FinalSizeError = qerr.FinalSizeError
// FrameEncodingError is the FRAME_ENCODING_ERROR transport error code.
FrameEncodingError = qerr.FrameEncodingError
// TransportParameterError is the TRANSPORT_PARAMETER_ERROR transport error code.
TransportParameterError = qerr.TransportParameterError
// ConnectionIDLimitError is the CONNECTION_ID_LIMIT_ERROR transport error code.
ConnectionIDLimitError = qerr.ConnectionIDLimitError
// ProtocolViolation is the PROTOCOL_VIOLATION transport error code.
ProtocolViolation = qerr.ProtocolViolation
// InvalidToken is the INVALID_TOKEN transport error code.
InvalidToken = qerr.InvalidToken
// ApplicationErrorErrorCode is the APPLICATION_ERROR transport error code.
ApplicationErrorErrorCode = qerr.ApplicationErrorErrorCode
// CryptoBufferExceeded is the CRYPTO_BUFFER_EXCEEDED transport error code.
CryptoBufferExceeded = qerr.CryptoBufferExceeded
// KeyUpdateError is the KEY_UPDATE_ERROR transport error code.
KeyUpdateError = qerr.KeyUpdateError
// AEADLimitReached is the AEAD_LIMIT_REACHED transport error code.
AEADLimitReached = qerr.AEADLimitReached
// NoViablePathError is the NO_VIABLE_PATH_ERROR transport error code.
NoViablePathError = qerr.NoViablePathError
)
// A StreamError is used to signal stream cancellations.
// It is returned from the Read and Write methods of the [ReceiveStream], [SendStream] and [Stream].
type StreamError struct {
StreamID StreamID
ErrorCode StreamErrorCode
Remote bool
}
func (e *StreamError) Is(target error) bool {
t, ok := target.(*StreamError)
return ok && e.StreamID == t.StreamID && e.ErrorCode == t.ErrorCode && e.Remote == t.Remote
}
func (e *StreamError) Error() string {
pers := "local"
if e.Remote {
pers = "remote"
}
return fmt.Sprintf("stream %d canceled by %s with error code %d", e.StreamID, pers, e.ErrorCode)
}
// DatagramTooLargeError is returned from Conn.SendDatagram if the payload is too large to be sent.
type DatagramTooLargeError struct {
MaxDatagramPayloadSize int64
}
func (e *DatagramTooLargeError) Is(target error) bool {
t, ok := target.(*DatagramTooLargeError)
return ok && e.MaxDatagramPayloadSize == t.MaxDatagramPayloadSize
}
func (e *DatagramTooLargeError) Error() string { return "DATAGRAM frame too large" }
package quic
import (
"sync"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
type receiveFlowController struct {
//nolint:structcheck // The mutex is used both by the stream and the connection flow controller
mutex sync.Mutex
bytesRead protocol.ByteCount
highestReceived protocol.ByteCount
receiveWindow protocol.ByteCount
receiveWindowSize protocol.ByteCount
maxReceiveWindowSize protocol.ByteCount
allowWindowIncrease func(size protocol.ByteCount) bool
epochStartTime monotime.Time
epochStartOffset protocol.ByteCount
rttStats *utils.RTTStats
logger utils.Logger
}
// needs to be called with locked mutex
func (c *receiveFlowController) addBytesRead(n protocol.ByteCount) {
c.bytesRead += n
}
func (c *receiveFlowController) hasWindowUpdate() bool {
bytesRemaining := c.receiveWindow - c.bytesRead
// update the window when more than the threshold was consumed
return bytesRemaining <= protocol.ByteCount(float64(c.receiveWindowSize)*(1-protocol.WindowUpdateThreshold))
}
// getWindowUpdate updates the receive window, if necessary
// it returns the new offset
func (c *receiveFlowController) getWindowUpdate(now monotime.Time) protocol.ByteCount {
if !c.hasWindowUpdate() {
return 0
}
c.maybeAdjustWindowSize(now)
c.receiveWindow = c.bytesRead + c.receiveWindowSize
return c.receiveWindow
}
// maybeAdjustWindowSize increases the receiveWindowSize if we're sending updates too often.
// For details about auto-tuning, see https://docs.google.com/document/d/1SExkMmGiz8VYzV3s9E35JQlJ73vhzCekKkDi85F1qCE/edit?usp=sharing.
func (c *receiveFlowController) maybeAdjustWindowSize(now monotime.Time) {
bytesReadInEpoch := c.bytesRead - c.epochStartOffset
// don't do anything if less than half the window has been consumed
if bytesReadInEpoch <= c.receiveWindowSize/2 {
return
}
rtt := c.rttStats.SmoothedRTT()
if rtt == 0 {
return
}
fraction := float64(bytesReadInEpoch) / float64(c.receiveWindowSize)
if now.Sub(c.epochStartTime) < time.Duration(4*fraction*float64(rtt)) {
// window is consumed too fast, try to increase the window size
newSize := min(2*c.receiveWindowSize, c.maxReceiveWindowSize)
if newSize > c.receiveWindowSize && (c.allowWindowIncrease == nil || c.allowWindowIncrease(newSize-c.receiveWindowSize)) {
c.receiveWindowSize = newSize
}
}
c.startNewAutoTuningEpoch(now)
}
func (c *receiveFlowController) startNewAutoTuningEpoch(now monotime.Time) {
c.epochStartTime = now
c.epochStartOffset = c.bytesRead
}
func (c *receiveFlowController) checkFlowControlViolation() bool {
return c.highestReceived > c.receiveWindow
}
package quic
import (
"errors"
"fmt"
"sync"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
)
type connectionFlowController struct {
receiveFlowController
// Protects send-side state, which TryWriteAll can access from application goroutines.
sendMutex sync.Mutex
bytesSent protocol.ByteCount
sendWindow protocol.ByteCount
lastBlockedAt protocol.ByteCount
}
// newConnectionFlowController gets a new flow controller for the connection.
// It is created before we receive the peer's transport parameters, thus it starts with a sendWindow of 0.
func newConnectionFlowController(
receiveWindow protocol.ByteCount,
maxReceiveWindow protocol.ByteCount,
allowWindowIncrease func(size protocol.ByteCount) bool,
rttStats *utils.RTTStats,
logger utils.Logger,
) *connectionFlowController {
return &connectionFlowController{
receiveFlowController: receiveFlowController{
rttStats: rttStats,
receiveWindow: receiveWindow,
receiveWindowSize: receiveWindow,
maxReceiveWindowSize: maxReceiveWindow,
allowWindowIncrease: allowWindowIncrease,
logger: logger,
},
}
}
// IncrementHighestReceived adds an increment to the highestReceived value
func (c *connectionFlowController) IncrementHighestReceived(increment protocol.ByteCount, now monotime.Time) error {
c.mutex.Lock()
defer c.mutex.Unlock()
// If this is the first frame received on this connection, start flow-control auto-tuning.
if c.highestReceived == 0 {
c.startNewAutoTuningEpoch(now)
}
c.highestReceived += increment
if c.checkFlowControlViolation() {
return &qerr.TransportError{
ErrorCode: qerr.FlowControlError,
ErrorMessage: fmt.Sprintf("received %d bytes for the connection, allowed %d bytes", c.highestReceived, c.receiveWindow),
}
}
return nil
}
func (c *connectionFlowController) AddBytesRead(n protocol.ByteCount) (hasWindowUpdate bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.addBytesRead(n)
return c.hasWindowUpdate()
}
func (c *connectionFlowController) TryAddBytesSent(n protocol.ByteCount) bool {
c.sendMutex.Lock()
defer c.sendMutex.Unlock()
if c.bytesSent > c.sendWindow || n > c.sendWindow-c.bytesSent {
return false
}
c.bytesSent += n
return true
}
// UpdateSendWindow is called after receiving a MAX_DATA frame.
func (c *connectionFlowController) UpdateSendWindow(offset protocol.ByteCount) (updated bool) {
c.sendMutex.Lock()
defer c.sendMutex.Unlock()
if offset > c.sendWindow {
c.sendWindow = offset
return true
}
return false
}
func (c *connectionFlowController) SendWindowSize() protocol.ByteCount {
c.sendMutex.Lock()
defer c.sendMutex.Unlock()
return c.sendWindow - c.bytesSent
}
// IsNewlyBlocked says if it is newly blocked by connection flow control.
// For every offset, it only returns true once.
// If it is blocked, the offset is returned.
func (c *connectionFlowController) IsNewlyBlocked() (bool, protocol.ByteCount) {
c.sendMutex.Lock()
defer c.sendMutex.Unlock()
if c.bytesSent < c.sendWindow || c.sendWindow == c.lastBlockedAt {
return false, 0
}
c.lastBlockedAt = c.sendWindow
return true, c.sendWindow
}
func (c *connectionFlowController) GetWindowUpdate(now monotime.Time) protocol.ByteCount {
c.mutex.Lock()
defer c.mutex.Unlock()
oldWindowSize := c.receiveWindowSize
offset := c.getWindowUpdate(now)
if c.logger.Debug() && oldWindowSize < c.receiveWindowSize {
c.logger.Debugf("Increasing receive flow control window for the connection to %d kB", c.receiveWindowSize/(1<<10))
}
return offset
}
// EnsureMinimumWindowSize sets a minimum window size
// it should make sure that the connection-level window is increased when a stream-level window grows
func (c *connectionFlowController) EnsureMinimumWindowSize(inc protocol.ByteCount, now monotime.Time) {
c.mutex.Lock()
defer c.mutex.Unlock()
if inc <= c.receiveWindowSize {
return
}
newSize := min(inc, c.maxReceiveWindowSize)
if delta := newSize - c.receiveWindowSize; delta > 0 && c.allowWindowIncrease(delta) {
c.receiveWindowSize = newSize
if c.logger.Debug() {
c.logger.Debugf("Increasing receive flow control window for the connection to %d, in response to stream flow control window increase", newSize)
}
}
c.startNewAutoTuningEpoch(now)
}
// Reset rests the flow controller. This happens when 0-RTT is rejected.
// All stream data is invalidated, it's as if we had never opened a stream and never sent any data.
// At that point, we only have sent stream data, but we didn't have the keys to open 1-RTT keys yet.
func (c *connectionFlowController) Reset() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.bytesRead > 0 || c.highestReceived > 0 || !c.epochStartTime.IsZero() {
return errors.New("flow controller reset after reading data")
}
c.sendMutex.Lock()
defer c.sendMutex.Unlock()
c.bytesSent = 0
c.lastBlockedAt = 0
c.sendWindow = 0
return nil
}
package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
)
type streamFlowController struct {
receiveFlowController
bytesSent protocol.ByteCount
sendWindow protocol.ByteCount
lastBlockedAt protocol.ByteCount
streamID protocol.StreamID
connection *connectionFlowController
receivedFinalOffset bool
}
// newStreamFlowController gets a new flow controller for a stream.
func newStreamFlowController(
streamID protocol.StreamID,
cfc *connectionFlowController,
receiveWindow protocol.ByteCount,
maxReceiveWindow protocol.ByteCount,
initialSendWindow protocol.ByteCount,
rttStats *utils.RTTStats,
logger utils.Logger,
) *streamFlowController {
return &streamFlowController{
streamID: streamID,
connection: cfc,
sendWindow: initialSendWindow,
receiveFlowController: receiveFlowController{
rttStats: rttStats,
receiveWindow: receiveWindow,
receiveWindowSize: receiveWindow,
maxReceiveWindowSize: maxReceiveWindow,
logger: logger,
},
}
}
// UpdateHighestReceived updates the highestReceived value, if the offset is higher.
func (c *streamFlowController) UpdateHighestReceived(offset protocol.ByteCount, final bool, now monotime.Time) error {
// If the final offset for this stream is already known, check for consistency.
if c.receivedFinalOffset {
// If we receive another final offset, check that it's the same.
if final && offset != c.highestReceived {
return &qerr.TransportError{
ErrorCode: qerr.FinalSizeError,
ErrorMessage: fmt.Sprintf("received inconsistent final offset for stream %d (old: %d, new: %d bytes)", c.streamID, c.highestReceived, offset),
}
}
// Check that the offset is below the final offset.
if offset > c.highestReceived {
return &qerr.TransportError{
ErrorCode: qerr.FinalSizeError,
ErrorMessage: fmt.Sprintf("received offset %d for stream %d, but final offset was already received at %d", offset, c.streamID, c.highestReceived),
}
}
}
if final {
c.receivedFinalOffset = true
}
if offset == c.highestReceived {
return nil
}
// A higher offset was received before. This can happen due to reordering.
if offset < c.highestReceived {
if final {
return &qerr.TransportError{
ErrorCode: qerr.FinalSizeError,
ErrorMessage: fmt.Sprintf("received final offset %d for stream %d, but already received offset %d before", offset, c.streamID, c.highestReceived),
}
}
return nil
}
// If this is the first frame received for this stream, start flow-control auto-tuning.
if c.highestReceived == 0 {
c.startNewAutoTuningEpoch(now)
}
increment := offset - c.highestReceived
c.highestReceived = offset
if c.checkFlowControlViolation() {
return &qerr.TransportError{
ErrorCode: qerr.FlowControlError,
ErrorMessage: fmt.Sprintf("received %d bytes on stream %d, allowed %d bytes", offset, c.streamID, c.receiveWindow),
}
}
return c.connection.IncrementHighestReceived(increment, now)
}
func (c *streamFlowController) AddBytesRead(n protocol.ByteCount) (hasStreamWindowUpdate, hasConnWindowUpdate bool) {
c.mutex.Lock()
c.addBytesRead(n)
hasStreamWindowUpdate = c.shouldQueueWindowUpdate()
c.mutex.Unlock()
hasConnWindowUpdate = c.connection.AddBytesRead(n)
return
}
func (c *streamFlowController) Abandon() {
c.mutex.Lock()
unread := c.highestReceived - c.bytesRead
c.bytesRead = c.highestReceived
c.mutex.Unlock()
if unread > 0 {
c.connection.AddBytesRead(unread)
}
}
func (c *streamFlowController) UpdateSendWindow(offset protocol.ByteCount) (updated bool) {
if offset > c.sendWindow {
c.sendWindow = offset
return true
}
return false
}
func (c *streamFlowController) TryAddBytesSent(n protocol.ByteCount) bool {
if c.bytesSent > c.sendWindow || n > c.sendWindow-c.bytesSent {
return false
}
if !c.connection.TryAddBytesSent(n) {
return false
}
c.bytesSent += n
return true
}
func (c *streamFlowController) SendWindowSize() protocol.ByteCount {
return min(c.sendWindow-c.bytesSent, c.connection.SendWindowSize())
}
func (c *streamFlowController) IsNewlyBlocked() bool {
blocked, _ := c.isNewlyBlocked()
return blocked
}
func (c *streamFlowController) isNewlyBlocked() (bool, protocol.ByteCount) {
if c.bytesSent < c.sendWindow || c.sendWindow == c.lastBlockedAt {
return false, 0
}
c.lastBlockedAt = c.sendWindow
return true, c.sendWindow
}
func (c *streamFlowController) shouldQueueWindowUpdate() bool {
return !c.receivedFinalOffset && c.hasWindowUpdate()
}
func (c *streamFlowController) GetWindowUpdate(now monotime.Time) protocol.ByteCount {
// If we already received the final offset for this stream, the peer won't need any additional flow control credit.
if c.receivedFinalOffset {
return 0
}
c.mutex.Lock()
defer c.mutex.Unlock()
oldWindowSize := c.receiveWindowSize
offset := c.getWindowUpdate(now)
if c.receiveWindowSize > oldWindowSize { // auto-tuning enlarged the window size
c.logger.Debugf("Increasing receive flow control window for stream %d to %d", c.streamID, c.receiveWindowSize)
c.connection.EnsureMinimumWindowSize(protocol.ByteCount(float64(c.receiveWindowSize)*protocol.ConnectionFlowControlMultiplier), now)
}
return offset
}
package quic
import (
"errors"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
list "github.com/quic-go/quic-go/internal/utils/linkedlist"
)
// byteInterval is an interval from one ByteCount to the other
type byteInterval struct {
Start protocol.ByteCount
End protocol.ByteCount
}
var byteIntervalElementPool sync.Pool
func init() {
byteIntervalElementPool = *list.NewPool[byteInterval]()
}
type frameSorterEntry struct {
Data []byte
DoneCb func()
}
type frameSorter struct {
queue map[protocol.ByteCount]frameSorterEntry
readPos protocol.ByteCount
gaps *list.List[byteInterval]
}
var errDuplicateStreamData = errors.New("duplicate stream data")
func newFrameSorter() *frameSorter {
s := frameSorter{
gaps: list.NewWithPool[byteInterval](&byteIntervalElementPool),
queue: make(map[protocol.ByteCount]frameSorterEntry),
}
s.gaps.PushFront(byteInterval{Start: 0, End: protocol.MaxByteCount})
return &s
}
func (s *frameSorter) Push(data []byte, offset protocol.ByteCount, doneCb func()) error {
err := s.push(data, offset, doneCb)
if err == errDuplicateStreamData {
if doneCb != nil {
doneCb()
}
return nil
}
return err
}
func (s *frameSorter) push(data []byte, offset protocol.ByteCount, doneCb func()) error {
if len(data) == 0 {
return errDuplicateStreamData
}
start := offset
end := offset + protocol.ByteCount(len(data))
if end <= s.gaps.Front().Value.Start {
return errDuplicateStreamData
}
startGap, startsInGap := s.findStartGap(start)
endGap, endsInGap := s.findEndGap(startGap, end)
startGapEqualsEndGap := startGap == endGap
if (startGapEqualsEndGap && end <= startGap.Value.Start) ||
(!startGapEqualsEndGap && startGap.Value.End >= endGap.Value.Start && end <= startGap.Value.Start) {
return errDuplicateStreamData
}
startGapNext := startGap.Next()
startGapEnd := startGap.Value.End // save it, in case startGap is modified
endGapStart := endGap.Value.Start // save it, in case endGap is modified
endGapEnd := endGap.Value.End // save it, in case endGap is modified
var adjustedStartGapEnd bool
var wasCut bool
pos := start
var hasReplacedAtLeastOne bool
for {
oldEntry, ok := s.queue[pos]
if !ok {
break
}
oldEntryLen := protocol.ByteCount(len(oldEntry.Data))
if end-pos > oldEntryLen || (hasReplacedAtLeastOne && end-pos == oldEntryLen) {
// The existing frame is shorter than the new frame. Replace it.
delete(s.queue, pos)
pos += oldEntryLen
hasReplacedAtLeastOne = true
if oldEntry.DoneCb != nil {
oldEntry.DoneCb()
}
} else {
if !hasReplacedAtLeastOne {
return errDuplicateStreamData
}
// The existing frame is longer than the new frame.
// Cut the new frame such that the end aligns with the start of the existing frame.
data = data[:pos-start]
end = pos
wasCut = true
break
}
}
if !startsInGap && !hasReplacedAtLeastOne {
// cut the frame, such that it starts at the start of the gap
data = data[startGap.Value.Start-start:]
start = startGap.Value.Start
wasCut = true
}
if start <= startGap.Value.Start {
if end >= startGap.Value.End {
// The frame covers the whole startGap. Delete the gap.
s.gaps.Remove(startGap)
} else {
startGap.Value.Start = end
}
} else if !hasReplacedAtLeastOne {
startGap.Value.End = start
adjustedStartGapEnd = true
}
if !startGapEqualsEndGap {
s.deleteConsecutive(startGapEnd)
var nextGap *list.Element[byteInterval]
for gap := startGapNext; gap.Value.End < endGapStart; gap = nextGap {
nextGap = gap.Next()
s.deleteConsecutive(gap.Value.End)
s.gaps.Remove(gap)
}
}
if !endsInGap && start != endGapEnd && end > endGapEnd {
// cut the frame, such that it ends at the end of the gap
data = data[:endGapEnd-start]
end = endGapEnd
wasCut = true
}
if end == endGapEnd {
if !startGapEqualsEndGap {
// The frame covers the whole endGap. Delete the gap.
s.gaps.Remove(endGap)
}
} else {
if startGapEqualsEndGap && adjustedStartGapEnd {
// The frame split the existing gap into two.
s.gaps.InsertAfter(byteInterval{Start: end, End: startGapEnd}, startGap)
} else if !startGapEqualsEndGap {
endGap.Value.Start = end
}
}
if wasCut && len(data) < protocol.MinStreamFrameBufferSize {
newData := make([]byte, len(data))
copy(newData, data)
data = newData
if doneCb != nil {
doneCb()
doneCb = nil
}
}
if s.gaps.Len() > protocol.MaxStreamFrameSorterGaps {
return errors.New("too many gaps in received data")
}
s.queue[start] = frameSorterEntry{Data: data, DoneCb: doneCb}
return nil
}
func (s *frameSorter) findStartGap(offset protocol.ByteCount) (*list.Element[byteInterval], bool) {
for gap := s.gaps.Front(); gap != nil; gap = gap.Next() {
if offset >= gap.Value.Start && offset <= gap.Value.End {
return gap, true
}
if offset < gap.Value.Start {
return gap, false
}
}
panic("no gap found")
}
func (s *frameSorter) findEndGap(startGap *list.Element[byteInterval], offset protocol.ByteCount) (*list.Element[byteInterval], bool) {
for gap := startGap; gap != nil; gap = gap.Next() {
if offset >= gap.Value.Start && offset < gap.Value.End {
return gap, true
}
if offset < gap.Value.Start {
return gap.Prev(), false
}
}
panic("no gap found")
}
// deleteConsecutive deletes consecutive frames from the queue, starting at pos
func (s *frameSorter) deleteConsecutive(pos protocol.ByteCount) {
for {
oldEntry, ok := s.queue[pos]
if !ok {
break
}
oldEntryLen := protocol.ByteCount(len(oldEntry.Data))
delete(s.queue, pos)
if oldEntry.DoneCb != nil {
oldEntry.DoneCb()
}
pos += oldEntryLen
}
}
func (s *frameSorter) Pop() (protocol.ByteCount, []byte, func()) {
entry, ok := s.queue[s.readPos]
if !ok {
return s.readPos, nil, nil
}
delete(s.queue, s.readPos)
offset := s.readPos
s.readPos += protocol.ByteCount(len(entry.Data))
if s.gaps.Front().Value.End <= s.readPos {
panic("frame sorter BUG: read position higher than a gap")
}
return offset, entry.Data, entry.DoneCb
}
// HasMoreData says if there is any more data queued at *any* offset.
func (s *frameSorter) HasMoreData() bool {
return len(s.queue) > 0
}
var errTooLittleData = errors.New("too little data")
// Peek copies len(p) consecutive bytes starting at offset into p, without removing them.
// It is only possible to peek from an offset where a frame starts.
//
// If there isn't enough consecutive data available, errTooLittleData is returned.
func (s *frameSorter) Peek(offset protocol.ByteCount, p []byte) error {
if len(p) == 0 {
return nil
}
// first, check if we have enough consecutive data available
pos := offset
remaining := len(p)
for remaining > 0 {
entry, ok := s.queue[pos]
if !ok {
return errTooLittleData
}
entryLen := len(entry.Data)
if remaining <= entryLen {
break // enough data available
}
remaining -= entryLen
pos += protocol.ByteCount(entryLen)
}
pos = offset
var copied int
for copied < len(p) {
entry := s.queue[pos] // the entry is guaranteed to exist from the check above
copied += copy(p[copied:], entry.Data)
pos += protocol.ByteCount(len(entry.Data))
}
return nil
}
package quic
import (
"slices"
"sync"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils/ringbuffer"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/quicvarint"
)
const (
maxPathResponses = 256
maxControlFrames = 16 << 10
)
// This is the largest possible size of a stream-related control frame
// (which is the RESET_STREAM frame).
const maxStreamControlFrameSize = 25
type streamFrameGetter interface {
popStreamFrame(protocol.ByteCount, protocol.Version) (ackhandler.StreamFrame, *wire.StreamDataBlockedFrame, bool)
}
type streamControlFrameGetter interface {
getControlFrame(monotime.Time) (_ ackhandler.Frame, ok, hasMore bool)
}
type framer struct {
mutex sync.Mutex
activeStreams map[protocol.StreamID]streamFrameGetter
streamQueue ringbuffer.RingBuffer[protocol.StreamID]
streamsWithControlFrames map[protocol.StreamID]streamControlFrameGetter
controlFrameMutex sync.Mutex
controlFrames []wire.Frame
pathResponses []*wire.PathResponseFrame
connFlowController *connectionFlowController
queuedTooManyControlFrames bool
}
func newFramer(connFlowController *connectionFlowController) *framer {
return &framer{
activeStreams: make(map[protocol.StreamID]streamFrameGetter),
streamsWithControlFrames: make(map[protocol.StreamID]streamControlFrameGetter),
connFlowController: connFlowController,
}
}
func (f *framer) HasData() bool {
f.mutex.Lock()
hasData := !f.streamQueue.Empty()
f.mutex.Unlock()
if hasData {
return true
}
f.controlFrameMutex.Lock()
defer f.controlFrameMutex.Unlock()
return len(f.streamsWithControlFrames) > 0 || len(f.controlFrames) > 0 || len(f.pathResponses) > 0
}
func (f *framer) 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)
}
func (f *framer) Append(
frames []ackhandler.Frame,
streamFrames []ackhandler.StreamFrame,
maxLen protocol.ByteCount,
now monotime.Time,
v protocol.Version,
) ([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount) {
f.controlFrameMutex.Lock()
frames, controlFrameLen := f.appendControlFrames(frames, maxLen, now, v)
maxLen -= controlFrameLen
var lastFrame ackhandler.StreamFrame
var streamFrameLen protocol.ByteCount
f.mutex.Lock()
// pop STREAM frames, until less than 128 bytes are left in the packet
numActiveStreams := f.streamQueue.Len()
for range numActiveStreams {
if protocol.MinStreamFrameSize > maxLen {
break
}
sf, blocked := f.getNextStreamFrame(maxLen, v)
if sf.Frame != nil {
streamFrames = append(streamFrames, sf)
maxLen -= sf.Frame.Length(v)
lastFrame = sf
streamFrameLen += sf.Frame.Length(v)
}
// If the stream just became blocked on stream flow control, attempt to pack the
// STREAM_DATA_BLOCKED into the same packet.
if blocked != nil {
l := blocked.Length(v)
// In case it doesn't fit, queue it for the next packet.
if maxLen < l {
f.controlFrames = append(f.controlFrames, blocked)
break
}
frames = append(frames, ackhandler.Frame{Frame: blocked})
maxLen -= l
controlFrameLen += l
}
}
// The only way to become blocked on connection-level flow control is by sending STREAM frames.
if isBlocked, offset := f.connFlowController.IsNewlyBlocked(); isBlocked {
blocked := &wire.DataBlockedFrame{MaximumData: offset}
l := blocked.Length(v)
// In case it doesn't fit, queue it for the next packet.
if maxLen >= l {
frames = append(frames, ackhandler.Frame{Frame: blocked})
controlFrameLen += l
} else {
f.controlFrames = append(f.controlFrames, blocked)
}
}
f.mutex.Unlock()
f.controlFrameMutex.Unlock()
if lastFrame.Frame != nil {
// account for the smaller size of the last STREAM frame
streamFrameLen -= lastFrame.Frame.Length(v)
lastFrame.Frame.DataLenPresent = false
streamFrameLen += lastFrame.Frame.Length(v)
}
return frames, streamFrames, controlFrameLen + streamFrameLen
}
func (f *framer) appendControlFrames(
frames []ackhandler.Frame,
maxLen protocol.ByteCount,
now monotime.Time,
v protocol.Version,
) ([]ackhandler.Frame, protocol.ByteCount) {
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:]
}
}
// add stream-related control frames
for id, str := range f.streamsWithControlFrames {
start:
remainingLen := maxLen - length
if remainingLen <= maxStreamControlFrameSize {
break
}
fr, ok, hasMore := str.getControlFrame(now)
if !hasMore {
delete(f.streamsWithControlFrames, id)
}
if !ok {
continue
}
frames = append(frames, fr)
length += fr.Frame.Length(v)
if hasMore {
// It is rare that a stream has more than one control frame to queue.
// We don't want to spawn another loop for just to cover that case.
goto start
}
}
for len(f.controlFrames) > 0 {
frame := f.controlFrames[len(f.controlFrames)-1]
frameLen := frame.Length(v)
if length+frameLen > maxLen {
break
}
frames = append(frames, ackhandler.Frame{Frame: frame})
length += frameLen
f.controlFrames = f.controlFrames[:len(f.controlFrames)-1]
}
return frames, length
}
// 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.
func (f *framer) QueuedTooManyControlFrames() bool {
return f.queuedTooManyControlFrames
}
func (f *framer) AddActiveStream(id protocol.StreamID, str streamFrameGetter) {
f.mutex.Lock()
if _, ok := f.activeStreams[id]; !ok {
f.streamQueue.PushBack(id)
f.activeStreams[id] = str
}
f.mutex.Unlock()
}
func (f *framer) AddStreamWithControlFrames(id protocol.StreamID, str streamControlFrameGetter) {
f.controlFrameMutex.Lock()
if _, ok := f.streamsWithControlFrames[id]; !ok {
f.streamsWithControlFrames[id] = str
}
f.controlFrameMutex.Unlock()
}
// RemoveActiveStream is called when a stream completes.
func (f *framer) RemoveActiveStream(id protocol.StreamID) {
f.mutex.Lock()
delete(f.activeStreams, id)
// We don't delete the stream from the streamQueue,
// since we'd have to iterate over the ringbuffer.
// Instead, we check if the stream is still in activeStreams when appending STREAM frames.
f.mutex.Unlock()
}
func (f *framer) getNextStreamFrame(maxLen protocol.ByteCount, v protocol.Version) (ackhandler.StreamFrame, *wire.StreamDataBlockedFrame) {
id := f.streamQueue.PopFront()
// This should never return an error. Better check it anyway.
// The stream will only be in the streamQueue, if it enqueued itself there.
str, ok := f.activeStreams[id]
// The stream might have been removed after being enqueued.
if !ok {
return ackhandler.StreamFrame{}, nil
}
// For the last STREAM frame, we'll remove the DataLen field later.
// Therefore, we can pretend to have more bytes available when popping
// the STREAM frame (which will always have the DataLen set).
maxLen += protocol.ByteCount(quicvarint.Len(uint64(maxLen)))
frame, blocked, hasMoreData := str.popStreamFrame(maxLen, v)
if hasMoreData { // put the stream back in the queue (at the end)
f.streamQueue.PushBack(id)
} else { // no more data to send. Stream is not active
delete(f.activeStreams, id)
}
// Note that the frame.Frame can be nil:
// * if the stream was canceled after it said it had data
// * the remaining size doesn't allow us to add another STREAM frame
return frame, blocked
}
func (f *framer) Handle0RTTRejection() {
f.mutex.Lock()
defer f.mutex.Unlock()
f.controlFrameMutex.Lock()
defer f.controlFrameMutex.Unlock()
f.streamQueue.Clear()
for id := range f.activeStreams {
delete(f.activeStreams, id)
}
clear(f.streamsWithControlFrames)
var j int
for i, frame := range f.controlFrames {
switch frame.(type) {
case *wire.MaxDataFrame, *wire.MaxStreamDataFrame, *wire.MaxStreamsFrame,
*wire.DataBlockedFrame, *wire.StreamDataBlockedFrame, *wire.StreamsBlockedFrame:
continue
default:
f.controlFrames[j] = f.controlFrames[i]
j++
}
}
f.controlFrames = slices.Delete(f.controlFrames, j, len(f.controlFrames))
}
package http3
import (
"context"
"errors"
"io"
"sync"
"github.com/quic-go/quic-go"
)
// Settingser allows waiting for and retrieving the peer's HTTP/3 settings.
type Settingser interface {
// ReceivedSettings returns a channel that is closed once the peer's SETTINGS frame was received.
// Settings can be obtained from the Settings method after the channel was closed.
ReceivedSettings() <-chan struct{}
// Settings returns the settings received on this connection.
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
Settings() *Settings
}
var errTooMuchData = errors.New("peer sent too much data")
// The body is used in the requestBody (for a http.Request) and the responseBody (for a http.Response).
type body struct {
str *Stream
remainingContentLength int64
violatedContentLength bool
hasContentLength bool
}
func newBody(str *Stream, contentLength int64) *body {
b := &body{str: str}
if contentLength >= 0 {
b.hasContentLength = true
b.remainingContentLength = contentLength
}
return b
}
func (r *body) StreamID() quic.StreamID { return r.str.StreamID() }
func (r *body) checkContentLengthViolation() error {
if !r.hasContentLength {
return nil
}
if r.remainingContentLength < 0 || r.remainingContentLength == 0 && r.str.hasMoreData() {
if !r.violatedContentLength {
r.str.CancelRead(quic.StreamErrorCode(ErrCodeMessageError))
r.str.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError))
r.violatedContentLength = true
}
return errTooMuchData
}
return nil
}
func (r *body) Read(b []byte) (int, error) {
if err := r.checkContentLengthViolation(); err != nil {
return 0, err
}
if r.hasContentLength {
b = b[:min(int64(len(b)), r.remainingContentLength)]
}
n, err := r.str.Read(b)
r.remainingContentLength -= int64(n)
if err := r.checkContentLengthViolation(); err != nil {
return n, err
}
return n, maybeReplaceError(err)
}
func (r *body) Close() error {
r.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
return nil
}
type requestBody struct {
body
connCtx context.Context
rcvdSettings <-chan struct{}
getSettings func() *Settings
}
var _ io.ReadCloser = &requestBody{}
func newRequestBody(str *Stream, contentLength int64, connCtx context.Context, rcvdSettings <-chan struct{}, getSettings func() *Settings) *requestBody {
return &requestBody{
body: *newBody(str, contentLength),
connCtx: connCtx,
rcvdSettings: rcvdSettings,
getSettings: getSettings,
}
}
type hijackableBody struct {
body body
// only set for the http.Response
// The channel is closed when the user is done with this response:
// either when Read() errors, or when Close() is called.
reqDone chan<- struct{}
reqDoneOnce sync.Once
}
var _ io.ReadCloser = &hijackableBody{}
func newResponseBody(str *Stream, contentLength int64, done chan<- struct{}) *hijackableBody {
return &hijackableBody{
body: *newBody(str, contentLength),
reqDone: done,
}
}
func (r *hijackableBody) Read(b []byte) (int, error) {
n, err := r.body.Read(b)
if err != nil {
r.requestDone()
}
return n, maybeReplaceError(err)
}
func (r *hijackableBody) requestDone() {
if r.reqDone != nil {
r.reqDoneOnce.Do(func() {
close(r.reqDone)
})
}
}
func (r *hijackableBody) Close() error {
r.requestDone()
// If the EOF was read, CancelRead() is a no-op.
r.body.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
return nil
}
package http3
import (
"errors"
"io"
"github.com/quic-go/quic-go/quicvarint"
)
// CapsuleType is the type of the capsule
type CapsuleType uint64
// CapsuleProtocolHeader is the header value used to advertise support for the capsule protocol
const CapsuleProtocolHeader = "Capsule-Protocol"
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
// CapsuleParser parses a sequence of capsules.
// A capsule's contents must be fully consumed or discarded before calling Next again.
type CapsuleParser struct {
noCopy noCopy
r quicvarint.Reader
generation uint64
remaining uint64
}
// NewCapsuleParser creates a parser that reads capsules from r.
func NewCapsuleParser(r io.Reader) *CapsuleParser {
return &CapsuleParser{r: quicvarint.NewReader(r)}
}
var (
errReaderInvalid = errors.New("http3: capsule reader is no longer valid")
errCapsuleNotConsumed = errors.New("http3: previous capsule was not fully consumed")
)
// Next returns the type and contents of the next capsule.
// The previous capsule's contents must be fully consumed or discarded before calling Next.
func (p *CapsuleParser) Next() (CapsuleType, CapsuleReader, error) {
if p.remaining > 0 {
return 0, CapsuleReader{}, errCapsuleNotConsumed
}
r := &countingByteReader{Reader: p.r}
ct, err := quicvarint.Read(r)
if err != nil {
// If an io.EOF is returned without consuming any bytes, return it unmodified.
// Otherwise, return an io.ErrUnexpectedEOF.
if err == io.EOF && r.NumRead > 0 {
return 0, CapsuleReader{}, io.ErrUnexpectedEOF
}
return 0, CapsuleReader{}, err
}
r.Reset()
l, err := quicvarint.Read(r)
if err != nil {
if err == io.EOF {
return 0, CapsuleReader{}, io.ErrUnexpectedEOF
}
return 0, CapsuleReader{}, err
}
p.generation++
p.remaining = l
return CapsuleType(ct), CapsuleReader{parser: p, generation: p.generation}, nil
}
// CapsuleReader reads the contents of a capsule.
// It becomes invalid when the parser advances to the next capsule.
type CapsuleReader struct {
parser *CapsuleParser
generation uint64
}
var _ quicvarint.Reader = CapsuleReader{}
// valid reports whether the reader still refers to the parser's current capsule.
func (r CapsuleReader) valid() bool {
return r.parser != nil && r.generation == r.parser.generation
}
// Read reads from the capsule contents.
func (r CapsuleReader) Read(b []byte) (int, error) {
if !r.valid() {
return 0, errReaderInvalid
}
if r.parser.remaining == 0 {
return 0, io.EOF
}
if uint64(len(b)) > r.parser.remaining {
b = b[:r.parser.remaining]
}
n, err := r.parser.r.Read(b)
r.parser.remaining -= uint64(n)
if err == io.EOF && r.parser.remaining > 0 {
return n, io.ErrUnexpectedEOF
}
return n, err
}
// ReadByte reads one byte from the capsule contents.
func (r CapsuleReader) ReadByte() (byte, error) {
if !r.valid() {
return 0, errReaderInvalid
}
if r.parser.remaining == 0 {
return 0, io.EOF
}
b, err := r.parser.r.ReadByte()
if err == io.EOF {
return 0, io.ErrUnexpectedEOF
}
if err == nil {
r.parser.remaining--
}
return b, err
}
// Remaining returns the number of bytes remaining in the capsule.
func (r CapsuleReader) Remaining() int64 {
if !r.valid() {
return 0
}
return int64(r.parser.remaining)
}
// Discard consumes the remaining capsule contents.
func (r CapsuleReader) Discard() error {
_, err := io.Copy(io.Discard, r)
return err
}
// WriteCapsule writes a capsule
func WriteCapsule(w quicvarint.Writer, ct CapsuleType, value []byte) error {
b := make([]byte, 0, 16)
b = quicvarint.Append(b, uint64(ct))
b = quicvarint.Append(b, uint64(len(value)))
if _, err := w.Write(b); err != nil {
return err
}
_, err := w.Write(value)
return err
}
package http3
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptrace"
"net/textproto"
"sync"
"time"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
const (
// MethodGet0RTT allows a GET request to be sent using 0-RTT.
// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
MethodGet0RTT = "GET_0RTT"
// MethodHead0RTT allows a HEAD request to be sent using 0-RTT.
// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
MethodHead0RTT = "HEAD_0RTT"
)
const (
defaultUserAgent = "quic-go HTTP/3"
defaultMaxResponseHeaderBytes = 10 * 1 << 20 // 10 MB
)
var errGoAway = errors.New("connection in graceful shutdown")
type errConnUnusable struct{ e error }
func (e *errConnUnusable) Unwrap() error { return e.e }
func (e *errConnUnusable) Error() string { return fmt.Sprintf("http3: conn unusable: %s", e.e.Error()) }
const max1xxResponses = 5 // arbitrary bound on number of informational responses
var defaultQuicConfig = &quic.Config{
MaxIncomingStreams: -1, // don't allow the server to create bidirectional streams
KeepAlivePeriod: 10 * time.Second,
}
// ClientConn is an HTTP/3 client doing requests to a single remote server.
type ClientConn struct {
conn *quic.Conn
rawConn *rawConn
decoder *qpack.Decoder
// Additional HTTP/3 settings.
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
additionalSettings map[uint64]uint64
// maxResponseHeaderBytes specifies a limit on how many response bytes are
// allowed in the server's response header.
maxResponseHeaderBytes int
// disableCompression, if true, prevents the Transport from requesting compression with an
// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
// If the Transport requests gzip on its own and gets a gzipped response, it's transparently
// decoded in the Response.Body.
// However, if the user explicitly requested gzip it is not automatically uncompressed.
disableCompression bool
streamMx sync.Mutex
maxStreamID quic.StreamID // set once a GOAWAY frame is received
goAwayCtx context.Context
goAwayCancel context.CancelFunc
qlogger qlogwriter.Recorder
logger *slog.Logger
requestWriter *requestWriter
}
var _ http.RoundTripper = &ClientConn{}
func newClientConn(
conn *quic.Conn,
enableDatagrams bool,
additionalSettings map[uint64]uint64,
maxResponseHeaderBytes int,
disableCompression bool,
logger *slog.Logger,
) *ClientConn {
var qlogger qlogwriter.Recorder
if qlogTrace := conn.QlogTrace(); qlogTrace != nil && qlogTrace.SupportsSchemas(qlog.EventSchema) {
qlogger = qlogTrace.AddProducer()
}
c := &ClientConn{
conn: conn,
additionalSettings: additionalSettings,
disableCompression: disableCompression,
maxStreamID: invalidStreamID,
logger: logger,
qlogger: qlogger,
decoder: qpack.NewDecoder(),
}
c.goAwayCtx, c.goAwayCancel = context.WithCancel(context.Background())
if maxResponseHeaderBytes <= 0 {
c.maxResponseHeaderBytes = defaultMaxResponseHeaderBytes
} else {
c.maxResponseHeaderBytes = maxResponseHeaderBytes
}
c.requestWriter = newRequestWriter()
c.rawConn = newRawConn(
conn,
enableDatagrams,
c.onStreamsEmpty,
c.handleControlStream,
qlogger,
c.logger,
)
// send the SETTINGs frame, using 0-RTT data, if possible
go func() {
_, err := c.rawConn.openControlStream(&settingsFrame{
Datagram: enableDatagrams,
Other: additionalSettings,
MaxFieldSectionSize: int64(c.maxResponseHeaderBytes),
})
if err != nil {
if c.logger != nil {
c.logger.Debug("setting up connection failed", "error", err)
}
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
return
}
}()
return c
}
// OpenRequestStream opens a new request stream on the HTTP/3 connection.
func (c *ClientConn) OpenRequestStream(ctx context.Context) (*RequestStream, error) {
return c.openRequestStream(ctx, c.requestWriter, nil, c.disableCompression, c.maxResponseHeaderBytes)
}
func (c *ClientConn) openRequestStream(
ctx context.Context,
requestWriter *requestWriter,
reqDone chan<- struct{},
disableCompression bool,
maxHeaderBytes int,
) (*RequestStream, error) {
// RFC 9114 Section 5.2 prohibits opening any new request streams after GOAWAY.
// The stream ID only identifies requests that were already in flight and might still be processed.
if c.goAwayCtx.Err() != nil {
return nil, errGoAway
}
openCtx, cancel := context.WithCancelCause(ctx)
// A request blocked in OpenStreamSync has no request stream yet, so it is not in flight.
stop := context.AfterFunc(c.goAwayCtx, func() { cancel(errGoAway) })
str, err := c.conn.OpenStreamSync(openCtx)
stop()
cancel(nil)
if err != nil {
if context.Cause(openCtx) == errGoAway {
return nil, errGoAway
}
return nil, err
}
// Check again in case GOAWAY raced with OpenStreamSync.
if c.goAwayCtx.Err() != nil {
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
return nil, errGoAway
}
hstr := c.rawConn.TrackStream(str)
rsp := &http.Response{}
trace := httptrace.ContextClientTrace(ctx)
return newRequestStream(
newStream(hstr, c.rawConn, trace, func(r io.Reader, hf *headersFrame) error {
hdr, err := decodeTrailers(r, hf, maxHeaderBytes, c.decoder, c.qlogger, str.StreamID())
if err != nil {
return err
}
rsp.Trailer = hdr
return nil
}, c.qlogger),
requestWriter,
reqDone,
c.decoder,
disableCompression,
maxHeaderBytes,
rsp,
), nil
}
func (c *ClientConn) handleUnidirectionalStream(str *quic.ReceiveStream) {
c.rawConn.handleUnidirectionalStream(str, false)
}
func (c *ClientConn) handleControlStream(str *quic.ReceiveStream, fp *frameParser) {
for {
f, err := fp.ParseNext(c.qlogger)
if err != nil {
var serr *quic.StreamError
if err == io.EOF || errors.As(err, &serr) {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeClosedCriticalStream), "")
return
}
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
return
}
// GOAWAY is the only frame allowed at this point:
// * unexpected frames are ignored by the frame parser
// * we don't support any extension that might add support for more frames
goaway, ok := f.(*goAwayFrame)
if !ok {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
return
}
if goaway.StreamID%4 != 0 { // client-initiated, bidirectional streams
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
return
}
c.streamMx.Lock()
// the server is not allowed to increase the Stream ID in subsequent GOAWAY frames
if c.maxStreamID != invalidStreamID && goaway.StreamID > c.maxStreamID {
c.streamMx.Unlock()
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
return
}
c.maxStreamID = goaway.StreamID
c.goAwayCancel()
c.streamMx.Unlock()
hasActiveStreams := c.rawConn.hasActiveStreams()
// immediately close the connection if there are currently no active requests
if !hasActiveStreams {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
return
}
}
}
func (c *ClientConn) onStreamsEmpty() {
c.streamMx.Lock()
defer c.streamMx.Unlock()
// The server is performing a graceful shutdown.
if c.maxStreamID != invalidStreamID {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
}
}
// RoundTrip executes a request and returns a response
func (c *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
rsp, err := c.roundTrip(req)
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 *ClientConn) roundTrip(req *http.Request) (*http.Response, error) {
// Immediately send out this request, if this is a 0-RTT request.
switch req.Method {
case MethodGet0RTT:
// don't modify the original request
reqCopy := *req
req = &reqCopy
req.Method = http.MethodGet
case MethodHead0RTT:
// don't modify the original request
reqCopy := *req
req = &reqCopy
req.Method = http.MethodHead
default:
// wait for the handshake to complete
select {
case <-c.conn.HandshakeComplete():
case <-req.Context().Done():
return nil, req.Context().Err()
}
}
// It is only possible to send an Extended CONNECT request once the SETTINGS were received.
// See section 3 of RFC 8441.
if isExtendedConnectRequest(req) {
connCtx := c.conn.Context()
// wait for the server's SETTINGS frame to arrive
select {
case <-c.rawConn.ReceivedSettings():
case <-connCtx.Done():
return nil, context.Cause(connCtx)
}
if !c.rawConn.Settings().EnableExtendedConnect {
return nil, errors.New("http3: server didn't enable Extended CONNECT")
}
}
reqDone := make(chan struct{})
str, err := c.openRequestStream(
req.Context(),
c.requestWriter,
reqDone,
c.disableCompression,
c.maxResponseHeaderBytes,
)
if err != nil {
return nil, &errConnUnusable{e: err}
}
// Request Cancellation:
// This go routine keeps running even after RoundTripOpt() returns.
// It is shut down when the application is done processing the body.
done := make(chan struct{})
go func() {
defer close(done)
select {
case <-req.Context().Done():
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
case <-reqDone:
}
}()
rsp, err := c.doRequest(req, str)
if err != nil { // if any error occurred
close(reqDone)
<-done
return nil, maybeReplaceError(err)
}
return rsp, maybeReplaceError(err)
}
// ReceivedSettings returns a channel that is closed once the server's HTTP/3 settings were received.
// Settings can be obtained from the Settings method after the channel was closed.
func (c *ClientConn) ReceivedSettings() <-chan struct{} {
return c.rawConn.ReceivedSettings()
}
// Settings returns the HTTP/3 settings for this connection.
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
func (c *ClientConn) Settings() *Settings {
return c.rawConn.Settings()
}
// CloseWithError closes the connection with the given error code and message.
// It is invalid to call this function after the connection was closed.
func (c *ClientConn) CloseWithError(code quic.ApplicationErrorCode, msg string) error {
return c.conn.CloseWithError(code, msg)
}
// Context returns a context that is cancelled when the connection is closed.
func (c *ClientConn) Context() context.Context {
return c.conn.Context()
}
// cancelingReader reads from the io.Reader.
// It cancels writing on the stream if any error other than io.EOF occurs.
type cancelingReader struct {
r io.Reader
str *RequestStream
}
func (r *cancelingReader) Read(b []byte) (int, error) {
n, err := r.r.Read(b)
if err != nil && err != io.EOF {
r.str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
}
return n, err
}
func (c *ClientConn) sendRequestBody(str *RequestStream, body io.ReadCloser, contentLength int64) error {
defer body.Close()
buf := make([]byte, bodyCopyBufferSize)
sr := &cancelingReader{str: str, r: body}
if contentLength == -1 {
_, err := io.CopyBuffer(str, sr, buf)
return err
}
// make sure we don't send more bytes than the content length
n, err := io.CopyBuffer(str, io.LimitReader(sr, contentLength), buf)
if err != nil {
return err
}
var extra int64
extra, err = io.CopyBuffer(io.Discard, sr, buf)
n += extra
if n > contentLength {
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
return fmt.Errorf("http: ContentLength=%d with Body length %d", contentLength, n)
}
return err
}
func (c *ClientConn) doRequest(req *http.Request, str *RequestStream) (*http.Response, error) {
trace := httptrace.ContextClientTrace(req.Context())
var sendingReqFailed bool
if err := str.sendRequestHeader(req); err != nil {
traceWroteRequest(trace, err)
if c.logger != nil {
c.logger.Debug("error writing request", "error", err)
}
sendingReqFailed = true
}
if !sendingReqFailed {
if req.Body == nil {
traceWroteRequest(trace, nil)
str.Close()
} else {
// send the request body asynchronously
go func() {
defer str.Close()
contentLength := int64(-1)
// According to the documentation for http.Request.ContentLength,
// a value of 0 with a non-nil Body is also treated as unknown content length.
if req.ContentLength > 0 {
contentLength = req.ContentLength
}
err := c.sendRequestBody(str, req.Body, contentLength)
traceWroteRequest(trace, err)
if err != nil {
if c.logger != nil {
c.logger.Debug("error writing request", "error", err)
}
return
}
if len(req.Trailer) > 0 {
if err := str.sendRequestTrailer(req); err != nil {
if c.logger != nil {
c.logger.Debug("error writing trailers", "error", err)
}
}
}
}()
}
}
// copy from net/http: support 1xx responses
var num1xx int // number of informational 1xx headers received
var res *http.Response
for {
var err error
res, err = str.ReadResponse()
if err != nil {
return nil, err
}
resCode := res.StatusCode
is1xx := 100 <= resCode && resCode <= 199
// treat 101 as a terminal status, see https://github.com/golang/go/issues/26161
is1xxNonTerminal := is1xx && resCode != http.StatusSwitchingProtocols
if is1xxNonTerminal {
num1xx++
if num1xx > max1xxResponses {
str.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad))
str.CancelWrite(quic.StreamErrorCode(ErrCodeExcessiveLoad))
return nil, errors.New("http3: too many 1xx informational responses")
}
traceGot1xxResponse(trace, resCode, textproto.MIMEHeader(res.Header))
if resCode == http.StatusContinue {
traceGot100Continue(trace)
}
continue
}
break
}
connState := c.conn.ConnectionState().TLS
res.TLS = &connState
res.Request = req
return res, nil
}
// RawClientConn is a low-level HTTP/3 client connection.
// It allows the application to take control of the stream accept loops,
// giving the application the ability to handle streams originating from the server.
type RawClientConn struct {
*ClientConn
}
// HandleUnidirectionalStream handles an incoming unidirectional stream.
func (c *RawClientConn) HandleUnidirectionalStream(str *quic.ReceiveStream) {
c.rawConn.handleUnidirectionalStream(str, false)
}
// HandleBidirectionalStream handles an incoming bidirectional stream.
func (c *ClientConn) HandleBidirectionalStream(str *quic.Stream) {
// According to RFC 9114, the server is not allowed to open bidirectional streams.
c.rawConn.CloseWithError(
quic.ApplicationErrorCode(ErrCodeStreamCreationError),
fmt.Sprintf("server opened bidirectional stream %d", str.StreamID()),
)
}
package http3
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"maps"
"net"
"sync"
"sync/atomic"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/quic-go/quicvarint"
)
const maxQuarterStreamID = 1<<60 - 1
// invalidStreamID is a stream ID that is invalid. The first valid stream ID in QUIC is 0.
const invalidStreamID = quic.StreamID(-1)
// rawConn is an HTTP/3 connection.
// It provides HTTP/3 specific functionality by wrapping a quic.Conn,
// in particular handling of unidirectional HTTP/3 streams, SETTINGS and datagrams.
type rawConn struct {
conn *quic.Conn
logger *slog.Logger
enableDatagrams bool
streamMx sync.Mutex
streams map[quic.StreamID]*stateTrackingStream
rcvdControlStr atomic.Bool
rcvdQPACKEncoderStr atomic.Bool
rcvdQPACKDecoderStr atomic.Bool
controlStrHandler func(*quic.ReceiveStream, *frameParser) // is called *after* the SETTINGS frame was parsed
onStreamsEmpty func()
settings *Settings
receivedSettings chan struct{}
qlogger qlogwriter.Recorder
qloggerWG sync.WaitGroup // tracks goroutines that may produce qlog events
}
func newRawConn(
quicConn *quic.Conn,
enableDatagrams bool,
onStreamsEmpty func(),
controlStrHandler func(*quic.ReceiveStream, *frameParser),
qlogger qlogwriter.Recorder,
logger *slog.Logger,
) *rawConn {
c := &rawConn{
conn: quicConn,
logger: logger,
enableDatagrams: enableDatagrams,
receivedSettings: make(chan struct{}),
streams: make(map[quic.StreamID]*stateTrackingStream),
qlogger: qlogger,
onStreamsEmpty: onStreamsEmpty,
controlStrHandler: controlStrHandler,
}
if qlogger != nil {
context.AfterFunc(quicConn.Context(), c.closeQlogger)
}
return c
}
func (c *rawConn) OpenUniStream() (*quic.SendStream, error) {
return c.conn.OpenUniStream()
}
// openControlStream opens the control stream and sends the SETTINGS frame.
// It returns the control stream (needed by the server for sending GOAWAY later).
func (c *rawConn) openControlStream(settings *settingsFrame) (*quic.SendStream, error) {
c.qloggerWG.Add(1)
defer c.qloggerWG.Done()
str, err := c.conn.OpenUniStream()
if err != nil {
return nil, err
}
b := make([]byte, 0, 64)
b = quicvarint.Append(b, streamTypeControlStream)
b = settings.Append(b)
if c.qlogger != nil {
sf := qlog.SettingsFrame{
MaxFieldSectionSize: settings.MaxFieldSectionSize,
Other: maps.Clone(settings.Other),
}
if settings.Datagram {
sf.Datagram = pointer(true)
}
if settings.ExtendedConnect {
sf.ExtendedConnect = pointer(true)
}
c.qlogger.RecordEvent(qlog.FrameCreated{
StreamID: str.StreamID(),
Raw: qlog.RawInfo{Length: len(b)},
Frame: qlog.Frame{Frame: sf},
})
}
if _, err := str.Write(b); err != nil {
return nil, err
}
return str, nil
}
func (c *rawConn) TrackStream(str *quic.Stream) *stateTrackingStream {
hstr := newStateTrackingStream(str, c, func(b []byte) error { return c.sendDatagram(str.StreamID(), b) })
c.streamMx.Lock()
c.streams[str.StreamID()] = hstr
c.qloggerWG.Add(1)
c.streamMx.Unlock()
return hstr
}
func (c *rawConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *rawConn) ConnectionState() quic.ConnectionState {
return c.conn.ConnectionState()
}
func (c *rawConn) clearStream(id quic.StreamID) {
c.streamMx.Lock()
defer c.streamMx.Unlock()
if _, ok := c.streams[id]; ok {
delete(c.streams, id)
c.qloggerWG.Done()
}
if len(c.streams) == 0 {
c.onStreamsEmpty()
}
}
func (c *rawConn) hasActiveStreams() bool {
c.streamMx.Lock()
defer c.streamMx.Unlock()
return len(c.streams) > 0
}
func (c *rawConn) CloseWithError(code quic.ApplicationErrorCode, msg string) error {
return c.conn.CloseWithError(code, msg)
}
func (c *rawConn) handleUnidirectionalStream(str *quic.ReceiveStream, isServer bool) {
c.qloggerWG.Add(1)
defer c.qloggerWG.Done()
streamType, err := quicvarint.Read(quicvarint.NewReader(str))
if err != nil {
if c.logger != nil {
c.logger.Debug("reading stream type on stream failed", "stream ID", str.StreamID(), "error", err)
}
return
}
// We're only interested in the control stream here.
switch streamType {
case streamTypeControlStream:
case streamTypeQPACKEncoderStream:
if isFirst := c.rcvdQPACKEncoderStr.CompareAndSwap(false, true); !isFirst {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK encoder stream")
}
// Our QPACK implementation doesn't use the dynamic table yet.
return
case streamTypeQPACKDecoderStream:
if isFirst := c.rcvdQPACKDecoderStr.CompareAndSwap(false, true); !isFirst {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK decoder stream")
}
// Our QPACK implementation doesn't use the dynamic table yet.
return
case streamTypePushStream:
if isServer {
// only the server can push
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "")
} else {
// we never increased the Push ID, so we don't expect any push streams
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
}
return
default:
str.CancelRead(quic.StreamErrorCode(ErrCodeStreamCreationError))
return
}
// Only a single control stream is allowed.
if isFirstControlStr := c.rcvdControlStr.CompareAndSwap(false, true); !isFirstControlStr {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
return
}
c.handleControlStream(str)
}
func (c *rawConn) handleControlStream(str *quic.ReceiveStream) {
fp := &frameParser{closeConn: c.conn.CloseWithError, r: str, streamID: str.StreamID()}
f, err := fp.ParseNext(c.qlogger)
if err != nil {
var serr *quic.StreamError
if err == io.EOF || errors.As(err, &serr) {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeClosedCriticalStream), "")
return
}
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
return
}
sf, ok := f.(*settingsFrame)
if !ok {
c.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeMissingSettings), "")
return
}
c.settings = &Settings{
EnableDatagrams: sf.Datagram,
EnableExtendedConnect: sf.ExtendedConnect,
Other: sf.Other,
}
close(c.receivedSettings)
if sf.Datagram {
// If datagram support was enabled on our side as well as on the server side,
// we can expect it to have been negotiated both on the transport and on the HTTP/3 layer.
// Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT).
if c.enableDatagrams && !c.ConnectionState().SupportsDatagrams.Remote {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeSettingsError), "missing QUIC Datagram support")
return
}
c.qloggerWG.Go(func() {
if err := c.receiveDatagrams(); err != nil {
if c.logger != nil {
c.logger.Debug("receiving datagrams failed", "error", err)
}
}
})
}
if c.controlStrHandler != nil {
c.controlStrHandler(str, fp)
}
}
func (c *rawConn) sendDatagram(streamID quic.StreamID, b []byte) error {
// TODO: this creates a lot of garbage and an additional copy
data := make([]byte, 0, len(b)+8)
quarterStreamID := uint64(streamID / 4)
data = quicvarint.Append(data, uint64(streamID/4))
data = append(data, b...)
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.DatagramCreated{
QuarterStreamID: quarterStreamID,
Raw: qlog.RawInfo{
Length: len(data),
PayloadLength: len(b),
},
})
}
return c.conn.SendDatagram(data)
}
func (c *rawConn) receiveDatagrams() error {
for {
b, err := c.conn.ReceiveDatagram(context.Background())
if err != nil {
return err
}
quarterStreamID, n, err := quicvarint.Parse(b)
if err != nil {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
return fmt.Errorf("could not read quarter stream id: %w", err)
}
if c.qlogger != nil {
c.qlogger.RecordEvent(qlog.DatagramParsed{
QuarterStreamID: quarterStreamID,
Raw: qlog.RawInfo{
Length: len(b),
PayloadLength: len(b) - n,
},
})
}
if quarterStreamID > maxQuarterStreamID {
c.CloseWithError(quic.ApplicationErrorCode(ErrCodeDatagramError), "")
return fmt.Errorf("invalid quarter stream id: %w", err)
}
streamID := quic.StreamID(4 * quarterStreamID)
c.streamMx.Lock()
dg, ok := c.streams[streamID]
c.streamMx.Unlock()
if !ok {
continue
}
dg.enqueueDatagram(b[n:])
}
}
// ReceivedSettings returns a channel that is closed once the peer's SETTINGS frame was received.
// Settings can be optained from the Settings method after the channel was closed.
func (c *rawConn) ReceivedSettings() <-chan struct{} { return c.receivedSettings }
// Settings returns the settings received on this connection.
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
func (c *rawConn) Settings() *Settings { return c.settings }
// closeQlogger waits for all goroutines that may produce qlog events to finish,
// then closes the qlogger.
func (c *rawConn) closeQlogger() {
if c.qlogger == nil {
return
}
c.qloggerWG.Wait()
c.qlogger.Close()
}
package http3
import (
"errors"
"fmt"
"github.com/quic-go/quic-go"
)
// Error is returned from the round tripper (for HTTP clients)
// and inside the HTTP handler (for HTTP servers) if an HTTP/3 error occurs.
// See section 8 of RFC 9114.
type Error struct {
Remote bool
ErrorCode ErrCode
ErrorMessage string
}
var _ error = &Error{}
func (e *Error) Error() string {
s := e.ErrorCode.string()
if s == "" {
s = fmt.Sprintf("H3 error (%#x)", uint64(e.ErrorCode))
}
// Usually errors are remote. Only make it explicit for local errors.
if !e.Remote {
s += " (local)"
}
if e.ErrorMessage != "" {
s += ": " + e.ErrorMessage
}
return s
}
func (e *Error) Is(target error) bool {
t, ok := target.(*Error)
return ok && e.ErrorCode == t.ErrorCode && e.Remote == t.Remote
}
func maybeReplaceError(err error) error {
if err == nil {
return nil
}
var (
e Error
strErr *quic.StreamError
appErr *quic.ApplicationError
)
switch {
default:
return err
case errors.As(err, &strErr):
e.Remote = strErr.Remote
e.ErrorCode = ErrCode(strErr.ErrorCode)
case errors.As(err, &appErr):
e.Remote = appErr.Remote
e.ErrorCode = ErrCode(appErr.ErrorCode)
e.ErrorMessage = appErr.ErrorMessage
}
return &e
}
package http3
import (
"fmt"
"github.com/quic-go/quic-go"
)
type ErrCode quic.ApplicationErrorCode
const (
ErrCodeNoError ErrCode = 0x100
ErrCodeGeneralProtocolError ErrCode = 0x101
ErrCodeInternalError ErrCode = 0x102
ErrCodeStreamCreationError ErrCode = 0x103
ErrCodeClosedCriticalStream ErrCode = 0x104
ErrCodeFrameUnexpected ErrCode = 0x105
ErrCodeFrameError ErrCode = 0x106
ErrCodeExcessiveLoad ErrCode = 0x107
ErrCodeIDError ErrCode = 0x108
ErrCodeSettingsError ErrCode = 0x109
ErrCodeMissingSettings ErrCode = 0x10a
ErrCodeRequestRejected ErrCode = 0x10b
ErrCodeRequestCanceled ErrCode = 0x10c
ErrCodeRequestIncomplete ErrCode = 0x10d
ErrCodeMessageError ErrCode = 0x10e
ErrCodeConnectError ErrCode = 0x10f
ErrCodeVersionFallback ErrCode = 0x110
ErrCodeDatagramError ErrCode = 0x33
ErrCodeQPACKDecompressionFailed ErrCode = 0x200
)
func (e ErrCode) String() string {
s := e.string()
if s != "" {
return s
}
return fmt.Sprintf("unknown error code: %#x", uint16(e))
}
func (e ErrCode) string() string {
switch e {
case ErrCodeNoError:
return "H3_NO_ERROR"
case ErrCodeGeneralProtocolError:
return "H3_GENERAL_PROTOCOL_ERROR"
case ErrCodeInternalError:
return "H3_INTERNAL_ERROR"
case ErrCodeStreamCreationError:
return "H3_STREAM_CREATION_ERROR"
case ErrCodeClosedCriticalStream:
return "H3_CLOSED_CRITICAL_STREAM"
case ErrCodeFrameUnexpected:
return "H3_FRAME_UNEXPECTED"
case ErrCodeFrameError:
return "H3_FRAME_ERROR"
case ErrCodeExcessiveLoad:
return "H3_EXCESSIVE_LOAD"
case ErrCodeIDError:
return "H3_ID_ERROR"
case ErrCodeSettingsError:
return "H3_SETTINGS_ERROR"
case ErrCodeMissingSettings:
return "H3_MISSING_SETTINGS"
case ErrCodeRequestRejected:
return "H3_REQUEST_REJECTED"
case ErrCodeRequestCanceled:
return "H3_REQUEST_CANCELLED"
case ErrCodeRequestIncomplete:
return "H3_INCOMPLETE_REQUEST"
case ErrCodeMessageError:
return "H3_MESSAGE_ERROR"
case ErrCodeConnectError:
return "H3_CONNECT_ERROR"
case ErrCodeVersionFallback:
return "H3_VERSION_FALLBACK"
case ErrCodeDatagramError:
return "H3_DATAGRAM_ERROR"
case ErrCodeQPACKDecompressionFailed:
return "QPACK_DECOMPRESSION_FAILED"
default:
return ""
}
}
package http3
import (
"bytes"
"errors"
"fmt"
"io"
"maps"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/quic-go/quicvarint"
)
// FrameType is the frame type of a HTTP/3 frame
type FrameType uint64
type frame any
// The maximum length of an encoded HTTP/3 frame header is 16:
// The frame has a type and length field, both QUIC varints (maximum 8 bytes in length)
const frameHeaderLen = 16
type countingByteReader struct {
quicvarint.Reader
NumRead int
}
func (r *countingByteReader) ReadByte() (byte, error) {
b, err := r.Reader.ReadByte()
if err == nil {
r.NumRead++
}
return b, err
}
func (r *countingByteReader) Read(b []byte) (int, error) {
n, err := r.Reader.Read(b)
r.NumRead += n
return n, err
}
func (r *countingByteReader) Reset() {
r.NumRead = 0
}
type frameParser struct {
r io.Reader
streamID quic.StreamID
closeConn func(quic.ApplicationErrorCode, string) error
}
func (p *frameParser) ParseNext(qlogger qlogwriter.Recorder) (frame, error) {
r := &countingByteReader{Reader: quicvarint.NewReader(p.r)}
for {
t, err := quicvarint.Read(r)
if err != nil {
return nil, err
}
l, err := quicvarint.Read(r)
if err != nil {
return nil, err
}
switch t {
case 0x0: // DATA
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{
Length: int(l) + r.NumRead,
PayloadLength: int(l),
},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
})
}
return &dataFrame{Length: l}, nil
case 0x1: // HEADERS
return &headersFrame{
Length: l,
headerLen: r.NumRead,
}, nil
case 0x4: // SETTINGS
return parseSettingsFrame(r, l, p.streamID, qlogger)
case 0x3: // unsupported: CANCEL_PUSH
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{Length: r.NumRead, PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.CancelPushFrame{}},
})
}
case 0x5: // unsupported: PUSH_PROMISE
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{Length: r.NumRead, PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.PushPromiseFrame{}},
})
}
case 0x7: // GOAWAY
return parseGoAwayFrame(r, l, p.streamID, qlogger)
case 0xd: // unsupported: MAX_PUSH_ID
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{Length: r.NumRead, PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.MaxPushIDFrame{}},
})
}
case 0x2, 0x6, 0x8, 0x9: // reserved frame types
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{Length: r.NumRead + int(l), PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.ReservedFrame{Type: t}},
})
}
p.closeConn(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
return nil, fmt.Errorf("http3: reserved frame type: %d", t)
default:
// unknown frame types
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: p.streamID,
Raw: qlog.RawInfo{Length: r.NumRead, PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.UnknownFrame{Type: t}},
})
}
}
// skip over the payload
if _, err := io.CopyN(io.Discard, r, int64(l)); err != nil {
return nil, err
}
r.Reset()
}
}
type dataFrame struct {
Length uint64
}
func (f *dataFrame) Append(b []byte) []byte {
b = quicvarint.Append(b, 0x0)
return quicvarint.Append(b, f.Length)
}
type headersFrame struct {
Length uint64
headerLen int // number of bytes read for type and length field
}
func (f *headersFrame) Append(b []byte) []byte {
b = quicvarint.Append(b, 0x1)
return quicvarint.Append(b, f.Length)
}
const (
// SETTINGS_MAX_FIELD_SECTION_SIZE
settingMaxFieldSectionSize = 0x6
// Extended CONNECT, RFC 9220
settingExtendedConnect = 0x8
// HTTP Datagrams, RFC 9297
settingDatagram = 0x33
)
type settingsFrame struct {
MaxFieldSectionSize int64 // SETTINGS_MAX_FIELD_SECTION_SIZE, -1 if not set
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 pointer[T any](v T) *T {
return &v
}
func parseSettingsFrame(r *countingByteReader, l uint64, streamID quic.StreamID, qlogger qlogwriter.Recorder) (*settingsFrame, error) {
if l > 8*(1<<10) {
return nil, fmt.Errorf("unexpected size for SETTINGS frame: %d", l)
}
buf := make([]byte, l)
if _, err := io.ReadFull(r, buf); err != nil {
if err == io.ErrUnexpectedEOF {
return nil, io.EOF
}
return nil, err
}
frame := &settingsFrame{MaxFieldSectionSize: -1}
b := bytes.NewReader(buf)
settingsFrame := qlog.SettingsFrame{MaxFieldSectionSize: -1}
var readMaxFieldSectionSize, readDatagram, readExtendedConnect bool
for b.Len() > 0 {
id, err := quicvarint.Read(b)
if err != nil { // should not happen. We allocated the whole frame already.
return nil, err
}
val, err := quicvarint.Read(b)
if err != nil { // should not happen. We allocated the whole frame already.
return nil, err
}
switch id {
case settingMaxFieldSectionSize:
if readMaxFieldSectionSize {
return nil, fmt.Errorf("duplicate setting: %d", id)
}
readMaxFieldSectionSize = true
frame.MaxFieldSectionSize = int64(val)
settingsFrame.MaxFieldSectionSize = int64(val)
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
if qlogger != nil {
settingsFrame.ExtendedConnect = pointer(frame.ExtendedConnect)
}
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 SETTINGS_H3_DATAGRAM: %d", val)
}
frame.Datagram = val == 1
if qlogger != nil {
settingsFrame.Datagram = pointer(frame.Datagram)
}
default:
if _, ok := frame.Other[id]; ok {
return nil, fmt.Errorf("duplicate setting: %d", id)
}
if frame.Other == nil {
frame.Other = make(map[uint64]uint64)
}
frame.Other[id] = val
}
}
if qlogger != nil {
settingsFrame.Other = maps.Clone(frame.Other)
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: streamID,
Raw: qlog.RawInfo{
Length: r.NumRead,
PayloadLength: int(l),
},
Frame: qlog.Frame{Frame: settingsFrame},
})
}
return frame, nil
}
func (f *settingsFrame) Append(b []byte) []byte {
b = quicvarint.Append(b, 0x4)
var l int
if f.MaxFieldSectionSize >= 0 {
l += quicvarint.Len(settingMaxFieldSectionSize) + quicvarint.Len(uint64(f.MaxFieldSectionSize))
}
for id, val := range f.Other {
l += quicvarint.Len(id) + quicvarint.Len(val)
}
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.MaxFieldSectionSize >= 0 {
b = quicvarint.Append(b, settingMaxFieldSectionSize)
b = quicvarint.Append(b, uint64(f.MaxFieldSectionSize))
}
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)
}
return b
}
type goAwayFrame struct {
StreamID quic.StreamID
}
func parseGoAwayFrame(r *countingByteReader, l uint64, streamID quic.StreamID, qlogger qlogwriter.Recorder) (*goAwayFrame, error) {
frame := &goAwayFrame{}
startLen := r.NumRead
id, err := quicvarint.Read(r)
if err != nil {
return nil, err
}
if r.NumRead-startLen != int(l) {
return nil, errors.New("GOAWAY frame: inconsistent length")
}
frame.StreamID = quic.StreamID(id)
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: streamID,
Raw: qlog.RawInfo{Length: r.NumRead, PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: frame.StreamID}},
})
}
return frame, nil
}
func (f *goAwayFrame) Append(b []byte) []byte {
b = quicvarint.Append(b, 0x7)
b = quicvarint.Append(b, uint64(quicvarint.Len(uint64(f.StreamID))))
return quicvarint.Append(b, uint64(f.StreamID))
}
package http3
// copied from net/transport.go
// gzipReader wraps a response body so it can lazily
// call gzip.NewReader on the first call to Read
import (
"compress/gzip"
"io"
)
// call gzip.NewReader on the first call to Read
type gzipReader struct {
body io.ReadCloser // underlying Response.Body
zr *gzip.Reader // lazily-initialized gzip reader
zerr error // sticky error
}
func newGzipReader(body io.ReadCloser) io.ReadCloser {
return &gzipReader{body: body}
}
func (gz *gzipReader) Read(p []byte) (n int, err error) {
if gz.zerr != nil {
return 0, gz.zerr
}
if gz.zr == nil {
gz.zr, err = gzip.NewReader(gz.body)
if err != nil {
gz.zerr = err
return 0, err
}
}
return gz.zr.Read(p)
}
func (gz *gzipReader) Close() error {
return gz.body.Close()
}
package http3
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/textproto"
"net/url"
"slices"
"strconv"
"strings"
"golang.org/x/net/http/httpguts"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
type qpackError struct{ err error }
func (e *qpackError) Error() string { return fmt.Sprintf("qpack: %v", e.err) }
func (e *qpackError) Unwrap() error { return e.err }
var errHeaderTooLarge = errors.New("http3: headers too large")
type header struct {
// Pseudo header fields defined in RFC 9114
Path string
Method string
Authority string
Scheme string
Status string
// for Extended connect
Protocol string
// parsed and deduplicated. -1 if no Content-Length header is sent
ContentLength int64
// all non-pseudo headers
Headers http.Header
}
// connection-specific header fields must not be sent on HTTP/3
var invalidHeaderFields = [...]string{
"connection",
"keep-alive",
"proxy-connection",
"transfer-encoding",
"upgrade",
}
func parseHeaders(decodeFn qpack.DecodeFunc, isRequest bool, sizeLimit int, headerFields *[]qpack.HeaderField) (header, error) {
hdr := header{Headers: make(http.Header)}
var readFirstRegularHeader, readContentLength bool
var contentLengthStr string
for {
h, err := decodeFn()
if err != nil {
if err == io.EOF {
break
}
return header{}, &qpackError{err}
}
if headerFields != nil {
*headerFields = append(*headerFields, h)
}
// RFC 9114, section 4.2.2:
// The size of a field list is calculated based on the uncompressed size of fields,
// including the length of the name and value in bytes plus an overhead of 32 bytes for each field.
sizeLimit -= len(h.Name) + len(h.Value) + 32
if sizeLimit < 0 {
return header{}, errHeaderTooLarge
}
if err := validateHeaderFieldNameAndValue(h); err != nil {
return header{}, err
}
if h.IsPseudo() {
if readFirstRegularHeader {
// all pseudo headers must appear before regular header fields, see section 4.3 of RFC 9114
return header{}, fmt.Errorf("received pseudo header %s after a regular header field", h.Name)
}
var isResponsePseudoHeader bool // pseudo headers are either valid for requests or for responses
var isDuplicatePseudoHeader bool // pseudo headers are allowed to appear exactly once
switch h.Name {
case ":path":
isDuplicatePseudoHeader = hdr.Path != ""
hdr.Path = h.Value
case ":method":
isDuplicatePseudoHeader = hdr.Method != ""
hdr.Method = h.Value
case ":authority":
isDuplicatePseudoHeader = hdr.Authority != ""
hdr.Authority = h.Value
case ":protocol": // RFC 9220
isDuplicatePseudoHeader = hdr.Protocol != ""
hdr.Protocol = h.Value
case ":scheme":
isDuplicatePseudoHeader = hdr.Scheme != ""
hdr.Scheme = h.Value
case ":status":
isDuplicatePseudoHeader = hdr.Status != ""
hdr.Status = h.Value
isResponsePseudoHeader = true
default:
return header{}, fmt.Errorf("unknown pseudo header: %s", h.Name)
}
if isDuplicatePseudoHeader {
return header{}, fmt.Errorf("duplicate pseudo header: %s", h.Name)
}
if isRequest && isResponsePseudoHeader {
return header{}, fmt.Errorf("invalid request pseudo header: %s", h.Name)
}
if !isRequest && !isResponsePseudoHeader {
return header{}, fmt.Errorf("invalid response pseudo header: %s", h.Name)
}
} else {
if err := validateRegularHeaderField(h); err != nil {
return header{}, err
}
readFirstRegularHeader = true
switch h.Name {
case "content-length":
// Ignore duplicate Content-Length headers.
// Fail if the duplicates differ.
if !readContentLength {
readContentLength = true
contentLengthStr = h.Value
} else if contentLengthStr != h.Value {
return header{}, fmt.Errorf("contradicting content lengths (%s and %s)", contentLengthStr, h.Value)
}
default:
hdr.Headers.Add(h.Name, h.Value)
}
}
}
hdr.ContentLength = -1
if len(contentLengthStr) > 0 {
// use ParseUint instead of ParseInt, so that parsing fails on negative values
cl, err := strconv.ParseUint(contentLengthStr, 10, 63)
if err != nil {
return header{}, fmt.Errorf("invalid content length: %w", err)
}
hdr.Headers.Set("Content-Length", contentLengthStr)
hdr.ContentLength = int64(cl)
}
return hdr, nil
}
func validateHeaderFieldNameAndValue(h qpack.HeaderField) error {
// field names need to be lowercase, see section 4.2 of RFC 9114
if strings.ToLower(h.Name) != h.Name {
return fmt.Errorf("header field is not lower-case: %s", h.Name)
}
if !httpguts.ValidHeaderFieldValue(h.Value) {
return fmt.Errorf("invalid header field value for %s: %q", h.Name, h.Value)
}
return nil
}
func validateRegularHeaderField(h qpack.HeaderField) error {
if !httpguts.ValidHeaderFieldName(h.Name) {
return fmt.Errorf("invalid header field name: %q", h.Name)
}
if slices.Contains(invalidHeaderFields[:], h.Name) {
return fmt.Errorf("invalid header field name: %q", h.Name)
}
if h.Name == "te" && h.Value != "trailers" {
return fmt.Errorf("invalid TE header field value: %q", h.Value)
}
return nil
}
func validateTrailerHeaderField(h qpack.HeaderField) error {
if err := validateRegularHeaderField(h); err != nil {
return err
}
if !httpguts.ValidTrailerHeader(h.Name) {
return fmt.Errorf("invalid trailer field name: %q", h.Name)
}
return nil
}
func parseTrailers(decodeFn qpack.DecodeFunc, sizeLimit int, headerFields *[]qpack.HeaderField) (http.Header, error) {
h := make(http.Header)
for {
hf, err := decodeFn()
if err != nil {
if err == io.EOF {
break
}
return nil, &qpackError{err}
}
if headerFields != nil {
*headerFields = append(*headerFields, hf)
}
// RFC 9114, section 4.2.2:
// The size of a field list is calculated based on the uncompressed size of fields,
// including the length of the name and value in bytes plus an overhead of 32 bytes for each field.
sizeLimit -= len(hf.Name) + len(hf.Value) + 32
if sizeLimit < 0 {
return nil, errHeaderTooLarge
}
if err := validateHeaderFieldNameAndValue(hf); err != nil {
return nil, err
}
if hf.IsPseudo() {
return nil, fmt.Errorf("http3: received pseudo header in trailer: %s", hf.Name)
}
if err := validateTrailerHeaderField(hf); err != nil {
return nil, err
}
h.Add(hf.Name, hf.Value)
}
return h, nil
}
func requestFromHeaders(decodeFn qpack.DecodeFunc, sizeLimit int, headerFields *[]qpack.HeaderField) (*http.Request, error) {
hdr, err := parseHeaders(decodeFn, true, sizeLimit, headerFields)
if err != nil {
return nil, err
}
// concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4
if len(hdr.Headers["Cookie"]) > 0 {
hdr.Headers.Set("Cookie", strings.Join(hdr.Headers["Cookie"], "; "))
}
isConnect := hdr.Method == http.MethodConnect
// Extended CONNECT, see https://datatracker.ietf.org/doc/html/rfc8441#section-4
isExtendedConnected := isConnect && hdr.Protocol != ""
if isExtendedConnected {
if !validExtendedConnectProtocol(hdr.Protocol) {
return nil, fmt.Errorf("invalid :protocol: %q", hdr.Protocol)
}
if hdr.Scheme == "" || hdr.Path == "" || hdr.Authority == "" {
return nil, errors.New("extended CONNECT: :scheme, :path and :authority must not be empty")
}
} else if isConnect {
if hdr.Path != "" || hdr.Authority == "" { // normal CONNECT
return nil, errors.New(":path must be empty and :authority must not be empty")
}
} else if len(hdr.Path) == 0 || len(hdr.Authority) == 0 || len(hdr.Method) == 0 {
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
protocol := "HTTP/3.0"
if isConnect {
u = &url.URL{}
if isExtendedConnected {
u, err = url.ParseRequestURI(hdr.Path)
if err != nil {
return nil, err
}
protocol = hdr.Protocol
} else {
u.Path = hdr.Path
}
requestURI = hdr.Authority
} else {
u, err = url.ParseRequestURI(hdr.Path)
if err != nil {
return nil, fmt.Errorf("invalid request URI: %w", err)
}
requestURI = hdr.Path
}
u.Scheme = hdr.Scheme
u.Host = hdr.Authority
req := &http.Request{
Method: hdr.Method,
URL: u,
Proto: protocol,
ProtoMajor: 3,
ProtoMinor: 0,
Header: hdr.Headers,
Body: nil,
ContentLength: hdr.ContentLength,
Host: hdr.Authority,
RequestURI: requestURI,
}
req.Trailer = extractAnnouncedTrailers(req.Header)
return req, nil
}
func validExtendedConnectProtocol(protocol string) bool {
// RFC 9220 specifies that the semantics of the :protocol pseudo are the same as defined in RFC 8441.
// RFC 8441, Section 4 specifies that :protocol is a single value from the HTTP Upgrade Token Registry.
// RFC 9110, Section 16.7 specifies that HTTP Upgrade Token Registry uses token grammar.
// Therefore, ValidHeaderFieldName is the right syntax check here, despite the misleading name.
return httpguts.ValidHeaderFieldName(protocol)
}
// updateResponseFromHeaders sets up http.Response as an HTTP/3 response,
// using the decoded qpack header filed.
// It is only called for the HTTP header (and not the HTTP trailer).
// It takes an http.Response as an argument to allow the caller to set the trailer later on.
func updateResponseFromHeaders(rsp *http.Response, decodeFn qpack.DecodeFunc, sizeLimit int, headerFields *[]qpack.HeaderField) error {
hdr, err := parseHeaders(decodeFn, false, sizeLimit, headerFields)
if err != nil {
return err
}
if hdr.Status == "" {
return errors.New("missing :status field")
}
rsp.Proto = "HTTP/3.0"
rsp.ProtoMajor = 3
rsp.Header = hdr.Headers
rsp.Trailer = extractAnnouncedTrailers(rsp.Header)
rsp.ContentLength = hdr.ContentLength
status, err := strconv.Atoi(hdr.Status)
if err != nil {
return fmt.Errorf("invalid status code: %w", err)
}
rsp.StatusCode = status
rsp.Status = hdr.Status + " " + http.StatusText(status)
return nil
}
// extractAnnouncedTrailers extracts trailer keys from the "Trailer" header.
// It returns a map with the announced keys set to nil values, and removes the "Trailer" header.
// It handles both duplicate as well as comma-separated values for the Trailer header.
// For example:
//
// Trailer: Trailer1, Trailer2
// Trailer: Trailer3
//
// Will result in a map containing the keys "Trailer1", "Trailer2", "Trailer3" with nil values.
func extractAnnouncedTrailers(header http.Header) http.Header {
rawTrailers, ok := header["Trailer"]
if !ok {
return nil
}
trailers := make(http.Header)
for _, rawVal := range rawTrailers {
for val := range strings.SplitSeq(rawVal, ",") {
trailers[http.CanonicalHeaderKey(textproto.TrimString(val))] = nil
}
}
delete(header, "Trailer")
return trailers
}
// writeTrailers encodes and writes HTTP trailers as a HEADERS frame.
// It returns true if trailers were written, false if there were no trailers to write.
func writeTrailers(wr io.Writer, trailers http.Header, streamID quic.StreamID, qlogger qlogwriter.Recorder) (bool, error) {
var hasValues bool
for k, vals := range trailers {
if httpguts.ValidTrailerHeader(k) && len(vals) > 0 {
hasValues = true
break
}
}
if !hasValues {
return false, nil
}
var buf bytes.Buffer
enc := qpack.NewEncoder(&buf)
var headerFields []qlog.HeaderField
if qlogger != nil {
headerFields = make([]qlog.HeaderField, 0, len(trailers))
}
for k, vals := range trailers {
if len(vals) == 0 {
continue
}
if !httpguts.ValidTrailerHeader(k) {
continue
}
lowercaseKey := strings.ToLower(k)
for _, v := range vals {
if err := enc.WriteField(qpack.HeaderField{Name: lowercaseKey, Value: v}); err != nil {
return false, err
}
if qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: lowercaseKey, Value: v})
}
}
}
b := make([]byte, 0, frameHeaderLen+buf.Len())
b = (&headersFrame{Length: uint64(buf.Len())}).Append(b)
b = append(b, buf.Bytes()...)
if qlogger != nil {
qlogCreatedHeadersFrame(qlogger, streamID, len(b), buf.Len(), headerFields)
}
_, err := wr.Write(b)
return true, err
}
func decodeTrailers(r io.Reader, hf *headersFrame, maxHeaderBytes int, decoder *qpack.Decoder, qlogger qlogwriter.Recorder, streamID quic.StreamID) (http.Header, error) {
if hf.Length > uint64(maxHeaderBytes) {
maybeQlogInvalidHeadersFrame(qlogger, streamID, hf.Length)
return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, maxHeaderBytes)
}
b := make([]byte, hf.Length)
if _, err := io.ReadFull(r, b); err != nil {
return nil, err
}
decodeFn := decoder.Decode(b)
var fields []qpack.HeaderField
var headerFields *[]qpack.HeaderField
if qlogger != nil {
fields = make([]qpack.HeaderField, 0, 16)
headerFields = &fields
}
trailers, err := parseTrailers(decodeFn, maxHeaderBytes, headerFields)
if err != nil {
maybeQlogInvalidHeadersFrame(qlogger, streamID, hf.Length)
return nil, err
}
if qlogger != nil {
qlogParsedHeadersFrame(qlogger, streamID, hf, fields)
}
return trailers, nil
}
package testdata
import (
"crypto/tls"
"crypto/x509"
"os"
"path"
"runtime"
)
var certPath string
func init() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("Failed to get current frame")
}
certPath = path.Dir(filename)
}
// GetCertificatePaths returns the paths to certificate and key
func GetCertificatePaths() (string, string) {
return path.Join(certPath, "cert.pem"), path.Join(certPath, "priv.key")
}
// GetTLSConfig returns a tls config for quic.clemente.io
func GetTLSConfig() *tls.Config {
cert, err := tls.LoadX509KeyPair(GetCertificatePaths())
if err != nil {
panic(err)
}
return &tls.Config{
MinVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{cert},
}
}
// AddRootCA adds the root CA certificate to a cert pool
func AddRootCA(certPool *x509.CertPool) {
caCertPath := path.Join(certPath, "ca.pem")
caCertRaw, err := os.ReadFile(caCertPath)
if err != nil {
panic(err)
}
if ok := certPool.AppendCertsFromPEM(caCertRaw); !ok {
panic("Could not add root ceritificate to pool.")
}
}
// GetRootCA returns an x509.CertPool containing (only) the CA certificate
func GetRootCA() *x509.CertPool {
pool := x509.NewCertPool()
AddRootCA(pool)
return pool
}
package http3
import (
"net"
"strings"
)
// An addrList represents a list of network endpoint addresses.
// Copy from [net.addrList] and change type from [net.Addr] to [net.IPAddr]
type addrList []net.IPAddr
// isIPv4 reports whether addr contains an IPv4 address.
func isIPv4(addr net.IPAddr) bool {
return addr.IP.To4() != nil
}
// isNotIPv4 reports whether addr does not contain an IPv4 address.
func isNotIPv4(addr net.IPAddr) bool { return !isIPv4(addr) }
// forResolve returns the most appropriate address in address for
// a call to ResolveTCPAddr, ResolveUDPAddr, or ResolveIPAddr.
// IPv4 is preferred, unless addr contains an IPv6 literal.
func (addrs addrList) forResolve(network, addr string) net.IPAddr {
var want6 bool
switch network {
case "ip":
// IPv6 literal (addr does NOT contain a port)
want6 = strings.ContainsRune(addr, ':')
case "tcp", "udp":
// IPv6 literal. (addr contains a port, so look for '[')
want6 = strings.ContainsRune(addr, '[')
}
if want6 {
return addrs.first(isNotIPv4)
}
return addrs.first(isIPv4)
}
// first returns the first address which satisfies strategy, or if
// none do, then the first address of any kind.
func (addrs addrList) first(strategy func(net.IPAddr) bool) net.IPAddr {
for _, addr := range addrs {
if strategy(addr) {
return addr
}
}
return addrs[0]
}
package http3
import (
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/qpack"
)
func maybeQlogInvalidHeadersFrame(qlogger qlogwriter.Recorder, streamID quic.StreamID, l uint64) {
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: streamID,
Raw: qlog.RawInfo{PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.HeadersFrame{}},
})
}
}
func qlogParsedHeadersFrame(qlogger qlogwriter.Recorder, streamID quic.StreamID, hf *headersFrame, hfs []qpack.HeaderField) {
headerFields := make([]qlog.HeaderField, len(hfs))
for i, hf := range hfs {
headerFields[i] = qlog.HeaderField{
Name: hf.Name,
Value: hf.Value,
}
}
qlogger.RecordEvent(qlog.FrameParsed{
StreamID: streamID,
Raw: qlog.RawInfo{
Length: int(hf.Length) + hf.headerLen,
PayloadLength: int(hf.Length),
},
Frame: qlog.Frame{Frame: qlog.HeadersFrame{
HeaderFields: headerFields,
}},
})
}
func qlogCreatedHeadersFrame(qlogger qlogwriter.Recorder, streamID quic.StreamID, length, payloadLength int, hfs []qlog.HeaderField) {
headerFields := make([]qlog.HeaderField, len(hfs))
for i, hf := range hfs {
headerFields[i] = qlog.HeaderField{
Name: hf.Name,
Value: hf.Value,
}
}
qlogger.RecordEvent(qlog.FrameCreated{
StreamID: streamID,
Raw: qlog.RawInfo{Length: length, PayloadLength: payloadLength},
Frame: qlog.Frame{Frame: qlog.HeadersFrame{
HeaderFields: headerFields,
}},
})
}
package qlog
import (
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
type encoderHelper struct {
enc *jsontext.Encoder
err error
}
func (h *encoderHelper) WriteToken(t jsontext.Token) {
if h.err != nil {
return
}
h.err = h.enc.WriteToken(t)
}
type RawInfo struct {
Length int // full packet length, including header and AEAD authentication tag
PayloadLength int // length of the packet payload, excluding AEAD tag
}
func (i RawInfo) HasValues() bool {
return i.Length != 0 || i.PayloadLength != 0
}
func (i RawInfo) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if i.Length != 0 {
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Uint(uint64(i.Length)))
}
if i.PayloadLength != 0 {
h.WriteToken(jsontext.String("payload_length"))
h.WriteToken(jsontext.Uint(uint64(i.PayloadLength)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type FrameParsed struct {
StreamID quic.StreamID
Raw RawInfo
Frame Frame
}
func (e FrameParsed) Name() string { return "http3:frame_parsed" }
func (e FrameParsed) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Uint(uint64(e.StreamID)))
if e.Raw.HasValues() {
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
}
h.WriteToken(jsontext.String("frame"))
if err := e.Frame.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type FrameCreated struct {
StreamID quic.StreamID
Raw RawInfo
Frame Frame
}
func (e FrameCreated) Name() string { return "http3:frame_created" }
func (e FrameCreated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Uint(uint64(e.StreamID)))
if e.Raw.HasValues() {
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
}
h.WriteToken(jsontext.String("frame"))
if err := e.Frame.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type DatagramCreated struct {
QuarterStreamID uint64
Raw RawInfo
}
func (e DatagramCreated) Name() string { return "http3:datagram_created" }
func (e DatagramCreated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("quarter_stream_id"))
h.WriteToken(jsontext.Uint(e.QuarterStreamID))
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type DatagramParsed struct {
QuarterStreamID uint64
Raw RawInfo
}
func (e DatagramParsed) Name() string { return "http3:datagram_parsed" }
func (e DatagramParsed) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("quarter_stream_id"))
h.WriteToken(jsontext.Uint(e.QuarterStreamID))
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
package qlog
import (
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
// Frame represents an HTTP/3 frame.
type Frame struct {
Frame any
}
func (f Frame) encode(enc *jsontext.Encoder) error {
switch frame := f.Frame.(type) {
case DataFrame:
return frame.encode(enc)
case HeadersFrame:
return frame.encode(enc)
case GoAwayFrame:
return frame.encode(enc)
case SettingsFrame:
return frame.encode(enc)
case PushPromiseFrame:
return frame.encode(enc)
case CancelPushFrame:
return frame.encode(enc)
case MaxPushIDFrame:
return frame.encode(enc)
case ReservedFrame:
return frame.encode(enc)
case UnknownFrame:
return frame.encode(enc)
}
// This shouldn't happen if the code is correctly logging frames.
// Write a null token to produce valid JSON.
return enc.WriteToken(jsontext.Null)
}
// A DataFrame is a DATA frame
type DataFrame struct{}
func (f *DataFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("data"))
h.WriteToken(jsontext.EndObject)
return h.err
}
type HeaderField struct {
Name string
Value string
}
// A HeadersFrame is a HEADERS frame
type HeadersFrame struct {
HeaderFields []HeaderField
}
func (f *HeadersFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("headers"))
if len(f.HeaderFields) > 0 {
h.WriteToken(jsontext.String("header_fields"))
h.WriteToken(jsontext.BeginArray)
for _, f := range f.HeaderFields {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String(f.Name))
h.WriteToken(jsontext.String("value"))
h.WriteToken(jsontext.String(f.Value))
h.WriteToken(jsontext.EndObject)
}
h.WriteToken(jsontext.EndArray)
}
h.WriteToken(jsontext.EndObject)
return h.err
}
// A GoAwayFrame is a GOAWAY frame
type GoAwayFrame struct {
StreamID quic.StreamID
}
func (f *GoAwayFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("goaway"))
h.WriteToken(jsontext.String("id"))
h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type SettingsFrame struct {
MaxFieldSectionSize int64
Datagram *bool
ExtendedConnect *bool
Other map[uint64]uint64
}
func (f *SettingsFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("settings"))
h.WriteToken(jsontext.String("settings"))
h.WriteToken(jsontext.BeginArray)
if f.MaxFieldSectionSize >= 0 {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String("settings_max_field_section_size"))
h.WriteToken(jsontext.String("value"))
h.WriteToken(jsontext.Uint(uint64(f.MaxFieldSectionSize)))
h.WriteToken(jsontext.EndObject)
}
if f.Datagram != nil {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String("settings_h3_datagram"))
h.WriteToken(jsontext.String("value"))
h.WriteToken(jsontext.Bool(*f.Datagram))
h.WriteToken(jsontext.EndObject)
}
if f.ExtendedConnect != nil {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String("settings_enable_connect_protocol"))
h.WriteToken(jsontext.String("value"))
h.WriteToken(jsontext.Bool(*f.ExtendedConnect))
h.WriteToken(jsontext.EndObject)
}
if len(f.Other) > 0 {
for k, v := range f.Other {
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String("unknown"))
h.WriteToken(jsontext.String("name_bytes"))
h.WriteToken(jsontext.Uint(k))
h.WriteToken(jsontext.String("value"))
h.WriteToken(jsontext.Uint(v))
h.WriteToken(jsontext.EndObject)
}
}
h.WriteToken(jsontext.EndArray)
h.WriteToken(jsontext.EndObject)
return h.err
}
// A PushPromiseFrame is a PUSH_PROMISE frame
type PushPromiseFrame struct{}
func (f *PushPromiseFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("push_promise"))
h.WriteToken(jsontext.EndObject)
return h.err
}
// A CancelPushFrame is a CANCEL_PUSH frame
type CancelPushFrame struct{}
func (f *CancelPushFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("cancel_push"))
h.WriteToken(jsontext.EndObject)
return h.err
}
// A MaxPushIDFrame is a MAX_PUSH_ID frame
type MaxPushIDFrame struct{}
func (f *MaxPushIDFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("max_push_id"))
h.WriteToken(jsontext.EndObject)
return h.err
}
// A ReservedFrame is one of the reserved frame types
type ReservedFrame struct {
Type uint64
}
func (f *ReservedFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("reserved"))
h.WriteToken(jsontext.String("frame_type_bytes"))
h.WriteToken(jsontext.Uint(f.Type))
h.WriteToken(jsontext.EndObject)
return h.err
}
// An UnknownFrame is an unknown frame type
type UnknownFrame struct {
Type uint64
}
func (f *UnknownFrame) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("unknown"))
h.WriteToken(jsontext.String("frame_type_bytes"))
h.WriteToken(jsontext.Uint(f.Type))
h.WriteToken(jsontext.EndObject)
return h.err
}
package qlog
import (
"context"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
const EventSchema = "urn:ietf:params:qlog:events:http3-12"
func DefaultConnectionTracer(ctx context.Context, isClient bool, connID quic.ConnectionID) qlogwriter.Trace {
return qlog.DefaultConnectionTracerWithSchemas(ctx, isClient, connID, []string{qlog.EventSchema, EventSchema})
}
package http3
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httptrace"
"strconv"
"strings"
"sync"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/idna"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
const bodyCopyBufferSize = 8 * 1024
type requestWriter struct {
mutex sync.Mutex
encoder *qpack.Encoder
headerBuf *bytes.Buffer
}
func newRequestWriter() *requestWriter {
headerBuf := &bytes.Buffer{}
encoder := qpack.NewEncoder(headerBuf)
return &requestWriter{
encoder: encoder,
headerBuf: headerBuf,
}
}
func (w *requestWriter) WriteRequestHeader(wr io.Writer, req *http.Request, gzip bool, streamID quic.StreamID, qlogger qlogwriter.Recorder) error {
buf := &bytes.Buffer{}
if err := w.writeHeaders(buf, req, gzip, streamID, qlogger); err != nil {
return err
}
if _, err := wr.Write(buf.Bytes()); err != nil {
return err
}
trace := httptrace.ContextClientTrace(req.Context())
traceWroteHeaders(trace)
return nil
}
func (w *requestWriter) writeHeaders(wr io.Writer, req *http.Request, gzip bool, streamID quic.StreamID, qlogger qlogwriter.Recorder) error {
w.mutex.Lock()
defer w.mutex.Unlock()
defer w.encoder.Close()
defer w.headerBuf.Reset()
var trailers string
if len(req.Trailer) > 0 {
keys := make([]string, 0, len(req.Trailer))
for k := range req.Trailer {
if httpguts.ValidTrailerHeader(k) {
keys = append(keys, k)
}
}
trailers = strings.Join(keys, ", ")
}
headerFields, err := w.encodeHeaders(req, gzip, trailers, actualContentLength(req), qlogger != nil)
if err != nil {
return err
}
b := make([]byte, 0, 128)
b = (&headersFrame{Length: uint64(w.headerBuf.Len())}).Append(b)
if qlogger != nil {
qlogCreatedHeadersFrame(qlogger, streamID, len(b)+w.headerBuf.Len(), w.headerBuf.Len(), headerFields)
}
if _, err := wr.Write(b); err != nil {
return err
}
_, err = wr.Write(w.headerBuf.Bytes())
return err
}
func isExtendedConnectRequest(req *http.Request) bool {
return req.Method == http.MethodConnect && req.Proto != "" && req.Proto != "HTTP/1.1"
}
// copied from net/transport.go
// Modified to support Extended CONNECT:
// Contrary to what the godoc for the http.Request says,
// we do respect the Proto field if the method is CONNECT.
//
// The returned header fields are only set if doQlog is true.
func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64, doQlog bool) ([]qlog.HeaderField, error) {
host := req.Host
if host == "" {
host = req.URL.Host
}
host, err := httpguts.PunycodeHostPort(host)
if err != nil {
return nil, err
}
if !httpguts.ValidHostHeader(host) {
return nil, errors.New("http3: invalid Host header")
}
// http.NewRequest sets this field to HTTP/1.1
isExtendedConnect := isExtendedConnectRequest(req)
if isExtendedConnect && !validExtendedConnectProtocol(req.Proto) {
return nil, fmt.Errorf("invalid request :protocol %q", req.Proto)
}
var path string
if req.Method != http.MethodConnect || isExtendedConnect {
path = req.URL.RequestURI()
if !validPseudoPath(path) {
orig := path
path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
if !validPseudoPath(path) {
if req.URL.Opaque != "" {
return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
} else {
return nil, fmt.Errorf("invalid request :path %q", orig)
}
}
}
}
// Check for any invalid headers and return an error before we
// potentially pollute our hpack state. (We want to be able to
// continue to reuse the hpack encoder for future requests)
for k, vv := range req.Header {
if !httpguts.ValidHeaderFieldName(k) {
return nil, fmt.Errorf("invalid HTTP header name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
}
}
}
enumerateHeaders := func(f func(name, value string)) {
// 8.1.2.3 Request Pseudo-Header Fields
// The :path pseudo-header field includes the path and query parts of the
// target URI (the path-absolute production and optionally a '?' character
// followed by the query production (see Sections 3.3 and 3.4 of
// [RFC3986]).
f(":authority", host)
f(":method", req.Method)
if req.Method != http.MethodConnect || isExtendedConnect {
f(":path", path)
f(":scheme", req.URL.Scheme)
}
if isExtendedConnect {
f(":protocol", req.Proto)
}
if trailers != "" {
f("trailer", trailers)
}
var didUA bool
for k, vv := range req.Header {
if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") {
// Host is :authority, already sent.
// Content-Length is automatic, set below.
continue
} else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") ||
strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") ||
strings.EqualFold(k, "keep-alive") {
// Per 8.1.2.2 Connection-Specific Header
// Fields, don't send connection-specific
// fields. We have already checked if any
// are error-worthy so just ignore the rest.
continue
} else if strings.EqualFold(k, "user-agent") {
// Match Go's http1 behavior: at most one
// User-Agent. If set to nil or empty string,
// then omit it. Otherwise if not mentioned,
// include the default (below).
didUA = true
if len(vv) < 1 {
continue
}
vv = vv[:1]
if vv[0] == "" {
continue
}
}
for _, v := range vv {
f(k, v)
}
}
if shouldSendReqContentLength(req.Method, contentLength) {
f("content-length", strconv.FormatInt(contentLength, 10))
}
if addGzipHeader {
f("accept-encoding", "gzip")
}
if !didUA {
f("user-agent", defaultUserAgent)
}
}
// Do a first pass over the headers counting bytes to ensure
// we don't exceed cc.peerMaxHeaderListSize. This is done as a
// separate pass before encoding the headers to prevent
// modifying the hpack state.
hlSize := uint64(0)
enumerateHeaders(func(name, value string) {
hf := hpack.HeaderField{Name: name, Value: value}
hlSize += uint64(hf.Size())
})
// TODO: check maximum header list size
// if hlSize > cc.peerMaxHeaderListSize {
// return errRequestHeaderListSize
// }
trace := httptrace.ContextClientTrace(req.Context())
traceHeaders := traceHasWroteHeaderField(trace)
// Header list size is ok. Write the headers.
var headerFields []qlog.HeaderField
if doQlog {
headerFields = make([]qlog.HeaderField, 0, len(req.Header))
}
enumerateHeaders(func(name, value string) {
name = strings.ToLower(name)
w.encoder.WriteField(qpack.HeaderField{Name: name, Value: value})
if traceHeaders {
traceWroteHeaderField(trace, name, value)
}
if doQlog {
headerFields = append(headerFields, qlog.HeaderField{Name: name, Value: value})
}
})
return headerFields, nil
}
// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
// and returns a host:port. The port 443 is added if needed.
func authorityAddr(authority string) (addr string) {
host, port, err := net.SplitHostPort(authority)
if err != nil { // authority didn't have a port
port = "443"
host = authority
}
if a, err := idna.ToASCII(host); err == nil {
host = a
}
// IPv6 address literal, without a port:
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
return host + ":" + port
}
return net.JoinHostPort(host, port)
}
// validPseudoPath reports whether v is a valid :path pseudo-header
// value. It must be either:
//
// *) a non-empty string starting with '/'
// *) the string '*', for OPTIONS requests.
//
// For now this is only used a quick check for deciding when to clean
// up Opaque URLs before sending requests from the Transport.
// See golang.org/issue/16847
//
// We used to enforce that the path also didn't start with "//", but
// Google's GFE accepts such paths and Chrome sends them, so ignore
// that part of the spec. See golang.org/issue/19103.
func validPseudoPath(v string) bool {
return (len(v) > 0 && v[0] == '/') || v == "*"
}
// actualContentLength returns a sanitized version of
// req.ContentLength, where 0 actually means zero (not unknown) and -1
// means unknown.
func actualContentLength(req *http.Request) int64 {
if req.Body == nil {
return 0
}
if req.ContentLength != 0 {
return req.ContentLength
}
return -1
}
// shouldSendReqContentLength reports whether the http2.Transport should send
// a "content-length" request header. This logic is basically a copy of the net/http
// transferWriter.shouldSendContentLength.
// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
// -1 means unknown.
func shouldSendReqContentLength(method string, contentLength int64) bool {
if contentLength > 0 {
return true
}
if contentLength < 0 {
return false
}
// For zero bodies, whether we send a content-length depends on the method.
// It also kinda doesn't matter for http2 either way, with END_STREAM.
switch method {
case "POST", "PUT", "PATCH":
return true
default:
return false
}
}
// WriteRequestTrailer writes HTTP trailers to the stream.
// It should be called after the request body has been fully written.
func (w *requestWriter) WriteRequestTrailer(wr io.Writer, req *http.Request, streamID quic.StreamID, qlogger qlogwriter.Recorder) error {
_, err := writeTrailers(wr, req.Trailer, streamID, qlogger)
return err
}
package http3
import (
"bytes"
"fmt"
"log/slog"
"net/http"
"net/textproto"
"strconv"
"strings"
"time"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go/http3/qlog"
"golang.org/x/net/http/httpguts"
)
// The HTTPStreamer allows taking over a HTTP/3 stream. The interface is implemented by the http.ResponseWriter.
// When a stream is taken over, it's the caller's responsibility to close the stream.
type HTTPStreamer interface {
HTTPStream() *Stream
}
const maxSmallResponseSize = 4096
type responseWriter struct {
str *Stream
conn *rawConn
header http.Header
trailers map[string]struct{}
buf []byte
status int // status code passed to WriteHeader
// for responses smaller than maxSmallResponseSize, we buffer calls to Write,
// and automatically add the Content-Length header
smallResponseBuf []byte
contentLen int64 // if handler set valid Content-Length header
numWritten int64 // bytes written
headerComplete bool // set once WriteHeader is called with a status code >= 200
headerWritten bool // set once the response header has been serialized to the stream
isHead bool
trailerWritten bool // set once the response trailers has been serialized to the stream
hijacked bool // set on HTTPStream is called
logger *slog.Logger
}
var (
_ http.ResponseWriter = &responseWriter{}
_ http.Flusher = &responseWriter{}
_ Settingser = &responseWriter{}
_ HTTPStreamer = &responseWriter{}
// make sure that we implement (some of the) methods used by the http.ResponseController
_ interface {
SetReadDeadline(time.Time) error
SetWriteDeadline(time.Time) error
Flush()
FlushError() error
} = &responseWriter{}
)
func newResponseWriter(str *Stream, conn *rawConn, isHead bool, logger *slog.Logger) *responseWriter {
return &responseWriter{
str: str,
conn: conn,
header: http.Header{},
buf: make([]byte, frameHeaderLen),
isHead: isHead,
logger: logger,
}
}
func (w *responseWriter) Header() http.Header {
return w.header
}
func (w *responseWriter) WriteHeader(status int) {
if w.headerComplete {
return
}
// http status must be 3 digits
if status < 100 || status > 999 {
panic(fmt.Sprintf("invalid WriteHeader code %v", status))
}
w.status = status
// immediately write 1xx headers
if status < 200 {
w.writeHeader(status)
return
}
// We're done with headers once we write a status >= 200.
w.headerComplete = true
// Add Date header.
// This is what the standard library does.
// Can be disabled by setting the Date header to nil.
if _, ok := w.header["Date"]; !ok {
w.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
}
// Content-Length checking
// use ParseUint instead of ParseInt, as negative values are invalid
if clen := w.header.Get("Content-Length"); clen != "" {
if cl, err := strconv.ParseUint(clen, 10, 63); err == nil {
w.contentLen = int64(cl)
} else {
// emit a warning for malformed Content-Length and remove it
logger := w.logger
if logger == nil {
logger = slog.Default()
}
logger.Error("Malformed Content-Length", "value", clen)
w.header.Del("Content-Length")
}
}
}
func (w *responseWriter) sniffContentType(p []byte) {
// If no content type, apply sniffing algorithm to body.
// We can't use `w.header.Get` here since if the Content-Type was set to nil, we shouldn't do sniffing.
_, haveType := w.header["Content-Type"]
// If the Content-Encoding was set and is non-blank, we shouldn't sniff the body.
hasCE := w.header.Get("Content-Encoding") != ""
if !hasCE && !haveType && len(p) > 0 {
w.header.Set("Content-Type", http.DetectContentType(p))
}
}
func (w *responseWriter) Write(p []byte) (int, error) {
bodyAllowed := bodyAllowedForStatus(w.status)
if !w.headerComplete {
w.sniffContentType(p)
w.WriteHeader(http.StatusOK)
bodyAllowed = true
}
if !bodyAllowed {
return 0, http.ErrBodyNotAllowed
}
w.numWritten += int64(len(p))
if w.contentLen != 0 && w.numWritten > w.contentLen {
return 0, http.ErrContentLength
}
if w.isHead {
return len(p), nil
}
if !w.headerWritten {
// Buffer small responses.
// This allows us to automatically set the Content-Length field.
if len(w.smallResponseBuf)+len(p) < maxSmallResponseSize {
w.smallResponseBuf = append(w.smallResponseBuf, p...)
return len(p), nil
}
}
return w.doWrite(p)
}
func (w *responseWriter) doWrite(p []byte) (int, error) {
if !w.headerWritten {
w.sniffContentType(w.smallResponseBuf)
if err := w.writeHeader(w.status); err != nil {
return 0, maybeReplaceError(err)
}
w.headerWritten = true
}
l := uint64(len(w.smallResponseBuf) + len(p))
if l == 0 {
return 0, nil
}
df := &dataFrame{Length: l}
w.buf = w.buf[:0]
w.buf = df.Append(w.buf)
if w.str.qlogger != nil {
w.str.qlogger.RecordEvent(qlog.FrameCreated{
StreamID: w.str.StreamID(),
Raw: qlog.RawInfo{Length: len(w.buf) + int(l), PayloadLength: int(l)},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
})
}
if _, err := w.str.writeUnframed(w.buf); err != nil {
return 0, maybeReplaceError(err)
}
if len(w.smallResponseBuf) > 0 {
if _, err := w.str.writeUnframed(w.smallResponseBuf); err != nil {
return 0, maybeReplaceError(err)
}
w.smallResponseBuf = nil
}
var n int
if len(p) > 0 {
var err error
n, err = w.str.writeUnframed(p)
if err != nil {
return n, maybeReplaceError(err)
}
}
return n, nil
}
func (w *responseWriter) writeHeader(status int) error {
var headerFields []qlog.HeaderField // only used for qlog
var headers bytes.Buffer
enc := qpack.NewEncoder(&headers)
if err := enc.WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}); err != nil {
return err
}
if w.str.qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
}
// Handle trailer fields
if vals, ok := w.header["Trailer"]; ok {
for _, val := range vals {
for trailer := range strings.SplitSeq(val, ",") {
// We need to convert to the canonical header key value here because this will be called when using
// headers.Add or headers.Set.
trailer = textproto.CanonicalMIMEHeaderKey(strings.TrimSpace(trailer))
w.declareTrailer(trailer)
}
}
}
for k, v := range w.header {
if _, excluded := w.trailers[k]; excluded {
continue
}
// Ignore "Trailer:" prefixed headers
if strings.HasPrefix(k, http.TrailerPrefix) {
continue
}
for index := range v {
name := strings.ToLower(k)
value := v[index]
if err := enc.WriteField(qpack.HeaderField{Name: name, Value: value}); err != nil {
return err
}
if w.str.qlogger != nil {
headerFields = append(headerFields, qlog.HeaderField{Name: name, Value: value})
}
}
}
buf := make([]byte, 0, frameHeaderLen+headers.Len())
buf = (&headersFrame{Length: uint64(headers.Len())}).Append(buf)
buf = append(buf, headers.Bytes()...)
if w.str.qlogger != nil {
qlogCreatedHeadersFrame(w.str.qlogger, w.str.StreamID(), len(buf), headers.Len(), headerFields)
}
_, err := w.str.writeUnframed(buf)
return err
}
func (w *responseWriter) FlushError() error {
if !w.headerComplete {
w.WriteHeader(http.StatusOK)
}
_, err := w.doWrite(nil)
return err
}
func (w *responseWriter) flushTrailers() {
if w.trailerWritten {
return
}
if err := w.writeTrailers(); err != nil {
if w.logger != nil {
w.logger.Debug("could not write trailers", "error", err)
}
}
}
func (w *responseWriter) Flush() {
if err := w.FlushError(); err != nil {
if w.logger != nil {
w.logger.Debug("could not flush to stream", "error", err)
}
}
}
// declareTrailer adds a trailer to the trailer list, while also validating that the trailer has a
// valid name.
func (w *responseWriter) declareTrailer(k string) {
if !httpguts.ValidTrailerHeader(k) {
// Forbidden by RFC 9110, section 6.5.1.
if w.logger != nil {
w.logger.Debug("ignoring invalid trailer", slog.String("header", k))
}
return
}
if w.trailers == nil {
w.trailers = make(map[string]struct{})
}
w.trailers[k] = struct{}{}
}
// writeTrailers will write trailers to the stream if there are any.
func (w *responseWriter) writeTrailers() error {
// promote headers added via "Trailer:" convention as trailers, these can be added after
// streaming the status/headers have been written.
for k := range w.header {
if strings.HasPrefix(k, http.TrailerPrefix) {
w.declareTrailer(k)
}
}
if len(w.trailers) == 0 {
return nil
}
trailers := make(http.Header, len(w.trailers))
for trailer := range w.trailers {
if vals, ok := w.header[trailer]; ok {
trailers[strings.TrimPrefix(trailer, http.TrailerPrefix)] = vals
}
}
written, err := writeTrailers(w.str.datagramStream, trailers, w.str.StreamID(), w.str.qlogger)
if written {
w.trailerWritten = true
}
return err
}
func (w *responseWriter) HTTPStream() *Stream {
w.hijacked = true
w.Flush()
return w.str
}
func (w *responseWriter) wasStreamHijacked() bool { return w.hijacked }
func (w *responseWriter) ReceivedSettings() <-chan struct{} {
return w.conn.ReceivedSettings()
}
func (w *responseWriter) Settings() *Settings {
return w.conn.Settings()
}
func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
return w.str.SetReadDeadline(deadline)
}
func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
return w.str.SetWriteDeadline(deadline)
}
// copied from http2/http2.go
// bodyAllowedForStatus reports whether a given response status code
// permits a body. See RFC 2616, section 4.4.
func bodyAllowedForStatus(status int) bool {
switch {
case status >= 100 && status <= 199:
return false
case status == http.StatusNoContent:
return false
case status == http.StatusNotModified:
return false
}
return true
}
package http3
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
// NextProtoH3 is the ALPN protocol negotiated during the TLS handshake, for QUIC v1 and v2.
const NextProtoH3 = "h3"
// StreamType is the stream type of a unidirectional stream.
type StreamType uint64
const (
streamTypeControlStream = 0
streamTypePushStream = 1
streamTypeQPACKEncoderStream = 2
streamTypeQPACKDecoderStream = 3
)
// A QUICListener listens for incoming QUIC connections.
type QUICListener interface {
Accept(context.Context) (*quic.Conn, error)
Addr() net.Addr
io.Closer
}
var _ QUICListener = &quic.EarlyListener{}
// ConfigureTLSConfig creates a new tls.Config which can be used
// to create a quic.Listener meant for serving HTTP/3.
func ConfigureTLSConfig(tlsConf *tls.Config) *tls.Config {
// Workaround for https://github.com/golang/go/issues/60506.
// This initializes the session tickets _before_ cloning the config.
_, _ = tlsConf.DecryptTicket(nil, tls.ConnectionState{})
config := tlsConf.Clone()
config.NextProtos = []string{NextProtoH3}
if gfc := config.GetConfigForClient; gfc != nil {
config.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
conf, err := gfc(ch)
if conf == nil || err != nil {
return conf, err
}
return ConfigureTLSConfig(conf), nil
}
}
return config
}
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation.
type contextKey struct {
name string
}
func (k *contextKey) String() string { return "quic-go/http3 context value " + k.name }
// ServerContextKey is a context key. It can be used in HTTP
// handlers with Context.Value to access the server that
// started the handler. The associated value will be of
// 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"}
// listener contains info about specific listener added with addListener
type listener struct {
ln *QUICListener
port int // 0 means that no info about port is available
// if this listener was constructed by the application, it won't be closed when the server is closed
createdLocally bool
}
// Server is a HTTP/3 server.
type Server struct {
// Addr optionally specifies the UDP address for the server to listen on,
// in the form "host:port".
//
// When used by ListenAndServe and ListenAndServeTLS methods, if empty,
// ":https" (port 443) is used. See net.Dial for details of the address
// format.
//
// Otherwise, if Port is not set and underlying QUIC listeners do not
// have valid port numbers, the port part is used in Alt-Svc headers set
// with SetQUICHeaders.
Addr string
// Port is used in Alt-Svc response headers set with SetQUICHeaders. If
// needed Port can be manually set when the Server is created.
//
// This is useful when a Layer 4 firewall is redirecting UDP traffic and
// clients must use a port different from the port the Server is
// listening on.
Port int
// TLSConfig provides a TLS configuration for use by server. It must be
// set for ListenAndServe and Serve methods.
TLSConfig *tls.Config
// QUICConfig provides the parameters for QUIC connection created with Serve.
// If nil, it uses reasonable default values.
//
// Configured versions are also used in Alt-Svc response header set with SetQUICHeaders.
QUICConfig *quic.Config
// Handler is the HTTP request handler to use. If not set, defaults to
// http.NotFound.
Handler http.Handler
// EnableDatagrams enables support for HTTP/3 datagrams (RFC 9297).
// If set to true, QUICConfig.EnableDatagrams will be set.
EnableDatagrams bool
// MaxHeaderBytes controls the maximum number of bytes the server will
// read parsing the request HEADERS frame. It does not limit the size of
// the request body. If zero or negative, http.DefaultMaxHeaderBytes is
// used.
MaxHeaderBytes int
// AdditionalSettings specifies additional HTTP/3 settings.
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
AdditionalSettings map[uint64]uint64
// IdleTimeout specifies how long until idle clients connection should be
// closed. Idle refers only to the HTTP/3 layer, activity at the QUIC layer
// like PING frames are not considered.
// If zero or negative, there is no timeout.
IdleTimeout time.Duration
// 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.Conn) context.Context
Logger *slog.Logger
mutex sync.RWMutex
listeners []listener
closed bool
closeCtx context.Context // canceled when the server is closed
closeCancel context.CancelFunc // cancels the closeCtx
graceCtx context.Context // canceled when the server is closed or gracefully closed
graceCancel context.CancelFunc // cancels the graceCtx
connCount atomic.Int64
connHandlingDone chan struct{}
altSvcHeader string
}
// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
//
// If s.Addr is blank, ":https" is used.
func (s *Server) ListenAndServe() error {
ln, err := s.setupListenerForConn(s.TLSConfig, nil)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
//
// If s.Addr is blank, ":https" is used.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
var err error
certs := make([]tls.Certificate, 1)
certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// We currently only use the cert-related stuff from tls.Config,
// so we don't need to make a full copy.
ln, err := s.setupListenerForConn(&tls.Config{Certificates: certs}, nil)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// Serve an existing UDP connection.
// It is possible to reuse the same connection for outgoing connections.
// Closing the server does not close the connection.
func (s *Server) Serve(conn net.PacketConn) error {
ln, err := s.setupListenerForConn(s.TLSConfig, conn)
if err != nil {
return err
}
defer s.removeListener(ln)
return s.serveListener(*ln)
}
// init initializes the contexts used for shutting down the server.
// It must be called with the mutex held.
func (s *Server) init() {
if s.closeCtx == nil {
s.closeCtx, s.closeCancel = context.WithCancel(context.Background())
s.graceCtx, s.graceCancel = context.WithCancel(s.closeCtx)
}
s.connHandlingDone = make(chan struct{}, 1)
}
func (s *Server) decreaseConnCount() {
if s.connCount.Add(-1) == 0 && s.graceCtx.Err() != nil {
close(s.connHandlingDone)
}
}
// ServeQUICConn serves a single QUIC connection.
func (s *Server) ServeQUICConn(conn *quic.Conn) error {
s.mutex.Lock()
if s.closed {
s.mutex.Unlock()
return http.ErrServerClosed
}
s.init()
s.mutex.Unlock()
s.connCount.Add(1)
defer s.decreaseConnCount()
return s.handleConn(conn)
}
// ServeListener serves an existing QUIC listener.
// Make sure you use http3.ConfigureTLSConfig to configure a tls.Config
// and use it to construct a http3-friendly QUIC listener.
// Closing the server does not close the listener. It is the application's responsibility to close them.
// ServeListener always returns a non-nil error. After Shutdown or Close, the returned error is http.ErrServerClosed.
func (s *Server) ServeListener(ln QUICListener) error {
s.mutex.Lock()
if err := s.addListener(&ln, false); err != nil {
s.mutex.Unlock()
return err
}
s.mutex.Unlock()
defer s.removeListener(&ln)
return s.serveListener(ln)
}
func (s *Server) serveListener(ln QUICListener) error {
for {
conn, err := ln.Accept(s.graceCtx)
// server closed
if errors.Is(err, quic.ErrServerClosed) || s.graceCtx.Err() != nil {
return http.ErrServerClosed
}
if err != nil {
return err
}
s.connCount.Add(1)
go func() {
defer s.decreaseConnCount()
if err := s.handleConn(conn); err != nil {
if s.Logger != nil {
s.Logger.Debug("handling connection failed", "error", err)
}
}
}()
}
}
var errServerWithoutTLSConfig = errors.New("use of http3.Server without TLSConfig")
func (s *Server) setupListenerForConn(tlsConf *tls.Config, conn net.PacketConn) (*QUICListener, error) {
if tlsConf == nil {
return nil, errServerWithoutTLSConfig
}
baseConf := ConfigureTLSConfig(tlsConf)
quicConf := s.QUICConfig
if quicConf == nil {
quicConf = &quic.Config{Allow0RTT: true}
} else {
quicConf = s.QUICConfig.Clone()
}
if s.EnableDatagrams {
quicConf.EnableDatagrams = true
}
s.mutex.Lock()
defer s.mutex.Unlock()
closed := s.closed
if closed {
return nil, http.ErrServerClosed
}
var ln QUICListener
var err error
if conn == nil {
addr := s.Addr
if addr == "" {
addr = ":https"
}
ln, err = quic.ListenAddrEarly(addr, baseConf, quicConf)
} else {
ln, err = quic.ListenEarly(conn, baseConf, quicConf)
}
if err != nil {
return nil, err
}
if err := s.addListener(&ln, true); err != nil {
return nil, err
}
return &ln, nil
}
func extractPort(addr string) (int, error) {
_, portStr, err := net.SplitHostPort(addr)
if err != nil {
return 0, err
}
portInt, err := net.LookupPort("tcp", portStr)
if err != nil {
return 0, err
}
return portInt, nil
}
func (s *Server) generateAltSvcHeader() {
if len(s.listeners) == 0 {
// Don't announce any ports since no one is listening for connections
s.altSvcHeader = ""
return
}
// This code assumes that we will use protocol.SupportedVersions if no quic.Config is passed.
var altSvc []string
addPort := func(port int) {
altSvc = append(altSvc, fmt.Sprintf(`%s=":%d"; ma=2592000`, NextProtoH3, port))
}
if s.Port != 0 {
// if Port is specified, we must use it instead of the
// listener addresses since there's a reason it's specified.
addPort(s.Port)
} else {
// if we have some listeners assigned, try to find ports
// which we can announce, otherwise nothing should be announced
validPortsFound := false
for _, info := range s.listeners {
if info.port != 0 {
addPort(info.port)
validPortsFound = true
}
}
if !validPortsFound {
if port, err := extractPort(s.Addr); err == nil {
addPort(port)
}
}
}
s.altSvcHeader = strings.Join(altSvc, ",")
}
func (s *Server) addListener(l *QUICListener, createdLocally bool) error {
if s.closed {
return http.ErrServerClosed
}
s.init()
laddr := (*l).Addr()
if port, err := extractPort(laddr.String()); err == nil {
s.listeners = append(s.listeners, listener{ln: l, port: port, createdLocally: createdLocally})
} else {
logger := s.Logger
if logger == nil {
logger = slog.Default()
}
logger.Error("Unable to extract port from listener, will not be announced using SetQUICHeaders", "local addr", laddr, "error", err)
s.listeners = append(s.listeners, listener{ln: l, port: 0, createdLocally: createdLocally})
}
s.generateAltSvcHeader()
return nil
}
func (s *Server) removeListener(l *QUICListener) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.listeners = slices.DeleteFunc(s.listeners, func(info listener) bool {
return info.ln == l
})
s.generateAltSvcHeader()
}
func (s *Server) NewRawServerConn(conn *quic.Conn) (*RawServerConn, error) {
hconn, _, _, err := s.newRawServerConn(conn)
if err != nil {
return nil, err
}
return hconn, nil
}
func (s *Server) newRawServerConn(conn *quic.Conn) (*RawServerConn, *quic.SendStream, qlogwriter.Recorder, error) {
var qlogger qlogwriter.Recorder
if qlogTrace := conn.QlogTrace(); qlogTrace != nil && qlogTrace.SupportsSchemas(qlog.EventSchema) {
qlogger = qlogTrace.AddProducer()
}
connCtx := conn.Context()
connCtx = context.WithValue(connCtx, ServerContextKey, s)
connCtx = context.WithValue(connCtx, http.LocalAddrContextKey, conn.LocalAddr())
connCtx = context.WithValue(connCtx, RemoteAddrContextKey, conn.RemoteAddr())
if s.ConnContext != nil {
connCtx = s.ConnContext(connCtx, conn)
if connCtx == nil {
panic("http3: ConnContext returned nil")
}
}
hconn := newRawServerConn(
conn,
s.EnableDatagrams,
s.IdleTimeout,
qlogger,
s.Logger,
connCtx,
s.Handler,
s.maxHeaderBytes(),
)
// open the control stream and send a SETTINGS frame, it's also used to send a GOAWAY frame later
// when the server is gracefully closed
ctrlStr, err := hconn.openControlStream(&settingsFrame{
MaxFieldSectionSize: int64(s.maxHeaderBytes()),
Datagram: s.EnableDatagrams,
ExtendedConnect: true,
Other: s.AdditionalSettings,
})
if err != nil {
return nil, nil, nil, fmt.Errorf("opening the control stream failed: %w", err)
}
return hconn, ctrlStr, qlogger, nil
}
// handleConn handles the HTTP/3 exchange on a QUIC connection.
// It blocks until all HTTP handlers for all streams have returned.
func (s *Server) handleConn(conn *quic.Conn) error {
hconn, ctrlStr, qlogger, err := s.newRawServerConn(conn)
if err != nil {
return err
}
var wg sync.WaitGroup
wg.Go(func() {
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
return
}
go hconn.HandleUnidirectionalStream(str)
}
})
var nextStreamID quic.StreamID
var handleErr error
var inGracefulShutdown bool
// Process all requests immediately.
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
ctx := s.graceCtx
for {
// The context used here is:
// * before graceful shutdown: s.graceCtx
// * after graceful shutdown: s.closeCtx
// This allows us to keep accepting (and resetting) streams after graceful shutdown has started.
str, err := conn.AcceptStream(ctx)
if err != nil {
// the underlying connection was closed (by either side)
if conn.Context().Err() != nil {
var appErr *quic.ApplicationError
if !errors.As(err, &appErr) || appErr.ErrorCode != quic.ApplicationErrorCode(ErrCodeNoError) {
handleErr = fmt.Errorf("accepting stream failed: %w", err)
}
break
}
// server (not gracefully) closed, close the connection immediately
if s.closeCtx.Err() != nil {
hconn.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
handleErr = http.ErrServerClosed
break
}
inGracefulShutdown = s.graceCtx.Err() != nil
if !inGracefulShutdown {
var appErr *quic.ApplicationError
if !errors.As(err, &appErr) || appErr.ErrorCode != quic.ApplicationErrorCode(ErrCodeNoError) {
handleErr = fmt.Errorf("accepting stream failed: %w", err)
}
break
}
// gracefully closed, send GOAWAY frame and wait for requests to complete or grace period to end
// new requests will be rejected and shouldn't be sent
if qlogger != nil {
qlogger.RecordEvent(qlog.FrameCreated{
StreamID: ctrlStr.StreamID(),
Frame: qlog.Frame{Frame: qlog.GoAwayFrame{StreamID: nextStreamID}},
})
}
// Send the GOAWAY frame in a separate Goroutine.
// Sending might block if the peer didn't grant enough flow control credit.
// Write is guaranteed to return once the connection is closed.
wg.Go(func() {
_, _ = ctrlStr.Write((&goAwayFrame{StreamID: nextStreamID}).Append(nil))
})
ctx = s.closeCtx
continue
}
if inGracefulShutdown {
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestRejected))
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestRejected))
continue
}
nextStreamID = str.StreamID() + 4
wg.Go(func() {
// HandleRequestStream will return once the request has been handled,
// or the underlying connection is closed.
hconn.HandleRequestStream(str)
})
}
wg.Wait()
return handleErr
}
func (s *Server) maxHeaderBytes() int {
if s.MaxHeaderBytes <= 0 {
return http.DefaultMaxHeaderBytes
}
return s.MaxHeaderBytes
}
// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
// It is the caller's responsibility to close any connection passed to ServeQUICConn.
func (s *Server) Close() error {
s.mutex.Lock()
defer s.mutex.Unlock()
s.closed = true
// server is never used
if s.closeCtx == nil {
return nil
}
s.closeCancel()
var err error
for _, l := range s.listeners {
if l.createdLocally {
if cerr := (*l.ln).Close(); cerr != nil && err == nil {
err = cerr
}
}
}
if s.connCount.Load() == 0 {
return err
}
// wait for all connections to be closed
<-s.connHandlingDone
return err
}
// Shutdown gracefully shuts down the server without interrupting any active connections.
// The server sends a GOAWAY frame first, then or for all running requests to complete.
// Shutdown in combination with ListenAndServe may race if it is called before a UDP socket is established.
// It is recommended to use Serve instead.
func (s *Server) Shutdown(ctx context.Context) error {
s.mutex.Lock()
s.closed = true
// server was never used
if s.closeCtx == nil {
s.mutex.Unlock()
return nil
}
s.graceCancel()
// close all listeners
var closeErrs []error
for _, l := range s.listeners {
if l.createdLocally {
if err := (*l.ln).Close(); err != nil {
closeErrs = append(closeErrs, err)
}
}
}
s.mutex.Unlock()
if len(closeErrs) > 0 {
return errors.Join(closeErrs...)
}
if s.connCount.Load() == 0 {
return s.Close()
}
select {
case <-s.connHandlingDone: // all connections were closed
// When receiving a GOAWAY frame, HTTP/3 clients are expected to close the connection
// once all requests were successfully handled...
return s.Close()
case <-ctx.Done():
// ... however, clients handling long-lived requests (and misbehaving clients),
// might not do so before the context is cancelled.
// In this case, we close the server, which closes all existing connections
// (expect those passed to ServeQUICConn).
_ = s.Close()
return ctx.Err()
}
}
// ErrNoAltSvcPort is the error returned by SetQUICHeaders when no port was found
// for Alt-Svc to announce. This can happen if listening on a PacketConn without a port
// (UNIX socket, for example) and no port is specified in Server.Port or Server.Addr.
var ErrNoAltSvcPort = errors.New("no port can be announced, specify it explicitly using Server.Port or Server.Addr")
// SetQUICHeaders can be used to set the proper headers that announce that this server supports HTTP/3.
// The values set by default advertise all the ports the server is listening on, but can be
// changed to a specific port by setting Server.Port before launching the server.
// If no listener's Addr().String() returns an address with a valid port, Server.Addr will be used
// to extract the port, if specified.
// For example, a server launched using ListenAndServe on an address with port 443 would set:
//
// Alt-Svc: h3=":443"; ma=2592000
func (s *Server) SetQUICHeaders(hdr http.Header) error {
s.mutex.RLock()
defer s.mutex.RUnlock()
if s.altSvcHeader == "" {
return ErrNoAltSvcPort
}
// use the map directly to avoid constant canonicalization since the key is already canonicalized
hdr["Alt-Svc"] = append(hdr["Alt-Svc"], s.altSvcHeader)
return nil
}
// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
server := &Server{
Addr: addr,
Handler: handler,
}
return server.ListenAndServeTLS(certFile, keyFile)
}
// ListenAndServeTLS 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.
func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler) error {
// Load certs
var err error
certs := make([]tls.Certificate, 1)
certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// We currently only use the cert-related stuff from tls.Config,
// so we don't need to make a full copy.
config := &tls.Config{
Certificates: certs,
}
if addr == "" {
addr = ":https"
}
// Open the listeners
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return err
}
udpConn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
defer udpConn.Close()
if handler == nil {
handler = http.DefaultServeMux
}
// Start the servers
quicServer := &Server{
TLSConfig: config,
Handler: handler,
}
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())
handler.ServeHTTP(w, r)
}))
}()
go func() {
qErr <- quicServer.Serve(udpConn)
}()
select {
case err := <-hErr:
quicServer.Close()
return err
case err := <-qErr:
// Cannot close the HTTP server or wait for requests to complete properly :/
return err
}
}
package http3
import (
"context"
"errors"
"io"
"log/slog"
"net/http"
"runtime"
"strconv"
"time"
"github.com/quic-go/qpack"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/qlogwriter"
)
// RawServerConn is an HTTP/3 server connection.
// It can be used for advanced use cases where the application wants to manage the QUIC connection lifecycle.
type RawServerConn struct {
rawConn rawConn
idleTimeout time.Duration
idleTimer *time.Timer
serverContext context.Context
requestHandler http.Handler
maxHeaderBytes int
decoder *qpack.Decoder
qlogger qlogwriter.Recorder
logger *slog.Logger
}
func newRawServerConn(
conn *quic.Conn,
enableDatagrams bool,
idleTimeout time.Duration,
qlogger qlogwriter.Recorder,
logger *slog.Logger,
serverContext context.Context,
requestHandler http.Handler,
maxHeaderBytes int,
) *RawServerConn {
c := &RawServerConn{
idleTimeout: idleTimeout,
serverContext: serverContext,
requestHandler: requestHandler,
maxHeaderBytes: maxHeaderBytes,
decoder: qpack.NewDecoder(),
qlogger: qlogger,
logger: logger,
}
c.rawConn = *newRawConn(conn, enableDatagrams, c.onStreamsEmpty, nil, qlogger, logger)
if idleTimeout > 0 {
c.idleTimer = time.AfterFunc(idleTimeout, func() {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "idle timeout")
})
}
return c
}
func (c *RawServerConn) onStreamsEmpty() {
if c.idleTimeout > 0 {
c.idleTimer.Reset(c.idleTimeout)
}
}
// CloseWithError closes the connection with the given error code and message.
func (c *RawServerConn) CloseWithError(code quic.ApplicationErrorCode, msg string) error {
if c.idleTimer != nil {
c.idleTimer.Stop()
}
return c.rawConn.CloseWithError(code, msg)
}
// HandleRequestStream handles an HTTP/3 request on a bidirectional request stream.
// The stream can either be obtained by calling AcceptStream on the underlying QUIC connection,
// or (internally) by using the server's stream accept loop.
func (c *RawServerConn) HandleRequestStream(str *quic.Stream) {
hstr := c.rawConn.TrackStream(str)
c.handleRequestStream(hstr)
}
func (c *RawServerConn) requestMaxHeaderBytes() int {
if c.maxHeaderBytes <= 0 {
return http.DefaultMaxHeaderBytes
}
return c.maxHeaderBytes
}
func (c *RawServerConn) openControlStream(settings *settingsFrame) (*quic.SendStream, error) {
return c.rawConn.openControlStream(settings)
}
func (c *RawServerConn) handleRequestStream(str *stateTrackingStream) {
if c.idleTimeout > 0 {
// This only applies if the stream is the first active stream,
// but it's ok to stop a stopped timer.
c.idleTimer.Stop()
}
conn := &c.rawConn
qlogger := c.qlogger
decoder := c.decoder
connCtx := c.serverContext
maxHeaderBytes := c.requestMaxHeaderBytes()
fp := &frameParser{closeConn: conn.CloseWithError, r: str, streamID: str.StreamID()}
frame, err := fp.ParseNext(qlogger)
if err != nil {
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
return
}
hf, ok := frame.(*headersFrame)
if !ok {
conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame")
return
}
if hf.Length > uint64(maxHeaderBytes) {
maybeQlogInvalidHeadersFrame(qlogger, str.StreamID(), hf.Length)
// stop the client from sending more data
str.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad))
// send a 431 Response (Request Header Fields Too Large)
c.rejectWithHeaderFieldsTooLarge(str)
return
}
headerBlock := make([]byte, hf.Length)
if _, err := io.ReadFull(str, headerBlock); err != nil {
maybeQlogInvalidHeadersFrame(qlogger, str.StreamID(), hf.Length)
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
return
}
decodeFn := decoder.Decode(headerBlock)
var hfs []qpack.HeaderField
if qlogger != nil {
hfs = make([]qpack.HeaderField, 0, 16)
}
req, err := requestFromHeaders(decodeFn, maxHeaderBytes, &hfs)
if qlogger != nil {
qlogParsedHeadersFrame(qlogger, str.StreamID(), hf, hfs)
}
if err != nil {
if errors.Is(err, errHeaderTooLarge) {
// stop the client from sending more data
str.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad))
// send a 431 Response (Request Header Fields Too Large)
c.rejectWithHeaderFieldsTooLarge(str)
return
}
errCode := ErrCodeMessageError
var qpackErr *qpackError
if errors.As(err, &qpackErr) {
errCode = ErrCodeQPACKDecompressionFailed
}
str.CancelRead(quic.StreamErrorCode(errCode))
str.CancelWrite(quic.StreamErrorCode(errCode))
return
}
connState := conn.ConnectionState().TLS
req.TLS = &connState
req.RemoteAddr = conn.RemoteAddr().String()
// Check that the client doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
// See section 4.1.2 of RFC 9114.
contentLength := int64(-1)
if _, ok := req.Header["Content-Length"]; ok && req.ContentLength >= 0 {
contentLength = req.ContentLength
}
hstr := newStream(str, conn, nil, func(r io.Reader, hf *headersFrame) error {
trailers, err := decodeTrailers(r, hf, maxHeaderBytes, decoder, qlogger, str.StreamID())
if err != nil {
return err
}
req.Trailer = trailers
return nil
}, qlogger)
body := newRequestBody(hstr, contentLength, connCtx, conn.ReceivedSettings(), conn.Settings)
req.Body = body
if c.logger != nil {
c.logger.Debug("handling request", "method", req.Method, "host", req.Host, "uri", req.RequestURI)
}
ctx, cancel := context.WithCancel(connCtx)
req = req.WithContext(ctx)
context.AfterFunc(str.Context(), cancel)
r := newResponseWriter(hstr, conn, req.Method == http.MethodHead, c.logger)
handler := c.requestHandler
if handler == nil {
handler = http.DefaultServeMux
}
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
var panicked bool
func() {
defer func() {
if p := recover(); p != nil {
panicked = true
if p == http.ErrAbortHandler {
return
}
// Copied from net/http/server.go
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
logger := c.logger
if logger == nil {
logger = slog.Default()
}
logger.Error("http3: panic serving", "arg", p, "trace", string(buf))
}
}()
handler.ServeHTTP(r, req)
}()
if r.wasStreamHijacked() {
return
}
// abort the stream when there is a panic
if panicked {
str.CancelRead(quic.StreamErrorCode(ErrCodeInternalError))
str.CancelWrite(quic.StreamErrorCode(ErrCodeInternalError))
return
}
// response not written to the client yet, set Content-Length
if !r.headerWritten {
if _, haveCL := r.header["Content-Length"]; !haveCL {
r.header.Set("Content-Length", strconv.FormatInt(r.numWritten, 10))
}
}
r.Flush()
r.flushTrailers()
// If the EOF was read by the handler, CancelRead() is a no-op.
str.CancelRead(quic.StreamErrorCode(ErrCodeNoError))
str.Close()
}
func (c *RawServerConn) rejectWithHeaderFieldsTooLarge(str *stateTrackingStream) {
hstr := newStream(str, &c.rawConn, nil, nil, c.qlogger)
defer hstr.Close()
r := newResponseWriter(hstr, &c.rawConn, false, c.logger)
r.WriteHeader(http.StatusRequestHeaderFieldsTooLarge)
r.Flush()
}
// HandleUnidirectionalStream handles an incoming unidirectional stream.
func (c *RawServerConn) HandleUnidirectionalStream(str *quic.ReceiveStream) {
c.rawConn.handleUnidirectionalStream(str, true)
}
package http3
import (
"context"
"errors"
"os"
"sync"
"github.com/quic-go/quic-go"
)
const streamDatagramQueueLen = 32
// stateTrackingStream is an implementation of quic.Stream that delegates
// to an underlying stream
// it takes care of proxying send and receive errors onto an implementation of
// the errorSetter interface (intended to be occupied by a datagrammer)
// it is also responsible for clearing the stream based on its ID from its
// parent connection, this is done through the streamClearer interface when
// both the send and receive sides are closed
type stateTrackingStream struct {
*quic.Stream
sendDatagram func([]byte) error
hasData chan struct{}
queue [][]byte // TODO: use a ring buffer
mx sync.Mutex
sendErr error
recvErr error
clearer streamClearer
}
var _ datagramStream = &stateTrackingStream{}
type streamClearer interface {
clearStream(quic.StreamID)
}
func newStateTrackingStream(s *quic.Stream, clearer streamClearer, sendDatagram func([]byte) error) *stateTrackingStream {
t := &stateTrackingStream{
Stream: s,
clearer: clearer,
sendDatagram: sendDatagram,
hasData: make(chan struct{}, 1),
}
context.AfterFunc(s.Context(), func() {
t.closeSend(context.Cause(s.Context()))
})
return t
}
func (s *stateTrackingStream) closeSend(e error) {
s.mx.Lock()
defer s.mx.Unlock()
// clear the stream the first time both the send
// and receive are finished
if s.sendErr == nil {
if s.recvErr != nil {
s.clearer.clearStream(s.StreamID())
}
s.sendErr = e
}
}
func (s *stateTrackingStream) closeReceive(e error) {
s.mx.Lock()
defer s.mx.Unlock()
// clear the stream the first time both the send
// and receive are finished
if s.recvErr == nil {
if s.sendErr != nil {
s.clearer.clearStream(s.StreamID())
}
s.recvErr = e
s.signalHasDatagram()
}
}
func (s *stateTrackingStream) Close() error {
s.closeSend(errors.New("write on closed stream"))
return s.Stream.Close()
}
func (s *stateTrackingStream) CancelWrite(e quic.StreamErrorCode) {
s.closeSend(&quic.StreamError{StreamID: s.StreamID(), ErrorCode: e})
s.Stream.CancelWrite(e)
}
func (s *stateTrackingStream) Write(b []byte) (int, error) {
n, err := s.Stream.Write(b)
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
s.closeSend(err)
}
return n, err
}
func (s *stateTrackingStream) CancelRead(e quic.StreamErrorCode) {
s.closeReceive(&quic.StreamError{StreamID: s.StreamID(), ErrorCode: e})
s.Stream.CancelRead(e)
}
func (s *stateTrackingStream) Read(b []byte) (int, error) {
n, err := s.Stream.Read(b)
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
s.closeReceive(err)
}
return n, err
}
func (s *stateTrackingStream) SendDatagram(b []byte) error {
s.mx.Lock()
sendErr := s.sendErr
s.mx.Unlock()
if sendErr != nil {
return sendErr
}
return s.sendDatagram(b)
}
func (s *stateTrackingStream) signalHasDatagram() {
select {
case s.hasData <- struct{}{}:
default:
}
}
func (s *stateTrackingStream) enqueueDatagram(data []byte) {
s.mx.Lock()
defer s.mx.Unlock()
if s.recvErr != nil {
return
}
if len(s.queue) >= streamDatagramQueueLen {
return
}
s.queue = append(s.queue, data)
s.signalHasDatagram()
}
func (s *stateTrackingStream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
start:
s.mx.Lock()
if len(s.queue) > 0 {
data := s.queue[0]
s.queue = s.queue[1:]
s.mx.Unlock()
return data, nil
}
if receiveErr := s.recvErr; receiveErr != nil {
s.mx.Unlock()
return nil, receiveErr
}
s.mx.Unlock()
select {
case <-ctx.Done():
return nil, context.Cause(ctx)
case <-s.hasData:
}
goto start
}
func (s *stateTrackingStream) QUICStream() *quic.Stream {
return s.Stream
}
package http3
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/qpack"
)
type datagramStream interface {
io.ReadWriteCloser
CancelRead(quic.StreamErrorCode)
CancelWrite(quic.StreamErrorCode)
StreamID() quic.StreamID
Context() context.Context
SetDeadline(time.Time) error
SetReadDeadline(time.Time) error
SetWriteDeadline(time.Time) error
SendDatagram(b []byte) error
ReceiveDatagram(ctx context.Context) ([]byte, error)
QUICStream() *quic.Stream
}
// A Stream is an HTTP/3 stream.
//
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
type Stream struct {
datagramStream
conn *rawConn
frameParser *frameParser
buf []byte // used as a temporary buffer when writing the HTTP/3 frame headers
bytesRemainingInFrame uint64
qlogger qlogwriter.Recorder
parseTrailer func(io.Reader, *headersFrame) error
parsedTrailer bool
}
func newStream(
str datagramStream,
conn *rawConn,
trace *httptrace.ClientTrace,
parseTrailer func(io.Reader, *headersFrame) error,
qlogger qlogwriter.Recorder,
) *Stream {
return &Stream{
datagramStream: str,
conn: conn,
buf: make([]byte, 16),
qlogger: qlogger,
parseTrailer: parseTrailer,
frameParser: &frameParser{
r: &tracingReader{Reader: str, trace: trace},
streamID: str.StreamID(),
closeConn: conn.CloseWithError,
},
}
}
func (s *Stream) Read(b []byte) (int, error) {
if s.bytesRemainingInFrame == 0 {
parseLoop:
for {
frame, err := s.frameParser.ParseNext(s.qlogger)
if err != nil {
return 0, err
}
switch f := frame.(type) {
case *dataFrame:
if s.parsedTrailer {
return 0, errors.New("DATA frame received after trailers")
}
s.bytesRemainingInFrame = f.Length
break parseLoop
case *headersFrame:
if s.parsedTrailer {
maybeQlogInvalidHeadersFrame(s.qlogger, s.StreamID(), f.Length)
return 0, errors.New("additional HEADERS frame received after trailers")
}
s.parsedTrailer = true
return 0, s.parseTrailer(s.datagramStream, f)
default:
s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
// parseNextFrame skips over unknown frame types
// Therefore, this condition is only entered when we parsed another known frame type.
return 0, fmt.Errorf("peer sent an unexpected frame: %T", f)
}
}
}
var n int
var err error
if s.bytesRemainingInFrame < uint64(len(b)) {
n, err = s.datagramStream.Read(b[:s.bytesRemainingInFrame])
} else {
n, err = s.datagramStream.Read(b)
}
s.bytesRemainingInFrame -= uint64(n)
return n, err
}
func (s *Stream) hasMoreData() bool {
return s.bytesRemainingInFrame > 0
}
func (s *Stream) Write(b []byte) (int, error) {
s.buf = s.buf[:0]
s.buf = (&dataFrame{Length: uint64(len(b))}).Append(s.buf)
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.FrameCreated{
StreamID: s.StreamID(),
Raw: qlog.RawInfo{
Length: len(s.buf) + len(b),
PayloadLength: len(b),
},
Frame: qlog.Frame{Frame: qlog.DataFrame{}},
})
}
if _, err := s.datagramStream.Write(s.buf); err != nil {
return 0, err
}
return s.datagramStream.Write(b)
}
func (s *Stream) writeUnframed(b []byte) (int, error) {
return s.datagramStream.Write(b)
}
func (s *Stream) StreamID() quic.StreamID {
return s.datagramStream.StreamID()
}
func (s *Stream) SendDatagram(b []byte) error {
// TODO: reject if datagrams are not negotiated (yet)
return s.datagramStream.SendDatagram(b)
}
func (s *Stream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
// TODO: reject if datagrams are not negotiated (yet)
return s.datagramStream.ReceiveDatagram(ctx)
}
// A RequestStream is a low-level abstraction representing an HTTP/3 request stream.
// It decouples sending of the HTTP request from reading the HTTP response, allowing
// the application to optimistically use the stream (and, for example, send datagrams)
// before receiving the response.
//
// This is only needed for advanced use case, e.g. WebTransport and the various
// MASQUE proxying protocols.
type RequestStream struct {
str *Stream
responseBody io.ReadCloser // set by ReadResponse
decoder *qpack.Decoder
requestWriter *requestWriter
maxHeaderBytes int
reqDone chan<- struct{}
disableCompression bool
response *http.Response
sentRequest bool
requestedGzip bool
isConnect bool
}
func newRequestStream(
str *Stream,
requestWriter *requestWriter,
reqDone chan<- struct{},
decoder *qpack.Decoder,
disableCompression bool,
maxHeaderBytes int,
rsp *http.Response,
) *RequestStream {
return &RequestStream{
str: str,
requestWriter: requestWriter,
reqDone: reqDone,
decoder: decoder,
disableCompression: disableCompression,
maxHeaderBytes: maxHeaderBytes,
response: rsp,
}
}
// Read reads data from the underlying stream.
//
// It can only be used after the request has been sent (using SendRequestHeader)
// and the response has been consumed (using ReadResponse).
func (s *RequestStream) Read(b []byte) (int, error) {
if s.responseBody == nil {
return 0, errors.New("http3: invalid use of RequestStream.Read before ReadResponse")
}
return s.responseBody.Read(b)
}
// StreamID returns the QUIC stream ID of the underlying QUIC stream.
func (s *RequestStream) StreamID() quic.StreamID {
return s.str.StreamID()
}
// Write writes data to the stream.
//
// It can only be used after the request has been sent (using SendRequestHeader).
func (s *RequestStream) Write(b []byte) (int, error) {
if !s.sentRequest {
return 0, errors.New("http3: invalid use of RequestStream.Write before SendRequestHeader")
}
return s.str.Write(b)
}
// Close closes the send-direction of the stream.
// It does not close the receive-direction of the stream.
func (s *RequestStream) Close() error {
return s.str.Close()
}
// CancelRead aborts receiving on this stream.
// See [quic.Stream.CancelRead] for more details.
func (s *RequestStream) CancelRead(errorCode quic.StreamErrorCode) {
s.str.CancelRead(errorCode)
}
// CancelWrite aborts sending on this stream.
// See [quic.Stream.CancelWrite] for more details.
func (s *RequestStream) CancelWrite(errorCode quic.StreamErrorCode) {
s.str.CancelWrite(errorCode)
}
// Context returns a context derived from the underlying QUIC stream's context.
// See [quic.Stream.Context] for more details.
func (s *RequestStream) Context() context.Context {
return s.str.Context()
}
// SetReadDeadline sets the deadline for Read calls.
func (s *RequestStream) SetReadDeadline(t time.Time) error {
return s.str.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for Write calls.
func (s *RequestStream) SetWriteDeadline(t time.Time) error {
return s.str.SetWriteDeadline(t)
}
// SetDeadline sets the read and write deadlines associated with the stream.
// It is equivalent to calling both SetReadDeadline and SetWriteDeadline.
func (s *RequestStream) SetDeadline(t time.Time) error {
return s.str.SetDeadline(t)
}
// SendDatagrams send a new HTTP Datagram (RFC 9297).
//
// It is only possible to send datagrams if the server enabled support for this extension.
// It is recommended (though not required) to send the request before calling this method,
// as the server might drop datagrams which it can't associate with an existing request.
func (s *RequestStream) SendDatagram(b []byte) error {
return s.str.SendDatagram(b)
}
// ReceiveDatagram receives HTTP Datagrams (RFC 9297).
//
// It is only possible if support for HTTP Datagrams was enabled, using the EnableDatagram
// option on the [Transport].
func (s *RequestStream) ReceiveDatagram(ctx context.Context) ([]byte, error) {
return s.str.ReceiveDatagram(ctx)
}
// SendRequestHeader sends the HTTP request.
//
// It can only used for requests that don't have a request body.
// It is invalid to call it more than once.
// It is invalid to call it after Write has been called.
func (s *RequestStream) SendRequestHeader(req *http.Request) error {
if req.Body != nil && req.Body != http.NoBody {
return errors.New("http3: invalid use of RequestStream.SendRequestHeader with a request that has a request body")
}
return s.sendRequestHeader(req)
}
func (s *RequestStream) sendRequestHeader(req *http.Request) error {
if s.sentRequest {
return errors.New("http3: invalid duplicate use of RequestStream.SendRequestHeader")
}
if !s.disableCompression && req.Method != http.MethodHead &&
req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" {
s.requestedGzip = true
}
s.isConnect = req.Method == http.MethodConnect
s.sentRequest = true
return s.requestWriter.WriteRequestHeader(s.str.datagramStream, req, s.requestedGzip, s.str.StreamID(), s.str.qlogger)
}
// sendRequestTrailer sends request trailers to the stream.
// It should be called after the request body has been fully written.
func (s *RequestStream) sendRequestTrailer(req *http.Request) error {
return s.requestWriter.WriteRequestTrailer(s.str.datagramStream, req, s.str.StreamID(), s.str.qlogger)
}
// ReadResponse reads the HTTP response from the stream.
//
// It must be called after sending the request (using SendRequestHeader).
// It is invalid to call it more than once.
// It doesn't set Response.Request and Response.TLS.
// It is invalid to call it after Read has been called.
func (s *RequestStream) ReadResponse() (*http.Response, error) {
if !s.sentRequest {
return nil, errors.New("http3: invalid use of RequestStream.ReadResponse before SendRequestHeader")
}
frame, err := s.str.frameParser.ParseNext(s.str.qlogger)
if err != nil {
s.str.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
s.str.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
return nil, fmt.Errorf("http3: parsing frame failed: %w", err)
}
hf, ok := frame.(*headersFrame)
if !ok {
s.str.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame")
return nil, errors.New("http3: expected first frame to be a HEADERS frame")
}
if hf.Length > uint64(s.maxHeaderBytes) {
maybeQlogInvalidHeadersFrame(s.str.qlogger, s.str.StreamID(), hf.Length)
s.str.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
s.str.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes)
}
headerBlock := make([]byte, hf.Length)
if _, err := io.ReadFull(s.str.datagramStream, headerBlock); err != nil {
maybeQlogInvalidHeadersFrame(s.str.qlogger, s.str.StreamID(), hf.Length)
s.str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
s.str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete))
return nil, fmt.Errorf("http3: failed to read response headers: %w", err)
}
decodeFn := s.decoder.Decode(headerBlock)
var hfs []qpack.HeaderField
if s.str.qlogger != nil {
hfs = make([]qpack.HeaderField, 0, 16)
}
res := s.response
err = updateResponseFromHeaders(res, decodeFn, s.maxHeaderBytes, &hfs)
if s.str.qlogger != nil {
qlogParsedHeadersFrame(s.str.qlogger, s.str.StreamID(), hf, hfs)
}
if err != nil {
errCode := ErrCodeMessageError
var qpackErr *qpackError
if errors.As(err, &qpackErr) {
errCode = ErrCodeQPACKDecompressionFailed
}
s.str.CancelRead(quic.StreamErrorCode(errCode))
s.str.CancelWrite(quic.StreamErrorCode(errCode))
return nil, fmt.Errorf("http3: invalid response: %w", err)
}
// Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set).
// See section 4.1.2 of RFC 9114.
respBody := newResponseBody(s.str, res.ContentLength, s.reqDone)
// Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2.
isInformational := res.StatusCode >= 100 && res.StatusCode < 200
isNoContent := res.StatusCode == http.StatusNoContent
isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300
if (isInformational || isNoContent || isSuccessfulConnect) && res.ContentLength == -1 {
res.ContentLength = 0
}
if s.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" {
res.Header.Del("Content-Encoding")
res.Header.Del("Content-Length")
res.ContentLength = -1
s.responseBody = newGzipReader(respBody)
res.Uncompressed = true
} else {
s.responseBody = respBody
}
res.Body = s.responseBody
return res, nil
}
type tracingReader struct {
io.Reader
readFirst bool
trace *httptrace.ClientTrace
}
func (r *tracingReader) Read(b []byte) (int, error) {
n, err := r.Reader.Read(b)
if n > 0 && !r.readFirst {
traceGotFirstResponseByte(r.trace)
r.readFirst = true
}
return n, err
}
package http3
import (
"crypto/tls"
"net"
"net/http/httptrace"
"net/textproto"
"time"
"github.com/quic-go/quic-go"
)
func traceGetConn(trace *httptrace.ClientTrace, hostPort string) {
if trace != nil && trace.GetConn != nil {
trace.GetConn(hostPort)
}
}
// fakeConn is a wrapper for quic.EarlyConnection
// because the quic connection does not implement net.Conn.
type fakeConn struct {
conn *quic.Conn
}
func (c *fakeConn) Close() error { panic("connection operation prohibited") }
func (c *fakeConn) Read(p []byte) (int, error) { panic("connection operation prohibited") }
func (c *fakeConn) Write(p []byte) (int, error) { panic("connection operation prohibited") }
func (c *fakeConn) SetDeadline(t time.Time) error { panic("connection operation prohibited") }
func (c *fakeConn) SetReadDeadline(t time.Time) error { panic("connection operation prohibited") }
func (c *fakeConn) SetWriteDeadline(t time.Time) error { panic("connection operation prohibited") }
func (c *fakeConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
func (c *fakeConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
func traceGotConn(trace *httptrace.ClientTrace, conn *quic.Conn, reused bool) {
if trace != nil && trace.GotConn != nil {
trace.GotConn(httptrace.GotConnInfo{
Conn: &fakeConn{conn: conn},
Reused: reused,
})
}
}
func traceGotFirstResponseByte(trace *httptrace.ClientTrace) {
if trace != nil && trace.GotFirstResponseByte != nil {
trace.GotFirstResponseByte()
}
}
func traceGot1xxResponse(trace *httptrace.ClientTrace, code int, header textproto.MIMEHeader) {
if trace != nil && trace.Got1xxResponse != nil {
trace.Got1xxResponse(code, header)
}
}
func traceGot100Continue(trace *httptrace.ClientTrace) {
if trace != nil && trace.Got100Continue != nil {
trace.Got100Continue()
}
}
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
return trace != nil && trace.WroteHeaderField != nil
}
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(k, []string{v})
}
}
func traceWroteHeaders(trace *httptrace.ClientTrace) {
if trace != nil && trace.WroteHeaders != nil {
trace.WroteHeaders()
}
}
func traceWroteRequest(trace *httptrace.ClientTrace, err error) {
if trace != nil && trace.WroteRequest != nil {
trace.WroteRequest(httptrace.WroteRequestInfo{Err: err})
}
}
func traceConnectStart(trace *httptrace.ClientTrace, network, addr string) {
if trace != nil && trace.ConnectStart != nil {
trace.ConnectStart(network, addr)
}
}
func traceConnectDone(trace *httptrace.ClientTrace, network, addr string, err error) {
if trace != nil && trace.ConnectDone != nil {
trace.ConnectDone(network, addr, err)
}
}
func traceTLSHandshakeStart(trace *httptrace.ClientTrace) {
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
}
func traceTLSHandshakeDone(trace *httptrace.ClientTrace, state tls.ConnectionState, err error) {
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(state, err)
}
}
package http3
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"strings"
"sync"
"sync/atomic"
"golang.org/x/net/http/httpguts"
"github.com/quic-go/quic-go"
)
// Settings are HTTP/3 settings that apply to the underlying connection.
type Settings struct {
// Support for HTTP/3 datagrams (RFC 9297)
EnableDatagrams 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 Transport may create a new QUIC connection.
// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
OnlyCachedConn bool
}
type clientConn interface {
OpenRequestStream(context.Context) (*RequestStream, error)
RoundTrip(*http.Request) (*http.Response, error)
handleUnidirectionalStream(*quic.ReceiveStream)
}
type roundTripperWithCount struct {
cancel context.CancelFunc
dialing chan struct{} // closed as soon as quic.Dial(Early) returned
dialErr error
conn *quic.Conn
clientConn clientConn
useCount atomic.Int64
}
func (r *roundTripperWithCount) Close() error {
r.cancel()
<-r.dialing
if r.conn != nil {
return r.conn.CloseWithError(0, "")
}
return nil
}
// Transport implements the http.RoundTripper interface
type Transport struct {
// TLSClientConfig specifies the TLS configuration to use with
// tls.Client. If nil, the default configuration is used.
TLSClientConfig *tls.Config
// QUICConfig is the quic.Config used for dialing new connections.
// If nil, reasonable default values will be used.
QUICConfig *quic.Config
// Dial specifies an optional dial function for creating QUIC
// connections for requests.
// If Dial is nil, a UDPConn will be created at the first request
// and will be reused for subsequent connections to other servers.
Dial func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error)
// 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.
// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
AdditionalSettings map[uint64]uint64
// MaxResponseHeaderBytes specifies a limit on how many response bytes are
// allowed in the server's response header.
// Zero means to use a default limit.
MaxResponseHeaderBytes int
// DisableCompression, if true, prevents the Transport from requesting compression with an
// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
// If the Transport requests gzip on its own and gets a gzipped response, it's transparently
// decoded in the Response.Body.
// However, if the user explicitly requested gzip it is not automatically uncompressed.
DisableCompression bool
Logger *slog.Logger
mutex sync.Mutex
initOnce sync.Once
initErr error
newClientConn func(*quic.Conn) clientConn
clients map[string]*roundTripperWithCount
transport *quic.Transport
closed bool
}
var (
_ http.RoundTripper = &Transport{}
_ io.Closer = &Transport{}
)
var (
// ErrNoCachedConn is returned when Transport.OnlyCachedConn is set
ErrNoCachedConn = errors.New("http3: no cached connection was available")
// ErrTransportClosed is returned when attempting to use a closed Transport
ErrTransportClosed = errors.New("http3: transport is closed")
)
func (t *Transport) init() error {
if t.newClientConn == nil {
t.newClientConn = func(conn *quic.Conn) clientConn {
return newClientConn(
conn,
t.EnableDatagrams,
t.AdditionalSettings,
t.MaxResponseHeaderBytes,
t.DisableCompression,
t.Logger,
)
}
}
if t.QUICConfig == nil {
t.QUICConfig = defaultQuicConfig.Clone()
t.QUICConfig.EnableDatagrams = t.EnableDatagrams
}
if t.EnableDatagrams && !t.QUICConfig.EnableDatagrams {
return errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
}
if len(t.QUICConfig.Versions) == 0 {
t.QUICConfig = t.QUICConfig.Clone()
t.QUICConfig.Versions = []quic.Version{quic.SupportedVersions()[0]}
}
if len(t.QUICConfig.Versions) != 1 {
return errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
}
if t.QUICConfig.MaxIncomingStreams == 0 {
t.QUICConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams
}
if t.Dial == nil {
udpConn, err := net.ListenUDP("udp", nil)
if err != nil {
return err
}
t.transport = &quic.Transport{Conn: udpConn}
}
return nil
}
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
rsp, err := t.roundTripOpt(req, opt)
if err != nil {
if req.Body != nil {
req.Body.Close()
}
return nil, err
}
return rsp, nil
}
func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
t.initOnce.Do(func() { t.initErr = t.init() })
if t.initErr != nil {
return nil, t.initErr
}
if req.URL == nil {
return nil, errors.New("http3: nil Request.URL")
}
if req.URL.Scheme != "https" {
return nil, fmt.Errorf("http3: unsupported protocol scheme: %s", req.URL.Scheme)
}
if req.URL.Host == "" {
return nil, errors.New("http3: no Host in request URL")
}
if req.Header == nil {
return nil, errors.New("http3: nil Request.Header")
}
if req.Method != "" && !validMethod(req.Method) {
return nil, fmt.Errorf("http3: invalid method %q", req.Method)
}
for k, vv := range req.Header {
if !httpguts.ValidHeaderFieldName(k) {
return nil, fmt.Errorf("http3: invalid http header field name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
return nil, fmt.Errorf("http3: invalid http header field value for key %q", k)
}
}
}
return t.doRoundTripOpt(req, opt, false)
}
func (t *Transport) doRoundTripOpt(req *http.Request, opt RoundTripOpt, isRetried bool) (*http.Response, error) {
hostname := authorityAddr(hostnameFromURL(req.URL))
trace := httptrace.ContextClientTrace(req.Context())
traceGetConn(trace, hostname)
cl, isReused, err := t.getClient(req.Context(), hostname, opt.OnlyCachedConn)
if err != nil {
return nil, err
}
select {
case <-cl.dialing:
case <-req.Context().Done():
return nil, context.Cause(req.Context())
}
if cl.dialErr != nil {
t.removeClient(hostname)
return nil, cl.dialErr
}
defer cl.useCount.Add(-1)
traceGotConn(trace, cl.conn, isReused)
rsp, err := cl.clientConn.RoundTrip(req)
if err != nil {
// request aborted due to context cancellation
select {
case <-req.Context().Done():
return nil, err
default:
}
if isRetried {
return nil, err
}
t.removeClient(hostname)
req, err = canRetryRequest(err, req)
if err != nil {
return nil, err
}
return t.doRoundTripOpt(req, opt, true)
}
return rsp, nil
}
func canRetryRequest(err error, req *http.Request) (*http.Request, error) {
// error occurred while opening the stream, we can be sure that the request wasn't sent out
var connErr *errConnUnusable
if errors.As(err, &connErr) {
return req, nil
}
// If the request stream is reset, we can only be sure that the request wasn't processed
// if the error code is H3_REQUEST_REJECTED.
var e *Error
if !errors.As(err, &e) || e.ErrorCode != ErrCodeRequestRejected {
return nil, err
}
// if the body is nil (or http.NoBody), it's safe to reuse this request and its body
if req.Body == nil || req.Body == http.NoBody {
return req, nil
}
// if the request body can be reset back to its original state via req.GetBody, do that
if req.GetBody != nil {
newBody, err := req.GetBody()
if err != nil {
return nil, err
}
reqCopy := *req
reqCopy.Body = newBody
req = &reqCopy
return &reqCopy, nil
}
return nil, fmt.Errorf("http3: Transport: cannot retry err [%w] after Request.Body was written; define Request.GetBody to avoid this error", err)
}
// RoundTrip does a round trip.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.RoundTripOpt(req, RoundTripOpt{})
}
func (t *Transport) getClient(ctx context.Context, hostname string, onlyCached bool) (rtc *roundTripperWithCount, isReused bool, err error) {
t.mutex.Lock()
defer t.mutex.Unlock()
if t.closed {
return nil, false, ErrTransportClosed
}
if t.clients == nil {
t.clients = make(map[string]*roundTripperWithCount)
}
cl, ok := t.clients[hostname]
if !ok {
if onlyCached {
return nil, false, ErrNoCachedConn
}
ctx, cancel := context.WithCancel(ctx)
cl = &roundTripperWithCount{
dialing: make(chan struct{}),
cancel: cancel,
}
go func() {
defer close(cl.dialing)
defer cancel()
conn, rt, err := t.dial(ctx, hostname)
if err != nil {
cl.dialErr = err
return
}
cl.conn = conn
cl.clientConn = rt
}()
t.clients[hostname] = cl
}
select {
case <-cl.dialing:
if cl.dialErr != nil {
delete(t.clients, hostname)
return nil, false, cl.dialErr
}
select {
case <-cl.conn.HandshakeComplete():
isReused = true
default:
}
default:
}
cl.useCount.Add(1)
return cl, isReused, nil
}
func (t *Transport) dial(ctx context.Context, hostname string) (*quic.Conn, clientConn, error) {
var tlsConf *tls.Config
if t.TLSClientConfig == nil {
tlsConf = &tls.Config{}
} else {
tlsConf = t.TLSClientConfig.Clone()
}
if tlsConf.ServerName == "" {
sni, _, err := net.SplitHostPort(hostname)
if err != nil {
// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
sni = hostname
}
tlsConf.ServerName = sni
}
// Replace existing ALPNs by H3
tlsConf.NextProtos = []string{NextProtoH3}
dial := t.Dial
if dial == nil {
dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
network := "udp"
udpAddr, err := t.resolveUDPAddr(ctx, network, addr)
if err != nil {
return nil, err
}
trace := httptrace.ContextClientTrace(ctx)
traceConnectStart(trace, network, udpAddr.String())
traceTLSHandshakeStart(trace)
conn, err := t.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
var state tls.ConnectionState
if conn != nil {
state = conn.ConnectionState().TLS
}
traceTLSHandshakeDone(trace, state, err)
traceConnectDone(trace, network, udpAddr.String(), err)
return conn, err
}
}
conn, err := dial(ctx, hostname, tlsConf, t.QUICConfig)
if err != nil {
return nil, nil, err
}
clientConn := t.newClientConn(conn)
go func() {
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
return
}
go clientConn.handleUnidirectionalStream(str)
}
}()
return conn, clientConn, nil
}
func (t *Transport) resolveUDPAddr(ctx context.Context, network, addr string) (*net.UDPAddr, error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := net.LookupPort(network, portStr)
if err != nil {
return nil, err
}
resolver := net.DefaultResolver
ipAddrs, err := resolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
addrs := addrList(ipAddrs)
ip := addrs.forResolve(network, addr)
return &net.UDPAddr{IP: ip.IP, Port: port, Zone: ip.Zone}, nil
}
func (t *Transport) removeClient(hostname string) {
t.mutex.Lock()
defer t.mutex.Unlock()
if t.clients == nil {
return
}
delete(t.clients, hostname)
}
// NewClientConn creates a new HTTP/3 client connection on top of a QUIC connection.
// Most users should use RoundTrip instead of creating a connection directly.
// Specifically, it is not needed to perform GET, POST, HEAD and CONNECT requests.
//
// Obtaining a ClientConn is only needed for more advanced use cases, such as
// using Extended CONNECT for WebTransport or the various MASQUE protocols.
func (t *Transport) NewClientConn(conn *quic.Conn) *ClientConn {
c := newClientConn(
conn,
t.EnableDatagrams,
t.AdditionalSettings,
t.MaxResponseHeaderBytes,
t.DisableCompression,
t.Logger,
)
go func() {
for {
str, err := conn.AcceptUniStream(context.Background())
if err != nil {
return
}
go c.handleUnidirectionalStream(str)
}
}()
return c
}
// NewRawClientConn creates a new low-level HTTP/3 client connection on top of a QUIC connection.
// Unlike NewClientConn, the returned RawClientConn allows the application to take control
// of the stream accept loops, by calling HandleUnidirectionalStream for incoming unidirectional
// streams and HandleBidirectionalStream for incoming bidirectional streams.
func (t *Transport) NewRawClientConn(conn *quic.Conn) *RawClientConn {
return &RawClientConn{
ClientConn: newClientConn(
conn,
t.EnableDatagrams,
t.AdditionalSettings,
t.MaxResponseHeaderBytes,
t.DisableCompression,
t.Logger,
),
}
}
// Close closes the QUIC connections that this Transport has used.
// A Transport cannot be used after it has been closed.
func (t *Transport) Close() error {
t.mutex.Lock()
defer t.mutex.Unlock()
for _, cl := range t.clients {
if err := cl.Close(); err != nil {
return err
}
}
t.clients = nil
if t.transport != nil {
if err := t.transport.Close(); err != nil {
return err
}
if err := t.transport.Conn.Close(); err != nil {
return err
}
t.transport = nil
}
t.closed = true
return nil
}
func hostnameFromURL(url *url.URL) string {
if url != nil {
return url.Host
}
return ""
}
func validMethod(method string) bool {
/*
Method = "OPTIONS" ; Section 9.2
| "GET" ; Section 9.3
| "HEAD" ; Section 9.4
| "POST" ; Section 9.5
| "PUT" ; Section 9.6
| "DELETE" ; Section 9.7
| "TRACE" ; Section 9.8
| "CONNECT" ; Section 9.9
| extension-method
extension-method = token
token = 1*<any CHAR except CTLs or separators>
*/
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
}
// copied from net/http/http.go
func isNotToken(r rune) bool {
return !httpguts.IsTokenRune(r)
}
// CloseIdleConnections closes any QUIC connections in the transport's pool that are currently idle.
// An idle connection is one that was previously used for requests but is now sitting unused.
// This method does not interrupt any connections currently in use.
// It also does not affect connections obtained via NewClientConn.
func (t *Transport) CloseIdleConnections() {
t.mutex.Lock()
defer t.mutex.Unlock()
for hostname, cl := range t.clients {
if cl.useCount.Load() == 0 {
cl.Close()
delete(t.clients, hostname)
}
}
}
package quic
import (
"context"
"crypto/tls"
"errors"
"net"
"slices"
"time"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/qlogwriter"
)
// The StreamID is the ID of a QUIC stream.
type StreamID = protocol.StreamID
// A Version is a QUIC version number.
type Version = protocol.Version
const (
// Version1 is RFC 9000
Version1 = protocol.Version1
// Version2 is RFC 9369
Version2 = protocol.Version2
)
// SupportedVersions returns the support versions, sorted in descending order of preference.
func SupportedVersions() []Version {
// clone the slice to prevent the caller from modifying the slice
return slices.Clone(protocol.SupportedVersions)
}
// A ClientToken is a token received by the client.
// It can be used to skip address validation on future connection attempts.
type ClientToken struct {
data []byte
rtt time.Duration
}
type TokenStore interface {
// Pop searches for a ClientToken associated with the given key.
// Since tokens are not supposed to be reused, it must remove the token from the cache.
// It returns nil when no token is found.
Pop(key string) (token *ClientToken)
// Put adds a token to the cache with the given key. It might get called
// multiple times in a connection.
Put(key string, token *ClientToken)
}
// Err0RTTRejected is the returned from:
// - Open{Uni}Stream{Sync}
// - Accept{Uni}Stream
// - Stream.Read and Stream.Write
//
// when the server rejects a 0-RTT connection attempt.
var Err0RTTRejected = errors.New("0-RTT rejected")
// ErrWouldBlock is returned by [SendStream.TryWriteAll] if the entire slice can't be queued immediately.
var ErrWouldBlock = errors.New("operation would block")
// QUICVersionContextKey can be used to find out the QUIC version of a TLS handshake from the
// context returned by tls.Config.ClientInfo.Context.
var QUICVersionContextKey = handshake.QUICVersionContextKey
// StatelessResetKey is a key used to derive stateless reset tokens.
type StatelessResetKey [32]byte
// TokenGeneratorKey is a key used to encrypt session resumption tokens.
type TokenGeneratorKey = handshake.TokenProtectorKey
// A ConnectionID is a QUIC Connection ID, as defined in RFC 9000.
// It is not able to handle QUIC Connection IDs longer than 20 bytes,
// as they are allowed by RFC 8999.
type ConnectionID = protocol.ConnectionID
// ConnectionIDFromBytes interprets b as a [ConnectionID]. It panics if b is
// longer than 20 bytes.
func ConnectionIDFromBytes(b []byte) ConnectionID {
return protocol.ParseConnectionID(b)
}
// A ConnectionIDGenerator allows the application to take control over the generation of Connection IDs.
// Connection IDs generated by an implementation must be of constant length.
type ConnectionIDGenerator interface {
// GenerateConnectionID generates a new Connection ID.
// Generated Connection IDs must be unique and observers should not be able to correlate two Connection IDs.
GenerateConnectionID() (ConnectionID, error)
// ConnectionIDLen returns the length of Connection IDs generated by this implementation.
// Implementations must return constant-length Connection IDs with lengths between 0 and 20 bytes.
// A length of 0 can only be used when an endpoint doesn't need to multiplex connections during migration.
ConnectionIDLen() int
}
// Config contains all configuration data needed for a QUIC server or client.
type Config struct {
// GetConfigForClient is called for incoming connections.
// If the error is not nil, the connection attempt is refused.
GetConfigForClient func(info *ClientInfo) (*Config, error)
// The QUIC versions that can be negotiated.
// If not set, it uses all versions available.
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.
// If this value is zero, the timeout is set to 5 seconds.
HandshakeIdleTimeout time.Duration
// MaxIdleTimeout is the maximum duration that may pass without any incoming network activity.
// The actual value for the idle timeout is the minimum of this value and the peer's.
// This value only applies after the handshake has completed.
// If the timeout is exceeded, the connection is closed.
// If this value is zero, the timeout is set to 30 seconds.
MaxIdleTimeout time.Duration
// 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
// otherwise the token is associated with the server's IP address.
TokenStore TokenStore
// InitialStreamReceiveWindow is the initial size of the stream-level flow control window for receiving data.
// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
// will increase the window up to MaxStreamReceiveWindow.
// If this value is zero, it will default to 512 KB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
InitialStreamReceiveWindow uint64
// MaxStreamReceiveWindow is the maximum stream-level flow control window for receiving data.
// If this value is zero, it will default to 6 MB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
MaxStreamReceiveWindow uint64
// InitialConnectionReceiveWindow is the initial size of the stream-level flow control window for receiving data.
// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
// will increase the window up to MaxConnectionReceiveWindow.
// If this value is zero, it will default to 512 KB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
InitialConnectionReceiveWindow uint64
// MaxConnectionReceiveWindow is the connection-level flow control window for receiving data.
// If this value is zero, it will default to 15 MB.
// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
MaxConnectionReceiveWindow uint64
// AllowConnectionWindowIncrease is called every time the connection flow controller attempts
// to increase the connection flow control window.
// If set, the caller can prevent an increase of the window. Typically, it would do so to
// limit the memory usage.
// To avoid deadlocks, it is not valid to call other functions on the connection or on streams
// in this callback.
AllowConnectionWindowIncrease func(conn *Conn, delta uint64) bool
// MaxIncomingStreams is the maximum number of concurrent bidirectional streams that a peer is allowed to open.
// If not set, it will default to 100.
// If set to a negative value, it doesn't allow any bidirectional streams.
// Values larger than 2^60 will be clipped to that value.
MaxIncomingStreams int64
// MaxIncomingUniStreams is the maximum number of concurrent unidirectional streams that a peer is allowed to open.
// If not set, it will default to 100.
// If set to a negative value, it doesn't allow any unidirectional streams.
// Values larger than 2^60 will be clipped to that value.
MaxIncomingUniStreams int64
// KeepAlivePeriod defines whether this peer will periodically send a packet to keep the connection alive.
// If set to 0, then no keep alive is sent. Otherwise, the keep alive is sent on that period (or at most
// every half of MaxIdleTimeout, whichever is smaller).
KeepAlivePeriod time.Duration
// InitialPacketSize is the initial size (and the lower limit) for packets sent.
// Under most circumstances, it is not necessary to manually set this value,
// since path MTU discovery quickly finds the path's MTU.
// If set too high, the path might not support packets of that size, leading to a timeout of the QUIC handshake.
// Values below 1200 are invalid.
InitialPacketSize uint16
// DisablePathMTUDiscovery disables Path MTU Discovery (RFC 8899).
// This allows the sending of QUIC packets that fully utilize the available MTU of the path.
// Path MTU discovery is only available on systems that allow setting of the Don't Fragment (DF) bit.
DisablePathMTUDiscovery bool
// Allow0RTT allows the application to decide if a 0-RTT connection attempt should be accepted.
// Only valid for the server.
Allow0RTT bool
// Enable QUIC datagram support (RFC 9221).
EnableDatagrams bool
// Enable QUIC Stream Resets with Partial Delivery.
// See https://datatracker.ietf.org/doc/html/draft-ietf-quic-reliable-stream-reset-09.
EnableStreamResetPartialDelivery bool
Tracer func(ctx context.Context, isClient bool, connID ConnectionID) qlogwriter.Trace
}
// ClientInfo contains information about an incoming connection attempt.
type ClientInfo 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.
type ConnectionState struct {
// TLS contains information about the TLS connection state, incl. the tls.ConnectionState.
TLS tls.ConnectionState
// SupportsDatagrams indicates support for QUIC datagrams (RFC 9221).
SupportsDatagrams struct {
// Remote is true if the peer advertised datagram support.
// Local is true if datagram support was enabled via Config.EnableDatagrams.
Remote, Local bool
}
// SupportsStreamResetPartialDelivery indicates support for QUIC Stream Resets with Partial Delivery.
SupportsStreamResetPartialDelivery struct {
// Remote is true if the peer advertised support.
// Local is true if support was enabled via Config.EnableStreamResetPartialDelivery.
Remote, Local bool
}
// Used0RTT says if 0-RTT resumption was used.
Used0RTT bool
// Version is the QUIC version of the QUIC connection.
Version Version
// GSO says if generic segmentation offload is used.
GSO bool
}
package ackhandler
import "github.com/quic-go/quic-go/internal/wire"
// IsFrameTypeAckEliciting returns true if the frame is ack-eliciting.
func IsFrameTypeAckEliciting(t wire.FrameType) bool {
//nolint:exhaustive // The default case catches the rest.
switch t {
case wire.FrameTypeAck, wire.FrameTypeAckECN:
return false
case wire.FrameTypeConnectionClose, wire.FrameTypeApplicationClose:
return false
default:
return true
}
}
// IsFrameAckEliciting returns true if the frame is ack-eliciting.
func IsFrameAckEliciting(f wire.Frame) bool {
_, isAck := f.(*wire.AckFrame)
_, isConnectionClose := f.(*wire.ConnectionCloseFrame)
return !isAck && !isConnectionClose
}
// HasAckElicitingFrames returns true if at least one frame is ack-eliciting.
func HasAckElicitingFrames(fs []Frame) bool {
for _, f := range fs {
if IsFrameAckEliciting(f.Frame) {
return true
}
}
return false
}
package ackhandler
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
type ecnState uint8
const (
ecnStateInitial ecnState = iota
ecnStateTesting
ecnStateUnknown
ecnStateCapable
ecnStateFailed
)
const (
// ecnFailedNoECNCounts is emitted when an ACK acknowledges ECN-marked packets,
// but doesn't contain any ECN counts
ecnFailedNoECNCounts = "ACK doesn't contain ECN marks"
// ecnFailedDecreasedECNCounts is emitted when an ACK frame decreases ECN counts
ecnFailedDecreasedECNCounts = "ACK decreases ECN counts"
// ecnFailedLostAllTestingPackets is emitted when all ECN testing packets are declared lost
ecnFailedLostAllTestingPackets = "all ECN testing packets declared lost"
// ecnFailedMoreECNCountsThanSent is emitted when an ACK contains more ECN counts than ECN-marked packets were sent
ecnFailedMoreECNCountsThanSent = "ACK contains more ECN counts than ECN-marked packets sent"
// ecnFailedTooFewECNCounts is emitted when an ACK contains fewer ECN counts than it acknowledges packets
ecnFailedTooFewECNCounts = "ACK contains fewer new ECN counts than acknowledged ECN-marked packets"
// ecnFailedManglingDetected is emitted when the path marks all ECN-marked packets as CE
ecnFailedManglingDetected = "ECN mangling detected"
)
// must fit into an uint8, otherwise numSentTesting and numLostTesting must have a larger type
const numECNTestingPackets = 10
type ecnHandler interface {
SentPacket(protocol.PacketNumber, protocol.ECN)
Mode() protocol.ECN
HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ect1, ecnce int64) (congested bool)
LostPacket(protocol.PacketNumber)
}
// The ecnTracker performs ECN validation of a path.
// Once failed, it doesn't do any re-validation of the path.
// It is designed only work for 1-RTT packets, it doesn't handle multiple packet number spaces.
// In order to avoid revealing any internal state to on-path observers,
// callers should make sure to start using ECN (i.e. calling Mode) for the very first 1-RTT packet sent.
// The validation logic implemented here strictly follows the algorithm described in RFC 9000 section 13.4.2 and A.4.
type ecnTracker struct {
state ecnState
numSentTesting, numLostTesting uint8
firstTestingPacket protocol.PacketNumber
lastTestingPacket protocol.PacketNumber
firstCapablePacket protocol.PacketNumber
numSentECT0, numSentECT1 int64
numAckedECT0, numAckedECT1, numAckedECNCE int64
qlogger qlogwriter.Recorder
logger utils.Logger
}
var _ ecnHandler = &ecnTracker{}
func newECNTracker(logger utils.Logger, qlogger qlogwriter.Recorder) *ecnTracker {
return &ecnTracker{
firstTestingPacket: protocol.InvalidPacketNumber,
lastTestingPacket: protocol.InvalidPacketNumber,
firstCapablePacket: protocol.InvalidPacketNumber,
state: ecnStateInitial,
logger: logger,
qlogger: qlogger,
}
}
func (e *ecnTracker) SentPacket(pn protocol.PacketNumber, ecn protocol.ECN) {
//nolint:exhaustive // These are the only ones we need to take care of.
switch ecn {
case protocol.ECNNon:
return
case protocol.ECT0:
e.numSentECT0++
case protocol.ECT1:
e.numSentECT1++
case protocol.ECNUnsupported:
if e.state != ecnStateFailed {
panic("didn't expect ECN to be unsupported")
}
default:
panic(fmt.Sprintf("sent packet with unexpected ECN marking: %s", ecn))
}
if e.state == ecnStateCapable && e.firstCapablePacket == protocol.InvalidPacketNumber {
e.firstCapablePacket = pn
}
if e.state != ecnStateTesting {
return
}
e.numSentTesting++
if e.firstTestingPacket == protocol.InvalidPacketNumber {
e.firstTestingPacket = pn
}
if e.numSentECT0+e.numSentECT1 >= numECNTestingPackets {
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateUnknown,
})
}
e.state = ecnStateUnknown
e.lastTestingPacket = pn
}
}
func (e *ecnTracker) Mode() protocol.ECN {
switch e.state {
case ecnStateInitial:
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateTesting,
})
}
e.state = ecnStateTesting
return e.Mode()
case ecnStateTesting, ecnStateCapable:
return protocol.ECT0
case ecnStateUnknown, ecnStateFailed:
return protocol.ECNNon
default:
panic(fmt.Sprintf("unknown ECN state: %d", e.state))
}
}
func (e *ecnTracker) LostPacket(pn protocol.PacketNumber) {
if e.state != ecnStateTesting && e.state != ecnStateUnknown {
return
}
if !e.isTestingPacket(pn) {
return
}
e.numLostTesting++
// Only proceed if we have sent all 10 testing packets.
if e.state != ecnStateUnknown {
return
}
if e.numLostTesting >= e.numSentTesting {
e.logger.Debugf("Disabling ECN. All testing packets were lost.")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedLostAllTestingPackets,
})
}
e.state = ecnStateFailed
return
}
// Path validation also fails if some testing packets are lost, and all other testing packets where CE-marked
e.failIfMangled()
}
// HandleNewlyAcked handles the ECN counts on an ACK frame.
// It must only be called for ACK frames that increase the largest acknowledged packet number,
// see section 13.4.2.1 of RFC 9000.
func (e *ecnTracker) HandleNewlyAcked(packets []packetWithPacketNumber, ect0, ect1, ecnce int64) (congested bool) {
if e.state == ecnStateFailed {
return false
}
// ECN validation can fail if the received total count for either ECT(0) or ECT(1) exceeds
// the total number of packets sent with each corresponding ECT codepoint.
if ect0 > e.numSentECT0 || ect1 > e.numSentECT1 {
e.logger.Debugf("Disabling ECN. Received more ECT(0) / ECT(1) acknowledgements than packets sent.")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedMoreECNCountsThanSent,
})
}
e.state = ecnStateFailed
return false
}
// Count ECT0 and ECT1 marks that we used when sending the packets that are now being acknowledged.
var ackedECT0, ackedECT1 int64
for _, p := range packets {
//nolint:exhaustive // We only ever send ECT(0) and ECT(1).
switch e.ecnMarking(p.PacketNumber) {
case protocol.ECT0:
ackedECT0++
case protocol.ECT1:
ackedECT1++
}
}
// If an ACK frame newly acknowledges a packet that the endpoint sent with either the ECT(0) or ECT(1)
// codepoint set, ECN validation fails if the corresponding ECN counts are not present in the ACK frame.
// This check detects:
// * paths that bleach all ECN marks, and
// * peers that don't report any ECN counts
if (ackedECT0 > 0 || ackedECT1 > 0) && ect0 == 0 && ect1 == 0 && ecnce == 0 {
e.logger.Debugf("Disabling ECN. ECN-marked packet acknowledged, but no ECN counts on ACK frame.")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedNoECNCounts,
})
}
e.state = ecnStateFailed
return false
}
// Determine the increase in ECT0, ECT1 and ECNCE marks
newECT0 := ect0 - e.numAckedECT0
newECT1 := ect1 - e.numAckedECT1
newECNCE := ecnce - e.numAckedECNCE
// We're only processing ACKs that increase the Largest Acked.
// Therefore, the ECN counters should only ever increase.
// Any decrease means that the peer's counting logic is broken.
if newECT0 < 0 || newECT1 < 0 || newECNCE < 0 {
e.logger.Debugf("Disabling ECN. ECN counts decreased unexpectedly.")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedDecreasedECNCounts,
})
}
e.state = ecnStateFailed
return false
}
// ECN validation also fails if the sum of the increase in ECT(0) and ECN-CE counts is less than the number
// of newly acknowledged packets that were originally sent with an ECT(0) marking.
// This could be the result of (partial) bleaching.
if newECT0+newECNCE < ackedECT0 {
e.logger.Debugf("Disabling ECN. Received less ECT(0) + ECN-CE than packets sent with ECT(0).")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedTooFewECNCounts,
})
}
e.state = ecnStateFailed
return false
}
// Similarly, ECN validation fails if the sum of the increases to ECT(1) and ECN-CE counts is less than
// the number of newly acknowledged packets sent with an ECT(1) marking.
if newECT1+newECNCE < ackedECT1 {
e.logger.Debugf("Disabling ECN. Received less ECT(1) + ECN-CE than packets sent with ECT(1).")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedTooFewECNCounts,
})
}
e.state = ecnStateFailed
return false
}
// update our counters
e.numAckedECT0 = ect0
e.numAckedECT1 = ect1
e.numAckedECNCE = ecnce
// Detect mangling (a path remarking all ECN-marked testing packets as CE),
// once all 10 testing packets have been sent out.
if e.state == ecnStateUnknown {
e.failIfMangled()
if e.state == ecnStateFailed {
return false
}
}
if e.state == ecnStateTesting || e.state == ecnStateUnknown {
var ackedTestingPacket bool
for _, p := range packets {
if e.isTestingPacket(p.PacketNumber) {
ackedTestingPacket = true
break
}
}
// This check won't succeed if the path is mangling ECN-marks (i.e. rewrites all ECN-marked packets to CE).
if ackedTestingPacket && (newECT0 > 0 || newECT1 > 0) {
e.logger.Debugf("ECN capability confirmed.")
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateCapable,
})
}
e.state = ecnStateCapable
}
}
// Don't trust CE marks before having confirmed ECN capability of the path.
// Otherwise, mangling would be misinterpreted as actual congestion.
return e.state == ecnStateCapable && newECNCE > 0
}
// failIfMangled fails ECN validation if all testing packets are lost or CE-marked.
func (e *ecnTracker) failIfMangled() {
numAckedECNCE := e.numAckedECNCE + int64(e.numLostTesting)
if e.numSentECT0+e.numSentECT1 > numAckedECNCE {
return
}
if e.qlogger != nil {
e.qlogger.RecordEvent(qlog.ECNStateUpdated{
State: qlog.ECNStateFailed,
Trigger: ecnFailedManglingDetected,
})
}
e.state = ecnStateFailed
}
func (e *ecnTracker) ecnMarking(pn protocol.PacketNumber) protocol.ECN {
if pn < e.firstTestingPacket || e.firstTestingPacket == protocol.InvalidPacketNumber {
return protocol.ECNNon
}
if pn < e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber {
return protocol.ECT0
}
if pn < e.firstCapablePacket || e.firstCapablePacket == protocol.InvalidPacketNumber {
return protocol.ECNNon
}
// We don't need to deal with the case when ECN validation fails,
// since we're ignoring any ECN counts reported in ACK frames in that case.
return protocol.ECT0
}
func (e *ecnTracker) isTestingPacket(pn protocol.PacketNumber) bool {
if e.firstTestingPacket == protocol.InvalidPacketNumber {
return false
}
return pn >= e.firstTestingPacket && (pn <= e.lastTestingPacket || e.lastTestingPacket == protocol.InvalidPacketNumber)
}
package ackhandler
import (
"iter"
"slices"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
)
type lostPacket struct {
PacketNumber protocol.PacketNumber
SendTime monotime.Time
}
type lostPacketTracker struct {
maxLength int
lostPackets []lostPacket
}
func newLostPacketTracker(maxLength int) *lostPacketTracker {
return &lostPacketTracker{
maxLength: maxLength,
// Preallocate a small slice only.
// Hopefully we won't lose many packets.
lostPackets: make([]lostPacket, 0, 4),
}
}
func (t *lostPacketTracker) Add(p protocol.PacketNumber, sendTime monotime.Time) {
if len(t.lostPackets) == t.maxLength {
t.lostPackets = t.lostPackets[1:]
}
t.lostPackets = append(t.lostPackets, lostPacket{
PacketNumber: p,
SendTime: sendTime,
})
}
// Delete deletes a packet from the lost packet tracker.
// This function is not optimized for performance if many packets are lost,
// but it is only used when a spurious loss is detected, which is rare.
func (t *lostPacketTracker) Delete(pn protocol.PacketNumber) {
t.lostPackets = slices.DeleteFunc(t.lostPackets, func(p lostPacket) bool {
return p.PacketNumber == pn
})
}
func (t *lostPacketTracker) All() iter.Seq2[protocol.PacketNumber, monotime.Time] {
return func(yield func(protocol.PacketNumber, monotime.Time) bool) {
for _, p := range t.lostPackets {
if !yield(p.PacketNumber, p.SendTime) {
return
}
}
}
}
func (t *lostPacketTracker) DeleteBefore(ti monotime.Time) {
if len(t.lostPackets) == 0 {
return
}
if !t.lostPackets[0].SendTime.Before(ti) {
return
}
var idx int
for ; idx < len(t.lostPackets); idx++ {
if !t.lostPackets[idx].SendTime.Before(ti) {
break
}
}
t.lostPackets = slices.Delete(t.lostPackets, 0, idx)
}
package ackhandler
import (
"sync"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
)
type packetWithPacketNumber struct {
PacketNumber protocol.PacketNumber
*packet
}
// A Packet is a packet
type packet struct {
SendTime monotime.Time
StreamFrames []StreamFrame
Frames []Frame
LargestAcked protocol.PacketNumber // InvalidPacketNumber if the packet doesn't contain an ACK
Length protocol.ByteCount
EncryptionLevel protocol.EncryptionLevel
IsPathMTUProbePacket bool // We don't report the loss of Path MTU probe packets to the congestion controller.
includedInBytesInFlight bool
isPathProbePacket bool
}
func (p *packet) Outstanding() bool {
return !p.IsPathMTUProbePacket && !p.isPathProbePacket && p.IsAckEliciting()
}
func (p *packet) IsAckEliciting() bool {
return len(p.StreamFrames) > 0 || len(p.Frames) > 0
}
var packetPool = sync.Pool{New: func() any { return &packet{} }}
func getPacket() *packet {
p := packetPool.Get().(*packet)
p.StreamFrames = nil
p.Frames = nil
p.LargestAcked = 0
p.Length = 0
p.EncryptionLevel = protocol.EncryptionLevel(0)
p.SendTime = 0
p.IsPathMTUProbePacket = false
p.includedInBytesInFlight = false
p.isPathProbePacket = false
return p
}
// We currently only return Packets back into the pool when they're acknowledged (not when they're lost).
// This simplifies the code, and gives the vast majority of the performance benefit we can gain from using the pool.
func putPacket(p *packet) {
p.Frames = nil
p.StreamFrames = nil
packetPool.Put(p)
}
package ackhandler
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
type packetNumberGenerator interface {
Peek() protocol.PacketNumber
// Pop pops the packet number.
// It reports if the packet number (before the one just popped) was skipped.
// It never skips more than one packet number in a row.
Pop() (skipped bool, _ protocol.PacketNumber)
}
type sequentialPacketNumberGenerator struct {
next protocol.PacketNumber
}
var _ packetNumberGenerator = &sequentialPacketNumberGenerator{}
func newSequentialPacketNumberGenerator(initial protocol.PacketNumber) packetNumberGenerator {
return &sequentialPacketNumberGenerator{next: initial}
}
func (p *sequentialPacketNumberGenerator) Peek() protocol.PacketNumber {
return p.next
}
func (p *sequentialPacketNumberGenerator) Pop() (bool, protocol.PacketNumber) {
next := p.next
p.next++
return false, next
}
// The skippingPacketNumberGenerator generates the packet number for the next packet
// it randomly skips a packet number every averagePeriod packets (on average).
// It is guaranteed to never skip two consecutive packet numbers.
type skippingPacketNumberGenerator struct {
period protocol.PacketNumber
maxPeriod protocol.PacketNumber
next protocol.PacketNumber
nextToSkip protocol.PacketNumber
rng utils.Rand
}
var _ packetNumberGenerator = &skippingPacketNumberGenerator{}
func newSkippingPacketNumberGenerator(initial, initialPeriod, maxPeriod protocol.PacketNumber) packetNumberGenerator {
g := &skippingPacketNumberGenerator{
next: initial,
period: initialPeriod,
maxPeriod: maxPeriod,
}
g.generateNewSkip()
return g
}
func (p *skippingPacketNumberGenerator) Peek() protocol.PacketNumber {
if p.next == p.nextToSkip {
return p.next + 1
}
return p.next
}
func (p *skippingPacketNumberGenerator) Pop() (bool, protocol.PacketNumber) {
next := p.next
if p.next == p.nextToSkip {
next++
p.next += 2
p.generateNewSkip()
return true, next
}
p.next++ // generate a new packet number for the next packet
return false, next
}
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 = min(2*p.period, p.maxPeriod)
}
package ackhandler
import (
"fmt"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
type ReceivedPacketHandler struct {
initialPackets *receivedPacketTracker
handshakePackets *receivedPacketTracker
appDataPackets appDataReceivedPacketTracker
lowest1RTTPacket protocol.PacketNumber
}
func NewReceivedPacketHandler(logger utils.Logger) *ReceivedPacketHandler {
return &ReceivedPacketHandler{
initialPackets: newReceivedPacketTracker(),
handshakePackets: newReceivedPacketTracker(),
appDataPackets: *newAppDataReceivedPacketTracker(logger),
lowest1RTTPacket: protocol.InvalidPacketNumber,
}
}
func (h *ReceivedPacketHandler) ReceivedPacket(
pn protocol.PacketNumber,
ecn protocol.ECN,
encLevel protocol.EncryptionLevel,
rcvTime monotime.Time,
ackEliciting bool,
) error {
switch encLevel {
case protocol.EncryptionInitial:
return h.initialPackets.ReceivedPacket(pn, ecn, ackEliciting)
case protocol.EncryptionHandshake:
// The Handshake packet number space might already have been dropped as a result
// of processing the CRYPTO frame that was contained in this packet.
if h.handshakePackets == nil {
return nil
}
return h.handshakePackets.ReceivedPacket(pn, ecn, ackEliciting)
case protocol.Encryption0RTT:
if h.lowest1RTTPacket != protocol.InvalidPacketNumber && pn > h.lowest1RTTPacket {
return fmt.Errorf("received packet number %d on a 0-RTT packet after receiving %d on a 1-RTT packet", pn, h.lowest1RTTPacket)
}
return h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
case protocol.Encryption1RTT:
if h.lowest1RTTPacket == protocol.InvalidPacketNumber || pn < h.lowest1RTTPacket {
h.lowest1RTTPacket = pn
}
return h.appDataPackets.ReceivedPacket(pn, ecn, rcvTime, ackEliciting)
default:
panic(fmt.Sprintf("received packet with unknown encryption level: %s", encLevel))
}
}
func (h *ReceivedPacketHandler) IgnorePacketsBelow(pn protocol.PacketNumber) {
h.appDataPackets.IgnoreBelow(pn)
}
func (h *ReceivedPacketHandler) DropPackets(encLevel protocol.EncryptionLevel) {
//nolint:exhaustive // 1-RTT packet number space is never dropped.
switch encLevel {
case protocol.EncryptionInitial:
h.initialPackets = nil
case protocol.EncryptionHandshake:
h.handshakePackets = nil
case protocol.Encryption0RTT:
// Nothing to do here.
// If we are rejecting 0-RTT, no 0-RTT packets will have been decrypted.
default:
panic(fmt.Sprintf("Cannot drop keys for encryption level %s", encLevel))
}
}
func (h *ReceivedPacketHandler) GetAlarmTimeout() monotime.Time {
return h.appDataPackets.GetAlarmTimeout()
}
func (h *ReceivedPacketHandler) GetAckFrame(encLevel protocol.EncryptionLevel, now monotime.Time, onlyIfQueued bool) *wire.AckFrame {
//nolint:exhaustive // 0-RTT packets can't contain ACK frames.
switch encLevel {
case protocol.EncryptionInitial:
if h.initialPackets != nil {
return h.initialPackets.GetAckFrame()
}
return nil
case protocol.EncryptionHandshake:
if h.handshakePackets != nil {
return h.handshakePackets.GetAckFrame()
}
return nil
case protocol.Encryption1RTT:
return h.appDataPackets.GetAckFrame(now, onlyIfQueued)
default:
// 0-RTT packets can't contain ACK frames
return nil
}
}
func (h *ReceivedPacketHandler) IsPotentiallyDuplicate(pn protocol.PacketNumber, encLevel protocol.EncryptionLevel) bool {
switch encLevel {
case protocol.EncryptionInitial:
if h.initialPackets != nil {
return h.initialPackets.IsPotentiallyDuplicate(pn)
}
case protocol.EncryptionHandshake:
if h.handshakePackets != nil {
return h.handshakePackets.IsPotentiallyDuplicate(pn)
}
case protocol.Encryption0RTT, protocol.Encryption1RTT:
return h.appDataPackets.IsPotentiallyDuplicate(pn)
}
panic("unexpected encryption level")
}
package ackhandler
import (
"iter"
"slices"
"github.com/quic-go/quic-go/internal/protocol"
)
// interval is an interval from one PacketNumber to the other
type interval struct {
Start protocol.PacketNumber
End protocol.PacketNumber
}
// The receivedPacketHistory stores if a packet number has already been received.
// It generates ACK ranges which can be used to assemble an ACK frame.
// It does not store packet contents.
type receivedPacketHistory struct {
ranges []interval // maximum length: protocol.MaxNumAckRanges
deletedBelow protocol.PacketNumber
}
func newReceivedPacketHistory() *receivedPacketHistory {
return &receivedPacketHistory{
deletedBelow: protocol.InvalidPacketNumber,
}
}
// ReceivedPacket registers a packet with PacketNumber p and updates the ranges
func (h *receivedPacketHistory) ReceivedPacket(p protocol.PacketNumber) bool /* is a new packet (and not a duplicate / delayed packet) */ {
// ignore delayed packets, if we already deleted the range
if p < h.deletedBelow {
return false
}
isNew := h.addToRanges(p)
// Delete old ranges, if we're tracking too many of them.
// This is a DoS defense against a peer that sends us too many gaps.
if len(h.ranges) > protocol.MaxNumAckRanges {
h.ranges = slices.Delete(h.ranges, 0, len(h.ranges)-protocol.MaxNumAckRanges)
}
return isNew
}
func (h *receivedPacketHistory) addToRanges(p protocol.PacketNumber) bool /* is a new packet (and not a duplicate / delayed packet) */ {
if len(h.ranges) == 0 {
h.ranges = append(h.ranges, interval{Start: p, End: p})
return true
}
for i := len(h.ranges) - 1; i >= 0; i-- {
// p already included in an existing range. Nothing to do here
if p >= h.ranges[i].Start && p <= h.ranges[i].End {
return false
}
if h.ranges[i].End == p-1 { // extend a range at the end
h.ranges[i].End = p
return true
}
if h.ranges[i].Start == p+1 { // extend a range at the beginning
h.ranges[i].Start = p
if i > 0 && h.ranges[i-1].End+1 == h.ranges[i].Start { // merge two ranges
h.ranges[i-1].End = h.ranges[i].End
h.ranges = slices.Delete(h.ranges, i, i+1)
}
return true
}
// create a new range after the current one
if p > h.ranges[i].End {
h.ranges = slices.Insert(h.ranges, i+1, interval{Start: p, End: p})
return true
}
}
// create a new range at the beginning
h.ranges = slices.Insert(h.ranges, 0, interval{Start: p, End: p})
return true
}
// DeleteBelow deletes all entries below (but not including) p
func (h *receivedPacketHistory) DeleteBelow(p protocol.PacketNumber) {
if p < h.deletedBelow {
return
}
h.deletedBelow = p
if len(h.ranges) == 0 {
return
}
idx := -1
for i := 0; i < len(h.ranges); i++ {
if h.ranges[i].End < p { // delete a whole range
idx = i
} else if p > h.ranges[i].Start && p <= h.ranges[i].End {
h.ranges[i].Start = p
break
} else { // no ranges affected. Nothing to do
break
}
}
if idx >= 0 {
h.ranges = slices.Delete(h.ranges, 0, idx+1)
}
}
// Backward returns an iterator over the ranges in reverse order
func (h *receivedPacketHistory) Backward() iter.Seq[interval] {
return func(yield func(interval) bool) {
for i := len(h.ranges) - 1; i >= 0; i-- {
if !yield(h.ranges[i]) {
return
}
}
}
}
func (h *receivedPacketHistory) HighestMissingUpTo(p protocol.PacketNumber) protocol.PacketNumber {
if len(h.ranges) == 0 || (h.deletedBelow != protocol.InvalidPacketNumber && p < h.deletedBelow) {
return protocol.InvalidPacketNumber
}
p = min(h.ranges[len(h.ranges)-1].End, p)
for i := len(h.ranges) - 1; i >= 0; i-- {
r := h.ranges[i]
if p >= r.Start && p <= r.End { // p is contained in this range
highest := r.Start - 1 // highest packet in the gap before this range
if h.deletedBelow != protocol.InvalidPacketNumber && highest < h.deletedBelow {
return protocol.InvalidPacketNumber
}
return highest
}
if i >= 1 && p > h.ranges[i-1].End && p <= r.Start {
// p is in the gap between the previous range and this range
return p
}
}
return p
}
func (h *receivedPacketHistory) IsPotentiallyDuplicate(p protocol.PacketNumber) bool {
if p < h.deletedBelow {
return true
}
// Iterating over the slices is faster than using a binary search (using slices.BinarySearchFunc).
for i := len(h.ranges) - 1; i >= 0; i-- {
if p > h.ranges[i].End {
return false
}
if p <= h.ranges[i].End && p >= h.ranges[i].Start {
return true
}
}
return false
}
package ackhandler
import (
"fmt"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
const reorderingThreshold = 1
// The receivedPacketTracker tracks packets for the Initial and Handshake packet number space.
// Every received packet is acknowledged immediately.
type receivedPacketTracker struct {
ect0, ect1, ecnce uint64
packetHistory receivedPacketHistory
lastAck *wire.AckFrame
hasNewAck bool // true as soon as we received an ack-eliciting new packet
}
func newReceivedPacketTracker() *receivedPacketTracker {
return &receivedPacketTracker{packetHistory: *newReceivedPacketHistory()}
}
func (h *receivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, ackEliciting bool) error {
if isNew := h.packetHistory.ReceivedPacket(pn); !isNew {
return fmt.Errorf("receivedPacketTracker BUG: ReceivedPacket called for old / duplicate packet %d", pn)
}
//nolint:exhaustive // Only need to count ECT(0), ECT(1) and ECN-CE.
switch ecn {
case protocol.ECT0:
h.ect0++
case protocol.ECT1:
h.ect1++
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
for r := range h.packetHistory.Backward() {
ack.AckRanges = append(ack.AckRanges, wire.AckRange{Smallest: r.Start, Largest: r.End})
}
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 monotime.Time
largestObserved protocol.PacketNumber
ignoreBelow protocol.PacketNumber
maxAckDelay time.Duration
ackQueued bool // true if we need send a new ACK
ackElicitingPacketsReceivedSinceLastAck int
ackAlarm monotime.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 monotime.Time, ackEliciting bool) error {
if err := h.receivedPacketTracker.ReceivedPacket(pn, ecn, 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 = 0 // 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 *appDataReceivedPacketTracker) IgnoreBelow(pn protocol.PacketNumber) {
if pn <= h.ignoreBelow {
return
}
h.ignoreBelow = pn
h.packetHistory.DeleteBelow(pn)
if h.logger.Debug() {
h.logger.Debugf("\tIgnoring all packets below %d.", pn)
}
}
// isMissing says if a packet was reported missing in the last ACK.
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 *appDataReceivedPacketTracker) hasNewMissingPackets() bool {
if h.lastAck == nil {
return false
}
if h.largestObserved < reorderingThreshold {
return false
}
highestMissing := h.packetHistory.HighestMissingUpTo(h.largestObserved - reorderingThreshold)
if highestMissing == protocol.InvalidPacketNumber {
return false
}
if highestMissing < h.lastAck.LargestAcked() {
// the packet was already reported missing in the last ACK
return false
}
return highestMissing > h.lastAck.LargestAcked()-reorderingThreshold
}
func (h *appDataReceivedPacketTracker) shouldQueueACK(pn protocol.PacketNumber, ecn protocol.ECN, wasMissing bool) bool {
// 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.
if wasMissing {
if h.logger.Debug() {
h.logger.Debugf("\tQueueing ACK because packet %d was missing before.", pn)
}
return true
}
// send an ACK every 2 ack-eliciting packets
if h.ackElicitingPacketsReceivedSinceLastAck >= packetsBeforeAck {
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)
}
return true
}
// 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.")
return true
}
// 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 *appDataReceivedPacketTracker) GetAckFrame(now monotime.Time, onlyIfQueued bool) *wire.AckFrame {
if onlyIfQueued && !h.ackQueued {
if h.ackAlarm.IsZero() || h.ackAlarm.After(now) {
return nil
}
if h.logger.Debug() && !h.ackAlarm.IsZero() {
h.logger.Debugf("Sending ACK because the ACK timer expired.")
}
}
ack := h.receivedPacketTracker.GetAckFrame()
if ack == nil {
return nil
}
ack.DelayTime = max(0, now.Sub(h.largestObservedRcvdTime))
h.ackQueued = false
h.ackAlarm = 0
h.ackElicitingPacketsReceivedSinceLastAck = 0
return ack
}
func (h *appDataReceivedPacketTracker) GetAlarmTimeout() monotime.Time { return h.ackAlarm }
package ackhandler
import "fmt"
// The SendMode says what kind of packets can be sent.
type SendMode uint8
const (
// SendNone means that no packets should be sent
SendNone SendMode = iota
// SendAck means an ACK-only packet should be sent
SendAck
// SendPTOInitial means that an Initial probe packet should be sent
SendPTOInitial
// SendPTOHandshake means that a Handshake probe packet should be sent
SendPTOHandshake
// SendPTOAppData means that an Application data probe packet should be sent
SendPTOAppData
// SendPacingLimited means that the pacer doesn't allow sending of a packet right now,
// but will do in a little while.
// The timestamp when sending is allowed again can be obtained via the SentPacketHandler.TimeUntilSend.
SendPacingLimited
// SendAny means that any packet should be sent
SendAny
)
func (s SendMode) String() string {
switch s {
case SendNone:
return "none"
case SendAck:
return "ack"
case SendPTOInitial:
return "pto (Initial)"
case SendPTOHandshake:
return "pto (Handshake)"
case SendPTOAppData:
return "pto (Application Data)"
case SendAny:
return "any"
case SendPacingLimited:
return "pacing limited"
default:
return fmt.Sprintf("invalid send mode: %d", s)
}
}
package ackhandler
import (
"errors"
"fmt"
"time"
"github.com/quic-go/quic-go/internal/congestion"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
const (
// Maximum reordering in time space before time based loss detection considers a packet lost.
// Specified as an RTT multiplier.
timeThreshold = 9.0 / 8
// Maximum reordering in packets before packet threshold loss detection considers a packet lost.
packetThreshold = 3
// Before validating the client's address, the server won't send more than 3x bytes than it received.
amplificationFactor = 3
// We use Retry packets to derive an RTT estimate. Make sure we don't set the RTT to a super low value yet.
minRTTAfterRetry = 5 * time.Millisecond
// The PTO duration uses exponential backoff, but is truncated to a maximum value, as allowed by RFC 8961, section 4.4.
maxPTODuration = 60 * time.Second
)
// Path probe packets are declared lost after this time.
const pathProbePacketLossTimeout = time.Second
type packetNumberSpace struct {
history sentPacketHistory
pns packetNumberGenerator
lossTime monotime.Time
lastAckElicitingPacketTime monotime.Time
largestAcked protocol.PacketNumber
largestSent protocol.PacketNumber
}
func newPacketNumberSpace(initialPN protocol.PacketNumber, isAppData bool) *packetNumberSpace {
var pns packetNumberGenerator
if isAppData {
pns = newSkippingPacketNumberGenerator(initialPN, protocol.SkipPacketInitialPeriod, protocol.SkipPacketMaxPeriod)
} else {
pns = newSequentialPacketNumberGenerator(initialPN)
}
return &packetNumberSpace{
history: *newSentPacketHistory(isAppData),
pns: pns,
largestSent: protocol.InvalidPacketNumber,
largestAcked: protocol.InvalidPacketNumber,
}
}
type alarmTimer struct {
Time monotime.Time
TimerType qlog.TimerType
EncryptionLevel protocol.EncryptionLevel
}
type sentPacketHandler struct {
initialPackets *packetNumberSpace
handshakePackets *packetNumberSpace
appDataPackets *packetNumberSpace
lostPackets lostPacketTracker // only for application-data packet number space
// send time of the largest acknowledged packet, across all packet number spaces
largestAckedTime monotime.Time
// Do we know that the peer completed address validation yet?
// Always true for the server.
peerCompletedAddressValidation bool
bytesReceived protocol.ByteCount
bytesSent protocol.ByteCount
// Have we validated the peer's address yet?
// Always true for the client.
peerAddressValidated bool
handshakeConfirmed bool
ignorePacketsBelow func(protocol.PacketNumber)
ackedPackets []packetWithPacketNumber // to avoid allocations in detectAndRemoveAckedPackets
bytesInFlight protocol.ByteCount
congestion congestion.SendAlgorithmWithDebugInfos
rttStats *utils.RTTStats
connStats *utils.ConnectionStats
// The number of times a PTO has been sent without receiving an ack.
ptoCount uint32
ptoMode SendMode
// The number of PTO probe packets that should be sent.
// Only applies to the application-data packet number space.
numProbesToSend int
// The alarm timeout
alarm alarmTimer
enableECN bool
ecnTracker ecnHandler
perspective protocol.Perspective
qlogger qlogwriter.Recorder
lastMetrics qlog.MetricsUpdated
logger utils.Logger
}
var _ SentPacketHandler = &sentPacketHandler{}
// clientAddressValidated indicates whether the address was validated beforehand by an address validation token.
// If the address was validated, the amplification limit doesn't apply. It has no effect for a client.
func NewSentPacketHandler(
initialPN protocol.PacketNumber,
initialMaxDatagramSize protocol.ByteCount,
rttStats *utils.RTTStats,
connStats *utils.ConnectionStats,
clientAddressValidated bool,
enableECN bool,
ignorePacketsBelow func(protocol.PacketNumber),
pers protocol.Perspective,
qlogger qlogwriter.Recorder,
logger utils.Logger,
) SentPacketHandler {
congestion := congestion.NewCubicSender(
congestion.DefaultClock{},
rttStats,
connStats,
initialMaxDatagramSize,
true, // use Reno
qlogger,
)
h := &sentPacketHandler{
peerCompletedAddressValidation: pers == protocol.PerspectiveServer,
peerAddressValidated: pers == protocol.PerspectiveClient || clientAddressValidated,
initialPackets: newPacketNumberSpace(initialPN, false),
handshakePackets: newPacketNumberSpace(0, false),
appDataPackets: newPacketNumberSpace(0, true),
lostPackets: *newLostPacketTracker(64),
rttStats: rttStats,
connStats: connStats,
congestion: congestion,
ignorePacketsBelow: ignorePacketsBelow,
perspective: pers,
qlogger: qlogger,
logger: logger,
}
if enableECN {
h.enableECN = true
h.ecnTracker = newECNTracker(logger, qlogger)
}
return h
}
func (h *sentPacketHandler) removeFromBytesInFlight(p *packet) {
if p.includedInBytesInFlight {
if p.Length > h.bytesInFlight {
panic("negative bytes_in_flight")
}
h.bytesInFlight -= p.Length
p.includedInBytesInFlight = false
}
}
func (h *sentPacketHandler) DropPackets(encLevel protocol.EncryptionLevel, now monotime.Time) {
// The server won't await address validation after the handshake is confirmed.
// This applies even if we didn't receive an ACK for a Handshake packet.
if h.perspective == protocol.PerspectiveClient && encLevel == protocol.EncryptionHandshake {
h.peerCompletedAddressValidation = true
}
// remove outstanding packets from bytes_in_flight
if encLevel == protocol.EncryptionInitial || encLevel == protocol.EncryptionHandshake {
pnSpace := h.getPacketNumberSpace(encLevel)
// We might already have dropped this packet number space.
if pnSpace == nil {
return
}
for _, p := range pnSpace.history.Packets() {
h.removeFromBytesInFlight(p)
}
}
// drop the packet history
//nolint:exhaustive // Not every packet number space can be dropped.
switch encLevel {
case protocol.EncryptionInitial:
h.initialPackets = nil
case protocol.EncryptionHandshake:
// Dropping the handshake packet number space means that the handshake is confirmed,
// see section 4.9.2 of RFC 9001.
h.handshakeConfirmed = true
h.handshakePackets = nil
case protocol.Encryption0RTT:
// This function is only called when 0-RTT is rejected,
// and not when the client drops 0-RTT keys when the handshake completes.
// When 0-RTT is rejected, all application data sent so far becomes invalid.
// Delete the packets from the history and remove them from bytes_in_flight.
for pn, p := range h.appDataPackets.history.Packets() {
if p.EncryptionLevel != protocol.Encryption0RTT {
break
}
h.removeFromBytesInFlight(p)
h.appDataPackets.history.Remove(pn)
}
default:
panic(fmt.Sprintf("Cannot drop keys for encryption level %s", encLevel))
}
if h.qlogger != nil && h.ptoCount != 0 {
h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
}
h.ptoCount = 0
h.numProbesToSend = 0
h.ptoMode = SendNone
h.setLossDetectionTimer(now)
}
func (h *sentPacketHandler) ReceivedBytes(n protocol.ByteCount, t monotime.Time) {
h.connStats.BytesReceived.Add(uint64(n))
wasAmplificationLimit := h.isAmplificationLimited()
h.bytesReceived += n
if wasAmplificationLimit && !h.isAmplificationLimited() {
h.setLossDetectionTimer(t)
}
}
func (h *sentPacketHandler) ReceivedPacket(l protocol.EncryptionLevel, t monotime.Time) {
h.connStats.PacketsReceived.Add(1)
if h.perspective == protocol.PerspectiveServer && l == protocol.EncryptionHandshake && !h.peerAddressValidated {
h.peerAddressValidated = true
h.setLossDetectionTimer(t)
}
}
func (h *sentPacketHandler) packetsInFlight() int {
packetsInFlight := h.appDataPackets.history.NumOutstanding()
if h.handshakePackets != nil {
packetsInFlight += h.handshakePackets.history.NumOutstanding()
}
if h.initialPackets != nil {
packetsInFlight += h.initialPackets.history.NumOutstanding()
}
return packetsInFlight
}
func (h *sentPacketHandler) SentPacket(
t monotime.Time,
pn, largestAcked protocol.PacketNumber,
streamFrames []StreamFrame,
frames []Frame,
encLevel protocol.EncryptionLevel,
ecn protocol.ECN,
size protocol.ByteCount,
isPathMTUProbePacket bool,
isPathProbePacket bool,
) {
h.bytesSent += size
h.connStats.BytesSent.Add(uint64(size))
h.connStats.PacketsSent.Add(1)
pnSpace := h.getPacketNumberSpace(encLevel)
if h.logger.Debug() && (pnSpace.history.HasOutstandingPackets() || pnSpace.history.HasOutstandingPathProbes()) {
for p := max(0, pnSpace.largestSent+1); p < pn; p++ {
h.logger.Debugf("Skipping packet number %d", p)
}
}
pnSpace.largestSent = pn
p := getPacket()
p.SendTime = t
p.EncryptionLevel = encLevel
p.Length = size
p.Frames = frames
p.LargestAcked = largestAcked
p.StreamFrames = streamFrames
p.IsPathMTUProbePacket = isPathMTUProbePacket
p.isPathProbePacket = isPathProbePacket
isAckEliciting := p.IsAckEliciting()
if isPathProbePacket {
pnSpace.history.SentPathProbePacket(pn, p)
h.setLossDetectionTimer(t)
return
}
if isAckEliciting {
pnSpace.lastAckElicitingPacketTime = t
h.bytesInFlight += size
p.includedInBytesInFlight = true
if h.numProbesToSend > 0 {
h.numProbesToSend--
}
}
h.congestion.OnPacketSent(t, h.bytesInFlight, pn, size, isAckEliciting)
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil {
h.ecnTracker.SentPacket(pn, ecn)
}
pnSpace.history.SentPacket(pn, p)
if !isAckEliciting {
if !h.peerCompletedAddressValidation {
h.setLossDetectionTimer(t)
}
return
}
if h.qlogger != nil {
h.qlogMetricsUpdated()
}
h.setLossDetectionTimer(t)
}
func (h *sentPacketHandler) qlogMetricsUpdated() {
var metricsUpdatedEvent qlog.MetricsUpdated
var updated bool
if h.rttStats.HasMeasurement() {
if h.lastMetrics.MinRTT != h.rttStats.MinRTT() {
metricsUpdatedEvent.MinRTT = h.rttStats.MinRTT()
h.lastMetrics.MinRTT = metricsUpdatedEvent.MinRTT
updated = true
}
if h.lastMetrics.SmoothedRTT != h.rttStats.SmoothedRTT() {
metricsUpdatedEvent.SmoothedRTT = h.rttStats.SmoothedRTT()
h.lastMetrics.SmoothedRTT = metricsUpdatedEvent.SmoothedRTT
updated = true
}
if h.lastMetrics.LatestRTT != h.rttStats.LatestRTT() {
metricsUpdatedEvent.LatestRTT = h.rttStats.LatestRTT()
h.lastMetrics.LatestRTT = metricsUpdatedEvent.LatestRTT
updated = true
}
if h.lastMetrics.RTTVariance != h.rttStats.MeanDeviation() {
metricsUpdatedEvent.RTTVariance = h.rttStats.MeanDeviation()
h.lastMetrics.RTTVariance = metricsUpdatedEvent.RTTVariance
updated = true
}
}
if cwnd := h.congestion.GetCongestionWindow(); h.lastMetrics.CongestionWindow != int(cwnd) {
metricsUpdatedEvent.CongestionWindow = int(cwnd)
h.lastMetrics.CongestionWindow = metricsUpdatedEvent.CongestionWindow
updated = true
}
if h.lastMetrics.BytesInFlight != int(h.bytesInFlight) {
metricsUpdatedEvent.BytesInFlight = int(h.bytesInFlight)
h.lastMetrics.BytesInFlight = metricsUpdatedEvent.BytesInFlight
updated = true
}
packetsInFlight := h.packetsInFlight()
if h.lastMetrics.PacketsInFlight != packetsInFlight {
metricsUpdatedEvent.PacketsInFlight = packetsInFlight
h.lastMetrics.PacketsInFlight = metricsUpdatedEvent.PacketsInFlight
updated = true
}
if updated {
h.qlogger.RecordEvent(metricsUpdatedEvent)
}
}
func (h *sentPacketHandler) getPacketNumberSpace(encLevel protocol.EncryptionLevel) *packetNumberSpace {
switch encLevel {
case protocol.EncryptionInitial:
return h.initialPackets
case protocol.EncryptionHandshake:
return h.handshakePackets
case protocol.Encryption0RTT, protocol.Encryption1RTT:
return h.appDataPackets
default:
panic("invalid packet number space")
}
}
func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) (bool /* contained 1-RTT packet */, error) {
pnSpace := h.getPacketNumberSpace(encLevel)
largestAcked := ack.LargestAcked()
if largestAcked > pnSpace.largestSent {
return false, &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "received ACK for an unsent packet",
}
}
// Servers complete address validation when a protected packet is received.
if h.perspective == protocol.PerspectiveClient && !h.peerCompletedAddressValidation &&
(encLevel == protocol.EncryptionHandshake || encLevel == protocol.Encryption1RTT) {
h.peerCompletedAddressValidation = true
h.logger.Debugf("Peer doesn't await address validation any longer.")
// Make sure that the timer is reset, even if this ACK doesn't acknowledge any (ack-eliciting) packets.
h.setLossDetectionTimer(rcvTime)
}
priorInFlight := h.bytesInFlight
ackedPackets, hasAckEliciting, err := h.detectAndRemoveAckedPackets(ack, encLevel)
if err != nil || len(ackedPackets) == 0 {
return false, err
}
// update the RTT, if:
// * the largest acked is newly acknowledged, AND
// * at least one new ack-eliciting packet was acknowledged
if len(ackedPackets) > 0 {
if p := ackedPackets[len(ackedPackets)-1]; p.PacketNumber == ack.LargestAcked() && !p.isPathProbePacket && hasAckEliciting {
// don't use the ack delay for Initial and Handshake packets
var ackDelay time.Duration
if encLevel == protocol.Encryption1RTT {
ackDelay = min(ack.DelayTime, h.rttStats.MaxAckDelay())
}
if h.largestAckedTime.IsZero() || !p.SendTime.Before(h.largestAckedTime) {
h.rttStats.UpdateRTT(rcvTime.Sub(p.SendTime), ackDelay)
if h.logger.Debug() {
h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation())
}
h.largestAckedTime = p.SendTime
}
h.congestion.MaybeExitSlowStart()
}
}
// Only inform the ECN tracker about new 1-RTT ACKs if the ACK increases the largest acked.
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil && largestAcked > pnSpace.largestAcked {
congested := h.ecnTracker.HandleNewlyAcked(ackedPackets, int64(ack.ECT0), int64(ack.ECT1), int64(ack.ECNCE))
if congested {
h.congestion.OnCongestionEvent(largestAcked, 0, priorInFlight)
}
}
pnSpace.largestAcked = max(pnSpace.largestAcked, largestAcked)
h.detectLostPackets(rcvTime, encLevel)
if encLevel == protocol.Encryption1RTT {
h.detectLostPathProbes(rcvTime)
}
var acked1RTTPacket bool
for _, p := range ackedPackets {
if p.includedInBytesInFlight {
h.congestion.OnPacketAcked(p.PacketNumber, p.Length, priorInFlight, rcvTime)
}
if p.EncryptionLevel == protocol.Encryption1RTT {
acked1RTTPacket = true
}
h.removeFromBytesInFlight(p.packet)
if !p.isPathProbePacket {
putPacket(p.packet)
}
}
// detect spurious losses for application data packets, if the ACK was not reordered
if encLevel == protocol.Encryption1RTT && largestAcked == pnSpace.largestAcked {
h.detectSpuriousLosses(
ack,
rcvTime.Add(-min(ack.DelayTime, h.rttStats.MaxAckDelay())),
)
// clean up lost packet history
h.lostPackets.DeleteBefore(rcvTime.Add(-3 * h.rttStats.PTO(false)))
}
// After this point, we must not use ackedPackets any longer!
// We've already returned the buffers.
ackedPackets = nil //nolint:ineffassign // This is just to be on the safe side.
clear(h.ackedPackets) // make sure the memory is released
h.ackedPackets = h.ackedPackets[:0]
// Reset the pto_count unless the client is unsure if the server has validated the client's address.
if h.peerCompletedAddressValidation {
if h.qlogger != nil && h.ptoCount != 0 {
h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
}
h.ptoCount = 0
}
h.numProbesToSend = 0
if h.qlogger != nil {
h.qlogMetricsUpdated()
}
h.setLossDetectionTimer(rcvTime)
return acked1RTTPacket, nil
}
func (h *sentPacketHandler) detectSpuriousLosses(ack *wire.AckFrame, ackTime monotime.Time) {
var maxPacketReordering protocol.PacketNumber
var maxTimeReordering time.Duration
ackRangeIdx := len(ack.AckRanges) - 1
var spuriousLosses []protocol.PacketNumber
for pn, sendTime := range h.lostPackets.All() {
ackRange := ack.AckRanges[ackRangeIdx]
for pn > ackRange.Largest {
// this should never happen, since detectSpuriousLosses is only called for ACKs that increase the largest acked
if ackRangeIdx == 0 {
break
}
ackRangeIdx--
ackRange = ack.AckRanges[ackRangeIdx]
}
if pn < ackRange.Smallest {
continue
}
if pn <= ackRange.Largest {
packetReordering := h.appDataPackets.history.Difference(ack.LargestAcked(), pn)
timeReordering := ackTime.Sub(sendTime)
maxPacketReordering = max(maxPacketReordering, packetReordering)
maxTimeReordering = max(maxTimeReordering, timeReordering)
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.SpuriousLoss{
EncryptionLevel: protocol.Encryption1RTT,
PacketNumber: pn,
PacketReordering: uint64(packetReordering),
TimeReordering: timeReordering,
})
}
spuriousLosses = append(spuriousLosses, pn)
}
}
for _, pn := range spuriousLosses {
h.lostPackets.Delete(pn)
}
}
// Packets are returned in ascending packet number order.
func (h *sentPacketHandler) detectAndRemoveAckedPackets(
ack *wire.AckFrame,
encLevel protocol.EncryptionLevel,
) (_ []packetWithPacketNumber, hasAckEliciting bool, _ error) {
if len(h.ackedPackets) > 0 {
return nil, false, errors.New("ackhandler BUG: ackedPackets slice not empty")
}
pnSpace := h.getPacketNumberSpace(encLevel)
if encLevel == protocol.Encryption1RTT {
for p := range pnSpace.history.SkippedPackets() {
if ack.AcksPacket(p) {
return nil, false, &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: fmt.Sprintf("received an ACK for skipped packet number: %d (%s)", p, encLevel),
}
}
}
}
var ackRangeIndex int
lowestAcked := ack.LowestAcked()
largestAcked := ack.LargestAcked()
for pn, p := range pnSpace.history.Packets() {
// ignore packets below the lowest acked
if pn < lowestAcked {
continue
}
if pn > largestAcked {
break
}
if ack.HasMissingRanges() {
ackRange := ack.AckRanges[len(ack.AckRanges)-1-ackRangeIndex]
for pn > ackRange.Largest && ackRangeIndex < len(ack.AckRanges)-1 {
ackRangeIndex++
ackRange = ack.AckRanges[len(ack.AckRanges)-1-ackRangeIndex]
}
if pn < ackRange.Smallest { // packet not contained in ACK range
continue
}
if pn > ackRange.Largest {
return nil, false, fmt.Errorf("BUG: ackhandler would have acked wrong packet %d, while evaluating range %d -> %d", pn, ackRange.Smallest, ackRange.Largest)
}
}
if p.isPathProbePacket {
probePacket := pnSpace.history.RemovePathProbe(pn)
// the probe packet might already have been declared lost
if probePacket != nil {
h.ackedPackets = append(h.ackedPackets, packetWithPacketNumber{PacketNumber: pn, packet: probePacket})
}
continue
}
if p.IsAckEliciting() {
hasAckEliciting = true
}
h.ackedPackets = append(h.ackedPackets, packetWithPacketNumber{PacketNumber: pn, packet: p})
}
if h.logger.Debug() && len(h.ackedPackets) > 0 {
pns := make([]protocol.PacketNumber, len(h.ackedPackets))
for i, p := range h.ackedPackets {
pns[i] = p.PacketNumber
}
h.logger.Debugf("\tnewly acked packets (%d): %d", len(pns), pns)
}
for _, p := range h.ackedPackets {
if p.LargestAcked != protocol.InvalidPacketNumber && encLevel == protocol.Encryption1RTT && h.ignorePacketsBelow != nil {
h.ignorePacketsBelow(p.LargestAcked + 1)
}
for _, f := range p.Frames {
if f.Handler != nil {
f.Handler.OnAcked(f.Frame)
}
}
for _, f := range p.StreamFrames {
if f.Handler != nil {
f.Handler.OnAcked(f.Frame)
}
}
if err := pnSpace.history.Remove(p.PacketNumber); err != nil {
return nil, false, err
}
}
// TODO: add support for the transport:packets_acked qlog event
return h.ackedPackets, hasAckEliciting, nil
}
func (h *sentPacketHandler) getLossTimeAndSpace() (monotime.Time, protocol.EncryptionLevel) {
var encLevel protocol.EncryptionLevel
var lossTime monotime.Time
if h.initialPackets != nil {
lossTime = h.initialPackets.lossTime
encLevel = protocol.EncryptionInitial
}
if h.handshakePackets != nil && (lossTime.IsZero() || (!h.handshakePackets.lossTime.IsZero() && h.handshakePackets.lossTime.Before(lossTime))) {
lossTime = h.handshakePackets.lossTime
encLevel = protocol.EncryptionHandshake
}
if lossTime.IsZero() || (!h.appDataPackets.lossTime.IsZero() && h.appDataPackets.lossTime.Before(lossTime)) {
lossTime = h.appDataPackets.lossTime
encLevel = protocol.Encryption1RTT
}
return lossTime, encLevel
}
func (h *sentPacketHandler) getScaledPTO(includeMaxAckDelay bool) time.Duration {
pto := h.rttStats.PTO(includeMaxAckDelay) << h.ptoCount
if pto > maxPTODuration || pto <= 0 {
return maxPTODuration
}
return pto
}
// same logic as getLossTimeAndSpace, but for lastAckElicitingPacketTime instead of lossTime
func (h *sentPacketHandler) getPTOTimeAndSpace(now monotime.Time) (pto monotime.Time, encLevel protocol.EncryptionLevel) {
// We only send application data probe packets once the handshake is confirmed,
// because before that, we don't have the keys to decrypt ACKs sent in 1-RTT packets.
if !h.handshakeConfirmed && !h.hasOutstandingCryptoPackets() {
if h.peerCompletedAddressValidation {
return
}
t := now.Add(h.getScaledPTO(false))
if h.initialPackets != nil {
return t, protocol.EncryptionInitial
}
return t, protocol.EncryptionHandshake
}
if h.initialPackets != nil && h.initialPackets.history.HasOutstandingPackets() &&
!h.initialPackets.lastAckElicitingPacketTime.IsZero() {
encLevel = protocol.EncryptionInitial
if t := h.initialPackets.lastAckElicitingPacketTime; !t.IsZero() {
pto = t.Add(h.getScaledPTO(false))
}
}
if h.handshakePackets != nil && h.handshakePackets.history.HasOutstandingPackets() &&
!h.handshakePackets.lastAckElicitingPacketTime.IsZero() {
t := h.handshakePackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(false))
if pto.IsZero() || (!t.IsZero() && t.Before(pto)) {
pto = t
encLevel = protocol.EncryptionHandshake
}
}
if h.handshakeConfirmed && h.appDataPackets.history.HasOutstandingPackets() &&
!h.appDataPackets.lastAckElicitingPacketTime.IsZero() {
t := h.appDataPackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(true))
if pto.IsZero() || (!t.IsZero() && t.Before(pto)) {
pto = t
encLevel = protocol.Encryption1RTT
}
}
return pto, encLevel
}
func (h *sentPacketHandler) hasOutstandingCryptoPackets() bool {
if h.initialPackets != nil && h.initialPackets.history.HasOutstandingPackets() {
return true
}
if h.handshakePackets != nil && h.handshakePackets.history.HasOutstandingPackets() {
return true
}
return false
}
func (h *sentPacketHandler) setLossDetectionTimer(now monotime.Time) {
oldAlarm := h.alarm // only needed in case tracing is enabled
newAlarm := h.lossDetectionTime(now)
h.alarm = newAlarm
hasAlarm := !newAlarm.Time.IsZero()
if !hasAlarm && !oldAlarm.Time.IsZero() {
h.logger.Debugf("Canceling loss detection timer.")
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.LossTimerUpdated{
Type: qlog.LossTimerUpdateTypeCancelled,
})
}
}
if h.qlogger != nil && hasAlarm && newAlarm != oldAlarm {
h.qlogger.RecordEvent(qlog.LossTimerUpdated{
Type: qlog.LossTimerUpdateTypeSet,
TimerType: newAlarm.TimerType,
EncLevel: newAlarm.EncryptionLevel,
Time: newAlarm.Time.ToTime(),
})
}
}
func (h *sentPacketHandler) lossDetectionTime(now monotime.Time) alarmTimer {
// cancel the alarm if no packets are outstanding
if h.peerCompletedAddressValidation && !h.hasOutstandingCryptoPackets() &&
!h.appDataPackets.history.HasOutstandingPackets() && !h.appDataPackets.history.HasOutstandingPathProbes() {
return alarmTimer{}
}
// cancel the alarm if amplification limited
if h.isAmplificationLimited() {
return alarmTimer{}
}
var pathProbeLossTime monotime.Time
if h.appDataPackets.history.HasOutstandingPathProbes() {
if _, p := h.appDataPackets.history.FirstOutstandingPathProbe(); p != nil {
pathProbeLossTime = p.SendTime.Add(pathProbePacketLossTimeout)
}
}
// early retransmit timer or time loss detection
lossTime, encLevel := h.getLossTimeAndSpace()
if !lossTime.IsZero() && (pathProbeLossTime.IsZero() || lossTime.Before(pathProbeLossTime)) {
return alarmTimer{
Time: lossTime,
TimerType: qlog.TimerTypeACK,
EncryptionLevel: encLevel,
}
}
ptoTime, encLevel := h.getPTOTimeAndSpace(now)
if !ptoTime.IsZero() && (pathProbeLossTime.IsZero() || ptoTime.Before(pathProbeLossTime)) {
return alarmTimer{
Time: ptoTime,
TimerType: qlog.TimerTypePTO,
EncryptionLevel: encLevel,
}
}
if !pathProbeLossTime.IsZero() {
return alarmTimer{
Time: pathProbeLossTime,
TimerType: qlog.TimerTypePathProbe,
EncryptionLevel: protocol.Encryption1RTT,
}
}
return alarmTimer{}
}
func (h *sentPacketHandler) detectLostPathProbes(now monotime.Time) {
if !h.appDataPackets.history.HasOutstandingPathProbes() {
return
}
lossTime := now.Add(-pathProbePacketLossTimeout)
// RemovePathProbe cannot be called while iterating.
var lostPathProbes []packetWithPacketNumber
for pn, p := range h.appDataPackets.history.PathProbes() {
if !p.SendTime.After(lossTime) {
lostPathProbes = append(lostPathProbes, packetWithPacketNumber{PacketNumber: pn, packet: p})
}
}
for _, p := range lostPathProbes {
for _, f := range p.Frames {
f.Handler.OnLost(f.Frame)
}
h.appDataPackets.history.RemovePathProbe(p.PacketNumber)
}
}
func (h *sentPacketHandler) detectLostPackets(now monotime.Time, encLevel protocol.EncryptionLevel) {
pnSpace := h.getPacketNumberSpace(encLevel)
pnSpace.lossTime = 0
maxRTT := float64(max(h.rttStats.LatestRTT(), h.rttStats.SmoothedRTT()))
lossDelay := time.Duration(timeThreshold * maxRTT)
// Minimum time of granularity before packets are deemed lost.
lossDelay = max(lossDelay, protocol.TimerGranularity)
// Packets sent before this time are deemed lost.
lostSendTime := now.Add(-lossDelay)
priorInFlight := h.bytesInFlight
for pn, p := range pnSpace.history.Packets() {
if pn > pnSpace.largestAcked {
break
}
var packetLost bool
if !p.SendTime.After(lostSendTime) {
packetLost = true
if !p.isPathProbePacket && p.IsAckEliciting() {
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (time threshold)", pn)
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.PacketLost{
Header: qlog.PacketHeader{
PacketType: qlog.EncryptionLevelToPacketType(p.EncryptionLevel),
PacketNumber: pn,
},
Trigger: qlog.PacketLossTimeThreshold,
})
}
}
} else if pnSpace.history.Difference(pnSpace.largestAcked, pn) >= packetThreshold {
packetLost = true
if !p.isPathProbePacket && p.IsAckEliciting() {
if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (reordering threshold)", pn)
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.PacketLost{
Header: qlog.PacketHeader{
PacketType: qlog.EncryptionLevelToPacketType(p.EncryptionLevel),
PacketNumber: pn,
},
Trigger: qlog.PacketLossReorderingThreshold,
})
}
}
} else if pnSpace.lossTime.IsZero() {
// Note: This conditional is only entered once per call
lossTime := p.SendTime.Add(lossDelay)
if h.logger.Debug() {
h.logger.Debugf("\tsetting loss timer for packet %d (%s) to %s (in %s)", pn, encLevel, lossDelay, lossTime)
}
pnSpace.lossTime = lossTime
}
if packetLost {
if encLevel == protocol.Encryption0RTT || encLevel == protocol.Encryption1RTT {
h.lostPackets.Add(pn, p.SendTime)
}
pnSpace.history.DeclareLost(pn)
if !p.isPathProbePacket && p.IsAckEliciting() {
// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p)
if !p.IsPathMTUProbePacket {
h.congestion.OnCongestionEvent(pn, p.Length, priorInFlight)
}
if encLevel == protocol.Encryption1RTT && h.ecnTracker != nil {
h.ecnTracker.LostPacket(pn)
}
}
}
}
}
func (h *sentPacketHandler) OnLossDetectionTimeout(now monotime.Time) error {
defer h.setLossDetectionTimer(now)
if h.handshakeConfirmed {
h.detectLostPathProbes(now)
}
earliestLossTime, encLevel := h.getLossTimeAndSpace()
if !earliestLossTime.IsZero() {
if h.logger.Debug() {
h.logger.Debugf("Loss detection alarm fired in loss timer mode. Loss time: %s", earliestLossTime)
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.LossTimerUpdated{
Type: qlog.LossTimerUpdateTypeExpired,
TimerType: qlog.TimerTypeACK,
EncLevel: encLevel,
})
}
// Early retransmit or time loss detection
h.detectLostPackets(now, encLevel)
return nil
}
// PTO
// When all outstanding are acknowledged, the alarm is canceled in setLossDetectionTimer.
// However, there's no way to reset the timer in the connection.
// When OnLossDetectionTimeout is called, we therefore need to make sure that there are
// actually packets outstanding.
if h.bytesInFlight == 0 && !h.peerCompletedAddressValidation {
h.ptoCount++
h.numProbesToSend++
if h.initialPackets != nil {
h.ptoMode = SendPTOInitial
} else if h.handshakePackets != nil {
h.ptoMode = SendPTOHandshake
} else {
return errors.New("sentPacketHandler BUG: PTO fired, but bytes_in_flight is 0 and Initial and Handshake already dropped")
}
return nil
}
ptoTime, encLevel := h.getPTOTimeAndSpace(now)
if ptoTime.IsZero() {
return nil
}
ps := h.getPacketNumberSpace(encLevel)
if !ps.history.HasOutstandingPackets() && !ps.history.HasOutstandingPathProbes() && !h.peerCompletedAddressValidation {
return nil
}
h.ptoCount++
if h.logger.Debug() {
h.logger.Debugf("Loss detection alarm for %s fired in PTO mode. PTO count: %d", encLevel, h.ptoCount)
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.LossTimerUpdated{
Type: qlog.LossTimerUpdateTypeExpired,
TimerType: qlog.TimerTypePTO,
EncLevel: encLevel,
})
h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: h.ptoCount})
}
h.numProbesToSend += 2
//nolint:exhaustive // We never arm a PTO timer for 0-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
h.ptoMode = SendPTOInitial
case protocol.EncryptionHandshake:
h.ptoMode = SendPTOHandshake
case protocol.Encryption1RTT:
// skip a packet number in order to elicit an immediate ACK
pn := h.PopPacketNumber(protocol.Encryption1RTT)
h.getPacketNumberSpace(protocol.Encryption1RTT).history.SkippedPacket(pn)
h.ptoMode = SendPTOAppData
default:
return fmt.Errorf("PTO timer in unexpected encryption level: %s", encLevel)
}
return nil
}
func (h *sentPacketHandler) GetLossDetectionTimeout() monotime.Time {
return h.alarm.Time
}
func (h *sentPacketHandler) ECNMode(isShortHeaderPacket bool) protocol.ECN {
if !h.enableECN {
return protocol.ECNUnsupported
}
if !isShortHeaderPacket {
return protocol.ECNNon
}
return h.ecnTracker.Mode()
}
func (h *sentPacketHandler) PeekPacketNumber(encLevel protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen) {
pnSpace := h.getPacketNumberSpace(encLevel)
pn := pnSpace.pns.Peek()
// See section 17.1 of RFC 9000.
return pn, protocol.PacketNumberLengthForHeader(pn, pnSpace.largestAcked)
}
func (h *sentPacketHandler) PopPacketNumber(encLevel protocol.EncryptionLevel) protocol.PacketNumber {
pnSpace := h.getPacketNumberSpace(encLevel)
skipped, pn := pnSpace.pns.Pop()
if skipped {
skippedPN := pn - 1
pnSpace.history.SkippedPacket(skippedPN)
if h.logger.Debug() {
h.logger.Debugf("Skipping packet number %d", skippedPN)
}
}
return pn
}
func (h *sentPacketHandler) SendMode(now monotime.Time) SendMode {
numTrackedPackets := h.appDataPackets.history.Len()
if h.initialPackets != nil {
numTrackedPackets += h.initialPackets.history.Len()
}
if h.handshakePackets != nil {
numTrackedPackets += h.handshakePackets.history.Len()
}
if h.isAmplificationLimited() {
h.logger.Debugf("Amplification window limited. Received %d bytes, already sent out %d bytes", h.bytesReceived, h.bytesSent)
return SendNone
}
// Don't send any packets if we're keeping track of the maximum number of packets.
// Note that since MaxOutstandingSentPackets is smaller than MaxTrackedSentPackets,
// we will stop sending out new data when reaching MaxOutstandingSentPackets,
// but still allow sending of retransmissions and ACKs.
if numTrackedPackets >= protocol.MaxTrackedSentPackets {
if h.logger.Debug() {
h.logger.Debugf("Limited by the number of tracked packets: tracking %d packets, maximum %d", numTrackedPackets, protocol.MaxTrackedSentPackets)
}
return SendNone
}
if h.numProbesToSend > 0 {
return h.ptoMode
}
// Only send ACKs if we're congestion limited.
if !h.congestion.CanSend(h.bytesInFlight) {
if h.logger.Debug() {
h.logger.Debugf("Congestion limited: bytes in flight %d, window %d", h.bytesInFlight, h.congestion.GetCongestionWindow())
}
return SendAck
}
if numTrackedPackets >= protocol.MaxOutstandingSentPackets {
if h.logger.Debug() {
h.logger.Debugf("Max outstanding limited: tracking %d packets, maximum: %d", numTrackedPackets, protocol.MaxOutstandingSentPackets)
}
return SendAck
}
if !h.congestion.HasPacingBudget(now) {
return SendPacingLimited
}
return SendAny
}
func (h *sentPacketHandler) TimeUntilSend() monotime.Time {
return h.congestion.TimeUntilSend(h.bytesInFlight)
}
func (h *sentPacketHandler) SetMaxDatagramSize(s protocol.ByteCount) {
h.congestion.SetMaxDatagramSize(s)
}
func (h *sentPacketHandler) isAmplificationLimited() bool {
if h.peerAddressValidated {
return false
}
return h.bytesSent >= amplificationFactor*h.bytesReceived
}
func (h *sentPacketHandler) QueueProbePacket(encLevel protocol.EncryptionLevel) bool {
pnSpace := h.getPacketNumberSpace(encLevel)
pn, p := pnSpace.history.FirstOutstanding()
if p == nil {
return false
}
// TODO: don't declare the packet lost here.
// Keep track of acknowledged frames instead.
// Call DeclareLost before queueFramesForRetransmission, which clears the packet's frames.
pnSpace.history.DeclareLost(pn)
h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p)
return true
}
func (h *sentPacketHandler) queueFramesForRetransmission(p *packet) {
if len(p.Frames) == 0 && len(p.StreamFrames) == 0 {
panic("no frames")
}
for _, f := range p.Frames {
if f.Handler != nil {
f.Handler.OnLost(f.Frame)
}
}
for _, f := range p.StreamFrames {
if f.Handler != nil {
f.Handler.OnLost(f.Frame)
}
}
p.StreamFrames = nil
p.Frames = nil
}
func (h *sentPacketHandler) ResetForRetry(now monotime.Time) {
h.bytesInFlight = 0
var firstPacketSendTime monotime.Time
for _, p := range h.initialPackets.history.Packets() {
if firstPacketSendTime.IsZero() {
firstPacketSendTime = p.SendTime
}
if p.IsAckEliciting() {
h.queueFramesForRetransmission(p)
}
}
// All application data packets sent at this point are 0-RTT packets.
// In the case of a Retry, we can assume that the server dropped all of them.
for _, p := range h.appDataPackets.history.Packets() {
if p.IsAckEliciting() {
h.queueFramesForRetransmission(p)
}
}
// Only use the Retry to estimate the RTT if we didn't send any retransmission for the Initial.
// 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(max(minRTTAfterRetry, now.Sub(firstPacketSendTime)), 0)
if h.logger.Debug() {
h.logger.Debugf("\tupdated RTT: %s (σ: %s)", h.rttStats.SmoothedRTT(), h.rttStats.MeanDeviation())
}
if h.qlogger != nil {
h.qlogMetricsUpdated()
}
}
h.initialPackets = newPacketNumberSpace(h.initialPackets.pns.Peek(), false)
h.appDataPackets = newPacketNumberSpace(h.appDataPackets.pns.Peek(), true)
oldAlarm := h.alarm
h.alarm = alarmTimer{}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.PTOCountUpdated{PTOCount: 0})
if !oldAlarm.Time.IsZero() {
h.qlogger.RecordEvent(qlog.LossTimerUpdated{
Type: qlog.LossTimerUpdateTypeCancelled,
})
}
}
h.ptoCount = 0
}
func (h *sentPacketHandler) MigratedPath(now monotime.Time, initialMaxDatagramSize protocol.ByteCount) {
h.rttStats.ResetForPathMigration()
for pn, p := range h.appDataPackets.history.Packets() {
h.appDataPackets.history.DeclareLost(pn)
if !p.isPathProbePacket {
h.removeFromBytesInFlight(p)
if p.IsAckEliciting() {
h.queueFramesForRetransmission(p)
}
}
}
for pn := range h.appDataPackets.history.PathProbes() {
h.appDataPackets.history.RemovePathProbe(pn)
}
h.congestion = congestion.NewCubicSender(
congestion.DefaultClock{},
h.rttStats,
h.connStats,
initialMaxDatagramSize,
true, // use Reno
h.qlogger,
)
h.setLossDetectionTimer(now)
}
package ackhandler
import (
"fmt"
"iter"
"slices"
"github.com/quic-go/quic-go/internal/protocol"
)
const maxSkippedPackets = 4
type sentPacketHistory struct {
packets []*packet
pathProbePackets []packetWithPacketNumber
skippedPackets []protocol.PacketNumber
numOutstanding int
firstPacketNumber protocol.PacketNumber
highestPacketNumber protocol.PacketNumber
}
func newSentPacketHistory(isAppData bool) *sentPacketHistory {
h := &sentPacketHistory{
highestPacketNumber: protocol.InvalidPacketNumber,
firstPacketNumber: protocol.InvalidPacketNumber,
}
if isAppData {
h.packets = make([]*packet, 0, 32)
h.skippedPackets = make([]protocol.PacketNumber, 0, maxSkippedPackets)
} else {
h.packets = make([]*packet, 0, 6)
}
return h
}
func (h *sentPacketHistory) checkSequentialPacketNumberUse(pn protocol.PacketNumber) {
if h.highestPacketNumber != protocol.InvalidPacketNumber {
if pn != h.highestPacketNumber+1 {
panic("non-sequential packet number use")
}
}
h.highestPacketNumber = pn
if len(h.packets) == 0 {
h.firstPacketNumber = pn
}
}
func (h *sentPacketHistory) SkippedPacket(pn protocol.PacketNumber) {
h.checkSequentialPacketNumberUse(pn)
if len(h.packets) > 0 {
h.packets = append(h.packets, nil)
}
if len(h.skippedPackets) == maxSkippedPackets {
h.skippedPackets = slices.Delete(h.skippedPackets, 0, 1)
}
h.skippedPackets = append(h.skippedPackets, pn)
}
func (h *sentPacketHistory) SentPacket(pn protocol.PacketNumber, p *packet) {
h.checkSequentialPacketNumberUse(pn)
h.packets = append(h.packets, p)
if p.Outstanding() {
h.numOutstanding++
}
}
func (h *sentPacketHistory) SentPathProbePacket(pn protocol.PacketNumber, p *packet) {
h.checkSequentialPacketNumberUse(pn)
h.packets = append(h.packets, &packet{isPathProbePacket: true})
h.pathProbePackets = append(h.pathProbePackets, packetWithPacketNumber{PacketNumber: pn, packet: p})
}
func (h *sentPacketHistory) Packets() iter.Seq2[protocol.PacketNumber, *packet] {
return func(yield func(protocol.PacketNumber, *packet) bool) {
// h.firstPacketNumber might be updated in the yield function,
// so we need to save it here.
firstPacketNumber := h.firstPacketNumber
for i, p := range h.packets {
if p == nil {
continue
}
if !yield(firstPacketNumber+protocol.PacketNumber(i), p) {
return
}
}
}
}
func (h *sentPacketHistory) PathProbes() iter.Seq2[protocol.PacketNumber, *packet] {
return func(yield func(protocol.PacketNumber, *packet) bool) {
for _, p := range h.pathProbePackets {
if !yield(p.PacketNumber, p.packet) {
return
}
}
}
}
// FirstOutstanding returns the first outstanding packet.
func (h *sentPacketHistory) FirstOutstanding() (protocol.PacketNumber, *packet) {
if !h.HasOutstandingPackets() {
return protocol.InvalidPacketNumber, nil
}
for i, p := range h.packets {
if p != nil && p.Outstanding() {
return h.firstPacketNumber + protocol.PacketNumber(i), p
}
}
return protocol.InvalidPacketNumber, nil
}
// FirstOutstandingPathProbe returns the first outstanding path probe packet
func (h *sentPacketHistory) FirstOutstandingPathProbe() (protocol.PacketNumber, *packet) {
if len(h.pathProbePackets) == 0 {
return protocol.InvalidPacketNumber, nil
}
return h.pathProbePackets[0].PacketNumber, h.pathProbePackets[0].packet
}
func (h *sentPacketHistory) SkippedPackets() iter.Seq[protocol.PacketNumber] {
return func(yield func(protocol.PacketNumber) bool) {
for _, p := range h.skippedPackets {
if !yield(p) {
return
}
}
}
}
func (h *sentPacketHistory) Len() int {
return len(h.packets)
}
func (h *sentPacketHistory) NumOutstanding() int {
return h.numOutstanding
}
// Remove removes a packet from the sent packet history.
// It must not be used for skipped packet numbers.
func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error {
idx, ok := h.getIndex(pn)
if !ok {
return fmt.Errorf("packet %d not found in sent packet history", pn)
}
p := h.packets[idx]
if p.Outstanding() {
h.numOutstanding--
if h.numOutstanding < 0 {
panic("negative number of outstanding packets")
}
}
h.packets[idx] = nil
// clean up all skipped packets directly before this packet number
var hasPacketBefore bool
for idx > 0 {
idx--
if h.packets[idx] != nil {
hasPacketBefore = true
break
}
}
if !hasPacketBefore {
h.cleanupStart()
}
if len(h.packets) > 0 && h.packets[0] == nil {
panic("cleanup failed")
}
return nil
}
// RemovePathProbe removes a path probe packet.
// It scales O(N), but that's ok, since we don't expect to send many path probe packets.
// It is not valid to call this function in IteratePathProbes.
func (h *sentPacketHistory) RemovePathProbe(pn protocol.PacketNumber) *packet {
var packetToDelete *packet
idx := -1
for i, p := range h.pathProbePackets {
if p.PacketNumber == pn {
packetToDelete = p.packet
idx = i
break
}
}
if idx != -1 {
// don't use slices.Delete, because it zeros the deleted element
copy(h.pathProbePackets[idx:], h.pathProbePackets[idx+1:])
h.pathProbePackets = h.pathProbePackets[:len(h.pathProbePackets)-1]
}
return packetToDelete
}
// getIndex gets the index of packet p in the packets slice.
func (h *sentPacketHistory) getIndex(p protocol.PacketNumber) (int, bool) {
if len(h.packets) == 0 {
return 0, false
}
if p < h.firstPacketNumber {
return 0, false
}
index := int(p - h.firstPacketNumber)
if index > len(h.packets)-1 {
return 0, false
}
return index, true
}
func (h *sentPacketHistory) HasOutstandingPackets() bool {
return h.numOutstanding > 0
}
func (h *sentPacketHistory) HasOutstandingPathProbes() bool {
return len(h.pathProbePackets) > 0
}
// delete all nil entries at the beginning of the packets slice
func (h *sentPacketHistory) cleanupStart() {
for i, p := range h.packets {
if p != nil {
h.packets = h.packets[i:]
h.firstPacketNumber += protocol.PacketNumber(i)
return
}
}
h.packets = h.packets[:0]
h.firstPacketNumber = protocol.InvalidPacketNumber
}
func (h *sentPacketHistory) LowestPacketNumber() protocol.PacketNumber {
if len(h.packets) == 0 {
return protocol.InvalidPacketNumber
}
return h.firstPacketNumber
}
func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) {
idx, ok := h.getIndex(pn)
if !ok {
return
}
p := h.packets[idx]
if p.Outstanding() {
h.numOutstanding--
if h.numOutstanding < 0 {
panic("negative number of outstanding packets")
}
}
h.packets[idx] = nil
if idx == 0 {
h.cleanupStart()
}
}
// Difference returns the difference between two packet numbers a and b (a - b),
// taking into account any skipped packet numbers between them.
//
// Note that old skipped packets are garbage collected at some point,
// so this function is not guaranteed to return the correct result after a while.
func (h *sentPacketHistory) Difference(a, b protocol.PacketNumber) protocol.PacketNumber {
diff := a - b
if len(h.skippedPackets) == 0 {
return diff
}
if a < h.skippedPackets[0] || b > h.skippedPackets[len(h.skippedPackets)-1] {
return diff
}
for _, p := range h.skippedPackets {
if p > b && p < a {
diff--
}
}
return diff
}
package congestion
import (
"time"
"github.com/quic-go/quic-go/internal/protocol"
)
// Bandwidth of a connection
type Bandwidth uint64
const (
// BitsPerSecond is 1 bit per second
BitsPerSecond Bandwidth = 1
// BytesPerSecond is 1 byte per second
BytesPerSecond = 8 * BitsPerSecond
)
// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta
func BandwidthFromDelta(bytes protocol.ByteCount, delta time.Duration) Bandwidth {
return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond
}
package congestion
import (
"github.com/quic-go/quic-go/internal/monotime"
)
// A Clock returns the current time
type Clock interface {
Now() monotime.Time
}
// DefaultClock implements the Clock interface using the Go stdlib clock.
type DefaultClock struct{}
var _ Clock = DefaultClock{}
// Now gets the current time
func (DefaultClock) Now() monotime.Time {
return monotime.Now()
}
package congestion
import (
"math"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
)
// This cubic implementation is based on the one found in Chromiums's QUIC
// implementation, in the files net/quic/congestion_control/cubic.{hh,cc}.
// Constants based on TCP defaults.
// The following constants are in 2^10 fractions of a second instead of ms to
// allow a 10 shift right to divide.
// 1024*1024^3 (first 1024 is from 0.100^3)
// where 0.100 is 100 ms which is the scaling round trip time.
const (
cubeScale = 40
cubeCongestionWindowScale = 410
cubeFactor = 1 << cubeScale / cubeCongestionWindowScale / maxDatagramSize
// TODO: when re-enabling cubic, make sure to use the actual packet size here
maxDatagramSize = protocol.ByteCount(protocol.InitialPacketSize)
)
const defaultNumConnections = 1
// Default Cubic backoff factor
const beta float32 = 0.7
// Additional backoff factor when loss occurs in the concave part of the Cubic
// curve. This additional backoff factor is expected to give up bandwidth to
// new concurrent flows and speed up convergence.
const betaLastMax float32 = 0.85
// Cubic implements the cubic algorithm from TCP
type Cubic struct {
clock Clock
// Number of connections to simulate.
numConnections int
// Time when this cycle started, after last loss event.
epoch monotime.Time
// Max congestion window used just before last loss event.
// Note: to improve fairness to other streams an additional back off is
// applied to this value if the new value is below our latest value.
lastMaxCongestionWindow protocol.ByteCount
// Number of acked bytes since the cycle started (epoch).
ackedBytesCount protocol.ByteCount
// TCP Reno equivalent congestion window in packets.
estimatedTCPcongestionWindow protocol.ByteCount
// Origin point of cubic function.
originPointCongestionWindow protocol.ByteCount
// Time to origin point of cubic function in 2^10 fractions of a second.
timeToOriginPoint uint32
// Last congestion window in packets computed by cubic function.
lastTargetCongestionWindow protocol.ByteCount
}
// NewCubic returns a new Cubic instance
func NewCubic(clock Clock) *Cubic {
c := &Cubic{
clock: clock,
numConnections: defaultNumConnections,
}
c.Reset()
return c
}
// Reset is called after a timeout to reset the cubic state
func (c *Cubic) Reset() {
c.epoch = 0
c.lastMaxCongestionWindow = 0
c.ackedBytesCount = 0
c.estimatedTCPcongestionWindow = 0
c.originPointCongestionWindow = 0
c.timeToOriginPoint = 0
c.lastTargetCongestionWindow = 0
}
func (c *Cubic) alpha() float32 {
// TCPFriendly alpha is described in Section 3.3 of the CUBIC paper. Note that
// beta here is a cwnd multiplier, and is equal to 1-beta from the paper.
// We derive the equivalent alpha for an N-connection emulation as:
b := c.beta()
return 3 * float32(c.numConnections) * float32(c.numConnections) * (1 - b) / (1 + b)
}
func (c *Cubic) beta() float32 {
// kNConnectionBeta is the backoff factor after loss for our N-connection
// emulation, which emulates the effective backoff of an ensemble of N
// TCP-Reno connections on a single loss event. The effective multiplier is
// computed as:
return (float32(c.numConnections) - 1 + beta) / float32(c.numConnections)
}
func (c *Cubic) betaLastMax() float32 {
// betaLastMax is the additional backoff factor after loss for our
// N-connection emulation, which emulates the additional backoff of
// an ensemble of N TCP-Reno connections on a single loss event. The
// effective multiplier is computed as:
return (float32(c.numConnections) - 1 + betaLastMax) / float32(c.numConnections)
}
// OnApplicationLimited is called on ack arrival when sender is unable to use
// the available congestion window. Resets Cubic state during quiescence.
func (c *Cubic) OnApplicationLimited() {
// When sender is not using the available congestion window, the window does
// not grow. But to be RTT-independent, Cubic assumes that the sender has been
// using the entire window during the time since the beginning of the current
// "epoch" (the end of the last loss recovery period). Since
// application-limited periods break this assumption, we reset the epoch when
// in such a period. This reset effectively freezes congestion window growth
// through application-limited periods and allows Cubic growth to continue
// when the entire window is being used.
c.epoch = 0
}
// CongestionWindowAfterPacketLoss computes a new congestion window to use after
// a loss event. Returns the new congestion window in packets. The new
// congestion window is a multiplicative decrease of our current window.
func (c *Cubic) CongestionWindowAfterPacketLoss(currentCongestionWindow protocol.ByteCount) protocol.ByteCount {
if currentCongestionWindow+maxDatagramSize < c.lastMaxCongestionWindow {
// We never reached the old max, so assume we are competing with another
// flow. Use our extra back off factor to allow the other flow to go up.
c.lastMaxCongestionWindow = protocol.ByteCount(c.betaLastMax() * float32(currentCongestionWindow))
} else {
c.lastMaxCongestionWindow = currentCongestionWindow
}
c.epoch = 0 // Reset time.
return protocol.ByteCount(float32(currentCongestionWindow) * c.beta())
}
// CongestionWindowAfterAck computes a new congestion window to use after a received ACK.
// Returns the new congestion window in packets. The new congestion window
// follows a cubic function that depends on the time passed since last
// packet loss.
func (c *Cubic) CongestionWindowAfterAck(
ackedBytes protocol.ByteCount,
currentCongestionWindow protocol.ByteCount,
delayMin time.Duration,
eventTime monotime.Time,
) protocol.ByteCount {
c.ackedBytesCount += ackedBytes
if c.epoch.IsZero() {
// First ACK after a loss event.
c.epoch = eventTime // Start of epoch.
c.ackedBytesCount = ackedBytes // Reset count.
// Reset estimated_tcp_congestion_window_ to be in sync with cubic.
c.estimatedTCPcongestionWindow = currentCongestionWindow
if c.lastMaxCongestionWindow <= currentCongestionWindow {
c.timeToOriginPoint = 0
c.originPointCongestionWindow = currentCongestionWindow
} else {
c.timeToOriginPoint = uint32(math.Cbrt(float64(cubeFactor * (c.lastMaxCongestionWindow - currentCongestionWindow))))
c.originPointCongestionWindow = c.lastMaxCongestionWindow
}
}
// Change the time unit from microseconds to 2^10 fractions per second. Take
// the round trip time in account. This is done to allow us to use shift as a
// divide operator.
elapsedTime := int64(eventTime.Add(delayMin).Sub(c.epoch)/time.Microsecond) << 10 / (1000 * 1000)
// Right-shifts of negative, signed numbers have implementation-dependent
// behavior, so force the offset to be positive, as is done in the kernel.
offset := int64(c.timeToOriginPoint) - elapsedTime
if offset < 0 {
offset = -offset
}
deltaCongestionWindow := protocol.ByteCount(cubeCongestionWindowScale*offset*offset*offset) * maxDatagramSize >> cubeScale
var targetCongestionWindow protocol.ByteCount
if elapsedTime > int64(c.timeToOriginPoint) {
targetCongestionWindow = c.originPointCongestionWindow + deltaCongestionWindow
} else {
targetCongestionWindow = c.originPointCongestionWindow - deltaCongestionWindow
}
// Limit the CWND increase to half the acked bytes.
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
// congestion windows (less than 25), the formula below will
// increase slightly slower than linearly per estimated tcp window
// of bytes.
c.estimatedTCPcongestionWindow += protocol.ByteCount(float32(c.ackedBytesCount) * c.alpha() * float32(maxDatagramSize) / float32(c.estimatedTCPcongestionWindow))
c.ackedBytesCount = 0
// We have a new cubic congestion window.
c.lastTargetCongestionWindow = targetCongestionWindow
// Compute target congestion_window based on cubic target and estimated TCP
// congestion_window, use highest (fastest).
if targetCongestionWindow < c.estimatedTCPcongestionWindow {
targetCongestionWindow = c.estimatedTCPcongestionWindow
}
return targetCongestionWindow
}
// SetNumConnections sets the number of emulated connections
func (c *Cubic) SetNumConnections(n int) {
c.numConnections = n
}
package congestion
import (
"fmt"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
const (
// maxDatagramSize is the default maximum packet size used in the Linux TCP implementation.
// Used in QUIC for congestion window computations in bytes.
initialMaxDatagramSize = protocol.ByteCount(protocol.InitialPacketSize)
maxBurstPackets = 3
renoBeta = 0.7 // Reno backoff factor.
minCongestionWindowPackets = 2
initialCongestionWindow = 32
)
type cubicSender struct {
hybridSlowStart HybridSlowStart
rttStats *utils.RTTStats
connStats *utils.ConnectionStats
cubic *Cubic
pacer *pacer
clock Clock
reno bool
// Track the largest packet that has been sent.
largestSentPacketNumber protocol.PacketNumber
// Track the largest packet that has been acked.
largestAckedPacketNumber protocol.PacketNumber
// Track the largest packet number outstanding when a CWND cutback occurs.
largestSentAtLastCutback protocol.PacketNumber
// Whether the last loss event caused us to exit slowstart.
// Used for stats collection of slowstartPacketsLost
lastCutbackExitedSlowstart bool
// Congestion window in bytes.
congestionWindow protocol.ByteCount
// Slow start congestion window in bytes, aka ssthresh.
slowStartThreshold protocol.ByteCount
// ACK counter for the Reno implementation.
numAckedPackets uint64
initialCongestionWindow protocol.ByteCount
initialMaxCongestionWindow protocol.ByteCount
maxDatagramSize protocol.ByteCount
lastState qlog.CongestionState
qlogger qlogwriter.Recorder
}
var (
_ SendAlgorithm = &cubicSender{}
_ SendAlgorithmWithDebugInfos = &cubicSender{}
)
// NewCubicSender makes a new cubic sender
func NewCubicSender(
clock Clock,
rttStats *utils.RTTStats,
connStats *utils.ConnectionStats,
initialMaxDatagramSize protocol.ByteCount,
reno bool,
qlogger qlogwriter.Recorder,
) *cubicSender {
return newCubicSender(
clock,
rttStats,
connStats,
reno,
initialMaxDatagramSize,
initialCongestionWindow*initialMaxDatagramSize,
protocol.MaxCongestionWindowPackets*initialMaxDatagramSize,
qlogger,
)
}
func newCubicSender(
clock Clock,
rttStats *utils.RTTStats,
connStats *utils.ConnectionStats,
reno bool,
initialMaxDatagramSize,
initialCongestionWindow,
initialMaxCongestionWindow protocol.ByteCount,
qlogger qlogwriter.Recorder,
) *cubicSender {
c := &cubicSender{
rttStats: rttStats,
connStats: connStats,
largestSentPacketNumber: protocol.InvalidPacketNumber,
largestAckedPacketNumber: protocol.InvalidPacketNumber,
largestSentAtLastCutback: protocol.InvalidPacketNumber,
initialCongestionWindow: initialCongestionWindow,
initialMaxCongestionWindow: initialMaxCongestionWindow,
congestionWindow: initialCongestionWindow,
slowStartThreshold: protocol.MaxByteCount,
cubic: NewCubic(clock),
clock: clock,
reno: reno,
qlogger: qlogger,
maxDatagramSize: initialMaxDatagramSize,
}
c.pacer = newPacer(c.BandwidthEstimate)
if c.qlogger != nil {
c.lastState = qlog.CongestionStateSlowStart
c.qlogger.RecordEvent(qlog.CongestionStateUpdated{
State: qlog.CongestionStateSlowStart,
})
}
return c
}
// TimeUntilSend returns when the next packet should be sent.
func (c *cubicSender) TimeUntilSend(_ protocol.ByteCount) monotime.Time {
return c.pacer.TimeUntilSend()
}
func (c *cubicSender) HasPacingBudget(now monotime.Time) bool {
return c.pacer.Budget(now) >= c.maxDatagramSize
}
func (c *cubicSender) maxCongestionWindow() protocol.ByteCount {
return c.maxDatagramSize * protocol.MaxCongestionWindowPackets
}
func (c *cubicSender) minCongestionWindow() protocol.ByteCount {
return c.maxDatagramSize * minCongestionWindowPackets
}
func (c *cubicSender) OnPacketSent(
sentTime monotime.Time,
_ protocol.ByteCount,
packetNumber protocol.PacketNumber,
bytes protocol.ByteCount,
isRetransmittable bool,
) {
c.pacer.SentPacket(sentTime, bytes)
if !isRetransmittable {
return
}
c.largestSentPacketNumber = packetNumber
c.hybridSlowStart.OnPacketSent(packetNumber)
}
func (c *cubicSender) CanSend(bytesInFlight protocol.ByteCount) bool {
return bytesInFlight < c.GetCongestionWindow()
}
func (c *cubicSender) InRecovery() bool {
return c.largestAckedPacketNumber != protocol.InvalidPacketNumber && c.largestAckedPacketNumber <= c.largestSentAtLastCutback
}
func (c *cubicSender) InSlowStart() bool {
return c.GetCongestionWindow() < c.slowStartThreshold
}
func (c *cubicSender) GetCongestionWindow() protocol.ByteCount {
return c.congestionWindow
}
func (c *cubicSender) MaybeExitSlowStart() {
if c.InSlowStart() &&
c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/c.maxDatagramSize) {
// exit slow start
c.slowStartThreshold = c.congestionWindow
c.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance)
}
}
func (c *cubicSender) OnPacketAcked(
ackedPacketNumber protocol.PacketNumber,
ackedBytes protocol.ByteCount,
priorInFlight protocol.ByteCount,
eventTime monotime.Time,
) {
c.largestAckedPacketNumber = max(ackedPacketNumber, c.largestAckedPacketNumber)
if c.InRecovery() {
return
}
c.maybeIncreaseCwnd(ackedPacketNumber, ackedBytes, priorInFlight, eventTime)
if c.InSlowStart() {
c.hybridSlowStart.OnPacketAcked(ackedPacketNumber)
}
}
func (c *cubicSender) OnCongestionEvent(packetNumber protocol.PacketNumber, lostBytes, priorInFlight protocol.ByteCount) {
c.connStats.PacketsLost.Add(1)
c.connStats.BytesLost.Add(uint64(lostBytes))
// TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
// already sent should be treated as a single loss event, since it's expected.
if packetNumber <= c.largestSentAtLastCutback {
return
}
c.lastCutbackExitedSlowstart = c.InSlowStart()
c.maybeQlogStateChange(qlog.CongestionStateRecovery)
if c.reno {
c.congestionWindow = protocol.ByteCount(float64(c.congestionWindow) * renoBeta)
} else {
c.congestionWindow = c.cubic.CongestionWindowAfterPacketLoss(c.congestionWindow)
}
if minCwnd := c.minCongestionWindow(); c.congestionWindow < minCwnd {
c.congestionWindow = minCwnd
}
c.slowStartThreshold = c.congestionWindow
c.largestSentAtLastCutback = c.largestSentPacketNumber
// reset packet count from congestion avoidance mode. We start
// counting again when we're out of recovery.
c.numAckedPackets = 0
}
// Called when we receive an ack. Normal TCP tracks how many packets one ack
// represents, but quic has a separate ack for each packet.
func (c *cubicSender) maybeIncreaseCwnd(
_ protocol.PacketNumber,
ackedBytes protocol.ByteCount,
priorInFlight protocol.ByteCount,
eventTime monotime.Time,
) {
// Do not increase the congestion window unless the sender is close to using
// the current window.
if !c.isCwndLimited(priorInFlight) {
c.cubic.OnApplicationLimited()
c.maybeQlogStateChange(qlog.CongestionStateApplicationLimited)
return
}
if c.congestionWindow >= c.maxCongestionWindow() {
return
}
if c.InSlowStart() {
// TCP slow start, exponential growth, increase by one for each ACK.
c.congestionWindow += c.maxDatagramSize
c.maybeQlogStateChange(qlog.CongestionStateSlowStart)
return
}
// Congestion avoidance
c.maybeQlogStateChange(qlog.CongestionStateCongestionAvoidance)
if c.reno {
// Classic Reno congestion avoidance.
c.numAckedPackets++
if c.numAckedPackets >= uint64(c.congestionWindow/c.maxDatagramSize) {
c.congestionWindow += c.maxDatagramSize
c.numAckedPackets = 0
}
} else {
c.congestionWindow = min(
c.maxCongestionWindow(),
c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime),
)
}
}
func (c *cubicSender) isCwndLimited(bytesInFlight protocol.ByteCount) bool {
congestionWindow := c.GetCongestionWindow()
if bytesInFlight >= congestionWindow {
return true
}
availableBytes := congestionWindow - bytesInFlight
slowStartLimited := c.InSlowStart() && bytesInFlight > congestionWindow/2
return slowStartLimited || availableBytes <= maxBurstPackets*c.maxDatagramSize
}
// BandwidthEstimate returns the current bandwidth estimate
func (c *cubicSender) BandwidthEstimate() Bandwidth {
srtt := c.rttStats.SmoothedRTT()
if srtt == 0 {
// This should never happen, but if it does, avoid division by zero.
srtt = protocol.TimerGranularity
}
return BandwidthFromDelta(c.GetCongestionWindow(), srtt)
}
// OnRetransmissionTimeout is called on an retransmission timeout
func (c *cubicSender) OnRetransmissionTimeout(packetsRetransmitted bool) {
c.largestSentAtLastCutback = protocol.InvalidPacketNumber
if !packetsRetransmitted {
return
}
c.hybridSlowStart.Restart()
c.cubic.Reset()
c.slowStartThreshold = c.congestionWindow / 2
c.congestionWindow = c.minCongestionWindow()
}
// OnConnectionMigration is called when the connection is migrated (?)
func (c *cubicSender) OnConnectionMigration() {
c.hybridSlowStart.Restart()
c.largestSentPacketNumber = protocol.InvalidPacketNumber
c.largestAckedPacketNumber = protocol.InvalidPacketNumber
c.largestSentAtLastCutback = protocol.InvalidPacketNumber
c.lastCutbackExitedSlowstart = false
c.cubic.Reset()
c.numAckedPackets = 0
c.congestionWindow = c.initialCongestionWindow
c.slowStartThreshold = c.initialMaxCongestionWindow
}
func (c *cubicSender) maybeQlogStateChange(new qlog.CongestionState) {
if c.qlogger == nil || new == c.lastState {
return
}
c.qlogger.RecordEvent(qlog.CongestionStateUpdated{State: new})
c.lastState = new
}
func (c *cubicSender) SetMaxDatagramSize(s protocol.ByteCount) {
if s < c.maxDatagramSize {
panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", c.maxDatagramSize, s))
}
cwndIsMinCwnd := c.congestionWindow == c.minCongestionWindow()
c.maxDatagramSize = s
if cwndIsMinCwnd {
c.congestionWindow = c.minCongestionWindow()
}
c.pacer.SetMaxDatagramSize(s)
}
package congestion
import (
"time"
"github.com/quic-go/quic-go/internal/protocol"
)
// Note(pwestin): the magic clamping numbers come from the original code in
// tcp_cubic.c.
const hybridStartLowWindow = protocol.ByteCount(16)
// Number of delay samples for detecting the increase of delay.
const hybridStartMinSamples = uint32(8)
// Exit slow start if the min rtt has increased by more than 1/8th.
const hybridStartDelayFactorExp = 3 // 2^3 = 8
// The original paper specifies 2 and 8ms, but those have changed over time.
const (
hybridStartDelayMinThresholdUs = int64(4000)
hybridStartDelayMaxThresholdUs = int64(16000)
)
// HybridSlowStart implements the TCP hybrid slow start algorithm
type HybridSlowStart struct {
endPacketNumber protocol.PacketNumber
lastSentPacketNumber protocol.PacketNumber
started bool
currentMinRTT time.Duration
rttSampleCount uint32
hystartFound bool
}
// StartReceiveRound is called for the start of each receive round (burst) in the slow start phase.
func (s *HybridSlowStart) StartReceiveRound(lastSent protocol.PacketNumber) {
s.endPacketNumber = lastSent
s.currentMinRTT = 0
s.rttSampleCount = 0
s.started = true
}
// IsEndOfRound returns true if this ack is the last packet number of our current slow start round.
func (s *HybridSlowStart) IsEndOfRound(ack protocol.PacketNumber) bool {
return s.endPacketNumber < ack
}
// ShouldExitSlowStart should be called on every new ack frame, since a new
// RTT measurement can be made then.
// rtt: the RTT for this ack packet.
// minRTT: is the lowest delay (RTT) we have seen during the session.
// congestionWindow: the congestion window in packets.
func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT time.Duration, congestionWindow protocol.ByteCount) bool {
if !s.started {
// Time to start the hybrid slow start.
s.StartReceiveRound(s.lastSentPacketNumber)
}
if s.hystartFound {
return true
}
// Second detection parameter - delay increase detection.
// Compare the minimum delay (s.currentMinRTT) of the current
// burst of packets relative to the minimum delay during the session.
// Note: we only look at the first few(8) packets in each burst, since we
// only want to compare the lowest RTT of the burst relative to previous
// bursts.
s.rttSampleCount++
if s.rttSampleCount <= hybridStartMinSamples {
if s.currentMinRTT == 0 || s.currentMinRTT > latestRTT {
s.currentMinRTT = latestRTT
}
}
// We only need to check this once per round.
if s.rttSampleCount == hybridStartMinSamples {
// 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 = min(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs)
minRTTincreaseThreshold := time.Duration(max(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond
if s.currentMinRTT > (minRTT + minRTTincreaseThreshold) {
s.hystartFound = true
}
}
// Exit from slow start if the cwnd is greater than 16 and
// increasing delay is found.
return congestionWindow >= hybridStartLowWindow && s.hystartFound
}
// OnPacketSent is called when a packet was sent
func (s *HybridSlowStart) OnPacketSent(packetNumber protocol.PacketNumber) {
s.lastSentPacketNumber = packetNumber
}
// OnPacketAcked gets invoked after ShouldExitSlowStart, so it's best to end
// the round when the final packet of the burst is received and start it on
// the next incoming ack.
func (s *HybridSlowStart) OnPacketAcked(ackedPacketNumber protocol.PacketNumber) {
if s.IsEndOfRound(ackedPacketNumber) {
s.started = false
}
}
// Started returns true if started
func (s *HybridSlowStart) Started() bool {
return s.started
}
// Restart the slow start phase
func (s *HybridSlowStart) Restart() {
s.started = false
s.hystartFound = false
}
package congestion
import (
"math"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
)
const maxBurstSizePackets = 10
// The pacer implements a token bucket pacing algorithm.
type pacer struct {
budgetAtLastSent protocol.ByteCount
maxDatagramSize protocol.ByteCount
lastSentTime monotime.Time
adjustedBandwidth func() uint64 // in bytes/s
}
func newPacer(getBandwidth func() Bandwidth) *pacer {
p := &pacer{
maxDatagramSize: initialMaxDatagramSize,
adjustedBandwidth: func() uint64 {
// Bandwidth is in bits/s. We need the value in bytes/s.
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,
// provided the congestion window is fully utilized and acknowledgments arrive at regular intervals.
return bw * 5 / 4
},
}
p.budgetAtLastSent = p.maxBurstSize()
return p
}
func (p *pacer) SentPacket(sendTime monotime.Time, size protocol.ByteCount) {
budget := p.Budget(sendTime)
if size >= budget {
p.budgetAtLastSent = 0
} else {
p.budgetAtLastSent = budget - size
}
p.lastSentTime = sendTime
}
func (p *pacer) Budget(now monotime.Time) protocol.ByteCount {
if p.lastSentTime.IsZero() {
return p.maxBurstSize()
}
delta := now.Sub(p.lastSentTime)
var added protocol.ByteCount
if delta > 0 {
added = p.timeScaledBandwidth(uint64(delta.Nanoseconds()))
}
budget := p.budgetAtLastSent + added
if added > 0 && budget < p.budgetAtLastSent {
budget = protocol.MaxByteCount
}
return min(p.maxBurstSize(), budget)
}
func (p *pacer) maxBurstSize() protocol.ByteCount {
return max(
p.timeScaledBandwidth(uint64((protocol.MinPacingDelay + protocol.TimerGranularity).Nanoseconds())),
maxBurstSizePackets*p.maxDatagramSize,
)
}
// timeScaledBandwidth calculates the number of bytes that may be sent within
// a given time interval (ns nanoseconds), based on the current bandwidth estimate.
// It caps the scaled value to the maximum allowed burst and handles overflows.
func (p *pacer) timeScaledBandwidth(ns uint64) protocol.ByteCount {
bw := p.adjustedBandwidth()
if bw == 0 {
return 0
}
const nsPerSecond = 1e9
maxBurst := maxBurstSizePackets * p.maxDatagramSize
var scaled protocol.ByteCount
if ns > math.MaxUint64/bw {
scaled = maxBurst
} else {
scaled = protocol.ByteCount(bw * ns / nsPerSecond)
}
return scaled
}
// TimeUntilSend returns when the next packet should be sent.
// It returns zero if a packet can be sent immediately.
func (p *pacer) TimeUntilSend() monotime.Time {
if p.budgetAtLastSent >= p.maxDatagramSize {
return 0
}
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) {
p.maxDatagramSize = s
}
package handshake
import (
"crypto/cipher"
"encoding/binary"
"github.com/quic-go/quic-go/internal/protocol"
)
func createAEAD(suite cipherSuite, trafficSecret []byte, v protocol.Version) cipher.AEAD {
keyLabel := hkdfLabelKeyV1
ivLabel := hkdfLabelIVV1
if v == protocol.Version2 {
keyLabel = hkdfLabelKeyV2
ivLabel = hkdfLabelIVV2
}
key := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, keyLabel, suite.KeyLen)
iv := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, ivLabel, suite.IVLen())
return suite.AEAD(key, iv)
}
type longHeaderSealer struct {
aead cipher.AEAD
headerProtector headerProtector
nonceBuf [8]byte
}
var _ LongHeaderSealer = &longHeaderSealer{}
func newLongHeaderSealer(aead cipher.AEAD, headerProtector headerProtector) LongHeaderSealer {
if aead.NonceSize() != 8 {
panic("unexpected nonce size")
}
return &longHeaderSealer{
aead: aead,
headerProtector: headerProtector,
}
}
func (s *longHeaderSealer) Seal(dst, src []byte, pn protocol.PacketNumber, ad []byte) []byte {
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) {
s.headerProtector.EncryptHeader(sample, firstByte, pnBytes)
}
func (s *longHeaderSealer) Overhead() int {
return s.aead.Overhead()
}
type longHeaderOpener struct {
aead cipher.AEAD
headerProtector headerProtector
highestRcvdPN protocol.PacketNumber // highest packet number received (which could be successfully unprotected)
// use a single array to avoid allocations
nonceBuf [8]byte
}
var _ LongHeaderOpener = &longHeaderOpener{}
func newLongHeaderOpener(aead cipher.AEAD, headerProtector headerProtector) LongHeaderOpener {
if aead.NonceSize() != 8 {
panic("unexpected nonce size")
}
return &longHeaderOpener{
aead: aead,
headerProtector: headerProtector,
}
}
func (o *longHeaderOpener) DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber {
return protocol.DecodePacketNumber(wirePNLen, o.highestRcvdPN, wirePN)
}
func (o *longHeaderOpener) Open(dst, src []byte, pn protocol.PacketNumber, ad []byte) ([]byte, error) {
binary.BigEndian.PutUint64(o.nonceBuf[:], uint64(pn))
dec, err := o.aead.Open(dst, o.nonceBuf[:], src, ad)
if err == nil {
o.highestRcvdPN = max(o.highestRcvdPN, pn)
} else {
err = ErrDecryptionFailed
}
return dec, err
}
func (o *longHeaderOpener) DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
o.headerProtector.DecryptHeader(sample, firstByte, pnBytes)
}
package handshake
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/fips140"
"crypto/tls"
"fmt"
"golang.org/x/crypto/chacha20poly1305"
)
// These cipher suite implementations are copied from the standard library crypto/tls package.
const aeadNonceLength = 12
type cipherSuite struct {
ID uint16
Hash crypto.Hash
KeyLen int
AEAD func(key, nonceMask []byte) cipher.AEAD
}
func (s cipherSuite) IVLen() int { return aeadNonceLength }
func getCipherSuite(id uint16) cipherSuite {
switch id {
case tls.TLS_AES_128_GCM_SHA256:
return cipherSuite{ID: tls.TLS_AES_128_GCM_SHA256, Hash: crypto.SHA256, KeyLen: 16, AEAD: aeadAESGCMTLS13}
case tls.TLS_CHACHA20_POLY1305_SHA256:
// The usual convention is to only panic on fips140.Enforced (and not on fips140.Enabled),
// but this function panics in the default case anyway, so we might as well panic here.
if fips140.Enabled() {
panic("tls: TLS_CHACHA20_POLY1305_SHA256 is not allowed in FIPS 140-3 mode")
}
return cipherSuite{ID: tls.TLS_CHACHA20_POLY1305_SHA256, Hash: crypto.SHA256, KeyLen: 32, AEAD: aeadChaCha20Poly1305}
case tls.TLS_AES_256_GCM_SHA384:
return cipherSuite{ID: tls.TLS_AES_256_GCM_SHA384, Hash: crypto.SHA384, KeyLen: 32, AEAD: aeadAESGCMTLS13}
default:
panic(fmt.Sprintf("unknown cypher suite: %d", id))
}
}
func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {
if fips140.Enabled() {
return aeadAESGCMTLS13FIPS140(key, nonceMask)
}
if len(nonceMask) != aeadNonceLength {
panic("tls: internal error: wrong nonce length")
}
aes, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(aes)
if err != nil {
panic(err)
}
ret := &xorNonceAEAD{aead: aead}
copy(ret.nonceMask[:], nonceMask)
return ret
}
func aeadChaCha20Poly1305(key, nonceMask []byte) cipher.AEAD {
if len(nonceMask) != aeadNonceLength {
panic("tls: internal error: wrong nonce length")
}
aead, err := chacha20poly1305.New(key)
if err != nil {
panic(err)
}
ret := &xorNonceAEAD{aead: aead}
copy(ret.nonceMask[:], nonceMask)
return ret
}
// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce
// before each call.
type xorNonceAEAD struct {
nonceMask [aeadNonceLength]byte
aead cipher.AEAD
}
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result
}
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result, err
}
package handshake
import (
"crypto/cipher"
_ "unsafe" // for go:linkname
)
// Reaching into crypto/tls is a bit of a hack, but it's the only way to get the FIPS 140
// compliant AEAD, because the standard library doesn't yet expose the NewGCMForQUIC constructor
// added in https://go-review.googlesource.com/c/go/+/723760.
// See https://github.com/golang/go/issues/79219 for details.
//
// Once the standard library exposes the necessary constructors, we can use a shared code path
// for both FIPS 140 and non-FIPS 140 modes.
//
//go:linkname cryptoTLSAEAD_AESGCMTLS13 crypto/tls.aeadAESGCMTLS13
func cryptoTLSAEAD_AESGCMTLS13(key, nonceMask []byte) cipher.AEAD
func aeadAESGCMTLS13FIPS140(key, nonceMask []byte) cipher.AEAD {
return &tls13AESGCMAEADFIPS140{aead: cryptoTLSAEAD_AESGCMTLS13(key, nonceMask)}
}
type tls13AESGCMAEADFIPS140 struct {
aead cipher.AEAD
primedSeal bool
}
func (f *tls13AESGCMAEADFIPS140) NonceSize() int { return f.aead.NonceSize() }
func (f *tls13AESGCMAEADFIPS140) Overhead() int { return f.aead.Overhead() }
func (f *tls13AESGCMAEADFIPS140) Seal(out, nonce, plaintext, additionalData []byte) []byte {
if !f.primedSeal {
f.primedSeal = true
if nonce[0]|nonce[1]|nonce[2]|nonce[3]|nonce[4]|nonce[5]|nonce[6]|nonce[7] != 0 {
// Go's TLS 1.3 AES-GCM AEAD learns the XOR mask from the first Seal
// call and enforces monotonically increasing packet numbers after that.
// QUIC packet numbers don't reset on key updates, so prime it with
// packet number 0 before the first real, non-zero packet number.
var zeroNonce [8]byte
f.aead.Seal(nil, zeroNonce[:], nil, nil)
}
}
return f.aead.Seal(out, nonce, plaintext, additionalData)
}
func (f *tls13AESGCMAEADFIPS140) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
return f.aead.Open(out, nonce, ciphertext, additionalData)
}
package handshake
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strings"
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
"github.com/quic-go/quic-go/quicvarint"
)
type quicVersionContextKey struct{}
var QUICVersionContextKey = &quicVersionContextKey{}
const clientSessionStateRevision = 5
type cryptoSetup struct {
tlsConf *tls.Config
conn *tls.QUICConn
events []Event
version protocol.Version
ourParams *wire.TransportParameters
peerParams *wire.TransportParameters
zeroRTTParameters *wire.TransportParameters
allow0RTT bool
rttStats *utils.RTTStats
qlogger qlogwriter.Recorder
logger utils.Logger
perspective protocol.Perspective
handshakeCompleteTime time.Time
zeroRTTOpener LongHeaderOpener // only set for the server
zeroRTTSealer LongHeaderSealer // only set for the client
initialOpener LongHeaderOpener
initialSealer LongHeaderSealer
handshakeOpener LongHeaderOpener
handshakeSealer LongHeaderSealer
used0RTT atomic.Bool
aead *updatableAEAD
has1RTTSealer bool
has1RTTOpener bool
}
var _ CryptoSetup = &cryptoSetup{}
// NewCryptoSetupClient creates a new crypto setup for the client
func NewCryptoSetupClient(
connID protocol.ConnectionID,
tp *wire.TransportParameters,
tlsConf *tls.Config,
enable0RTT bool,
rttStats *utils.RTTStats,
qlogger qlogwriter.Recorder,
logger utils.Logger,
version protocol.Version,
) CryptoSetup {
cs := newCryptoSetup(
connID,
tp,
rttStats,
qlogger,
logger,
protocol.PerspectiveClient,
version,
)
tlsConf = setupConfigForClient(tlsConf)
cs.tlsConf = tlsConf
cs.allow0RTT = enable0RTT
cs.conn = tls.QUICClient(&tls.QUICConfig{
TLSConfig: tlsConf,
EnableSessionEvents: true,
})
cs.conn.SetTransportParameters(cs.ourParams.Marshal(protocol.PerspectiveClient))
return cs
}
// NewCryptoSetupServer creates a new crypto setup for the server
func NewCryptoSetupServer(
connID protocol.ConnectionID,
localAddr, remoteAddr net.Addr,
tp *wire.TransportParameters,
tlsConf *tls.Config,
allow0RTT bool,
rttStats *utils.RTTStats,
qlogger qlogwriter.Recorder,
logger utils.Logger,
version protocol.Version,
) CryptoSetup {
cs := newCryptoSetup(
connID,
tp,
rttStats,
qlogger,
logger,
protocol.PerspectiveServer,
version,
)
cs.allow0RTT = allow0RTT
tlsConf = setupConfigForServer(tlsConf, localAddr, remoteAddr)
cs.tlsConf = tlsConf
cs.conn = tls.QUICServer(getQUICConfig(tlsConf, localAddr, remoteAddr))
return cs
}
func newCryptoSetup(
connID protocol.ConnectionID,
tp *wire.TransportParameters,
rttStats *utils.RTTStats,
qlogger qlogwriter.Recorder,
logger utils.Logger,
perspective protocol.Perspective,
version protocol.Version,
) *cryptoSetup {
initialSealer, initialOpener := NewInitialAEAD(connID, perspective, version)
if qlogger != nil {
qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient),
})
qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer),
})
}
return &cryptoSetup{
initialSealer: initialSealer,
initialOpener: initialOpener,
aead: newUpdatableAEAD(rttStats, qlogger, logger, version),
events: make([]Event, 0, 16),
ourParams: tp,
rttStats: rttStats,
qlogger: qlogger,
logger: logger,
perspective: perspective,
version: version,
}
}
func (h *cryptoSetup) ChangeConnectionID(id protocol.ConnectionID) {
initialSealer, initialOpener := NewInitialAEAD(id, h.perspective, h.version)
h.initialSealer = initialSealer
h.initialOpener = initialOpener
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveClient),
})
h.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.EncryptionInitial, protocol.PerspectiveServer),
})
}
}
func (h *cryptoSetup) SetLargest1RTTAcked(pn protocol.PacketNumber) error {
return h.aead.SetLargestAcked(pn)
}
func (h *cryptoSetup) StartHandshake(ctx context.Context) error {
err := h.conn.Start(context.WithValue(ctx, QUICVersionContextKey, h.version))
if err != nil {
return wrapError(err)
}
for {
ev := h.conn.NextEvent()
if err := h.handleEvent(ev); err != nil {
return wrapError(err)
}
if ev.Kind == tls.QUICNoEvent {
break
}
}
if h.perspective == protocol.PerspectiveClient {
if h.zeroRTTSealer != nil && h.zeroRTTParameters != nil {
h.logger.Debugf("Doing 0-RTT.")
h.events = append(h.events, Event{Kind: EventRestoredTransportParameters, TransportParameters: h.zeroRTTParameters})
} else {
h.logger.Debugf("Not doing 0-RTT. Has sealer: %t, has params: %t", h.zeroRTTSealer != nil, h.zeroRTTParameters != nil)
}
}
return nil
}
// Close closes the crypto setup.
// It aborts the handshake, if it is still running.
func (h *cryptoSetup) Close() error {
return h.conn.Close()
}
// HandleMessage handles a TLS handshake message.
// It is called by the crypto streams when a new message is available.
func (h *cryptoSetup) HandleMessage(data []byte, encLevel protocol.EncryptionLevel) error {
if err := h.handleMessage(data, encLevel); err != nil {
return wrapError(err)
}
return nil
}
func (h *cryptoSetup) handleMessage(data []byte, encLevel protocol.EncryptionLevel) error {
if err := h.conn.HandleData(encLevel.ToTLSEncryptionLevel(), data); err != nil {
return err
}
for {
ev := h.conn.NextEvent()
if err := h.handleEvent(ev); err != nil {
return err
}
if ev.Kind == tls.QUICNoEvent {
return nil
}
}
}
func (h *cryptoSetup) handleEvent(ev tls.QUICEvent) (err error) {
switch ev.Kind {
case tls.QUICNoEvent:
return nil
case tls.QUICSetReadSecret:
h.setReadKey(ev.Level, ev.Suite, ev.Data)
return nil
case tls.QUICSetWriteSecret:
h.setWriteKey(ev.Level, ev.Suite, ev.Data)
return nil
case tls.QUICTransportParameters:
return h.handleTransportParameters(ev.Data)
case tls.QUICTransportParametersRequired:
h.conn.SetTransportParameters(h.ourParams.Marshal(h.perspective))
return nil
case tls.QUICRejectedEarlyData:
h.rejected0RTT()
return nil
case tls.QUICWriteData:
h.writeRecord(ev.Level, ev.Data)
return nil
case tls.QUICHandshakeDone:
h.handshakeComplete()
return nil
case tls.QUICStoreSession:
if h.perspective == protocol.PerspectiveServer {
panic("cryptoSetup BUG: unexpected QUICStoreSession event for the server")
}
ev.SessionState.Extra = append(
ev.SessionState.Extra,
addSessionStateExtraPrefix(h.marshalDataForSessionState(ev.SessionState.EarlyData)),
)
return h.conn.StoreSession(ev.SessionState)
case tls.QUICResumeSession:
var allowEarlyData bool
switch h.perspective {
case protocol.PerspectiveClient:
// for clients, this event occurs when a session ticket is selected
allowEarlyData = h.handleDataFromSessionState(
findSessionStateExtraData(ev.SessionState.Extra),
ev.SessionState.EarlyData,
)
case protocol.PerspectiveServer:
// for servers, this event occurs when receiving the client's session ticket
allowEarlyData = h.handleSessionTicket(
findSessionStateExtraData(ev.SessionState.Extra),
ev.SessionState.EarlyData,
)
}
if ev.SessionState.EarlyData {
ev.SessionState.EarlyData = allowEarlyData
}
return nil
case quicErrorEvent:
return extractQUICEventError(ev)
default:
// Unknown events should be ignored.
// crypto/tls will ensure that this is safe to do.
// See the discussion following https://github.com/golang/go/issues/68124#issuecomment-2187042510 for details.
return nil
}
}
func (h *cryptoSetup) NextEvent() Event {
if len(h.events) == 0 {
return Event{Kind: EventNoEvent}
}
ev := h.events[0]
h.events = h.events[1:]
return ev
}
func (h *cryptoSetup) handleTransportParameters(data []byte) error {
var tp wire.TransportParameters
if err := tp.Unmarshal(data, h.perspective.Opposite()); err != nil {
return err
}
h.peerParams = &tp
h.events = append(h.events, Event{Kind: EventReceivedTransportParameters, TransportParameters: h.peerParams})
return nil
}
// must be called after receiving the transport parameters
func (h *cryptoSetup) marshalDataForSessionState(earlyData bool) []byte {
b := make([]byte, 0, 256)
b = quicvarint.Append(b, clientSessionStateRevision)
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, earlyData bool) (allowEarlyData bool) {
tp, err := decodeDataFromSessionState(data, earlyData)
if err != nil {
h.logger.Debugf("Restoring of transport parameters from session ticket failed: %s", err.Error())
return
}
// 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 decodeDataFromSessionState(b []byte, earlyData bool) (*wire.TransportParameters, error) {
ver, l, err := quicvarint.Parse(b)
if err != nil {
return nil, err
}
b = b[l:]
if ver != clientSessionStateRevision {
return nil, fmt.Errorf("mismatching version. Got %d, expected %d", ver, clientSessionStateRevision)
}
if !earlyData {
return nil, nil
}
var tp wire.TransportParameters
if err := tp.UnmarshalFromSessionTicket(b); err != nil {
return nil, err
}
return &tp, nil
}
func (h *cryptoSetup) getDataForSessionTicket() []byte {
return (&sessionTicket{
Parameters: h.ourParams,
}).Marshal()
}
// GetSessionTicket generates a new session ticket.
// 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 := h.conn.SendSessionTicket(tls.QUICSessionTicketOptions{
EarlyData: h.allow0RTT,
Extra: [][]byte{addSessionStateExtraPrefix(h.getDataForSessionTicket())},
}); 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.
// See https://github.com/golang/go/issues/62032.
// This error assertion can be removed once we drop support for Go 1.25.
if strings.Contains(err.Error(), "session ticket keys unavailable") {
return nil, nil
}
return nil, err
}
// If session tickets are disabled, NextEvent will immediately return QUICNoEvent,
// and we will return a nil ticket.
var ticket []byte
for {
ev := h.conn.NextEvent()
if ev.Kind == tls.QUICNoEvent {
break
}
if ev.Kind == tls.QUICWriteData && ev.Level == tls.QUICEncryptionLevelApplication {
if ticket != nil {
h.logger.Errorf("unexpected multiple session tickets")
continue
}
ticket = ev.Data
} else {
h.logger.Errorf("unexpected event: %v", ev.Kind)
}
}
return ticket, nil
}
// handleSessionTicket is called for the server when receiving the client's session ticket.
// 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(data []byte, using0RTT bool) (allowEarlyData bool) {
var t sessionTicket
if err := t.Unmarshal(data); err != nil {
h.logger.Debugf("Unmarshalling session ticket failed: %s", err.Error())
return false
}
if !using0RTT {
return false
}
valid := h.ourParams.ValidFor0RTT(t.Parameters)
if !valid {
h.logger.Debugf("Transport parameters changed. Rejecting 0-RTT.")
return false
}
if !h.allow0RTT {
h.logger.Debugf("0-RTT not allowed. Rejecting 0-RTT.")
return false
}
return true
}
// rejected0RTT is called for the client when the server rejects 0-RTT.
func (h *cryptoSetup) rejected0RTT() {
h.logger.Debugf("0-RTT was rejected. Dropping 0-RTT keys.")
had0RTTKeys := h.zeroRTTSealer != nil
h.zeroRTTSealer = nil
if had0RTTKeys {
h.events = append(h.events, Event{Kind: EventDiscard0RTTKeys})
}
}
func (h *cryptoSetup) setReadKey(el tls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
suite := getCipherSuite(suiteID)
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
switch el {
case tls.QUICEncryptionLevelEarly:
if h.perspective == protocol.PerspectiveClient {
panic("Received 0-RTT read key for the client")
}
h.zeroRTTOpener = newLongHeaderOpener(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
)
h.used0RTT.Store(true)
if h.logger.Debug() {
h.logger.Debugf("Installed 0-RTT Read keys (using %s)", tls.CipherSuiteName(suite.ID))
}
case tls.QUICEncryptionLevelHandshake:
h.handshakeOpener = newLongHeaderOpener(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
)
if h.logger.Debug() {
h.logger.Debugf("Installed Handshake Read keys (using %s)", tls.CipherSuiteName(suite.ID))
}
case tls.QUICEncryptionLevelApplication:
h.aead.SetReadKey(suite, trafficSecret)
h.has1RTTOpener = true
if h.logger.Debug() {
h.logger.Debugf("Installed 1-RTT Read keys (using %s)", tls.CipherSuiteName(suite.ID))
}
default:
panic("unexpected read encryption level")
}
h.events = append(h.events, Event{Kind: EventReceivedReadKeys})
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(el), h.perspective.Opposite()),
})
}
}
func (h *cryptoSetup) setWriteKey(el tls.QUICEncryptionLevel, suiteID uint16, trafficSecret []byte) {
suite := getCipherSuite(suiteID)
//nolint:exhaustive // The TLS stack doesn't export Initial keys.
switch el {
case tls.QUICEncryptionLevelEarly:
if h.perspective == protocol.PerspectiveServer {
panic("Received 0-RTT write key for the server")
}
h.zeroRTTSealer = newLongHeaderSealer(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
)
if h.logger.Debug() {
h.logger.Debugf("Installed 0-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.Encryption0RTT, h.perspective),
})
}
// don't set used0RTT here. 0-RTT might still get rejected.
return
case tls.QUICEncryptionLevelHandshake:
h.handshakeSealer = newLongHeaderSealer(
createAEAD(suite, trafficSecret, h.version),
newHeaderProtector(suite, trafficSecret, true, h.version),
)
if h.logger.Debug() {
h.logger.Debugf("Installed Handshake Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
case tls.QUICEncryptionLevelApplication:
h.aead.SetWriteKey(suite, trafficSecret)
h.has1RTTSealer = true
if h.logger.Debug() {
h.logger.Debugf("Installed 1-RTT Write keys (using %s)", tls.CipherSuiteName(suite.ID))
}
if h.zeroRTTSealer != nil {
// Once we receive handshake keys, we know that 0-RTT was not rejected.
h.used0RTT.Store(true)
h.zeroRTTSealer = nil
h.logger.Debugf("Dropping 0-RTT keys.")
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT})
}
}
default:
panic("unexpected write encryption level")
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateTLS,
KeyType: encLevelToKeyType(protocol.FromTLSEncryptionLevel(el), h.perspective),
})
}
}
// 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 tls.QUICEncryptionLevelInitial:
h.events = append(h.events, Event{Kind: EventWriteInitialData, Data: p})
case tls.QUICEncryptionLevelHandshake:
h.events = append(h.events, Event{Kind: EventWriteHandshakeData, Data: p})
case tls.QUICEncryptionLevelApplication:
panic("unexpected write")
default:
panic(fmt.Sprintf("unexpected write encryption level: %s", encLevel))
}
}
func (h *cryptoSetup) DiscardInitialKeys() {
dropped := h.initialOpener != nil
h.initialOpener = nil
h.initialSealer = nil
if dropped {
h.logger.Debugf("Dropping Initial keys.")
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientInitial})
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerInitial})
}
}
}
func (h *cryptoSetup) handshakeComplete() {
h.handshakeCompleteTime = time.Now()
h.events = append(h.events, Event{Kind: EventHandshakeComplete})
}
func (h *cryptoSetup) SetHandshakeConfirmed() {
h.aead.SetHandshakeConfirmed()
// drop Handshake keys
var dropped bool
if h.handshakeOpener != nil {
h.handshakeOpener = nil
h.handshakeSealer = nil
dropped = true
}
if dropped {
h.logger.Debugf("Dropping Handshake keys.")
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClientHandshake})
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeServerHandshake})
}
}
}
func (h *cryptoSetup) GetInitialSealer() (LongHeaderSealer, error) {
if h.initialSealer == nil {
return nil, ErrKeysDropped
}
return h.initialSealer, nil
}
func (h *cryptoSetup) Get0RTTSealer() (LongHeaderSealer, error) {
if h.zeroRTTSealer == nil {
return nil, ErrKeysDropped
}
return h.zeroRTTSealer, nil
}
func (h *cryptoSetup) GetHandshakeSealer() (LongHeaderSealer, error) {
if h.handshakeSealer == nil {
if h.initialSealer == nil {
return nil, ErrKeysDropped
}
return nil, ErrKeysNotYetAvailable
}
return h.handshakeSealer, nil
}
func (h *cryptoSetup) Get1RTTSealer() (ShortHeaderSealer, error) {
if !h.has1RTTSealer {
return nil, ErrKeysNotYetAvailable
}
return h.aead, nil
}
func (h *cryptoSetup) GetInitialOpener() (LongHeaderOpener, error) {
if h.initialOpener == nil {
return nil, ErrKeysDropped
}
return h.initialOpener, nil
}
func (h *cryptoSetup) Get0RTTOpener() (LongHeaderOpener, error) {
if h.zeroRTTOpener == nil {
if h.initialOpener != nil {
return nil, ErrKeysNotYetAvailable
}
// if the initial opener is also not available, the keys were already dropped
return nil, ErrKeysDropped
}
return h.zeroRTTOpener, nil
}
func (h *cryptoSetup) GetHandshakeOpener() (LongHeaderOpener, error) {
if h.handshakeOpener == nil {
if h.initialOpener != nil {
return nil, ErrKeysNotYetAvailable
}
// if the initial opener is also not available, the keys were already dropped
return nil, ErrKeysDropped
}
return h.handshakeOpener, nil
}
func (h *cryptoSetup) Get1RTTOpener() (ShortHeaderOpener, error) {
if h.zeroRTTOpener != nil && time.Since(h.handshakeCompleteTime) > 3*h.rttStats.PTO(true) {
h.zeroRTTOpener = nil
h.logger.Debugf("Dropping 0-RTT keys.")
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.KeyDiscarded{KeyType: qlog.KeyTypeClient0RTT})
}
}
if !h.has1RTTOpener {
return nil, ErrKeysNotYetAvailable
}
return h.aead, nil
}
func (h *cryptoSetup) ConnectionState() ConnectionState {
return ConnectionState{
ConnectionState: h.conn.ConnectionState(),
Used0RTT: h.used0RTT.Load(),
}
}
func wrapError(err error) error {
if alertErr := tls.AlertError(0); errors.As(err, &alertErr) {
return qerr.NewLocalCryptoError(uint8(alertErr), err)
}
return &qerr.TransportError{ErrorCode: qerr.InternalError, ErrorMessage: err.Error()}
}
func encLevelToKeyType(encLevel protocol.EncryptionLevel, pers protocol.Perspective) qlog.KeyType {
if pers == protocol.PerspectiveServer {
switch encLevel {
case protocol.EncryptionInitial:
return qlog.KeyTypeServerInitial
case protocol.EncryptionHandshake:
return qlog.KeyTypeServerHandshake
case protocol.Encryption0RTT:
return qlog.KeyTypeServer0RTT
case protocol.Encryption1RTT:
return qlog.KeyTypeServer1RTT
default:
return ""
}
}
switch encLevel {
case protocol.EncryptionInitial:
return qlog.KeyTypeClientInitial
case protocol.EncryptionHandshake:
return qlog.KeyTypeClientHandshake
case protocol.Encryption0RTT:
return qlog.KeyTypeClient0RTT
case protocol.Encryption1RTT:
return qlog.KeyTypeClient1RTT
default:
return ""
}
}
package handshake
import (
"net"
"time"
)
type conn struct {
localAddr, remoteAddr net.Addr
}
var _ net.Conn = &conn{}
func (c *conn) Read([]byte) (int, error) { return 0, nil }
func (c *conn) Write([]byte) (int, error) { return 0, nil }
func (c *conn) Close() error { return nil }
func (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *conn) LocalAddr() net.Addr { return c.localAddr }
func (c *conn) SetReadDeadline(time.Time) error { return nil }
func (c *conn) SetWriteDeadline(time.Time) error { return nil }
func (c *conn) SetDeadline(time.Time) error { return nil }
//go:build !go1.26
package handshake
func withoutFIPSEnforcement(f func()) {
f()
}
package handshake
import (
"crypto/aes"
"crypto/cipher"
"crypto/tls"
"encoding/binary"
"fmt"
"golang.org/x/crypto/chacha20"
"github.com/quic-go/quic-go/internal/protocol"
)
type headerProtector interface {
EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte)
DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte)
}
func hkdfHeaderProtectionLabel(v protocol.Version) string {
if v == protocol.Version2 {
return "quicv2 hp"
}
return "quic hp"
}
func newHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.Version) headerProtector {
hkdfLabel := hkdfHeaderProtectionLabel(v)
switch suite.ID {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
return newAESHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel)
case tls.TLS_CHACHA20_POLY1305_SHA256:
return newChaChaHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel)
default:
panic(fmt.Sprintf("Invalid cipher suite id: %d", suite.ID))
}
}
type aesHeaderProtector struct {
mask [16]byte // AES always has a 16 byte block size
block cipher.Block
isLongHeader bool
}
var _ headerProtector = &aesHeaderProtector{}
func newAESHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
block, err := aes.NewCipher(hpKey)
if err != nil {
panic(fmt.Sprintf("error creating new AES cipher: %s", err))
}
return &aesHeaderProtector{
block: block,
isLongHeader: isLongHeader,
}
}
func (p *aesHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
p.apply(sample, firstByte, hdrBytes)
}
func (p *aesHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
p.apply(sample, firstByte, hdrBytes)
}
func (p *aesHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) {
if len(sample) != len(p.mask) {
panic("invalid sample size")
}
p.block.Encrypt(p.mask[:], sample)
if p.isLongHeader {
*firstByte ^= p.mask[0] & 0xf
} else {
*firstByte ^= p.mask[0] & 0x1f
}
for i := range hdrBytes {
hdrBytes[i] ^= p.mask[i+1]
}
}
type chachaHeaderProtector struct {
mask [5]byte
key [32]byte
isLongHeader bool
}
var _ headerProtector = &chachaHeaderProtector{}
func newChaChaHeaderProtector(suite cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
p := &chachaHeaderProtector{
isLongHeader: isLongHeader,
}
copy(p.key[:], hpKey)
return p
}
func (p *chachaHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
p.apply(sample, firstByte, hdrBytes)
}
func (p *chachaHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
p.apply(sample, firstByte, hdrBytes)
}
func (p *chachaHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) {
if len(sample) != 16 {
panic("invalid sample size")
}
for i := range 5 {
p.mask[i] = 0
}
cipher, err := chacha20.NewUnauthenticatedCipher(p.key[:], sample[4:])
if err != nil {
panic(err)
}
cipher.SetCounter(binary.LittleEndian.Uint32(sample[:4]))
cipher.XORKeyStream(p.mask[:], p.mask[:])
p.applyMask(firstByte, hdrBytes)
}
func (p *chachaHeaderProtector) applyMask(firstByte *byte, hdrBytes []byte) {
if p.isLongHeader {
*firstByte ^= p.mask[0] & 0xf
} else {
*firstByte ^= p.mask[0] & 0x1f
}
for i := range hdrBytes {
hdrBytes[i] ^= p.mask[i+1]
}
}
package handshake
import (
"crypto"
"crypto/hkdf"
"encoding/binary"
"fmt"
)
// hkdfExpandLabel HKDF expands a label as defined in RFC 8446, section 7.1.
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
b := make([]byte, 3, 3+6+len(label)+1+len(context))
binary.BigEndian.PutUint16(b, uint16(length))
b[2] = uint8(6 + len(label))
b = append(b, []byte("tls13 ")...)
b = append(b, []byte(label)...)
b = b[:3+6+len(label)+1]
b[3+6+len(label)] = uint8(len(context))
b = append(b, context...)
expanded, err := hkdf.Expand(hash.New, secret, string(b), length)
if err != nil {
panic(fmt.Errorf("quic: HKDF-Expand-Label invocation failed unexpectedly: %v", err))
}
return expanded
}
package handshake
import (
"crypto"
"crypto/hkdf"
"crypto/tls"
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
)
var (
quicSaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
quicSaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
)
const (
hkdfLabelKeyV1 = "quic key"
hkdfLabelKeyV2 = "quicv2 key"
hkdfLabelIVV1 = "quic iv"
hkdfLabelIVV2 = "quicv2 iv"
)
var initialSuite = getCipherSuite(tls.TLS_AES_128_GCM_SHA256)
// NewInitialAEAD creates a new AEAD for Initial encryption / decryption.
func NewInitialAEAD(connID protocol.ConnectionID, pers protocol.Perspective, v protocol.Version) (LongHeaderSealer, LongHeaderOpener) {
var sealer LongHeaderSealer
var opener LongHeaderOpener
// The keys for the Initial AEAD are derived from the connection ID and constants defined in RFC 9001, Section 5.2.
// By design, the Initial encryption level provides no confidentiality against any attacker who has read the RFC.
// Its sole purpose is integrity protection. The Initial encryption level is therefore out of scope for FIPS 140.
// See also this thread on the IETF QUIC mailing list: https://mailarchive.ietf.org/arch/msg/quic/k2kl2W_n5WDEZBbt3O31Ef2XBbM.
withoutFIPSEnforcement(func() {
clientSecret, serverSecret := computeSecrets(connID, v)
var mySecret, otherSecret []byte
if pers == protocol.PerspectiveClient {
mySecret = clientSecret
otherSecret = serverSecret
} else {
mySecret = serverSecret
otherSecret = clientSecret
}
myKey, myIV := computeInitialKeyAndIV(mySecret, v)
otherKey, otherIV := computeInitialKeyAndIV(otherSecret, v)
encrypter := initialSuite.AEAD(myKey, myIV)
decrypter := initialSuite.AEAD(otherKey, otherIV)
sealer = newLongHeaderSealer(encrypter, newHeaderProtector(initialSuite, mySecret, true, v))
opener = newLongHeaderOpener(decrypter, newHeaderProtector(initialSuite, otherSecret, true, v))
})
return sealer, opener
}
func computeSecrets(connID protocol.ConnectionID, v protocol.Version) (clientSecret, serverSecret []byte) {
salt := quicSaltV1
if v == protocol.Version2 {
salt = quicSaltV2
}
initialSecret, err := hkdf.Extract(crypto.SHA256.New, connID.Bytes(), salt)
if err != nil {
panic(fmt.Errorf("quic: HKDF-Extract invocation failed unexpectedly: %v", err))
}
clientSecret = hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
serverSecret = hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "server in", crypto.SHA256.Size())
return
}
func computeInitialKeyAndIV(secret []byte, v protocol.Version) (key, iv []byte) {
keyLabel := hkdfLabelKeyV1
ivLabel := hkdfLabelIVV1
if v == protocol.Version2 {
keyLabel = hkdfLabelKeyV2
ivLabel = hkdfLabelIVV2
}
key = hkdfExpandLabel(crypto.SHA256, secret, []byte{}, keyLabel, 16)
iv = hkdfExpandLabel(crypto.SHA256, secret, []byte{}, ivLabel, 12)
return
}
package handshake
import (
"context"
"crypto/tls"
"errors"
"io"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
var (
// ErrKeysNotYetAvailable is returned when an opener or a sealer is requested for an encryption level,
// but the corresponding opener has not yet been initialized
// This can happen when packets arrive out of order.
ErrKeysNotYetAvailable = errors.New("CryptoSetup: keys at this encryption level not yet available")
// ErrKeysDropped is returned when an opener or a sealer is requested for an encryption level,
// but the corresponding keys have already been dropped.
ErrKeysDropped = errors.New("CryptoSetup: keys were already dropped")
// ErrDecryptionFailed is returned when the AEAD fails to open the packet.
ErrDecryptionFailed = errors.New("decryption failed")
)
type headerDecryptor interface {
DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte)
}
// LongHeaderOpener opens a long header packet
type LongHeaderOpener interface {
headerDecryptor
DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber
Open(dst, src []byte, pn protocol.PacketNumber, associatedData []byte) ([]byte, error)
}
// ShortHeaderOpener opens a short header packet
type ShortHeaderOpener interface {
headerDecryptor
DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber
Open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, associatedData []byte) ([]byte, error)
}
// LongHeaderSealer seals a long header packet
type LongHeaderSealer interface {
Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte
EncryptHeader(sample []byte, firstByte *byte, pnBytes []byte)
Overhead() int
}
// ShortHeaderSealer seals a short header packet
type ShortHeaderSealer interface {
LongHeaderSealer
KeyPhase() protocol.KeyPhaseBit
}
type ConnectionState struct {
tls.ConnectionState
Used0RTT bool
}
// EventKind is the kind of handshake event.
type EventKind uint8
const (
// EventNoEvent signals that there are no new handshake events
EventNoEvent EventKind = iota + 1
// EventWriteInitialData contains new CRYPTO data to send at the Initial encryption level
EventWriteInitialData
// EventWriteHandshakeData contains new CRYPTO data to send at the Handshake encryption level
EventWriteHandshakeData
// EventReceivedReadKeys signals that new decryption keys are available.
// It doesn't say which encryption level those keys are for.
EventReceivedReadKeys
// EventDiscard0RTTKeys signals that the Handshake keys were discarded.
EventDiscard0RTTKeys
// EventReceivedTransportParameters contains the transport parameters sent by the peer.
EventReceivedTransportParameters
// EventRestoredTransportParameters contains the transport parameters restored from the session ticket.
// It is only used for the client.
EventRestoredTransportParameters
// EventHandshakeComplete signals that the TLS handshake was completed.
EventHandshakeComplete
)
func (k EventKind) String() string {
switch k {
case EventNoEvent:
return "EventNoEvent"
case EventWriteInitialData:
return "EventWriteInitialData"
case EventWriteHandshakeData:
return "EventWriteHandshakeData"
case EventReceivedReadKeys:
return "EventReceivedReadKeys"
case EventDiscard0RTTKeys:
return "EventDiscard0RTTKeys"
case EventReceivedTransportParameters:
return "EventReceivedTransportParameters"
case EventRestoredTransportParameters:
return "EventRestoredTransportParameters"
case EventHandshakeComplete:
return "EventHandshakeComplete"
default:
return "Unknown EventKind"
}
}
// Event is a handshake event.
type Event struct {
Kind EventKind
Data []byte
TransportParameters *wire.TransportParameters
}
// CryptoSetup handles the handshake and protecting / unprotecting packets
type CryptoSetup interface {
StartHandshake(context.Context) error
io.Closer
ChangeConnectionID(protocol.ConnectionID)
GetSessionTicket() ([]byte, error)
HandleMessage([]byte, protocol.EncryptionLevel) error
NextEvent() Event
SetLargest1RTTAcked(protocol.PacketNumber) error
DiscardInitialKeys()
SetHandshakeConfirmed()
ConnectionState() ConnectionState
GetInitialOpener() (LongHeaderOpener, error)
GetHandshakeOpener() (LongHeaderOpener, error)
Get0RTTOpener() (LongHeaderOpener, error)
Get1RTTOpener() (ShortHeaderOpener, error)
GetInitialSealer() (LongHeaderSealer, error)
GetHandshakeSealer() (LongHeaderSealer, error)
Get0RTTSealer() (LongHeaderSealer, error)
Get1RTTSealer() (ShortHeaderSealer, error)
}
//go:build go1.25 && !go1.26
package handshake
import "crypto/tls"
const quicErrorEvent tls.QUICEventKind = -1
func extractQUICEventError(tls.QUICEvent) error {
return nil
}
//go:build !go1.26
package handshake
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
)
// Instead of using an init function, the AEADs are created lazily.
// For more details see https://github.com/quic-go/quic-go/issues/4894.
var (
retryAEADv1 cipher.AEAD // used for QUIC v1 (RFC 9000)
retryAEADv2 cipher.AEAD // used for QUIC v2 (RFC 9369)
)
func initAEAD(key [16]byte) cipher.AEAD {
aes, err := aes.NewCipher(key[:])
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(aes)
if err != nil {
panic(err)
}
return aead
}
var (
retryBuf bytes.Buffer
retryMutex sync.Mutex
retryNonceV1 = [12]byte{0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb}
retryNonceV2 = [12]byte{0xd8, 0x69, 0x69, 0xbc, 0x2d, 0x7c, 0x6d, 0x99, 0x90, 0xef, 0xb0, 0x4a}
)
// GetRetryIntegrityTag calculates the integrity tag on a Retry packet
func GetRetryIntegrityTag(retry []byte, origDestConnID protocol.ConnectionID, version protocol.Version) *[16]byte {
retryMutex.Lock()
defer retryMutex.Unlock()
retryBuf.WriteByte(uint8(origDestConnID.Len()))
retryBuf.Write(origDestConnID.Bytes())
retryBuf.Write(retry)
defer retryBuf.Reset()
var tag [16]byte
var sealed []byte
if version == protocol.Version2 {
if retryAEADv2 == nil {
retryAEADv2 = initAEAD([16]byte{0x8f, 0xb4, 0xb0, 0x1b, 0x56, 0xac, 0x48, 0xe2, 0x60, 0xfb, 0xcb, 0xce, 0xad, 0x7c, 0xcc, 0x92})
}
sealed = retryAEADv2.Seal(tag[:0], retryNonceV2[:], nil, retryBuf.Bytes())
} else {
if retryAEADv1 == nil {
retryAEADv1 = initAEAD([16]byte{0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e})
}
sealed = retryAEADv1.Seal(tag[:0], retryNonceV1[:], nil, retryBuf.Bytes())
}
if len(sealed) != 16 {
panic(fmt.Sprintf("unexpected Retry integrity tag length: %d", len(sealed)))
}
return &tag
}
package handshake
import (
"bytes"
"errors"
"fmt"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/quicvarint"
)
const sessionTicketRevision = 5
type sessionTicket struct {
Parameters *wire.TransportParameters
}
func (t *sessionTicket) Marshal() []byte {
b := make([]byte, 0, 256)
b = quicvarint.Append(b, sessionTicketRevision)
return t.Parameters.MarshalForSessionTicket(b)
}
func (t *sessionTicket) Unmarshal(b []byte) error {
rev, l, err := quicvarint.Parse(b)
if err != nil {
return errors.New("failed to read session ticket revision")
}
b = b[l:]
if rev != sessionTicketRevision {
return fmt.Errorf("unknown session ticket revision: %d", rev)
}
var tp wire.TransportParameters
if err := tp.UnmarshalFromSessionTicket(b); err != nil {
return fmt.Errorf("unmarshaling transport parameters from session ticket failed: %s", err.Error())
}
t.Parameters = &tp
return nil
}
const extraPrefix = "quic-go1"
func addSessionStateExtraPrefix(b []byte) []byte {
return append([]byte(extraPrefix), b...)
}
func findSessionStateExtraData(extras [][]byte) []byte {
prefix := []byte(extraPrefix)
for _, extra := range extras {
if data, ok := bytes.CutPrefix(extra, prefix); ok {
return data
}
}
return nil
}
//go:build !go1.27
package handshake
import (
"crypto/tls"
"net"
)
func setupConfigForClient(conf *tls.Config) *tls.Config {
conf = conf.Clone()
conf.MinVersion = tls.VersionTLS13
return conf
}
func setupConfigForServer(conf *tls.Config, localAddr, remoteAddr net.Addr) *tls.Config {
// Workaround for https://github.com/golang/go/issues/60506.
// This initializes the session tickets _before_ cloning the config.
_, _ = conf.DecryptTicket(nil, tls.ConnectionState{})
conf = conf.Clone()
conf.MinVersion = tls.VersionTLS13
// The tls.Config contains two callbacks that pass in a tls.ClientHelloInfo.
// Since crypto/tls doesn't do it, we need to make sure to set the Conn field with a fake net.Conn
// that allows the caller to get the local and the remote address.
if conf.GetConfigForClient != nil {
gcfc := conf.GetConfigForClient
conf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
c, err := gcfc(info)
if c != nil {
// we're returning a tls.Config here, so we need to apply this recursively
c = setupConfigForServer(c, localAddr, remoteAddr)
}
return c, err
}
}
if conf.GetCertificate != nil {
gc := conf.GetCertificate
conf.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
info.Conn = &conn{localAddr: localAddr, remoteAddr: remoteAddr}
return gc(info)
}
}
return conf
}
func getQUICConfig(tlsConf *tls.Config, _, _ net.Addr) *tls.QUICConfig {
return &tls.QUICConfig{
TLSConfig: tlsConf,
EnableSessionEvents: true,
}
}
package handshake
import (
"bytes"
"encoding/asn1"
"fmt"
"net"
"time"
"github.com/quic-go/quic-go/internal/protocol"
)
const (
tokenPrefixIP byte = iota
tokenPrefixString
)
// A Token is derived from the client address and can be used to verify the ownership of this address.
type Token struct {
IsRetryToken bool
SentTime time.Time
encodedRemoteAddr []byte
// only set for tokens sent in NEW_TOKEN frames
RTT time.Duration
// only set for retry tokens
OriginalDestConnectionID protocol.ConnectionID
RetrySrcConnectionID protocol.ConnectionID
}
// ValidateRemoteAddr validates the address, but does not check expiration
func (t *Token) ValidateRemoteAddr(addr net.Addr) bool {
return bytes.Equal(encodeRemoteAddr(addr), t.encodedRemoteAddr)
}
// token is the struct that is used for ASN1 serialization and deserialization
type token struct {
IsRetryToken bool
RemoteAddr []byte
Timestamp int64
RTT int64 // in mus
OriginalDestConnectionID []byte
RetrySrcConnectionID []byte
}
// A TokenGenerator generates tokens
type TokenGenerator struct {
tokenProtector tokenProtector
}
// NewTokenGenerator initializes a new TokenGenerator
func NewTokenGenerator(key TokenProtectorKey) *TokenGenerator {
return &TokenGenerator{tokenProtector: *newTokenProtector(key)}
}
// NewRetryToken generates a new token for a Retry for a given source address
func (g *TokenGenerator) NewRetryToken(
raddr net.Addr,
origDestConnID protocol.ConnectionID,
retrySrcConnID protocol.ConnectionID,
) ([]byte, error) {
data, err := asn1.Marshal(token{
IsRetryToken: true,
RemoteAddr: encodeRemoteAddr(raddr),
OriginalDestConnectionID: origDestConnID.Bytes(),
RetrySrcConnectionID: retrySrcConnID.Bytes(),
Timestamp: time.Now().UnixNano(),
})
if err != nil {
return nil, err
}
return g.tokenProtector.NewToken(data)
}
// NewToken generates a new token to be sent in a NEW_TOKEN frame
func (g *TokenGenerator) NewToken(raddr net.Addr, rtt time.Duration) ([]byte, error) {
data, err := asn1.Marshal(token{
RemoteAddr: encodeRemoteAddr(raddr),
Timestamp: time.Now().UnixNano(),
RTT: rtt.Microseconds(),
})
if err != nil {
return nil, err
}
return g.tokenProtector.NewToken(data)
}
// DecodeToken decodes a token
func (g *TokenGenerator) DecodeToken(encrypted []byte) (*Token, error) {
// if the client didn't send any token, DecodeToken will be called with a nil-slice
if len(encrypted) == 0 {
return nil, nil
}
data, err := g.tokenProtector.DecodeToken(encrypted)
if err != nil {
return nil, err
}
t := &token{}
rest, err := asn1.Unmarshal(data, t)
if err != nil {
return nil, err
}
if len(rest) != 0 {
return nil, fmt.Errorf("rest when unpacking token: %d", len(rest))
}
token := &Token{
IsRetryToken: t.IsRetryToken,
SentTime: time.Unix(0, t.Timestamp),
encodedRemoteAddr: t.RemoteAddr,
}
if t.IsRetryToken {
token.OriginalDestConnectionID = protocol.ParseConnectionID(t.OriginalDestConnectionID)
token.RetrySrcConnectionID = protocol.ParseConnectionID(t.RetrySrcConnectionID)
} else {
token.RTT = time.Duration(t.RTT) * time.Microsecond
}
return token, nil
}
// encodeRemoteAddr encodes a remote address such that it can be saved in the token
func encodeRemoteAddr(remoteAddr net.Addr) []byte {
if udpAddr, ok := remoteAddr.(*net.UDPAddr); ok {
return append([]byte{tokenPrefixIP}, udpAddr.IP...)
}
return append([]byte{tokenPrefixString}, []byte(remoteAddr.String())...)
}
package handshake
import (
"crypto/aes"
"crypto/cipher"
"crypto/hkdf"
"crypto/rand"
"crypto/sha256"
"fmt"
)
// TokenProtectorKey is the key used to encrypt both Retry and session resumption tokens.
type TokenProtectorKey [32]byte
const tokenSaltSize = 32
// tokenProtector is used to create and verify a token
type tokenProtector struct {
key TokenProtectorKey
}
// newTokenProtector creates a source for source address tokens
func newTokenProtector(key TokenProtectorKey) *tokenProtector {
return &tokenProtector{key: key}
}
// NewToken encodes data into a new token.
func (s *tokenProtector) NewToken(data []byte) ([]byte, error) {
var salt [tokenSaltSize]byte
if _, err := rand.Read(salt[:]); err != nil {
return nil, err
}
aead, err := s.createAEAD(salt[:])
if err != nil {
return nil, err
}
return append(salt[:], aead.Seal(nil, nil, data, nil)...), nil
}
// DecodeToken decodes a token.
func (s *tokenProtector) DecodeToken(p []byte) ([]byte, error) {
if len(p) < tokenSaltSize {
return nil, fmt.Errorf("token too short: %d", len(p))
}
salt := p[:tokenSaltSize]
aead, err := s.createAEAD(salt)
if err != nil {
return nil, err
}
if len(p[tokenSaltSize:]) < aead.Overhead() {
return nil, fmt.Errorf("token too short: %d", len(p))
}
return aead.Open(nil, nil, p[tokenSaltSize:], nil)
}
const tokenProtectorHKDFInfo = "quic-go token source"
func (s *tokenProtector) createAEAD(salt []byte) (cipher.AEAD, error) {
prk, err := hkdf.Extract(sha256.New, s.key[:], salt)
if err != nil {
return nil, err
}
key, err := hkdf.Expand(sha256.New, prk, tokenProtectorHKDFInfo, 32)
if err != nil {
return nil, err
}
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aead, err := cipher.NewGCMWithRandomNonce(c)
if err != nil {
return nil, err
}
return aead, nil
}
package handshake
import (
"crypto"
"crypto/cipher"
"crypto/tls"
"encoding/binary"
"fmt"
"sync/atomic"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
var keyUpdateInterval atomic.Uint64
func init() {
keyUpdateInterval.Store(protocol.KeyUpdateInterval)
}
func SetKeyUpdateInterval(v uint64) (reset func()) {
old := keyUpdateInterval.Swap(v)
return func() { keyUpdateInterval.Store(old) }
}
// FirstKeyUpdateInterval is the maximum number of packets we send or receive before initiating the first key update.
// It's a package-level variable to allow modifying it for testing purposes.
var FirstKeyUpdateInterval uint64 = 100
type updatableAEAD struct {
suite cipherSuite
keyPhase protocol.KeyPhase
largestAcked protocol.PacketNumber
firstPacketNumber protocol.PacketNumber
handshakeConfirmed bool
invalidPacketLimit uint64
invalidPacketCount uint64
// Time when the keys should be dropped. Keys are dropped on the next call to Open().
prevRcvAEADExpiry monotime.Time
prevRcvAEAD cipher.AEAD
firstRcvdWithCurrentKey protocol.PacketNumber
firstSentWithCurrentKey protocol.PacketNumber
highestRcvdPN protocol.PacketNumber // highest packet number received (which could be successfully unprotected)
numRcvdWithCurrentKey uint64
numSentWithCurrentKey uint64
rcvAEAD cipher.AEAD
sendAEAD cipher.AEAD
// caches cipher.AEAD.Overhead(). This speeds up calls to Overhead().
aeadOverhead int
nextRcvAEAD cipher.AEAD
nextSendAEAD cipher.AEAD
nextRcvTrafficSecret []byte
nextSendTrafficSecret []byte
headerDecrypter headerProtector
headerEncrypter headerProtector
rttStats *utils.RTTStats
qlogger qlogwriter.Recorder
logger utils.Logger
version protocol.Version
// use a single slice to avoid allocations
nonceBuf []byte
}
var (
_ ShortHeaderOpener = &updatableAEAD{}
_ ShortHeaderSealer = &updatableAEAD{}
)
func newUpdatableAEAD(rttStats *utils.RTTStats, qlogger qlogwriter.Recorder, logger utils.Logger, version protocol.Version) *updatableAEAD {
return &updatableAEAD{
firstPacketNumber: protocol.InvalidPacketNumber,
largestAcked: protocol.InvalidPacketNumber,
firstRcvdWithCurrentKey: protocol.InvalidPacketNumber,
firstSentWithCurrentKey: protocol.InvalidPacketNumber,
rttStats: rttStats,
qlogger: qlogger,
logger: logger,
version: version,
}
}
func (a *updatableAEAD) rollKeys() {
if a.prevRcvAEAD != nil {
a.logger.Debugf("Dropping key phase %d ahead of scheduled time. Drop time was: %s", a.keyPhase-1, a.prevRcvAEADExpiry)
if a.qlogger != nil {
a.qlogger.RecordEvent(qlog.KeyDiscarded{
KeyType: qlog.KeyTypeClient1RTT,
KeyPhase: a.keyPhase - 1,
})
a.qlogger.RecordEvent(qlog.KeyDiscarded{
KeyType: qlog.KeyTypeServer1RTT,
KeyPhase: a.keyPhase - 1,
})
}
a.prevRcvAEADExpiry = 0
}
a.keyPhase++
a.firstRcvdWithCurrentKey = protocol.InvalidPacketNumber
a.firstSentWithCurrentKey = protocol.InvalidPacketNumber
a.numRcvdWithCurrentKey = 0
a.numSentWithCurrentKey = 0
a.prevRcvAEAD = a.rcvAEAD
a.rcvAEAD = a.nextRcvAEAD
a.sendAEAD = a.nextSendAEAD
a.nextRcvTrafficSecret = a.getNextTrafficSecret(a.suite.Hash, a.nextRcvTrafficSecret)
a.nextSendTrafficSecret = a.getNextTrafficSecret(a.suite.Hash, a.nextSendTrafficSecret)
a.nextRcvAEAD = createAEAD(a.suite, a.nextRcvTrafficSecret, a.version)
a.nextSendAEAD = createAEAD(a.suite, a.nextSendTrafficSecret, a.version)
}
func (a *updatableAEAD) startKeyDropTimer(now monotime.Time) {
d := 3 * a.rttStats.PTO(true)
a.logger.Debugf("Starting key drop timer to drop key phase %d (in %s)", a.keyPhase-1, d)
a.prevRcvAEADExpiry = now.Add(d)
}
func (a *updatableAEAD) getNextTrafficSecret(hash crypto.Hash, ts []byte) []byte {
return hkdfExpandLabel(hash, ts, []byte{}, "quic ku", hash.Size())
}
// SetReadKey sets the read key.
// For the client, this function is called before SetWriteKey.
// For the server, this function is called after SetWriteKey.
func (a *updatableAEAD) SetReadKey(suite cipherSuite, trafficSecret []byte) {
a.rcvAEAD = createAEAD(suite, trafficSecret, a.version)
a.headerDecrypter = newHeaderProtector(suite, trafficSecret, false, a.version)
if a.suite.ID == 0 { // suite is not set yet
a.setAEADParameters(a.rcvAEAD, suite)
}
a.nextRcvTrafficSecret = a.getNextTrafficSecret(suite.Hash, trafficSecret)
a.nextRcvAEAD = createAEAD(suite, a.nextRcvTrafficSecret, a.version)
}
// SetWriteKey sets the write key.
// For the client, this function is called after SetReadKey.
// For the server, this function is called before SetReadKey.
func (a *updatableAEAD) SetWriteKey(suite cipherSuite, trafficSecret []byte) {
a.sendAEAD = createAEAD(suite, trafficSecret, a.version)
a.headerEncrypter = newHeaderProtector(suite, trafficSecret, false, a.version)
if a.suite.ID == 0 { // suite is not set yet
a.setAEADParameters(a.sendAEAD, suite)
}
a.nextSendTrafficSecret = a.getNextTrafficSecret(suite.Hash, trafficSecret)
a.nextSendAEAD = createAEAD(suite, a.nextSendTrafficSecret, a.version)
}
func (a *updatableAEAD) setAEADParameters(aead cipher.AEAD, suite cipherSuite) {
a.nonceBuf = make([]byte, aead.NonceSize())
a.aeadOverhead = aead.Overhead()
a.suite = suite
switch suite.ID {
case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
a.invalidPacketLimit = protocol.InvalidPacketLimitAES
case tls.TLS_CHACHA20_POLY1305_SHA256:
a.invalidPacketLimit = protocol.InvalidPacketLimitChaCha
default:
panic(fmt.Sprintf("unknown cipher suite %d", suite.ID))
}
}
func (a *updatableAEAD) DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber {
return protocol.DecodePacketNumber(wirePNLen, a.highestRcvdPN, wirePN)
}
func (a *updatableAEAD) Open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
dec, err := a.open(dst, src, rcvTime, pn, kp, ad)
if err == ErrDecryptionFailed {
a.invalidPacketCount++
if a.invalidPacketCount >= a.invalidPacketLimit {
return nil, &qerr.TransportError{ErrorCode: qerr.AEADLimitReached}
}
}
if err == nil {
a.highestRcvdPN = max(a.highestRcvdPN, pn)
}
return dec, err
}
func (a *updatableAEAD) open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, ad []byte) ([]byte, error) {
if a.prevRcvAEAD != nil && !a.prevRcvAEADExpiry.IsZero() && rcvTime.After(a.prevRcvAEADExpiry) {
a.prevRcvAEAD = nil
a.logger.Debugf("Dropping key phase %d", a.keyPhase-1)
a.prevRcvAEADExpiry = 0
if a.qlogger != nil {
a.qlogger.RecordEvent(qlog.KeyDiscarded{
KeyType: qlog.KeyTypeClient1RTT,
KeyPhase: a.keyPhase - 1,
})
a.qlogger.RecordEvent(qlog.KeyDiscarded{
KeyType: qlog.KeyTypeServer1RTT,
KeyPhase: a.keyPhase - 1,
})
}
}
binary.BigEndian.PutUint64(a.nonceBuf[len(a.nonceBuf)-8:], uint64(pn))
if kp != a.keyPhase.Bit() {
if a.keyPhase > 0 && a.firstRcvdWithCurrentKey == protocol.InvalidPacketNumber || pn < a.firstRcvdWithCurrentKey {
if a.prevRcvAEAD == nil {
return nil, ErrKeysDropped
}
// we updated the key, but the peer hasn't updated yet
dec, err := a.prevRcvAEAD.Open(dst, a.nonceBuf, src, ad)
if err != nil {
err = ErrDecryptionFailed
}
return dec, err
}
// try opening the packet with the next key phase
dec, err := a.nextRcvAEAD.Open(dst, a.nonceBuf, src, ad)
if err != nil {
return nil, ErrDecryptionFailed
}
// Opening succeeded. Check if the peer was allowed to update.
if a.keyPhase > 0 && a.firstSentWithCurrentKey == protocol.InvalidPacketNumber {
return nil, &qerr.TransportError{
ErrorCode: qerr.KeyUpdateError,
ErrorMessage: "keys updated too quickly",
}
}
a.rollKeys()
a.logger.Debugf("Peer updated keys to %d", a.keyPhase)
// The peer initiated this key update. It's safe to drop the keys for the previous generation now.
// Start a timer to drop the previous key generation.
a.startKeyDropTimer(rcvTime)
if a.qlogger != nil {
a.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateRemote,
KeyType: qlog.KeyTypeClient1RTT,
KeyPhase: a.keyPhase,
})
a.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateRemote,
KeyType: qlog.KeyTypeServer1RTT,
KeyPhase: a.keyPhase,
})
}
a.firstRcvdWithCurrentKey = pn
return dec, err
}
// 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 := a.rcvAEAD.Open(dst, a.nonceBuf, src, ad)
if err != nil {
return dec, ErrDecryptionFailed
}
a.numRcvdWithCurrentKey++
if a.firstRcvdWithCurrentKey == protocol.InvalidPacketNumber {
// We initiated the key updated, and now we received the first packet protected with the new key phase.
// Therefore, we are certain that the peer rolled its keys as well. Start a timer to drop the old keys.
if a.keyPhase > 0 {
a.logger.Debugf("Peer confirmed key update to phase %d", a.keyPhase)
a.startKeyDropTimer(rcvTime)
}
a.firstRcvdWithCurrentKey = pn
}
return dec, err
}
func (a *updatableAEAD) Seal(dst, src []byte, pn protocol.PacketNumber, ad []byte) []byte {
if a.firstSentWithCurrentKey == protocol.InvalidPacketNumber {
a.firstSentWithCurrentKey = pn
}
if a.firstPacketNumber == protocol.InvalidPacketNumber {
a.firstPacketNumber = pn
}
a.numSentWithCurrentKey++
binary.BigEndian.PutUint64(a.nonceBuf[len(a.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 a.sendAEAD.Seal(dst, a.nonceBuf, src, ad)
}
func (a *updatableAEAD) SetLargestAcked(pn protocol.PacketNumber) error {
if a.firstSentWithCurrentKey != protocol.InvalidPacketNumber &&
pn >= a.firstSentWithCurrentKey && a.numRcvdWithCurrentKey == 0 {
return &qerr.TransportError{
ErrorCode: qerr.KeyUpdateError,
ErrorMessage: fmt.Sprintf("received ACK for key phase %d, but peer didn't update keys", a.keyPhase),
}
}
a.largestAcked = pn
return nil
}
func (a *updatableAEAD) SetHandshakeConfirmed() {
a.handshakeConfirmed = true
}
func (a *updatableAEAD) updateAllowed() bool {
if !a.handshakeConfirmed {
return false
}
// the first key update is allowed as soon as the handshake is confirmed
return a.keyPhase == 0 ||
// subsequent key updates as soon as a packet sent with that key phase has been acknowledged
(a.firstSentWithCurrentKey != protocol.InvalidPacketNumber &&
a.largestAcked != protocol.InvalidPacketNumber &&
a.largestAcked >= a.firstSentWithCurrentKey)
}
func (a *updatableAEAD) shouldInitiateKeyUpdate() bool {
if !a.updateAllowed() {
return false
}
// Initiate the first key update shortly after the handshake, in order to exercise the key update mechanism.
if a.keyPhase == 0 {
if a.numRcvdWithCurrentKey >= FirstKeyUpdateInterval || a.numSentWithCurrentKey >= FirstKeyUpdateInterval {
return true
}
}
if a.numRcvdWithCurrentKey >= keyUpdateInterval.Load() {
a.logger.Debugf("Received %d packets with current key phase. Initiating key update to the next key phase: %d", a.numRcvdWithCurrentKey, a.keyPhase+1)
return true
}
if a.numSentWithCurrentKey >= keyUpdateInterval.Load() {
a.logger.Debugf("Sent %d packets with current key phase. Initiating key update to the next key phase: %d", a.numSentWithCurrentKey, a.keyPhase+1)
return true
}
return false
}
func (a *updatableAEAD) KeyPhase() protocol.KeyPhaseBit {
if a.shouldInitiateKeyUpdate() {
a.rollKeys()
if a.qlogger != nil {
a.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateLocal,
KeyType: qlog.KeyTypeClient1RTT,
KeyPhase: a.keyPhase,
})
a.qlogger.RecordEvent(qlog.KeyUpdated{
Trigger: qlog.KeyUpdateLocal,
KeyType: qlog.KeyTypeServer1RTT,
KeyPhase: a.keyPhase,
})
}
}
return a.keyPhase.Bit()
}
func (a *updatableAEAD) Overhead() int {
return a.aeadOverhead
}
func (a *updatableAEAD) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
a.headerEncrypter.EncryptHeader(sample, firstByte, hdrBytes)
}
func (a *updatableAEAD) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
a.headerDecrypter.DecryptHeader(sample, firstByte, hdrBytes)
}
func (a *updatableAEAD) FirstPacketNumber() protocol.PacketNumber {
return a.firstPacketNumber
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/ackhandler (interfaces: SentPacketHandler)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mockackhandler -destination ackhandler/sent_packet_handler.go github.com/quic-go/quic-go/internal/ackhandler SentPacketHandler
//
// Package mockackhandler is a generated GoMock package.
package mockackhandler
import (
reflect "reflect"
ackhandler "github.com/quic-go/quic-go/internal/ackhandler"
monotime "github.com/quic-go/quic-go/internal/monotime"
protocol "github.com/quic-go/quic-go/internal/protocol"
wire "github.com/quic-go/quic-go/internal/wire"
gomock "go.uber.org/mock/gomock"
)
// MockSentPacketHandler is a mock of SentPacketHandler interface.
type MockSentPacketHandler struct {
ctrl *gomock.Controller
recorder *MockSentPacketHandlerMockRecorder
isgomock struct{}
}
// MockSentPacketHandlerMockRecorder is the mock recorder for MockSentPacketHandler.
type MockSentPacketHandlerMockRecorder struct {
mock *MockSentPacketHandler
}
// NewMockSentPacketHandler creates a new mock instance.
func NewMockSentPacketHandler(ctrl *gomock.Controller) *MockSentPacketHandler {
mock := &MockSentPacketHandler{ctrl: ctrl}
mock.recorder = &MockSentPacketHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSentPacketHandler) EXPECT() *MockSentPacketHandlerMockRecorder {
return m.recorder
}
// DropPackets mocks base method.
func (m *MockSentPacketHandler) DropPackets(arg0 protocol.EncryptionLevel, rcvTime monotime.Time) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DropPackets", arg0, rcvTime)
}
// DropPackets indicates an expected call of DropPackets.
func (mr *MockSentPacketHandlerMockRecorder) DropPackets(arg0, rcvTime any) *MockSentPacketHandlerDropPacketsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropPackets", reflect.TypeOf((*MockSentPacketHandler)(nil).DropPackets), arg0, rcvTime)
return &MockSentPacketHandlerDropPacketsCall{Call: call}
}
// MockSentPacketHandlerDropPacketsCall wrap *gomock.Call
type MockSentPacketHandlerDropPacketsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerDropPacketsCall) Return() *MockSentPacketHandlerDropPacketsCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerDropPacketsCall) Do(f func(protocol.EncryptionLevel, monotime.Time)) *MockSentPacketHandlerDropPacketsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerDropPacketsCall) DoAndReturn(f func(protocol.EncryptionLevel, monotime.Time)) *MockSentPacketHandlerDropPacketsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ECNMode mocks base method.
func (m *MockSentPacketHandler) ECNMode(isShortHeaderPacket bool) protocol.ECN {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ECNMode", isShortHeaderPacket)
ret0, _ := ret[0].(protocol.ECN)
return ret0
}
// ECNMode indicates an expected call of ECNMode.
func (mr *MockSentPacketHandlerMockRecorder) ECNMode(isShortHeaderPacket any) *MockSentPacketHandlerECNModeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ECNMode", reflect.TypeOf((*MockSentPacketHandler)(nil).ECNMode), isShortHeaderPacket)
return &MockSentPacketHandlerECNModeCall{Call: call}
}
// MockSentPacketHandlerECNModeCall wrap *gomock.Call
type MockSentPacketHandlerECNModeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerECNModeCall) Return(arg0 protocol.ECN) *MockSentPacketHandlerECNModeCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerECNModeCall) Do(f func(bool) protocol.ECN) *MockSentPacketHandlerECNModeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerECNModeCall) DoAndReturn(f func(bool) protocol.ECN) *MockSentPacketHandlerECNModeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetLossDetectionTimeout mocks base method.
func (m *MockSentPacketHandler) GetLossDetectionTimeout() monotime.Time {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLossDetectionTimeout")
ret0, _ := ret[0].(monotime.Time)
return ret0
}
// GetLossDetectionTimeout indicates an expected call of GetLossDetectionTimeout.
func (mr *MockSentPacketHandlerMockRecorder) GetLossDetectionTimeout() *MockSentPacketHandlerGetLossDetectionTimeoutCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLossDetectionTimeout", reflect.TypeOf((*MockSentPacketHandler)(nil).GetLossDetectionTimeout))
return &MockSentPacketHandlerGetLossDetectionTimeoutCall{Call: call}
}
// MockSentPacketHandlerGetLossDetectionTimeoutCall wrap *gomock.Call
type MockSentPacketHandlerGetLossDetectionTimeoutCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerGetLossDetectionTimeoutCall) Return(arg0 monotime.Time) *MockSentPacketHandlerGetLossDetectionTimeoutCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerGetLossDetectionTimeoutCall) Do(f func() monotime.Time) *MockSentPacketHandlerGetLossDetectionTimeoutCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerGetLossDetectionTimeoutCall) DoAndReturn(f func() monotime.Time) *MockSentPacketHandlerGetLossDetectionTimeoutCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MigratedPath mocks base method.
func (m *MockSentPacketHandler) MigratedPath(now monotime.Time, initialMaxPacketSize protocol.ByteCount) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "MigratedPath", now, initialMaxPacketSize)
}
// MigratedPath indicates an expected call of MigratedPath.
func (mr *MockSentPacketHandlerMockRecorder) MigratedPath(now, initialMaxPacketSize any) *MockSentPacketHandlerMigratedPathCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MigratedPath", reflect.TypeOf((*MockSentPacketHandler)(nil).MigratedPath), now, initialMaxPacketSize)
return &MockSentPacketHandlerMigratedPathCall{Call: call}
}
// MockSentPacketHandlerMigratedPathCall wrap *gomock.Call
type MockSentPacketHandlerMigratedPathCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerMigratedPathCall) Return() *MockSentPacketHandlerMigratedPathCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerMigratedPathCall) Do(f func(monotime.Time, protocol.ByteCount)) *MockSentPacketHandlerMigratedPathCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerMigratedPathCall) DoAndReturn(f func(monotime.Time, protocol.ByteCount)) *MockSentPacketHandlerMigratedPathCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnLossDetectionTimeout mocks base method.
func (m *MockSentPacketHandler) OnLossDetectionTimeout(now monotime.Time) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OnLossDetectionTimeout", now)
ret0, _ := ret[0].(error)
return ret0
}
// OnLossDetectionTimeout indicates an expected call of OnLossDetectionTimeout.
func (mr *MockSentPacketHandlerMockRecorder) OnLossDetectionTimeout(now any) *MockSentPacketHandlerOnLossDetectionTimeoutCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnLossDetectionTimeout", reflect.TypeOf((*MockSentPacketHandler)(nil).OnLossDetectionTimeout), now)
return &MockSentPacketHandlerOnLossDetectionTimeoutCall{Call: call}
}
// MockSentPacketHandlerOnLossDetectionTimeoutCall wrap *gomock.Call
type MockSentPacketHandlerOnLossDetectionTimeoutCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerOnLossDetectionTimeoutCall) Return(arg0 error) *MockSentPacketHandlerOnLossDetectionTimeoutCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerOnLossDetectionTimeoutCall) Do(f func(monotime.Time) error) *MockSentPacketHandlerOnLossDetectionTimeoutCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerOnLossDetectionTimeoutCall) DoAndReturn(f func(monotime.Time) error) *MockSentPacketHandlerOnLossDetectionTimeoutCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// PeekPacketNumber mocks base method.
func (m *MockSentPacketHandler) PeekPacketNumber(arg0 protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PeekPacketNumber", arg0)
ret0, _ := ret[0].(protocol.PacketNumber)
ret1, _ := ret[1].(protocol.PacketNumberLen)
return ret0, ret1
}
// PeekPacketNumber indicates an expected call of PeekPacketNumber.
func (mr *MockSentPacketHandlerMockRecorder) PeekPacketNumber(arg0 any) *MockSentPacketHandlerPeekPacketNumberCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeekPacketNumber", reflect.TypeOf((*MockSentPacketHandler)(nil).PeekPacketNumber), arg0)
return &MockSentPacketHandlerPeekPacketNumberCall{Call: call}
}
// MockSentPacketHandlerPeekPacketNumberCall wrap *gomock.Call
type MockSentPacketHandlerPeekPacketNumberCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerPeekPacketNumberCall) Return(arg0 protocol.PacketNumber, arg1 protocol.PacketNumberLen) *MockSentPacketHandlerPeekPacketNumberCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerPeekPacketNumberCall) Do(f func(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)) *MockSentPacketHandlerPeekPacketNumberCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerPeekPacketNumberCall) DoAndReturn(f func(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)) *MockSentPacketHandlerPeekPacketNumberCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// PopPacketNumber mocks base method.
func (m *MockSentPacketHandler) PopPacketNumber(arg0 protocol.EncryptionLevel) protocol.PacketNumber {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PopPacketNumber", arg0)
ret0, _ := ret[0].(protocol.PacketNumber)
return ret0
}
// PopPacketNumber indicates an expected call of PopPacketNumber.
func (mr *MockSentPacketHandlerMockRecorder) PopPacketNumber(arg0 any) *MockSentPacketHandlerPopPacketNumberCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PopPacketNumber", reflect.TypeOf((*MockSentPacketHandler)(nil).PopPacketNumber), arg0)
return &MockSentPacketHandlerPopPacketNumberCall{Call: call}
}
// MockSentPacketHandlerPopPacketNumberCall wrap *gomock.Call
type MockSentPacketHandlerPopPacketNumberCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerPopPacketNumberCall) Return(arg0 protocol.PacketNumber) *MockSentPacketHandlerPopPacketNumberCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerPopPacketNumberCall) Do(f func(protocol.EncryptionLevel) protocol.PacketNumber) *MockSentPacketHandlerPopPacketNumberCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerPopPacketNumberCall) DoAndReturn(f func(protocol.EncryptionLevel) protocol.PacketNumber) *MockSentPacketHandlerPopPacketNumberCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// QueueProbePacket mocks base method.
func (m *MockSentPacketHandler) QueueProbePacket(arg0 protocol.EncryptionLevel) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueueProbePacket", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// QueueProbePacket indicates an expected call of QueueProbePacket.
func (mr *MockSentPacketHandlerMockRecorder) QueueProbePacket(arg0 any) *MockSentPacketHandlerQueueProbePacketCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueProbePacket", reflect.TypeOf((*MockSentPacketHandler)(nil).QueueProbePacket), arg0)
return &MockSentPacketHandlerQueueProbePacketCall{Call: call}
}
// MockSentPacketHandlerQueueProbePacketCall wrap *gomock.Call
type MockSentPacketHandlerQueueProbePacketCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerQueueProbePacketCall) Return(arg0 bool) *MockSentPacketHandlerQueueProbePacketCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerQueueProbePacketCall) Do(f func(protocol.EncryptionLevel) bool) *MockSentPacketHandlerQueueProbePacketCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerQueueProbePacketCall) DoAndReturn(f func(protocol.EncryptionLevel) bool) *MockSentPacketHandlerQueueProbePacketCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ReceivedAck mocks base method.
func (m *MockSentPacketHandler) ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime monotime.Time) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReceivedAck", f, encLevel, rcvTime)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReceivedAck indicates an expected call of ReceivedAck.
func (mr *MockSentPacketHandlerMockRecorder) ReceivedAck(f, encLevel, rcvTime any) *MockSentPacketHandlerReceivedAckCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedAck", reflect.TypeOf((*MockSentPacketHandler)(nil).ReceivedAck), f, encLevel, rcvTime)
return &MockSentPacketHandlerReceivedAckCall{Call: call}
}
// MockSentPacketHandlerReceivedAckCall wrap *gomock.Call
type MockSentPacketHandlerReceivedAckCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerReceivedAckCall) Return(arg0 bool, arg1 error) *MockSentPacketHandlerReceivedAckCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerReceivedAckCall) Do(f func(*wire.AckFrame, protocol.EncryptionLevel, monotime.Time) (bool, error)) *MockSentPacketHandlerReceivedAckCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerReceivedAckCall) DoAndReturn(f func(*wire.AckFrame, protocol.EncryptionLevel, monotime.Time) (bool, error)) *MockSentPacketHandlerReceivedAckCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ReceivedBytes mocks base method.
func (m *MockSentPacketHandler) ReceivedBytes(arg0 protocol.ByteCount, rcvTime monotime.Time) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ReceivedBytes", arg0, rcvTime)
}
// ReceivedBytes indicates an expected call of ReceivedBytes.
func (mr *MockSentPacketHandlerMockRecorder) ReceivedBytes(arg0, rcvTime any) *MockSentPacketHandlerReceivedBytesCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedBytes", reflect.TypeOf((*MockSentPacketHandler)(nil).ReceivedBytes), arg0, rcvTime)
return &MockSentPacketHandlerReceivedBytesCall{Call: call}
}
// MockSentPacketHandlerReceivedBytesCall wrap *gomock.Call
type MockSentPacketHandlerReceivedBytesCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerReceivedBytesCall) Return() *MockSentPacketHandlerReceivedBytesCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerReceivedBytesCall) Do(f func(protocol.ByteCount, monotime.Time)) *MockSentPacketHandlerReceivedBytesCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerReceivedBytesCall) DoAndReturn(f func(protocol.ByteCount, monotime.Time)) *MockSentPacketHandlerReceivedBytesCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ReceivedPacket mocks base method.
func (m *MockSentPacketHandler) ReceivedPacket(arg0 protocol.EncryptionLevel, arg1 monotime.Time) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ReceivedPacket", arg0, arg1)
}
// ReceivedPacket indicates an expected call of ReceivedPacket.
func (mr *MockSentPacketHandlerMockRecorder) ReceivedPacket(arg0, arg1 any) *MockSentPacketHandlerReceivedPacketCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceivedPacket", reflect.TypeOf((*MockSentPacketHandler)(nil).ReceivedPacket), arg0, arg1)
return &MockSentPacketHandlerReceivedPacketCall{Call: call}
}
// MockSentPacketHandlerReceivedPacketCall wrap *gomock.Call
type MockSentPacketHandlerReceivedPacketCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerReceivedPacketCall) Return() *MockSentPacketHandlerReceivedPacketCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerReceivedPacketCall) Do(f func(protocol.EncryptionLevel, monotime.Time)) *MockSentPacketHandlerReceivedPacketCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerReceivedPacketCall) DoAndReturn(f func(protocol.EncryptionLevel, monotime.Time)) *MockSentPacketHandlerReceivedPacketCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ResetForRetry mocks base method.
func (m *MockSentPacketHandler) ResetForRetry(rcvTime monotime.Time) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetForRetry", rcvTime)
}
// ResetForRetry indicates an expected call of ResetForRetry.
func (mr *MockSentPacketHandlerMockRecorder) ResetForRetry(rcvTime any) *MockSentPacketHandlerResetForRetryCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetForRetry", reflect.TypeOf((*MockSentPacketHandler)(nil).ResetForRetry), rcvTime)
return &MockSentPacketHandlerResetForRetryCall{Call: call}
}
// MockSentPacketHandlerResetForRetryCall wrap *gomock.Call
type MockSentPacketHandlerResetForRetryCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerResetForRetryCall) Return() *MockSentPacketHandlerResetForRetryCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerResetForRetryCall) Do(f func(monotime.Time)) *MockSentPacketHandlerResetForRetryCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerResetForRetryCall) DoAndReturn(f func(monotime.Time)) *MockSentPacketHandlerResetForRetryCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SendMode mocks base method.
func (m *MockSentPacketHandler) SendMode(now monotime.Time) ackhandler.SendMode {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendMode", now)
ret0, _ := ret[0].(ackhandler.SendMode)
return ret0
}
// SendMode indicates an expected call of SendMode.
func (mr *MockSentPacketHandlerMockRecorder) SendMode(now any) *MockSentPacketHandlerSendModeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMode", reflect.TypeOf((*MockSentPacketHandler)(nil).SendMode), now)
return &MockSentPacketHandlerSendModeCall{Call: call}
}
// MockSentPacketHandlerSendModeCall wrap *gomock.Call
type MockSentPacketHandlerSendModeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerSendModeCall) Return(arg0 ackhandler.SendMode) *MockSentPacketHandlerSendModeCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerSendModeCall) Do(f func(monotime.Time) ackhandler.SendMode) *MockSentPacketHandlerSendModeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerSendModeCall) DoAndReturn(f func(monotime.Time) ackhandler.SendMode) *MockSentPacketHandlerSendModeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SentPacket mocks base method.
func (m *MockSentPacketHandler) SentPacket(t monotime.Time, pn, largestAcked protocol.PacketNumber, streamFrames []ackhandler.StreamFrame, frames []ackhandler.Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket, isPathProbePacket bool) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SentPacket", t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket, isPathProbePacket)
}
// SentPacket indicates an expected call of SentPacket.
func (mr *MockSentPacketHandlerMockRecorder) SentPacket(t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket, isPathProbePacket any) *MockSentPacketHandlerSentPacketCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SentPacket", reflect.TypeOf((*MockSentPacketHandler)(nil).SentPacket), t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket, isPathProbePacket)
return &MockSentPacketHandlerSentPacketCall{Call: call}
}
// MockSentPacketHandlerSentPacketCall wrap *gomock.Call
type MockSentPacketHandlerSentPacketCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerSentPacketCall) Return() *MockSentPacketHandlerSentPacketCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerSentPacketCall) Do(f func(monotime.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool, bool)) *MockSentPacketHandlerSentPacketCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerSentPacketCall) DoAndReturn(f func(monotime.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool, bool)) *MockSentPacketHandlerSentPacketCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetMaxDatagramSize mocks base method.
func (m *MockSentPacketHandler) SetMaxDatagramSize(count protocol.ByteCount) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetMaxDatagramSize", count)
}
// SetMaxDatagramSize indicates an expected call of SetMaxDatagramSize.
func (mr *MockSentPacketHandlerMockRecorder) SetMaxDatagramSize(count any) *MockSentPacketHandlerSetMaxDatagramSizeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxDatagramSize", reflect.TypeOf((*MockSentPacketHandler)(nil).SetMaxDatagramSize), count)
return &MockSentPacketHandlerSetMaxDatagramSizeCall{Call: call}
}
// MockSentPacketHandlerSetMaxDatagramSizeCall wrap *gomock.Call
type MockSentPacketHandlerSetMaxDatagramSizeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerSetMaxDatagramSizeCall) Return() *MockSentPacketHandlerSetMaxDatagramSizeCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerSetMaxDatagramSizeCall) Do(f func(protocol.ByteCount)) *MockSentPacketHandlerSetMaxDatagramSizeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerSetMaxDatagramSizeCall) DoAndReturn(f func(protocol.ByteCount)) *MockSentPacketHandlerSetMaxDatagramSizeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// TimeUntilSend mocks base method.
func (m *MockSentPacketHandler) TimeUntilSend() monotime.Time {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TimeUntilSend")
ret0, _ := ret[0].(monotime.Time)
return ret0
}
// TimeUntilSend indicates an expected call of TimeUntilSend.
func (mr *MockSentPacketHandlerMockRecorder) TimeUntilSend() *MockSentPacketHandlerTimeUntilSendCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TimeUntilSend", reflect.TypeOf((*MockSentPacketHandler)(nil).TimeUntilSend))
return &MockSentPacketHandlerTimeUntilSendCall{Call: call}
}
// MockSentPacketHandlerTimeUntilSendCall wrap *gomock.Call
type MockSentPacketHandlerTimeUntilSendCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSentPacketHandlerTimeUntilSendCall) Return(arg0 monotime.Time) *MockSentPacketHandlerTimeUntilSendCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerTimeUntilSendCall) Do(f func() monotime.Time) *MockSentPacketHandlerTimeUntilSendCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerTimeUntilSendCall) DoAndReturn(f func() monotime.Time) *MockSentPacketHandlerTimeUntilSendCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/congestion (interfaces: SendAlgorithmWithDebugInfos)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mocks -destination congestion.go github.com/quic-go/quic-go/internal/congestion SendAlgorithmWithDebugInfos
//
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
monotime "github.com/quic-go/quic-go/internal/monotime"
protocol "github.com/quic-go/quic-go/internal/protocol"
gomock "go.uber.org/mock/gomock"
)
// MockSendAlgorithmWithDebugInfos is a mock of SendAlgorithmWithDebugInfos interface.
type MockSendAlgorithmWithDebugInfos struct {
ctrl *gomock.Controller
recorder *MockSendAlgorithmWithDebugInfosMockRecorder
isgomock struct{}
}
// MockSendAlgorithmWithDebugInfosMockRecorder is the mock recorder for MockSendAlgorithmWithDebugInfos.
type MockSendAlgorithmWithDebugInfosMockRecorder struct {
mock *MockSendAlgorithmWithDebugInfos
}
// NewMockSendAlgorithmWithDebugInfos creates a new mock instance.
func NewMockSendAlgorithmWithDebugInfos(ctrl *gomock.Controller) *MockSendAlgorithmWithDebugInfos {
mock := &MockSendAlgorithmWithDebugInfos{ctrl: ctrl}
mock.recorder = &MockSendAlgorithmWithDebugInfosMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSendAlgorithmWithDebugInfos) EXPECT() *MockSendAlgorithmWithDebugInfosMockRecorder {
return m.recorder
}
// CanSend mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) CanSend(bytesInFlight protocol.ByteCount) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CanSend", bytesInFlight)
ret0, _ := ret[0].(bool)
return ret0
}
// CanSend indicates an expected call of CanSend.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) CanSend(bytesInFlight any) *MockSendAlgorithmWithDebugInfosCanSendCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanSend", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).CanSend), bytesInFlight)
return &MockSendAlgorithmWithDebugInfosCanSendCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosCanSendCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosCanSendCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosCanSendCall) Return(arg0 bool) *MockSendAlgorithmWithDebugInfosCanSendCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosCanSendCall) Do(f func(protocol.ByteCount) bool) *MockSendAlgorithmWithDebugInfosCanSendCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosCanSendCall) DoAndReturn(f func(protocol.ByteCount) bool) *MockSendAlgorithmWithDebugInfosCanSendCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetCongestionWindow mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) GetCongestionWindow() protocol.ByteCount {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCongestionWindow")
ret0, _ := ret[0].(protocol.ByteCount)
return ret0
}
// GetCongestionWindow indicates an expected call of GetCongestionWindow.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) GetCongestionWindow() *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCongestionWindow", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).GetCongestionWindow))
return &MockSendAlgorithmWithDebugInfosGetCongestionWindowCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosGetCongestionWindowCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosGetCongestionWindowCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall) Return(arg0 protocol.ByteCount) *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall) Do(f func() protocol.ByteCount) *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall) DoAndReturn(f func() protocol.ByteCount) *MockSendAlgorithmWithDebugInfosGetCongestionWindowCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// HasPacingBudget mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) HasPacingBudget(now monotime.Time) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HasPacingBudget", now)
ret0, _ := ret[0].(bool)
return ret0
}
// HasPacingBudget indicates an expected call of HasPacingBudget.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) HasPacingBudget(now any) *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasPacingBudget", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).HasPacingBudget), now)
return &MockSendAlgorithmWithDebugInfosHasPacingBudgetCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosHasPacingBudgetCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosHasPacingBudgetCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall) Return(arg0 bool) *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall) Do(f func(monotime.Time) bool) *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall) DoAndReturn(f func(monotime.Time) bool) *MockSendAlgorithmWithDebugInfosHasPacingBudgetCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// InRecovery mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) InRecovery() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InRecovery")
ret0, _ := ret[0].(bool)
return ret0
}
// InRecovery indicates an expected call of InRecovery.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) InRecovery() *MockSendAlgorithmWithDebugInfosInRecoveryCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InRecovery", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).InRecovery))
return &MockSendAlgorithmWithDebugInfosInRecoveryCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosInRecoveryCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosInRecoveryCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosInRecoveryCall) Return(arg0 bool) *MockSendAlgorithmWithDebugInfosInRecoveryCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosInRecoveryCall) Do(f func() bool) *MockSendAlgorithmWithDebugInfosInRecoveryCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosInRecoveryCall) DoAndReturn(f func() bool) *MockSendAlgorithmWithDebugInfosInRecoveryCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// InSlowStart mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) InSlowStart() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InSlowStart")
ret0, _ := ret[0].(bool)
return ret0
}
// InSlowStart indicates an expected call of InSlowStart.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) InSlowStart() *MockSendAlgorithmWithDebugInfosInSlowStartCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InSlowStart", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).InSlowStart))
return &MockSendAlgorithmWithDebugInfosInSlowStartCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosInSlowStartCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosInSlowStartCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosInSlowStartCall) Return(arg0 bool) *MockSendAlgorithmWithDebugInfosInSlowStartCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosInSlowStartCall) Do(f func() bool) *MockSendAlgorithmWithDebugInfosInSlowStartCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosInSlowStartCall) DoAndReturn(f func() bool) *MockSendAlgorithmWithDebugInfosInSlowStartCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MaybeExitSlowStart mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) MaybeExitSlowStart() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "MaybeExitSlowStart")
}
// MaybeExitSlowStart indicates an expected call of MaybeExitSlowStart.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) MaybeExitSlowStart() *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MaybeExitSlowStart", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).MaybeExitSlowStart))
return &MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall) Return() *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall) Do(f func()) *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall) DoAndReturn(f func()) *MockSendAlgorithmWithDebugInfosMaybeExitSlowStartCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnCongestionEvent mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) OnCongestionEvent(number protocol.PacketNumber, lostBytes, priorInFlight protocol.ByteCount) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnCongestionEvent", number, lostBytes, priorInFlight)
}
// OnCongestionEvent indicates an expected call of OnCongestionEvent.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) OnCongestionEvent(number, lostBytes, priorInFlight any) *MockSendAlgorithmWithDebugInfosOnCongestionEventCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnCongestionEvent", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).OnCongestionEvent), number, lostBytes, priorInFlight)
return &MockSendAlgorithmWithDebugInfosOnCongestionEventCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosOnCongestionEventCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosOnCongestionEventCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosOnCongestionEventCall) Return() *MockSendAlgorithmWithDebugInfosOnCongestionEventCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosOnCongestionEventCall) Do(f func(protocol.PacketNumber, protocol.ByteCount, protocol.ByteCount)) *MockSendAlgorithmWithDebugInfosOnCongestionEventCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosOnCongestionEventCall) DoAndReturn(f func(protocol.PacketNumber, protocol.ByteCount, protocol.ByteCount)) *MockSendAlgorithmWithDebugInfosOnCongestionEventCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnPacketAcked mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) OnPacketAcked(number protocol.PacketNumber, ackedBytes, priorInFlight protocol.ByteCount, eventTime monotime.Time) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnPacketAcked", number, ackedBytes, priorInFlight, eventTime)
}
// OnPacketAcked indicates an expected call of OnPacketAcked.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) OnPacketAcked(number, ackedBytes, priorInFlight, eventTime any) *MockSendAlgorithmWithDebugInfosOnPacketAckedCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnPacketAcked", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).OnPacketAcked), number, ackedBytes, priorInFlight, eventTime)
return &MockSendAlgorithmWithDebugInfosOnPacketAckedCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosOnPacketAckedCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosOnPacketAckedCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosOnPacketAckedCall) Return() *MockSendAlgorithmWithDebugInfosOnPacketAckedCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosOnPacketAckedCall) Do(f func(protocol.PacketNumber, protocol.ByteCount, protocol.ByteCount, monotime.Time)) *MockSendAlgorithmWithDebugInfosOnPacketAckedCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosOnPacketAckedCall) DoAndReturn(f func(protocol.PacketNumber, protocol.ByteCount, protocol.ByteCount, monotime.Time)) *MockSendAlgorithmWithDebugInfosOnPacketAckedCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnPacketSent mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) OnPacketSent(sentTime monotime.Time, bytesInFlight protocol.ByteCount, packetNumber protocol.PacketNumber, bytes protocol.ByteCount, isRetransmittable bool) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnPacketSent", sentTime, bytesInFlight, packetNumber, bytes, isRetransmittable)
}
// OnPacketSent indicates an expected call of OnPacketSent.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) OnPacketSent(sentTime, bytesInFlight, packetNumber, bytes, isRetransmittable any) *MockSendAlgorithmWithDebugInfosOnPacketSentCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnPacketSent", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).OnPacketSent), sentTime, bytesInFlight, packetNumber, bytes, isRetransmittable)
return &MockSendAlgorithmWithDebugInfosOnPacketSentCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosOnPacketSentCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosOnPacketSentCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosOnPacketSentCall) Return() *MockSendAlgorithmWithDebugInfosOnPacketSentCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosOnPacketSentCall) Do(f func(monotime.Time, protocol.ByteCount, protocol.PacketNumber, protocol.ByteCount, bool)) *MockSendAlgorithmWithDebugInfosOnPacketSentCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosOnPacketSentCall) DoAndReturn(f func(monotime.Time, protocol.ByteCount, protocol.PacketNumber, protocol.ByteCount, bool)) *MockSendAlgorithmWithDebugInfosOnPacketSentCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnRetransmissionTimeout mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) OnRetransmissionTimeout(packetsRetransmitted bool) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnRetransmissionTimeout", packetsRetransmitted)
}
// OnRetransmissionTimeout indicates an expected call of OnRetransmissionTimeout.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) OnRetransmissionTimeout(packetsRetransmitted any) *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnRetransmissionTimeout", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).OnRetransmissionTimeout), packetsRetransmitted)
return &MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall) Return() *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall) Do(f func(bool)) *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall) DoAndReturn(f func(bool)) *MockSendAlgorithmWithDebugInfosOnRetransmissionTimeoutCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetMaxDatagramSize mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) SetMaxDatagramSize(arg0 protocol.ByteCount) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetMaxDatagramSize", arg0)
}
// SetMaxDatagramSize indicates an expected call of SetMaxDatagramSize.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) SetMaxDatagramSize(arg0 any) *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxDatagramSize", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).SetMaxDatagramSize), arg0)
return &MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall) Return() *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall) Do(f func(protocol.ByteCount)) *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall) DoAndReturn(f func(protocol.ByteCount)) *MockSendAlgorithmWithDebugInfosSetMaxDatagramSizeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// TimeUntilSend mocks base method.
func (m *MockSendAlgorithmWithDebugInfos) TimeUntilSend(bytesInFlight protocol.ByteCount) monotime.Time {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TimeUntilSend", bytesInFlight)
ret0, _ := ret[0].(monotime.Time)
return ret0
}
// TimeUntilSend indicates an expected call of TimeUntilSend.
func (mr *MockSendAlgorithmWithDebugInfosMockRecorder) TimeUntilSend(bytesInFlight any) *MockSendAlgorithmWithDebugInfosTimeUntilSendCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TimeUntilSend", reflect.TypeOf((*MockSendAlgorithmWithDebugInfos)(nil).TimeUntilSend), bytesInFlight)
return &MockSendAlgorithmWithDebugInfosTimeUntilSendCall{Call: call}
}
// MockSendAlgorithmWithDebugInfosTimeUntilSendCall wrap *gomock.Call
type MockSendAlgorithmWithDebugInfosTimeUntilSendCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockSendAlgorithmWithDebugInfosTimeUntilSendCall) Return(arg0 monotime.Time) *MockSendAlgorithmWithDebugInfosTimeUntilSendCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockSendAlgorithmWithDebugInfosTimeUntilSendCall) Do(f func(protocol.ByteCount) monotime.Time) *MockSendAlgorithmWithDebugInfosTimeUntilSendCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSendAlgorithmWithDebugInfosTimeUntilSendCall) DoAndReturn(f func(protocol.ByteCount) monotime.Time) *MockSendAlgorithmWithDebugInfosTimeUntilSendCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/handshake (interfaces: CryptoSetup)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mocks -destination crypto_setup.go github.com/quic-go/quic-go/internal/handshake CryptoSetup
//
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
reflect "reflect"
handshake "github.com/quic-go/quic-go/internal/handshake"
protocol "github.com/quic-go/quic-go/internal/protocol"
gomock "go.uber.org/mock/gomock"
)
// MockCryptoSetup is a mock of CryptoSetup interface.
type MockCryptoSetup struct {
ctrl *gomock.Controller
recorder *MockCryptoSetupMockRecorder
isgomock struct{}
}
// MockCryptoSetupMockRecorder is the mock recorder for MockCryptoSetup.
type MockCryptoSetupMockRecorder struct {
mock *MockCryptoSetup
}
// NewMockCryptoSetup creates a new mock instance.
func NewMockCryptoSetup(ctrl *gomock.Controller) *MockCryptoSetup {
mock := &MockCryptoSetup{ctrl: ctrl}
mock.recorder = &MockCryptoSetupMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCryptoSetup) EXPECT() *MockCryptoSetupMockRecorder {
return m.recorder
}
// ChangeConnectionID mocks base method.
func (m *MockCryptoSetup) ChangeConnectionID(arg0 protocol.ConnectionID) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ChangeConnectionID", arg0)
}
// ChangeConnectionID indicates an expected call of ChangeConnectionID.
func (mr *MockCryptoSetupMockRecorder) ChangeConnectionID(arg0 any) *MockCryptoSetupChangeConnectionIDCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeConnectionID", reflect.TypeOf((*MockCryptoSetup)(nil).ChangeConnectionID), arg0)
return &MockCryptoSetupChangeConnectionIDCall{Call: call}
}
// MockCryptoSetupChangeConnectionIDCall wrap *gomock.Call
type MockCryptoSetupChangeConnectionIDCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupChangeConnectionIDCall) Return() *MockCryptoSetupChangeConnectionIDCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupChangeConnectionIDCall) Do(f func(protocol.ConnectionID)) *MockCryptoSetupChangeConnectionIDCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupChangeConnectionIDCall) DoAndReturn(f func(protocol.ConnectionID)) *MockCryptoSetupChangeConnectionIDCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Close mocks base method.
func (m *MockCryptoSetup) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockCryptoSetupMockRecorder) Close() *MockCryptoSetupCloseCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockCryptoSetup)(nil).Close))
return &MockCryptoSetupCloseCall{Call: call}
}
// MockCryptoSetupCloseCall wrap *gomock.Call
type MockCryptoSetupCloseCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupCloseCall) Return(arg0 error) *MockCryptoSetupCloseCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupCloseCall) Do(f func() error) *MockCryptoSetupCloseCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupCloseCall) DoAndReturn(f func() error) *MockCryptoSetupCloseCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// ConnectionState mocks base method.
func (m *MockCryptoSetup) ConnectionState() handshake.ConnectionState {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ConnectionState")
ret0, _ := ret[0].(handshake.ConnectionState)
return ret0
}
// ConnectionState indicates an expected call of ConnectionState.
func (mr *MockCryptoSetupMockRecorder) ConnectionState() *MockCryptoSetupConnectionStateCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectionState", reflect.TypeOf((*MockCryptoSetup)(nil).ConnectionState))
return &MockCryptoSetupConnectionStateCall{Call: call}
}
// MockCryptoSetupConnectionStateCall wrap *gomock.Call
type MockCryptoSetupConnectionStateCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupConnectionStateCall) Return(arg0 handshake.ConnectionState) *MockCryptoSetupConnectionStateCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupConnectionStateCall) Do(f func() handshake.ConnectionState) *MockCryptoSetupConnectionStateCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupConnectionStateCall) DoAndReturn(f func() handshake.ConnectionState) *MockCryptoSetupConnectionStateCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DiscardInitialKeys mocks base method.
func (m *MockCryptoSetup) DiscardInitialKeys() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DiscardInitialKeys")
}
// DiscardInitialKeys indicates an expected call of DiscardInitialKeys.
func (mr *MockCryptoSetupMockRecorder) DiscardInitialKeys() *MockCryptoSetupDiscardInitialKeysCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiscardInitialKeys", reflect.TypeOf((*MockCryptoSetup)(nil).DiscardInitialKeys))
return &MockCryptoSetupDiscardInitialKeysCall{Call: call}
}
// MockCryptoSetupDiscardInitialKeysCall wrap *gomock.Call
type MockCryptoSetupDiscardInitialKeysCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupDiscardInitialKeysCall) Return() *MockCryptoSetupDiscardInitialKeysCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupDiscardInitialKeysCall) Do(f func()) *MockCryptoSetupDiscardInitialKeysCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupDiscardInitialKeysCall) DoAndReturn(f func()) *MockCryptoSetupDiscardInitialKeysCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Get0RTTOpener mocks base method.
func (m *MockCryptoSetup) Get0RTTOpener() (handshake.LongHeaderOpener, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get0RTTOpener")
ret0, _ := ret[0].(handshake.LongHeaderOpener)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get0RTTOpener indicates an expected call of Get0RTTOpener.
func (mr *MockCryptoSetupMockRecorder) Get0RTTOpener() *MockCryptoSetupGet0RTTOpenerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get0RTTOpener", reflect.TypeOf((*MockCryptoSetup)(nil).Get0RTTOpener))
return &MockCryptoSetupGet0RTTOpenerCall{Call: call}
}
// MockCryptoSetupGet0RTTOpenerCall wrap *gomock.Call
type MockCryptoSetupGet0RTTOpenerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGet0RTTOpenerCall) Return(arg0 handshake.LongHeaderOpener, arg1 error) *MockCryptoSetupGet0RTTOpenerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGet0RTTOpenerCall) Do(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGet0RTTOpenerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGet0RTTOpenerCall) DoAndReturn(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGet0RTTOpenerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Get0RTTSealer mocks base method.
func (m *MockCryptoSetup) Get0RTTSealer() (handshake.LongHeaderSealer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get0RTTSealer")
ret0, _ := ret[0].(handshake.LongHeaderSealer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get0RTTSealer indicates an expected call of Get0RTTSealer.
func (mr *MockCryptoSetupMockRecorder) Get0RTTSealer() *MockCryptoSetupGet0RTTSealerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get0RTTSealer", reflect.TypeOf((*MockCryptoSetup)(nil).Get0RTTSealer))
return &MockCryptoSetupGet0RTTSealerCall{Call: call}
}
// MockCryptoSetupGet0RTTSealerCall wrap *gomock.Call
type MockCryptoSetupGet0RTTSealerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGet0RTTSealerCall) Return(arg0 handshake.LongHeaderSealer, arg1 error) *MockCryptoSetupGet0RTTSealerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGet0RTTSealerCall) Do(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGet0RTTSealerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGet0RTTSealerCall) DoAndReturn(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGet0RTTSealerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Get1RTTOpener mocks base method.
func (m *MockCryptoSetup) Get1RTTOpener() (handshake.ShortHeaderOpener, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get1RTTOpener")
ret0, _ := ret[0].(handshake.ShortHeaderOpener)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get1RTTOpener indicates an expected call of Get1RTTOpener.
func (mr *MockCryptoSetupMockRecorder) Get1RTTOpener() *MockCryptoSetupGet1RTTOpenerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get1RTTOpener", reflect.TypeOf((*MockCryptoSetup)(nil).Get1RTTOpener))
return &MockCryptoSetupGet1RTTOpenerCall{Call: call}
}
// MockCryptoSetupGet1RTTOpenerCall wrap *gomock.Call
type MockCryptoSetupGet1RTTOpenerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGet1RTTOpenerCall) Return(arg0 handshake.ShortHeaderOpener, arg1 error) *MockCryptoSetupGet1RTTOpenerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGet1RTTOpenerCall) Do(f func() (handshake.ShortHeaderOpener, error)) *MockCryptoSetupGet1RTTOpenerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGet1RTTOpenerCall) DoAndReturn(f func() (handshake.ShortHeaderOpener, error)) *MockCryptoSetupGet1RTTOpenerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Get1RTTSealer mocks base method.
func (m *MockCryptoSetup) Get1RTTSealer() (handshake.ShortHeaderSealer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get1RTTSealer")
ret0, _ := ret[0].(handshake.ShortHeaderSealer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get1RTTSealer indicates an expected call of Get1RTTSealer.
func (mr *MockCryptoSetupMockRecorder) Get1RTTSealer() *MockCryptoSetupGet1RTTSealerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get1RTTSealer", reflect.TypeOf((*MockCryptoSetup)(nil).Get1RTTSealer))
return &MockCryptoSetupGet1RTTSealerCall{Call: call}
}
// MockCryptoSetupGet1RTTSealerCall wrap *gomock.Call
type MockCryptoSetupGet1RTTSealerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGet1RTTSealerCall) Return(arg0 handshake.ShortHeaderSealer, arg1 error) *MockCryptoSetupGet1RTTSealerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGet1RTTSealerCall) Do(f func() (handshake.ShortHeaderSealer, error)) *MockCryptoSetupGet1RTTSealerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGet1RTTSealerCall) DoAndReturn(f func() (handshake.ShortHeaderSealer, error)) *MockCryptoSetupGet1RTTSealerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetHandshakeOpener mocks base method.
func (m *MockCryptoSetup) GetHandshakeOpener() (handshake.LongHeaderOpener, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetHandshakeOpener")
ret0, _ := ret[0].(handshake.LongHeaderOpener)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetHandshakeOpener indicates an expected call of GetHandshakeOpener.
func (mr *MockCryptoSetupMockRecorder) GetHandshakeOpener() *MockCryptoSetupGetHandshakeOpenerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandshakeOpener", reflect.TypeOf((*MockCryptoSetup)(nil).GetHandshakeOpener))
return &MockCryptoSetupGetHandshakeOpenerCall{Call: call}
}
// MockCryptoSetupGetHandshakeOpenerCall wrap *gomock.Call
type MockCryptoSetupGetHandshakeOpenerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGetHandshakeOpenerCall) Return(arg0 handshake.LongHeaderOpener, arg1 error) *MockCryptoSetupGetHandshakeOpenerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGetHandshakeOpenerCall) Do(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGetHandshakeOpenerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGetHandshakeOpenerCall) DoAndReturn(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGetHandshakeOpenerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetHandshakeSealer mocks base method.
func (m *MockCryptoSetup) GetHandshakeSealer() (handshake.LongHeaderSealer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetHandshakeSealer")
ret0, _ := ret[0].(handshake.LongHeaderSealer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetHandshakeSealer indicates an expected call of GetHandshakeSealer.
func (mr *MockCryptoSetupMockRecorder) GetHandshakeSealer() *MockCryptoSetupGetHandshakeSealerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandshakeSealer", reflect.TypeOf((*MockCryptoSetup)(nil).GetHandshakeSealer))
return &MockCryptoSetupGetHandshakeSealerCall{Call: call}
}
// MockCryptoSetupGetHandshakeSealerCall wrap *gomock.Call
type MockCryptoSetupGetHandshakeSealerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGetHandshakeSealerCall) Return(arg0 handshake.LongHeaderSealer, arg1 error) *MockCryptoSetupGetHandshakeSealerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGetHandshakeSealerCall) Do(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGetHandshakeSealerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGetHandshakeSealerCall) DoAndReturn(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGetHandshakeSealerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetInitialOpener mocks base method.
func (m *MockCryptoSetup) GetInitialOpener() (handshake.LongHeaderOpener, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInitialOpener")
ret0, _ := ret[0].(handshake.LongHeaderOpener)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInitialOpener indicates an expected call of GetInitialOpener.
func (mr *MockCryptoSetupMockRecorder) GetInitialOpener() *MockCryptoSetupGetInitialOpenerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInitialOpener", reflect.TypeOf((*MockCryptoSetup)(nil).GetInitialOpener))
return &MockCryptoSetupGetInitialOpenerCall{Call: call}
}
// MockCryptoSetupGetInitialOpenerCall wrap *gomock.Call
type MockCryptoSetupGetInitialOpenerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGetInitialOpenerCall) Return(arg0 handshake.LongHeaderOpener, arg1 error) *MockCryptoSetupGetInitialOpenerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGetInitialOpenerCall) Do(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGetInitialOpenerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGetInitialOpenerCall) DoAndReturn(f func() (handshake.LongHeaderOpener, error)) *MockCryptoSetupGetInitialOpenerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetInitialSealer mocks base method.
func (m *MockCryptoSetup) GetInitialSealer() (handshake.LongHeaderSealer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInitialSealer")
ret0, _ := ret[0].(handshake.LongHeaderSealer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInitialSealer indicates an expected call of GetInitialSealer.
func (mr *MockCryptoSetupMockRecorder) GetInitialSealer() *MockCryptoSetupGetInitialSealerCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInitialSealer", reflect.TypeOf((*MockCryptoSetup)(nil).GetInitialSealer))
return &MockCryptoSetupGetInitialSealerCall{Call: call}
}
// MockCryptoSetupGetInitialSealerCall wrap *gomock.Call
type MockCryptoSetupGetInitialSealerCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGetInitialSealerCall) Return(arg0 handshake.LongHeaderSealer, arg1 error) *MockCryptoSetupGetInitialSealerCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGetInitialSealerCall) Do(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGetInitialSealerCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGetInitialSealerCall) DoAndReturn(f func() (handshake.LongHeaderSealer, error)) *MockCryptoSetupGetInitialSealerCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetSessionTicket mocks base method.
func (m *MockCryptoSetup) GetSessionTicket() ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSessionTicket")
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSessionTicket indicates an expected call of GetSessionTicket.
func (mr *MockCryptoSetupMockRecorder) GetSessionTicket() *MockCryptoSetupGetSessionTicketCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionTicket", reflect.TypeOf((*MockCryptoSetup)(nil).GetSessionTicket))
return &MockCryptoSetupGetSessionTicketCall{Call: call}
}
// MockCryptoSetupGetSessionTicketCall wrap *gomock.Call
type MockCryptoSetupGetSessionTicketCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupGetSessionTicketCall) Return(arg0 []byte, arg1 error) *MockCryptoSetupGetSessionTicketCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupGetSessionTicketCall) Do(f func() ([]byte, error)) *MockCryptoSetupGetSessionTicketCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupGetSessionTicketCall) DoAndReturn(f func() ([]byte, error)) *MockCryptoSetupGetSessionTicketCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// HandleMessage mocks base method.
func (m *MockCryptoSetup) HandleMessage(arg0 []byte, arg1 protocol.EncryptionLevel) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// HandleMessage indicates an expected call of HandleMessage.
func (mr *MockCryptoSetupMockRecorder) HandleMessage(arg0, arg1 any) *MockCryptoSetupHandleMessageCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockCryptoSetup)(nil).HandleMessage), arg0, arg1)
return &MockCryptoSetupHandleMessageCall{Call: call}
}
// MockCryptoSetupHandleMessageCall wrap *gomock.Call
type MockCryptoSetupHandleMessageCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupHandleMessageCall) Return(arg0 error) *MockCryptoSetupHandleMessageCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupHandleMessageCall) Do(f func([]byte, protocol.EncryptionLevel) error) *MockCryptoSetupHandleMessageCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupHandleMessageCall) DoAndReturn(f func([]byte, protocol.EncryptionLevel) error) *MockCryptoSetupHandleMessageCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// NextEvent mocks base method.
func (m *MockCryptoSetup) NextEvent() handshake.Event {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NextEvent")
ret0, _ := ret[0].(handshake.Event)
return ret0
}
// NextEvent indicates an expected call of NextEvent.
func (mr *MockCryptoSetupMockRecorder) NextEvent() *MockCryptoSetupNextEventCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NextEvent", reflect.TypeOf((*MockCryptoSetup)(nil).NextEvent))
return &MockCryptoSetupNextEventCall{Call: call}
}
// MockCryptoSetupNextEventCall wrap *gomock.Call
type MockCryptoSetupNextEventCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupNextEventCall) Return(arg0 handshake.Event) *MockCryptoSetupNextEventCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupNextEventCall) Do(f func() handshake.Event) *MockCryptoSetupNextEventCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupNextEventCall) DoAndReturn(f func() handshake.Event) *MockCryptoSetupNextEventCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetHandshakeConfirmed mocks base method.
func (m *MockCryptoSetup) SetHandshakeConfirmed() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetHandshakeConfirmed")
}
// SetHandshakeConfirmed indicates an expected call of SetHandshakeConfirmed.
func (mr *MockCryptoSetupMockRecorder) SetHandshakeConfirmed() *MockCryptoSetupSetHandshakeConfirmedCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHandshakeConfirmed", reflect.TypeOf((*MockCryptoSetup)(nil).SetHandshakeConfirmed))
return &MockCryptoSetupSetHandshakeConfirmedCall{Call: call}
}
// MockCryptoSetupSetHandshakeConfirmedCall wrap *gomock.Call
type MockCryptoSetupSetHandshakeConfirmedCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupSetHandshakeConfirmedCall) Return() *MockCryptoSetupSetHandshakeConfirmedCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupSetHandshakeConfirmedCall) Do(f func()) *MockCryptoSetupSetHandshakeConfirmedCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupSetHandshakeConfirmedCall) DoAndReturn(f func()) *MockCryptoSetupSetHandshakeConfirmedCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetLargest1RTTAcked mocks base method.
func (m *MockCryptoSetup) SetLargest1RTTAcked(arg0 protocol.PacketNumber) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetLargest1RTTAcked", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetLargest1RTTAcked indicates an expected call of SetLargest1RTTAcked.
func (mr *MockCryptoSetupMockRecorder) SetLargest1RTTAcked(arg0 any) *MockCryptoSetupSetLargest1RTTAckedCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLargest1RTTAcked", reflect.TypeOf((*MockCryptoSetup)(nil).SetLargest1RTTAcked), arg0)
return &MockCryptoSetupSetLargest1RTTAckedCall{Call: call}
}
// MockCryptoSetupSetLargest1RTTAckedCall wrap *gomock.Call
type MockCryptoSetupSetLargest1RTTAckedCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupSetLargest1RTTAckedCall) Return(arg0 error) *MockCryptoSetupSetLargest1RTTAckedCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupSetLargest1RTTAckedCall) Do(f func(protocol.PacketNumber) error) *MockCryptoSetupSetLargest1RTTAckedCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupSetLargest1RTTAckedCall) DoAndReturn(f func(protocol.PacketNumber) error) *MockCryptoSetupSetLargest1RTTAckedCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// StartHandshake mocks base method.
func (m *MockCryptoSetup) StartHandshake(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StartHandshake", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// StartHandshake indicates an expected call of StartHandshake.
func (mr *MockCryptoSetupMockRecorder) StartHandshake(arg0 any) *MockCryptoSetupStartHandshakeCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartHandshake", reflect.TypeOf((*MockCryptoSetup)(nil).StartHandshake), arg0)
return &MockCryptoSetupStartHandshakeCall{Call: call}
}
// MockCryptoSetupStartHandshakeCall wrap *gomock.Call
type MockCryptoSetupStartHandshakeCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockCryptoSetupStartHandshakeCall) Return(arg0 error) *MockCryptoSetupStartHandshakeCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockCryptoSetupStartHandshakeCall) Do(f func(context.Context) error) *MockCryptoSetupStartHandshakeCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockCryptoSetupStartHandshakeCall) DoAndReturn(f func(context.Context) error) *MockCryptoSetupStartHandshakeCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/handshake (interfaces: LongHeaderOpener)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mocks -destination long_header_opener.go github.com/quic-go/quic-go/internal/handshake LongHeaderOpener
//
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
protocol "github.com/quic-go/quic-go/internal/protocol"
gomock "go.uber.org/mock/gomock"
)
// MockLongHeaderOpener is a mock of LongHeaderOpener interface.
type MockLongHeaderOpener struct {
ctrl *gomock.Controller
recorder *MockLongHeaderOpenerMockRecorder
isgomock struct{}
}
// MockLongHeaderOpenerMockRecorder is the mock recorder for MockLongHeaderOpener.
type MockLongHeaderOpenerMockRecorder struct {
mock *MockLongHeaderOpener
}
// NewMockLongHeaderOpener creates a new mock instance.
func NewMockLongHeaderOpener(ctrl *gomock.Controller) *MockLongHeaderOpener {
mock := &MockLongHeaderOpener{ctrl: ctrl}
mock.recorder = &MockLongHeaderOpenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLongHeaderOpener) EXPECT() *MockLongHeaderOpenerMockRecorder {
return m.recorder
}
// DecodePacketNumber mocks base method.
func (m *MockLongHeaderOpener) DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecodePacketNumber", wirePN, wirePNLen)
ret0, _ := ret[0].(protocol.PacketNumber)
return ret0
}
// DecodePacketNumber indicates an expected call of DecodePacketNumber.
func (mr *MockLongHeaderOpenerMockRecorder) DecodePacketNumber(wirePN, wirePNLen any) *MockLongHeaderOpenerDecodePacketNumberCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodePacketNumber", reflect.TypeOf((*MockLongHeaderOpener)(nil).DecodePacketNumber), wirePN, wirePNLen)
return &MockLongHeaderOpenerDecodePacketNumberCall{Call: call}
}
// MockLongHeaderOpenerDecodePacketNumberCall wrap *gomock.Call
type MockLongHeaderOpenerDecodePacketNumberCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockLongHeaderOpenerDecodePacketNumberCall) Return(arg0 protocol.PacketNumber) *MockLongHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockLongHeaderOpenerDecodePacketNumberCall) Do(f func(protocol.PacketNumber, protocol.PacketNumberLen) protocol.PacketNumber) *MockLongHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockLongHeaderOpenerDecodePacketNumberCall) DoAndReturn(f func(protocol.PacketNumber, protocol.PacketNumberLen) protocol.PacketNumber) *MockLongHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DecryptHeader mocks base method.
func (m *MockLongHeaderOpener) DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DecryptHeader", sample, firstByte, pnBytes)
}
// DecryptHeader indicates an expected call of DecryptHeader.
func (mr *MockLongHeaderOpenerMockRecorder) DecryptHeader(sample, firstByte, pnBytes any) *MockLongHeaderOpenerDecryptHeaderCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptHeader", reflect.TypeOf((*MockLongHeaderOpener)(nil).DecryptHeader), sample, firstByte, pnBytes)
return &MockLongHeaderOpenerDecryptHeaderCall{Call: call}
}
// MockLongHeaderOpenerDecryptHeaderCall wrap *gomock.Call
type MockLongHeaderOpenerDecryptHeaderCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockLongHeaderOpenerDecryptHeaderCall) Return() *MockLongHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockLongHeaderOpenerDecryptHeaderCall) Do(f func([]byte, *byte, []byte)) *MockLongHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockLongHeaderOpenerDecryptHeaderCall) DoAndReturn(f func([]byte, *byte, []byte)) *MockLongHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Open mocks base method.
func (m *MockLongHeaderOpener) Open(dst, src []byte, pn protocol.PacketNumber, associatedData []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Open", dst, src, pn, associatedData)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Open indicates an expected call of Open.
func (mr *MockLongHeaderOpenerMockRecorder) Open(dst, src, pn, associatedData any) *MockLongHeaderOpenerOpenCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockLongHeaderOpener)(nil).Open), dst, src, pn, associatedData)
return &MockLongHeaderOpenerOpenCall{Call: call}
}
// MockLongHeaderOpenerOpenCall wrap *gomock.Call
type MockLongHeaderOpenerOpenCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockLongHeaderOpenerOpenCall) Return(arg0 []byte, arg1 error) *MockLongHeaderOpenerOpenCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockLongHeaderOpenerOpenCall) Do(f func([]byte, []byte, protocol.PacketNumber, []byte) ([]byte, error)) *MockLongHeaderOpenerOpenCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockLongHeaderOpenerOpenCall) DoAndReturn(f func([]byte, []byte, protocol.PacketNumber, []byte) ([]byte, error)) *MockLongHeaderOpenerOpenCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/handshake (interfaces: ShortHeaderOpener)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mocks -destination short_header_opener.go github.com/quic-go/quic-go/internal/handshake ShortHeaderOpener
//
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
monotime "github.com/quic-go/quic-go/internal/monotime"
protocol "github.com/quic-go/quic-go/internal/protocol"
gomock "go.uber.org/mock/gomock"
)
// MockShortHeaderOpener is a mock of ShortHeaderOpener interface.
type MockShortHeaderOpener struct {
ctrl *gomock.Controller
recorder *MockShortHeaderOpenerMockRecorder
isgomock struct{}
}
// MockShortHeaderOpenerMockRecorder is the mock recorder for MockShortHeaderOpener.
type MockShortHeaderOpenerMockRecorder struct {
mock *MockShortHeaderOpener
}
// NewMockShortHeaderOpener creates a new mock instance.
func NewMockShortHeaderOpener(ctrl *gomock.Controller) *MockShortHeaderOpener {
mock := &MockShortHeaderOpener{ctrl: ctrl}
mock.recorder = &MockShortHeaderOpenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockShortHeaderOpener) EXPECT() *MockShortHeaderOpenerMockRecorder {
return m.recorder
}
// DecodePacketNumber mocks base method.
func (m *MockShortHeaderOpener) DecodePacketNumber(wirePN protocol.PacketNumber, wirePNLen protocol.PacketNumberLen) protocol.PacketNumber {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecodePacketNumber", wirePN, wirePNLen)
ret0, _ := ret[0].(protocol.PacketNumber)
return ret0
}
// DecodePacketNumber indicates an expected call of DecodePacketNumber.
func (mr *MockShortHeaderOpenerMockRecorder) DecodePacketNumber(wirePN, wirePNLen any) *MockShortHeaderOpenerDecodePacketNumberCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodePacketNumber", reflect.TypeOf((*MockShortHeaderOpener)(nil).DecodePacketNumber), wirePN, wirePNLen)
return &MockShortHeaderOpenerDecodePacketNumberCall{Call: call}
}
// MockShortHeaderOpenerDecodePacketNumberCall wrap *gomock.Call
type MockShortHeaderOpenerDecodePacketNumberCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderOpenerDecodePacketNumberCall) Return(arg0 protocol.PacketNumber) *MockShortHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderOpenerDecodePacketNumberCall) Do(f func(protocol.PacketNumber, protocol.PacketNumberLen) protocol.PacketNumber) *MockShortHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderOpenerDecodePacketNumberCall) DoAndReturn(f func(protocol.PacketNumber, protocol.PacketNumberLen) protocol.PacketNumber) *MockShortHeaderOpenerDecodePacketNumberCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// DecryptHeader mocks base method.
func (m *MockShortHeaderOpener) DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DecryptHeader", sample, firstByte, pnBytes)
}
// DecryptHeader indicates an expected call of DecryptHeader.
func (mr *MockShortHeaderOpenerMockRecorder) DecryptHeader(sample, firstByte, pnBytes any) *MockShortHeaderOpenerDecryptHeaderCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptHeader", reflect.TypeOf((*MockShortHeaderOpener)(nil).DecryptHeader), sample, firstByte, pnBytes)
return &MockShortHeaderOpenerDecryptHeaderCall{Call: call}
}
// MockShortHeaderOpenerDecryptHeaderCall wrap *gomock.Call
type MockShortHeaderOpenerDecryptHeaderCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderOpenerDecryptHeaderCall) Return() *MockShortHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderOpenerDecryptHeaderCall) Do(f func([]byte, *byte, []byte)) *MockShortHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderOpenerDecryptHeaderCall) DoAndReturn(f func([]byte, *byte, []byte)) *MockShortHeaderOpenerDecryptHeaderCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Open mocks base method.
func (m *MockShortHeaderOpener) Open(dst, src []byte, rcvTime monotime.Time, pn protocol.PacketNumber, kp protocol.KeyPhaseBit, associatedData []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Open", dst, src, rcvTime, pn, kp, associatedData)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Open indicates an expected call of Open.
func (mr *MockShortHeaderOpenerMockRecorder) Open(dst, src, rcvTime, pn, kp, associatedData any) *MockShortHeaderOpenerOpenCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockShortHeaderOpener)(nil).Open), dst, src, rcvTime, pn, kp, associatedData)
return &MockShortHeaderOpenerOpenCall{Call: call}
}
// MockShortHeaderOpenerOpenCall wrap *gomock.Call
type MockShortHeaderOpenerOpenCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderOpenerOpenCall) Return(arg0 []byte, arg1 error) *MockShortHeaderOpenerOpenCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderOpenerOpenCall) Do(f func([]byte, []byte, monotime.Time, protocol.PacketNumber, protocol.KeyPhaseBit, []byte) ([]byte, error)) *MockShortHeaderOpenerOpenCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderOpenerOpenCall) DoAndReturn(f func([]byte, []byte, monotime.Time, protocol.PacketNumber, protocol.KeyPhaseBit, []byte) ([]byte, error)) *MockShortHeaderOpenerOpenCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/quic-go/quic-go/internal/handshake (interfaces: ShortHeaderSealer)
//
// Generated by this command:
//
// mockgen -typed -build_flags=-tags=gomock -package mocks -destination short_header_sealer.go github.com/quic-go/quic-go/internal/handshake ShortHeaderSealer
//
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
protocol "github.com/quic-go/quic-go/internal/protocol"
gomock "go.uber.org/mock/gomock"
)
// MockShortHeaderSealer is a mock of ShortHeaderSealer interface.
type MockShortHeaderSealer struct {
ctrl *gomock.Controller
recorder *MockShortHeaderSealerMockRecorder
isgomock struct{}
}
// MockShortHeaderSealerMockRecorder is the mock recorder for MockShortHeaderSealer.
type MockShortHeaderSealerMockRecorder struct {
mock *MockShortHeaderSealer
}
// NewMockShortHeaderSealer creates a new mock instance.
func NewMockShortHeaderSealer(ctrl *gomock.Controller) *MockShortHeaderSealer {
mock := &MockShortHeaderSealer{ctrl: ctrl}
mock.recorder = &MockShortHeaderSealerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockShortHeaderSealer) EXPECT() *MockShortHeaderSealerMockRecorder {
return m.recorder
}
// EncryptHeader mocks base method.
func (m *MockShortHeaderSealer) EncryptHeader(sample []byte, firstByte *byte, pnBytes []byte) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "EncryptHeader", sample, firstByte, pnBytes)
}
// EncryptHeader indicates an expected call of EncryptHeader.
func (mr *MockShortHeaderSealerMockRecorder) EncryptHeader(sample, firstByte, pnBytes any) *MockShortHeaderSealerEncryptHeaderCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptHeader", reflect.TypeOf((*MockShortHeaderSealer)(nil).EncryptHeader), sample, firstByte, pnBytes)
return &MockShortHeaderSealerEncryptHeaderCall{Call: call}
}
// MockShortHeaderSealerEncryptHeaderCall wrap *gomock.Call
type MockShortHeaderSealerEncryptHeaderCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderSealerEncryptHeaderCall) Return() *MockShortHeaderSealerEncryptHeaderCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderSealerEncryptHeaderCall) Do(f func([]byte, *byte, []byte)) *MockShortHeaderSealerEncryptHeaderCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderSealerEncryptHeaderCall) DoAndReturn(f func([]byte, *byte, []byte)) *MockShortHeaderSealerEncryptHeaderCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// KeyPhase mocks base method.
func (m *MockShortHeaderSealer) KeyPhase() protocol.KeyPhaseBit {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyPhase")
ret0, _ := ret[0].(protocol.KeyPhaseBit)
return ret0
}
// KeyPhase indicates an expected call of KeyPhase.
func (mr *MockShortHeaderSealerMockRecorder) KeyPhase() *MockShortHeaderSealerKeyPhaseCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyPhase", reflect.TypeOf((*MockShortHeaderSealer)(nil).KeyPhase))
return &MockShortHeaderSealerKeyPhaseCall{Call: call}
}
// MockShortHeaderSealerKeyPhaseCall wrap *gomock.Call
type MockShortHeaderSealerKeyPhaseCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderSealerKeyPhaseCall) Return(arg0 protocol.KeyPhaseBit) *MockShortHeaderSealerKeyPhaseCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderSealerKeyPhaseCall) Do(f func() protocol.KeyPhaseBit) *MockShortHeaderSealerKeyPhaseCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderSealerKeyPhaseCall) DoAndReturn(f func() protocol.KeyPhaseBit) *MockShortHeaderSealerKeyPhaseCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Overhead mocks base method.
func (m *MockShortHeaderSealer) Overhead() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Overhead")
ret0, _ := ret[0].(int)
return ret0
}
// Overhead indicates an expected call of Overhead.
func (mr *MockShortHeaderSealerMockRecorder) Overhead() *MockShortHeaderSealerOverheadCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Overhead", reflect.TypeOf((*MockShortHeaderSealer)(nil).Overhead))
return &MockShortHeaderSealerOverheadCall{Call: call}
}
// MockShortHeaderSealerOverheadCall wrap *gomock.Call
type MockShortHeaderSealerOverheadCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderSealerOverheadCall) Return(arg0 int) *MockShortHeaderSealerOverheadCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderSealerOverheadCall) Do(f func() int) *MockShortHeaderSealerOverheadCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderSealerOverheadCall) DoAndReturn(f func() int) *MockShortHeaderSealerOverheadCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Seal mocks base method.
func (m *MockShortHeaderSealer) Seal(dst, src []byte, packetNumber protocol.PacketNumber, associatedData []byte) []byte {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Seal", dst, src, packetNumber, associatedData)
ret0, _ := ret[0].([]byte)
return ret0
}
// Seal indicates an expected call of Seal.
func (mr *MockShortHeaderSealerMockRecorder) Seal(dst, src, packetNumber, associatedData any) *MockShortHeaderSealerSealCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seal", reflect.TypeOf((*MockShortHeaderSealer)(nil).Seal), dst, src, packetNumber, associatedData)
return &MockShortHeaderSealerSealCall{Call: call}
}
// MockShortHeaderSealerSealCall wrap *gomock.Call
type MockShortHeaderSealerSealCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockShortHeaderSealerSealCall) Return(arg0 []byte) *MockShortHeaderSealerSealCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockShortHeaderSealerSealCall) Do(f func([]byte, []byte, protocol.PacketNumber, []byte) []byte) *MockShortHeaderSealerSealCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockShortHeaderSealerSealCall) DoAndReturn(f func([]byte, []byte, protocol.PacketNumber, []byte) []byte) *MockShortHeaderSealerSealCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// Package monotime provides a monotonic time representation that is useful for
// measuring elapsed time.
// It is designed as a memory optimized drop-in replacement for time.Time, with
// a monotime.Time consuming just 8 bytes instead of 24 bytes.
package monotime
import (
"time"
)
// The absolute value doesn't matter, but it should be in the past,
// so that every timestamp obtained with Now() is non-zero,
// even on systems with low timer resolutions (e.g. Windows).
var start = time.Now().Add(-time.Hour)
// A Time represents an instant in monotonic time.
// Times can be compared using the comparison operators, but the specific
// value is implementation-dependent and should not be relied upon.
// The zero value of Time doesn't have any specific meaning.
type Time int64
// Now returns the current monotonic time.
func Now() Time {
return Time(time.Since(start).Nanoseconds())
}
// Sub returns the duration t-t2. If the result exceeds the maximum (or minimum)
// value that can be stored in a Duration, the maximum (or minimum) duration
// will be returned.
// To compute t-d for a duration d, use t.Add(-d).
func (t Time) Sub(t2 Time) time.Duration {
return time.Duration(t - t2)
}
// Add returns the time t+d.
func (t Time) Add(d time.Duration) Time {
return Time(int64(t) + d.Nanoseconds())
}
// After reports whether the time instant t is after t2.
func (t Time) After(t2 Time) bool {
return t > t2
}
// Before reports whether the time instant t is before t2.
func (t Time) Before(t2 Time) bool {
return t < t2
}
// IsZero reports whether t represents the zero time instant.
func (t Time) IsZero() bool {
return t == 0
}
// Equal reports whether t and t2 represent the same time instant.
func (t Time) Equal(t2 Time) bool {
return t == t2
}
// ToTime converts the monotonic time to a time.Time value.
// The returned time.Time will have the same instant as the monotonic time,
// but may be subject to clock adjustments.
func (t Time) ToTime() time.Time {
if t.IsZero() {
return time.Time{}
}
return start.Add(time.Duration(t))
}
// Since returns the time elapsed since t. It is shorthand for Now().Sub(t).
func Since(t Time) time.Duration {
return Now().Sub(t)
}
// Until returns the duration until t.
// It is shorthand for t.Sub(Now()).
// If t is in the past, the returned duration will be negative.
func Until(t Time) time.Duration {
return time.Duration(t - Now())
}
// FromTime converts a time.Time to a monotonic Time.
// The conversion is relative to the package's start time and may lose
// precision if the time.Time is far from the start time.
func FromTime(t time.Time) Time {
if t.IsZero() {
return 0
}
return Time(t.Sub(start).Nanoseconds())
}
package protocol
import (
"crypto/rand"
"encoding/hex"
"errors"
"io"
)
var ErrInvalidConnectionIDLen = errors.New("invalid Connection ID length")
// An ArbitraryLenConnectionID is a QUIC Connection ID able to represent Connection IDs according to RFC 8999.
// Future QUIC versions might allow connection ID lengths up to 255 bytes, while QUIC v1
// restricts the length to 20 bytes.
type ArbitraryLenConnectionID []byte
func (c ArbitraryLenConnectionID) Len() int {
return len(c)
}
func (c ArbitraryLenConnectionID) Bytes() []byte {
return c
}
func (c ArbitraryLenConnectionID) String() string {
if c.Len() == 0 {
return "(empty)"
}
return hex.EncodeToString(c.Bytes())
}
const maxConnectionIDLen = 20
// A ConnectionID in QUIC
type ConnectionID struct {
b [20]byte
l uint8
}
// GenerateConnectionID generates a connection ID using cryptographic random
func GenerateConnectionID(l int) (ConnectionID, error) {
var c ConnectionID
c.l = uint8(l)
_, err := rand.Read(c.b[:l])
return c, err
}
// ParseConnectionID interprets b as a Connection ID.
// It panics if b is longer than 20 bytes.
func ParseConnectionID(b []byte) ConnectionID {
if len(b) > maxConnectionIDLen {
panic("invalid conn id length")
}
var c ConnectionID
c.l = uint8(len(b))
copy(c.b[:c.l], b)
return c
}
// GenerateConnectionIDForInitial generates a connection ID for the Initial packet.
// It uses a length randomly chosen between 8 and 20 bytes.
func GenerateConnectionIDForInitial() (ConnectionID, error) {
r := make([]byte, 1)
if _, err := rand.Read(r); err != nil {
return ConnectionID{}, err
}
l := MinConnectionIDLenInitial + int(r[0])%(maxConnectionIDLen-MinConnectionIDLenInitial+1)
return GenerateConnectionID(l)
}
// ReadConnectionID reads a connection ID of length len from the given io.Reader.
// It returns io.EOF if there are not enough bytes to read.
func ReadConnectionID(r io.Reader, l int) (ConnectionID, error) {
var c ConnectionID
if l == 0 {
return c, nil
}
if l > maxConnectionIDLen {
return c, ErrInvalidConnectionIDLen
}
c.l = uint8(l)
_, err := io.ReadFull(r, c.b[:l])
if err == io.ErrUnexpectedEOF {
return c, io.EOF
}
return c, err
}
// Len returns the length of the connection ID in bytes
func (c ConnectionID) Len() int {
return int(c.l)
}
// Bytes returns the byte representation
func (c ConnectionID) Bytes() []byte {
return c.b[:c.l]
}
func (c ConnectionID) String() string {
if c.Len() == 0 {
return "(empty)"
}
return hex.EncodeToString(c.Bytes())
}
type DefaultConnectionIDGenerator struct {
ConnLen int
}
func (d *DefaultConnectionIDGenerator) GenerateConnectionID() (ConnectionID, error) {
return GenerateConnectionID(d.ConnLen)
}
func (d *DefaultConnectionIDGenerator) ConnectionIDLen() int {
return d.ConnLen
}
package protocol
import (
"crypto/tls"
"fmt"
)
// EncryptionLevel is the encryption level
// Default value is Unencrypted
type EncryptionLevel uint8
const (
// EncryptionInitial is the Initial encryption level
EncryptionInitial EncryptionLevel = 1 + iota
// EncryptionHandshake is the Handshake encryption level
EncryptionHandshake
// Encryption0RTT is the 0-RTT encryption level
Encryption0RTT
// Encryption1RTT is the 1-RTT encryption level
Encryption1RTT
)
func (e EncryptionLevel) String() string {
switch e {
case EncryptionInitial:
return "Initial"
case EncryptionHandshake:
return "Handshake"
case Encryption0RTT:
return "0-RTT"
case Encryption1RTT:
return "1-RTT"
}
return "unknown"
}
func (e EncryptionLevel) ToTLSEncryptionLevel() tls.QUICEncryptionLevel {
switch e {
case EncryptionInitial:
return tls.QUICEncryptionLevelInitial
case EncryptionHandshake:
return tls.QUICEncryptionLevelHandshake
case Encryption1RTT:
return tls.QUICEncryptionLevelApplication
case Encryption0RTT:
return tls.QUICEncryptionLevelEarly
default:
panic(fmt.Sprintf("unexpected encryption level: %s", e))
}
}
func FromTLSEncryptionLevel(e tls.QUICEncryptionLevel) EncryptionLevel {
switch e {
case tls.QUICEncryptionLevelInitial:
return EncryptionInitial
case tls.QUICEncryptionLevelHandshake:
return EncryptionHandshake
case tls.QUICEncryptionLevelApplication:
return Encryption1RTT
case tls.QUICEncryptionLevelEarly:
return Encryption0RTT
default:
panic(fmt.Sprintf("unexpect encryption level: %s", e))
}
}
package protocol
// KeyPhase is the key phase
type KeyPhase uint64
// Bit determines the key phase bit
func (p KeyPhase) Bit() KeyPhaseBit {
if p%2 == 0 {
return KeyPhaseZero
}
return KeyPhaseOne
}
// KeyPhaseBit is the key phase bit
type KeyPhaseBit uint8
const (
// KeyPhaseUndefined is an undefined key phase
KeyPhaseUndefined KeyPhaseBit = iota
// KeyPhaseZero is key phase 0
KeyPhaseZero
// KeyPhaseOne is key phase 1
KeyPhaseOne
)
func (p KeyPhaseBit) String() string {
//nolint:exhaustive
switch p {
case KeyPhaseZero:
return "0"
case KeyPhaseOne:
return "1"
default:
return "undefined"
}
}
package protocol
// A PacketNumber in QUIC
type PacketNumber int64
// InvalidPacketNumber is a packet number that is never sent.
// In QUIC, 0 is a valid packet number.
const InvalidPacketNumber PacketNumber = -1
// PacketNumberLen is the length of the packet number in bytes
type PacketNumberLen uint8
const (
// PacketNumberLen1 is a packet number length of 1 byte
PacketNumberLen1 PacketNumberLen = 1
// PacketNumberLen2 is a packet number length of 2 bytes
PacketNumberLen2 PacketNumberLen = 2
// PacketNumberLen3 is a packet number length of 3 bytes
PacketNumberLen3 PacketNumberLen = 3
// PacketNumberLen4 is a packet number length of 4 bytes
PacketNumberLen4 PacketNumberLen = 4
)
// DecodePacketNumber calculates the packet number based its length and the last seen packet number
// This function is taken from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.3.
func DecodePacketNumber(length PacketNumberLen, largest PacketNumber, truncated PacketNumber) PacketNumber {
expected := largest + 1
win := PacketNumber(1 << (length * 8))
hwin := win / 2
mask := win - 1
candidate := (expected & ^mask) | truncated
if candidate <= expected-hwin && candidate < 1<<62-win {
return candidate + win
}
if candidate > expected+hwin && candidate >= win {
return candidate - win
}
return candidate
}
// PacketNumberLengthForHeader gets the length of the packet number for the public header
// it never chooses a PacketNumberLen of 1 byte, since this is too short under certain circumstances
func PacketNumberLengthForHeader(pn, largestAcked PacketNumber) PacketNumberLen {
var numUnacked PacketNumber
if largestAcked == InvalidPacketNumber {
numUnacked = pn + 1
} else {
numUnacked = pn - largestAcked
}
if numUnacked < 1<<(16-1) {
return PacketNumberLen2
}
if numUnacked < 1<<(24-1) {
return PacketNumberLen3
}
return PacketNumberLen4
}
package protocol
// Perspective determines if we're acting as a server or a client
type Perspective int
// the perspectives
const (
PerspectiveServer Perspective = 1
PerspectiveClient Perspective = 2
)
// Opposite returns the perspective of the peer
func (p Perspective) Opposite() Perspective {
return 3 - p
}
func (p Perspective) String() string {
switch p {
case PerspectiveServer:
return "server"
case PerspectiveClient:
return "client"
default:
return "invalid perspective"
}
}
package protocol
import (
"fmt"
"time"
)
// The PacketType is the Long Header Type
type PacketType uint8
const (
// PacketTypeInitial is the packet type of an Initial packet
PacketTypeInitial PacketType = 1 + iota
// PacketTypeRetry is the packet type of a Retry packet
PacketTypeRetry
// PacketTypeHandshake is the packet type of a Handshake packet
PacketTypeHandshake
// PacketType0RTT is the packet type of a 0-RTT packet
PacketType0RTT
)
func (t PacketType) String() string {
switch t {
case PacketTypeInitial:
return "Initial"
case PacketTypeRetry:
return "Retry"
case PacketTypeHandshake:
return "Handshake"
case PacketType0RTT:
return "0-RTT Protected"
default:
return fmt.Sprintf("unknown packet type: %d", t)
}
}
type ECN uint8
const (
ECNUnsupported ECN = iota
ECNNon // 00
ECT1 // 01
ECT0 // 10
ECNCE // 11
)
func ParseECNHeaderBits(bits byte) ECN {
switch bits {
case 0:
return ECNNon
case 0b00000010:
return ECT0
case 0b00000001:
return ECT1
case 0b00000011:
return ECNCE
default:
panic("invalid ECN bits")
}
}
func (e ECN) ToHeaderBits() byte {
//nolint:exhaustive // There are only 4 values.
switch e {
case ECNNon:
return 0
case ECT0:
return 0b00000010
case ECT1:
return 0b00000001
case ECNCE:
return 0b00000011
default:
panic("ECN unsupported")
}
}
func (e ECN) String() string {
switch e {
case ECNUnsupported:
return "ECN unsupported"
case ECNNon:
return "Not-ECT"
case ECT1:
return "ECT(1)"
case ECT0:
return "ECT(0)"
case ECNCE:
return "CE"
default:
return fmt.Sprintf("invalid ECN value: %d", e)
}
}
// A ByteCount in QUIC
type ByteCount int64
// MaxByteCount is the maximum value of a ByteCount
const MaxByteCount = ByteCount(1<<62 - 1)
// InvalidByteCount is an invalid byte count
const InvalidByteCount ByteCount = -1
// A StatelessResetToken is a stateless reset token.
type StatelessResetToken [16]byte
// MaxPacketBufferSize maximum packet size of any QUIC packet, based on
// ethernet's max size, minus the IP and UDP headers. IPv6 has a 40 byte header,
// UDP adds an additional 8 bytes. This is a total overhead of 48 bytes.
// Ethernet's max packet size is 1500 bytes, 1500 - 48 = 1452.
const MaxPacketBufferSize = 1452
// MaxLargePacketBufferSize is used when using GSO
const MaxLargePacketBufferSize = 20 * 1024
// MinInitialPacketSize is the minimum size an Initial packet is required to have.
const MinInitialPacketSize = 1200
// MinUnknownVersionPacketSize is the minimum size a packet with an unknown version
// needs to have in order to trigger a Version Negotiation packet.
const MinUnknownVersionPacketSize = MinInitialPacketSize
// MinStatelessResetSize is the minimum size of a stateless reset packet that we send
const MinStatelessResetSize = 1 /* first byte */ + 20 /* max. conn ID length */ + 4 /* max. packet number length */ + 1 /* min. payload length */ + 16 /* token */
// MinReceivedStatelessResetSize is the minimum size of a received stateless reset,
// as specified in section 10.3 of RFC 9000.
const MinReceivedStatelessResetSize = 5 + 16
// MinConnectionIDLenInitial is the minimum length of the destination connection ID on an Initial packet.
const MinConnectionIDLenInitial = 8
// DefaultAckDelayExponent is the default ack delay exponent
const DefaultAckDelayExponent = 3
// DefaultActiveConnectionIDLimit is the default active connection ID limit
const DefaultActiveConnectionIDLimit = 2
// MaxAckDelayExponent is the maximum ack delay exponent
const MaxAckDelayExponent = 20
// DefaultMaxAckDelay is the default max_ack_delay
const DefaultMaxAckDelay = 25 * time.Millisecond
// MaxMaxAckDelay is the maximum max_ack_delay
const MaxMaxAckDelay = (1<<14 - 1) * time.Millisecond
// MaxConnIDLen is the maximum length of the connection ID
const MaxConnIDLen = 20
// InvalidPacketLimitAES is the maximum number of packets that we can fail to decrypt when using
// AEAD_AES_128_GCM or AEAD_AES_265_GCM.
const InvalidPacketLimitAES = 1 << 52
// InvalidPacketLimitChaCha is the maximum number of packets that we can fail to decrypt when using AEAD_CHACHA20_POLY1305.
const InvalidPacketLimitChaCha = 1 << 36
package protocol
import "github.com/quic-go/quic-go/quicvarint"
// StreamType encodes if this is a unidirectional or bidirectional stream
type StreamType uint8
const (
// StreamTypeUni is a unidirectional stream
StreamTypeUni StreamType = iota
// StreamTypeBidi is a bidirectional stream
StreamTypeBidi
)
// InvalidPacketNumber is a stream ID that is invalid.
// The first valid stream ID in QUIC is 0.
const InvalidStreamID StreamID = -1
// StreamNum is the stream number
type StreamNum int64
const (
// InvalidStreamNum is an invalid stream number.
InvalidStreamNum = -1
// MaxStreamCount is the maximum stream count value that can be sent in MAX_STREAMS frames
// and as the stream count in the transport parameters
MaxStreamCount StreamNum = 1 << 60
// MaxStreamID is the maximum stream ID
MaxStreamID StreamID = quicvarint.Max
)
const (
// FirstOutgoingBidiStreamClient is the first bidirectional stream opened by the client
FirstOutgoingBidiStreamClient StreamID = 0
// FirstOutgoingUniStreamClient is the first unidirectional stream opened by the client
FirstOutgoingUniStreamClient StreamID = 2
// FirstOutgoingBidiStreamServer is the first bidirectional stream opened by the server
FirstOutgoingBidiStreamServer StreamID = 1
// FirstOutgoingUniStreamServer is the first unidirectional stream opened by the server
FirstOutgoingUniStreamServer StreamID = 3
)
const (
// FirstIncomingBidiStreamServer is the first bidirectional stream accepted by the server
FirstIncomingBidiStreamServer = FirstOutgoingBidiStreamClient
// FirstIncomingUniStreamServer is the first unidirectional stream accepted by the server
FirstIncomingUniStreamServer = FirstOutgoingUniStreamClient
// FirstIncomingBidiStreamClient is the first bidirectional stream accepted by the client
FirstIncomingBidiStreamClient = FirstOutgoingBidiStreamServer
// FirstIncomingUniStreamClient is the first unidirectional stream accepted by the client
FirstIncomingUniStreamClient = FirstOutgoingUniStreamServer
)
// StreamID calculates the stream ID.
func (s StreamNum) StreamID(stype StreamType, pers Perspective) StreamID {
if s == 0 {
return InvalidStreamID
}
var first StreamID
switch stype {
case StreamTypeBidi:
switch pers {
case PerspectiveClient:
first = 0
case PerspectiveServer:
first = 1
}
case StreamTypeUni:
switch pers {
case PerspectiveClient:
first = 2
case PerspectiveServer:
first = 3
}
}
return first + 4*StreamID(s-1)
}
// A StreamID in QUIC
type StreamID int64
// StreamInitiator says if the stream was initiated by the client or by the server.
func StreamInitiator(id StreamID) Perspective {
if id%2 == 0 {
return PerspectiveClient
}
return PerspectiveServer
}
// StreamTypeOf says if this is a unidirectional or bidirectional stream.
func StreamTypeOf(id StreamID) StreamType {
if id%4 >= 2 {
return StreamTypeUni
}
return StreamTypeBidi
}
// StreamNum returns how many streams in total are below this
// Example: for stream 9 it returns 3 (i.e. streams 1, 5 and 9)
func (s StreamID) StreamNum() StreamNum {
return StreamNum(s/4) + 1
}
package protocol
import (
"crypto/rand"
"encoding/binary"
"fmt"
"math"
mrand "math/rand/v2"
"slices"
"sync"
)
// Version is a version number as int
type Version uint32
// gQUIC version range as defined in the wiki: https://github.com/quicwg/base-drafts/wiki/QUIC-Versions
const (
gquicVersion0 = 0x51303030
maxGquicVersion = 0x51303439
)
// The version numbers, making grepping easier
const (
VersionUnknown Version = math.MaxUint32
versionDraft29 Version = 0xff00001d // draft-29 used to be a widely deployed version
Version1 Version = 0x1
Version2 Version = 0x6b3343cf
)
// SupportedVersions lists the versions that the server supports
// must be in sorted descending order
var SupportedVersions = []Version{Version1, Version2}
// IsValidVersion says if the version is known to quic-go
func IsValidVersion(v Version) bool {
return v == Version1 || IsSupportedVersion(SupportedVersions, v)
}
func (vn Version) String() string {
switch vn {
case VersionUnknown:
return "unknown"
case versionDraft29:
return "draft-29"
case Version1:
return "v1"
case Version2:
return "v2"
default:
if vn.isGQUIC() {
return fmt.Sprintf("gQUIC %d", vn.toGQUICVersion())
}
return fmt.Sprintf("%#x", uint32(vn))
}
}
func (vn Version) isGQUIC() bool {
return vn > gquicVersion0 && vn <= maxGquicVersion
}
func (vn Version) toGQUICVersion() int {
return int(10*(vn-gquicVersion0)/0x100) + int(vn%0x10)
}
// IsSupportedVersion returns true if the server supports this version
func IsSupportedVersion(supported []Version, v Version) bool {
return slices.Contains(supported, v)
}
// ChooseSupportedVersion finds the best version in the overlap of ours and theirs
// ours is a slice of versions that we support, sorted by our preference (descending)
// theirs is a slice of versions offered by the peer. The order does not matter.
// The bool returned indicates if a matching version was found.
func ChooseSupportedVersion(ours, theirs []Version) (Version, bool) {
for _, ourVer := range ours {
if slices.Contains(theirs, ourVer) {
return ourVer, true
}
}
return 0, false
}
var (
versionNegotiationMx sync.Mutex
versionNegotiationRand mrand.Rand
)
func init() {
var seed [16]byte
rand.Read(seed[:])
versionNegotiationRand = *mrand.New(mrand.NewPCG(
binary.BigEndian.Uint64(seed[:8]),
binary.BigEndian.Uint64(seed[8:]),
))
}
// generateReservedVersion generates a reserved version (v & 0x0f0f0f0f == 0x0a0a0a0a)
func generateReservedVersion() Version {
var b [4]byte
binary.BigEndian.PutUint32(b[:], versionNegotiationRand.Uint32())
return Version((binary.BigEndian.Uint32(b[:]) | 0x0a0a0a0a) & 0xfafafafa)
}
// GetGreasedVersions adds one reserved version number to a slice of version numbers, at a random position.
// It doesn't modify the supported slice.
func GetGreasedVersions(supported []Version) []Version {
versionNegotiationMx.Lock()
defer versionNegotiationMx.Unlock()
randPos := versionNegotiationRand.IntN(len(supported) + 1)
greased := make([]Version, len(supported)+1)
copy(greased, supported[:randPos])
greased[randPos] = generateReservedVersion()
copy(greased[randPos+1:], supported[randPos:])
return greased
}
package qerr
import (
"crypto/tls"
"fmt"
)
// TransportErrorCode is a QUIC transport error.
type TransportErrorCode uint64
// The error codes defined by QUIC
const (
NoError TransportErrorCode = 0x0
InternalError TransportErrorCode = 0x1
ConnectionRefused TransportErrorCode = 0x2
FlowControlError TransportErrorCode = 0x3
StreamLimitError TransportErrorCode = 0x4
StreamStateError TransportErrorCode = 0x5
FinalSizeError TransportErrorCode = 0x6
FrameEncodingError TransportErrorCode = 0x7
TransportParameterError TransportErrorCode = 0x8
ConnectionIDLimitError TransportErrorCode = 0x9
ProtocolViolation TransportErrorCode = 0xa
InvalidToken TransportErrorCode = 0xb
ApplicationErrorErrorCode TransportErrorCode = 0xc
CryptoBufferExceeded TransportErrorCode = 0xd
KeyUpdateError TransportErrorCode = 0xe
AEADLimitReached TransportErrorCode = 0xf
NoViablePathError TransportErrorCode = 0x10
)
func (e TransportErrorCode) IsCryptoError() bool {
return e >= 0x100 && e < 0x200
}
// Message is a description of the error.
// It only returns a non-empty string for crypto errors.
func (e TransportErrorCode) Message() string {
if !e.IsCryptoError() {
return ""
}
return tls.AlertError(e - 0x100).Error()
}
func (e TransportErrorCode) String() string {
switch e {
case NoError:
return "NO_ERROR"
case InternalError:
return "INTERNAL_ERROR"
case ConnectionRefused:
return "CONNECTION_REFUSED"
case FlowControlError:
return "FLOW_CONTROL_ERROR"
case StreamLimitError:
return "STREAM_LIMIT_ERROR"
case StreamStateError:
return "STREAM_STATE_ERROR"
case FinalSizeError:
return "FINAL_SIZE_ERROR"
case FrameEncodingError:
return "FRAME_ENCODING_ERROR"
case TransportParameterError:
return "TRANSPORT_PARAMETER_ERROR"
case ConnectionIDLimitError:
return "CONNECTION_ID_LIMIT_ERROR"
case ProtocolViolation:
return "PROTOCOL_VIOLATION"
case InvalidToken:
return "INVALID_TOKEN"
case ApplicationErrorErrorCode:
return "APPLICATION_ERROR"
case CryptoBufferExceeded:
return "CRYPTO_BUFFER_EXCEEDED"
case KeyUpdateError:
return "KEY_UPDATE_ERROR"
case AEADLimitReached:
return "AEAD_LIMIT_REACHED"
case NoViablePathError:
return "NO_VIABLE_PATH"
default:
if e.IsCryptoError() {
return fmt.Sprintf("CRYPTO_ERROR %#x", uint16(e))
}
return fmt.Sprintf("unknown error code: %#x", uint16(e))
}
}
package qerr
import (
"fmt"
"net"
"github.com/quic-go/quic-go/internal/protocol"
)
var (
ErrHandshakeTimeout = &HandshakeTimeoutError{}
ErrIdleTimeout = &IdleTimeoutError{}
)
type TransportError struct {
Remote bool
FrameType uint64
ErrorCode TransportErrorCode
ErrorMessage string
error error // only set for local errors, sometimes
}
var _ error = &TransportError{}
// NewLocalCryptoError create a new TransportError instance for a crypto error
func NewLocalCryptoError(tlsAlert uint8, err error) *TransportError {
return &TransportError{
ErrorCode: 0x100 + TransportErrorCode(tlsAlert),
error: err,
}
}
func (e *TransportError) Error() string {
str := fmt.Sprintf("%s (%s)", e.ErrorCode.String(), getRole(e.Remote))
if e.FrameType != 0 {
str += fmt.Sprintf(" (frame type: %#x)", e.FrameType)
}
msg := e.ErrorMessage
if len(msg) == 0 && e.error != nil {
msg = e.error.Error()
}
if len(msg) == 0 {
msg = e.ErrorCode.Message()
}
if len(msg) == 0 {
return str
}
return str + ": " + msg
}
func (e *TransportError) Unwrap() []error { return []error{net.ErrClosed, e.error} }
func (e *TransportError) Is(target error) bool {
t, ok := target.(*TransportError)
return ok && e.ErrorCode == t.ErrorCode && e.FrameType == t.FrameType && e.Remote == t.Remote
}
// An ApplicationErrorCode is an application-defined error code.
type ApplicationErrorCode uint64
// A StreamErrorCode is an error code used to cancel streams.
type StreamErrorCode uint64
type ApplicationError struct {
Remote bool
ErrorCode ApplicationErrorCode
ErrorMessage string
}
var _ error = &ApplicationError{}
func (e *ApplicationError) Error() string {
if len(e.ErrorMessage) == 0 {
return fmt.Sprintf("Application error %#x (%s)", e.ErrorCode, getRole(e.Remote))
}
return fmt.Sprintf("Application error %#x (%s): %s", e.ErrorCode, getRole(e.Remote), e.ErrorMessage)
}
func (e *ApplicationError) Unwrap() error { return net.ErrClosed }
func (e *ApplicationError) Is(target error) bool {
t, ok := target.(*ApplicationError)
return ok && e.ErrorCode == t.ErrorCode && e.Remote == t.Remote
}
type IdleTimeoutError struct{}
var _ error = &IdleTimeoutError{}
func (e *IdleTimeoutError) Timeout() bool { return true }
func (e *IdleTimeoutError) Temporary() bool { return false }
func (e *IdleTimeoutError) Error() string { return "timeout: no recent network activity" }
func (e *IdleTimeoutError) Unwrap() error { return net.ErrClosed }
type HandshakeTimeoutError struct{}
var _ error = &HandshakeTimeoutError{}
func (e *HandshakeTimeoutError) Timeout() bool { return true }
func (e *HandshakeTimeoutError) Temporary() bool { return false }
func (e *HandshakeTimeoutError) Error() string { return "timeout: handshake did not complete in time" }
func (e *HandshakeTimeoutError) Unwrap() error { return net.ErrClosed }
// A VersionNegotiationError occurs when the client and the server can't agree on a QUIC version.
type VersionNegotiationError struct {
Ours []protocol.Version
Theirs []protocol.Version
}
func (e *VersionNegotiationError) Error() string {
return fmt.Sprintf("no compatible QUIC version found (we support %s, server offered %s)", e.Ours, e.Theirs)
}
func (e *VersionNegotiationError) Unwrap() error { return net.ErrClosed }
// A StatelessResetError occurs when we receive a stateless reset.
type StatelessResetError struct{}
var _ net.Error = &StatelessResetError{}
func (e *StatelessResetError) Error() string {
return "received a stateless reset"
}
func (e *StatelessResetError) Unwrap() error { return net.ErrClosed }
func (e *StatelessResetError) Timeout() bool { return false }
func (e *StatelessResetError) Temporary() bool { return true }
func getRole(remote bool) string {
if remote {
return "remote"
}
return "local"
}
package qtls
import (
"crypto/tls"
"fmt"
"unsafe"
)
//go:linkname cipherSuitesTLS13 crypto/tls.cipherSuitesTLS13
var cipherSuitesTLS13 []unsafe.Pointer
//go:linkname defaultCipherSuitesTLS13 crypto/tls.defaultCipherSuitesTLS13
var defaultCipherSuitesTLS13 []uint16
//go:linkname defaultCipherSuitesTLS13NoAES crypto/tls.defaultCipherSuitesTLS13NoAES
var defaultCipherSuitesTLS13NoAES []uint16
var cipherSuitesModified bool
// SetCipherSuite modifies the cipherSuiteTLS13 slice of cipher suites inside qtls
// such that it only contains the cipher suite with the chosen id.
// The reset function returned resets them back to the original value.
func SetCipherSuite(id uint16) (reset func()) {
if cipherSuitesModified {
panic("cipher suites modified multiple times without resetting")
}
cipherSuitesModified = true
origCipherSuitesTLS13 := append([]unsafe.Pointer{}, cipherSuitesTLS13...)
origDefaultCipherSuitesTLS13 := append([]uint16{}, defaultCipherSuitesTLS13...)
origDefaultCipherSuitesTLS13NoAES := append([]uint16{}, defaultCipherSuitesTLS13NoAES...)
// The order is given by the order of the slice elements in cipherSuitesTLS13 in qtls.
switch id {
case tls.TLS_AES_128_GCM_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[:1]
case tls.TLS_CHACHA20_POLY1305_SHA256:
cipherSuitesTLS13 = cipherSuitesTLS13[1:2]
case tls.TLS_AES_256_GCM_SHA384:
cipherSuitesTLS13 = cipherSuitesTLS13[2:]
default:
panic(fmt.Sprintf("unexpected cipher suite: %d", id))
}
defaultCipherSuitesTLS13 = []uint16{id}
defaultCipherSuitesTLS13NoAES = []uint16{id}
return func() {
cipherSuitesTLS13 = origCipherSuitesTLS13
defaultCipherSuitesTLS13 = origDefaultCipherSuitesTLS13
defaultCipherSuitesTLS13NoAES = origDefaultCipherSuitesTLS13NoAES
cipherSuitesModified = false
}
}
package testdata
import (
"crypto/tls"
"crypto/x509"
"os"
"path"
"runtime"
)
var certPath string
func init() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("Failed to get current frame")
}
certPath = path.Dir(filename)
}
// GetCertificatePaths returns the paths to certificate and key
func GetCertificatePaths() (string, string) {
return path.Join(certPath, "cert.pem"), path.Join(certPath, "priv.key")
}
// GetTLSConfig returns a tls config for quic.clemente.io
func GetTLSConfig() *tls.Config {
cert, err := tls.LoadX509KeyPair(GetCertificatePaths())
if err != nil {
panic(err)
}
return &tls.Config{
MinVersion: tls.VersionTLS13,
Certificates: []tls.Certificate{cert},
}
}
// AddRootCA adds the root CA certificate to a cert pool
func AddRootCA(certPool *x509.CertPool) {
caCertPath := path.Join(certPath, "ca.pem")
caCertRaw, err := os.ReadFile(caCertPath)
if err != nil {
panic(err)
}
if ok := certPool.AppendCertsFromPEM(caCertRaw); !ok {
panic("Could not add root ceritificate to pool.")
}
}
// GetRootCA returns an x509.CertPool containing (only) the CA certificate
func GetRootCA() *x509.CertPool {
pool := x509.NewCertPool()
AddRootCA(pool)
return pool
}
package utils
import (
"bufio"
"io"
)
type bufferedWriteCloser struct {
*bufio.Writer
io.Closer
}
// NewBufferedWriteCloser creates an io.WriteCloser from a bufio.Writer and an io.Closer
func NewBufferedWriteCloser(writer *bufio.Writer, closer io.Closer) io.WriteCloser {
return &bufferedWriteCloser{
Writer: writer,
Closer: closer,
}
}
func (h bufferedWriteCloser) Close() error {
if err := h.Flush(); err != nil {
return err
}
return h.Closer.Close()
}
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package list implements a doubly linked list.
//
// To iterate over a list (where l is a *List[T]):
//
// for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value
// }
package list
import "sync"
func NewPool[T any]() *sync.Pool {
return &sync.Pool{New: func() any { return &Element[T]{} }}
}
// Element is an element of a linked list.
type Element[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element[T]
// The list to which this element belongs.
list *List[T]
// The value stored with this element.
Value T
}
// Next returns the next list element or nil.
func (e *Element[T]) Next() *Element[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *Element[T]) Prev() *Element[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func (e *Element[T]) List() *List[T] {
return e.list
}
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List[T any] struct {
root Element[T] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
pool *sync.Pool
}
// Init initializes or clears list l.
func (l *List[T]) Init() *List[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// New returns an initialized list.
func New[T any]() *List[T] { return new(List[T]).Init() }
// NewWithPool returns an initialized list, using a sync.Pool for list elements.
func NewWithPool[T any](pool *sync.Pool) *List[T] {
l := &List[T]{pool: pool}
return l.Init()
}
// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List[T]) Len() int { return l.len }
// Front returns the first element of list l or nil if the list is empty.
func (l *List[T]) Front() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
// Back returns the last element of list l or nil if the list is empty.
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
// lazyInit lazily initializes a zero List value.
func (l *List[T]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
// insert inserts e after at, increments l.len, and returns e.
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
var e *Element[T]
if l.pool != nil {
e = l.pool.Get().(*Element[T])
} else {
e = &Element[T]{}
}
e.Value = v
return l.insert(e, at)
}
// remove removes e from its list, decrements l.len
func (l *List[T]) remove(e *Element[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
if l.pool != nil {
l.pool.Put(e)
}
l.len--
}
// move moves e to next to at.
func (l *List[T]) move(e, at *Element[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}
// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List[T]) Remove(e *Element[T]) T {
v := e.Value
if e.list == l {
// if e.list == l, l must have been initialized when e was inserted
// in l or l == nil (e is a zero Element) and l.remove will crash
l.remove(e)
}
return v
}
// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List[T]) PushFront(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, &l.root)
}
// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List[T]) PushBack(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark.prev)
}
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
// see comment in List.Remove about initialization of l
return l.insertValue(v, mark)
}
// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List[T]) MoveToBack(e *Element[T]) {
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
// PushBackList inserts a copy of another list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushBackList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of another list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List[T]) PushFrontList(other *List[T]) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}
package utils
import (
"fmt"
"log"
"os"
"strings"
"time"
)
// LogLevel of quic-go
type LogLevel uint8
const (
// LogLevelNothing disables
LogLevelNothing LogLevel = iota
// LogLevelError enables err logs
LogLevelError
// LogLevelInfo enables info logs (e.g. packets)
LogLevelInfo
// LogLevelDebug enables debug logs (e.g. packet contents)
LogLevelDebug
)
const logEnv = "QUIC_GO_LOG_LEVEL"
// A Logger logs.
type Logger interface {
SetLogLevel(LogLevel)
SetLogTimeFormat(format string)
WithPrefix(prefix string) Logger
Debug() bool
Errorf(format string, args ...any)
Infof(format string, args ...any)
Debugf(format string, args ...any)
}
// DefaultLogger is used by quic-go for logging.
var DefaultLogger Logger
type defaultLogger struct {
prefix string
logLevel LogLevel
timeFormat string
}
var _ Logger = &defaultLogger{}
// SetLogLevel sets the log level
func (l *defaultLogger) SetLogLevel(level LogLevel) {
l.logLevel = level
}
// SetLogTimeFormat sets the format of the timestamp
// an empty string disables the logging of timestamps
func (l *defaultLogger) SetLogTimeFormat(format string) {
log.SetFlags(0) // disable timestamp logging done by the log package
l.timeFormat = format
}
// Debugf logs something
func (l *defaultLogger) Debugf(format string, args ...any) {
if l.logLevel == LogLevelDebug {
l.logMessage(format, args...)
}
}
// Infof logs something
func (l *defaultLogger) Infof(format string, args ...any) {
if l.logLevel >= LogLevelInfo {
l.logMessage(format, args...)
}
}
// Errorf logs something
func (l *defaultLogger) Errorf(format string, args ...any) {
if l.logLevel >= LogLevelError {
l.logMessage(format, args...)
}
}
func (l *defaultLogger) logMessage(format string, args ...any) {
var pre string
if len(l.timeFormat) > 0 {
pre = time.Now().Format(l.timeFormat) + " "
}
if len(l.prefix) > 0 {
pre += l.prefix + " "
}
log.Printf(pre+format, args...)
}
func (l *defaultLogger) WithPrefix(prefix string) Logger {
if len(l.prefix) > 0 {
prefix = l.prefix + " " + prefix
}
return &defaultLogger{
logLevel: l.logLevel,
timeFormat: l.timeFormat,
prefix: prefix,
}
}
// Debug returns true if the log level is LogLevelDebug
func (l *defaultLogger) Debug() bool {
return l.logLevel == LogLevelDebug
}
func init() {
DefaultLogger = &defaultLogger{}
DefaultLogger.SetLogLevel(readLoggingEnv())
}
func readLoggingEnv() LogLevel {
switch strings.ToLower(os.Getenv(logEnv)) {
case "":
return LogLevelNothing
case "debug":
return LogLevelDebug
case "info":
return LogLevelInfo
case "error":
return LogLevelError
default:
fmt.Fprintln(os.Stderr, "invalid quic-go log level, see https://github.com/quic-go/quic-go/wiki/Logging")
return LogLevelNothing
}
}
package utils
import (
"crypto/rand"
"encoding/binary"
)
// Rand is a wrapper around crypto/rand that adds some convenience functions known from math/rand.
type Rand struct {
buf [4]byte
}
func (r *Rand) Int31() int32 {
rand.Read(r.buf[:])
return int32(binary.BigEndian.Uint32(r.buf[:]) & ^uint32(1<<31))
}
// copied from the standard library math/rand implementation of Int63n
func (r *Rand) Int31n(n int32) int32 {
if n&(n-1) == 0 { // n is power of two, can mask
return r.Int31() & (n - 1)
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := r.Int31()
for v > max {
v = r.Int31()
}
return v % n
}
package ringbuffer
// A RingBuffer is a ring buffer.
// It acts as a heap that doesn't cause any allocations.
type RingBuffer[T any] struct {
ring []T
headPos, tailPos int
full bool
}
// Init preallocates a buffer with a certain size.
func (r *RingBuffer[T]) Init(size int) {
r.ring = make([]T, size)
}
// Len returns the number of elements in the ring buffer.
func (r *RingBuffer[T]) Len() int {
if r.full {
return len(r.ring)
}
if r.tailPos >= r.headPos {
return r.tailPos - r.headPos
}
return r.tailPos - r.headPos + len(r.ring)
}
// Empty says if the ring buffer is empty.
func (r *RingBuffer[T]) Empty() bool {
return !r.full && r.headPos == r.tailPos
}
// PushBack adds a new element.
// If the ring buffer is full, its capacity is increased first.
func (r *RingBuffer[T]) PushBack(t T) {
if r.full || len(r.ring) == 0 {
r.grow()
}
r.ring[r.tailPos] = t
r.tailPos++
if r.tailPos == len(r.ring) {
r.tailPos = 0
}
if r.tailPos == r.headPos {
r.full = true
}
}
// PopFront returns the next element.
// It must not be called when the buffer is empty, that means that
// callers might need to check if there are elements in the buffer first.
func (r *RingBuffer[T]) PopFront() T {
if r.Empty() {
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue")
}
r.full = false
t := r.ring[r.headPos]
r.ring[r.headPos] = *new(T)
r.headPos++
if r.headPos == len(r.ring) {
r.headPos = 0
}
return t
}
// PeekFront returns the next element.
// It must not be called when the buffer is empty, that means that
// callers might need to check if there are elements in the buffer first.
func (r *RingBuffer[T]) PeekFront() T {
if r.Empty() {
panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: peek from an empty queue")
}
return r.ring[r.headPos]
}
// Grow the maximum size of the queue.
// This method assume the queue is full.
func (r *RingBuffer[T]) grow() {
oldRing := r.ring
newSize := len(oldRing) * 2
if newSize == 0 {
newSize = 1
}
r.ring = make([]T, newSize)
headLen := copy(r.ring, oldRing[r.headPos:])
copy(r.ring[headLen:], oldRing[:r.headPos])
r.headPos, r.tailPos, r.full = 0, len(oldRing), false
}
// Clear removes all elements.
func (r *RingBuffer[T]) Clear() {
var zeroValue T
for i := range r.ring {
r.ring[i] = zeroValue
}
r.headPos, r.tailPos, r.full = 0, 0, false
}
package utils
import (
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/protocol"
)
const (
rttAlpha = 0.125
oneMinusAlpha = 1 - rttAlpha
rttBeta = 0.25
oneMinusBeta = 1 - rttBeta
)
// The default RTT used before an RTT sample is taken
const DefaultInitialRTT = 100 * time.Millisecond
// RTTStats provides round-trip statistics
type RTTStats struct {
hasMeasurement bool
minRTT atomic.Int64 // nanoseconds
latestRTT atomic.Int64 // nanoseconds
smoothedRTT atomic.Int64 // nanoseconds
meanDeviation atomic.Int64 // nanoseconds
maxAckDelay atomic.Int64 // nanoseconds
}
func NewRTTStats() *RTTStats {
var rttStats RTTStats
rttStats.minRTT.Store(DefaultInitialRTT.Nanoseconds())
rttStats.latestRTT.Store(DefaultInitialRTT.Nanoseconds())
rttStats.smoothedRTT.Store(DefaultInitialRTT.Nanoseconds())
return &rttStats
}
// MinRTT Returns the minRTT for the entire connection.
// May return Zero if no valid updates have occurred.
func (r *RTTStats) MinRTT() time.Duration {
return time.Duration(r.minRTT.Load())
}
// LatestRTT returns the most recent rtt measurement.
// May return Zero if no valid updates have occurred.
func (r *RTTStats) LatestRTT() time.Duration {
return time.Duration(r.latestRTT.Load())
}
// SmoothedRTT returns the smoothed RTT for the connection.
// May return Zero if no valid updates have occurred.
func (r *RTTStats) SmoothedRTT() time.Duration {
return time.Duration(r.smoothedRTT.Load())
}
// MeanDeviation gets the mean deviation
func (r *RTTStats) MeanDeviation() time.Duration {
return time.Duration(r.meanDeviation.Load())
}
// MaxAckDelay gets the max_ack_delay advertised by the peer
func (r *RTTStats) MaxAckDelay() time.Duration {
return time.Duration(r.maxAckDelay.Load())
}
// PTO gets the probe timeout duration.
func (r *RTTStats) PTO(includeMaxAckDelay bool) time.Duration {
if !r.hasMeasurement {
return 2 * DefaultInitialRTT
}
pto := r.SmoothedRTT() + max(4*r.MeanDeviation(), protocol.TimerGranularity)
if includeMaxAckDelay {
pto += r.MaxAckDelay()
}
return pto
}
// UpdateRTT updates the RTT based on a new sample.
func (r *RTTStats) UpdateRTT(sendDelta, ackDelay time.Duration) {
if sendDelta <= 0 {
return
}
// Update r.minRTT first. r.minRTT does not use an rttSample corrected for
// ackDelay but the raw observed sendDelta, since poor clock granularity at
// the client may cause a high ackDelay to result in underestimation of the
// r.minRTT.
minRTT := time.Duration(r.minRTT.Load())
if !r.hasMeasurement || minRTT > sendDelta {
minRTT = sendDelta
r.minRTT.Store(sendDelta.Nanoseconds())
}
// Correct for ackDelay if information received from the peer results in a
// an RTT sample at least as large as minRTT. Otherwise, only use the
// sendDelta.
sample := sendDelta
if sample-minRTT >= ackDelay {
sample -= ackDelay
}
r.latestRTT.Store(sample.Nanoseconds())
// First time call.
if !r.hasMeasurement {
r.hasMeasurement = true
r.smoothedRTT.Store(sample.Nanoseconds())
r.meanDeviation.Store(sample.Nanoseconds() / 2)
} else {
smoothedRTT := r.SmoothedRTT()
meanDev := time.Duration(oneMinusBeta*float32(r.MeanDeviation()/time.Microsecond)+rttBeta*float32((smoothedRTT-sample).Abs()/time.Microsecond)) * time.Microsecond
newSmoothedRTT := time.Duration((float32(smoothedRTT/time.Microsecond)*oneMinusAlpha)+(float32(sample/time.Microsecond)*rttAlpha)) * time.Microsecond
r.meanDeviation.Store(meanDev.Nanoseconds())
r.smoothedRTT.Store(newSmoothedRTT.Nanoseconds())
}
}
func (r *RTTStats) HasMeasurement() bool {
return r.hasMeasurement
}
// SetMaxAckDelay sets the max_ack_delay
func (r *RTTStats) SetMaxAckDelay(mad time.Duration) {
r.maxAckDelay.Store(int64(mad))
}
// SetInitialRTT sets the initial RTT.
// It is used during handshake when restoring the RTT stats from the token.
func (r *RTTStats) SetInitialRTT(t time.Duration) {
// On the server side, by the time we get to process the session ticket,
// we might already have obtained an RTT measurement.
// This can happen if we received the ClientHello in multiple pieces, and one of those pieces was lost.
// Discard the restored value. A fresh measurement is always better.
if r.hasMeasurement {
return
}
r.smoothedRTT.Store(int64(t))
r.latestRTT.Store(int64(t))
}
func (r *RTTStats) ResetForPathMigration() {
r.hasMeasurement = false
r.minRTT.Store(DefaultInitialRTT.Nanoseconds())
r.latestRTT.Store(DefaultInitialRTT.Nanoseconds())
r.smoothedRTT.Store(DefaultInitialRTT.Nanoseconds())
r.meanDeviation.Store(0)
// max_ack_delay remains valid
}
func (r *RTTStats) Clone() *RTTStats {
out := &RTTStats{}
out.hasMeasurement = r.hasMeasurement
out.minRTT.Store(r.minRTT.Load())
out.latestRTT.Store(r.latestRTT.Load())
out.smoothedRTT.Store(r.smoothedRTT.Load())
out.meanDeviation.Store(r.meanDeviation.Load())
out.maxAckDelay.Store(r.maxAckDelay.Load())
return out
}
package wire
import (
"errors"
"math"
"sort"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
var errInvalidAckRanges = errors.New("AckFrame: ACK frame contains invalid ACK ranges")
// An AckFrame is an ACK frame
type AckFrame struct {
AckRanges []AckRange // has to be ordered. The highest ACK range goes first, the lowest ACK range goes last
DelayTime time.Duration
ECT0, ECT1, ECNCE uint64
}
// parseAckFrame reads an ACK frame
func parseAckFrame(frame *AckFrame, b []byte, typ FrameType, ackDelayExponent uint8, _ protocol.Version) (int, error) {
startLen := len(b)
ecn := typ == FrameTypeAckECN
la, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
largestAcked := protocol.PacketNumber(la)
delay, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
delayTime := time.Duration(delay*1<<ackDelayExponent) * time.Microsecond
if delayTime < 0 {
// If the delay time overflows, set it to the maximum encode-able value.
delayTime = time.Duration(math.MaxInt64)
}
frame.DelayTime = delayTime
numBlocks, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
// read the first ACK range
ab, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
ackBlock := protocol.PacketNumber(ab)
if ackBlock > largestAcked {
return 0, errors.New("invalid first ACK range")
}
smallest := largestAcked - ackBlock
frame.AckRanges = append(frame.AckRanges, AckRange{Smallest: smallest, Largest: largestAcked})
// read all the other ACK ranges
for range numBlocks {
g, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
gap := protocol.PacketNumber(g)
if smallest < gap+2 {
return 0, errInvalidAckRanges
}
largest := smallest - gap - 2
ab, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
ackBlock := protocol.PacketNumber(ab)
if ackBlock > largest {
return 0, errInvalidAckRanges
}
smallest = largest - ackBlock
frame.AckRanges = append(frame.AckRanges, AckRange{Smallest: smallest, Largest: largest})
}
if !frame.validateAckRanges() {
return 0, errInvalidAckRanges
}
if ecn {
ect0, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
frame.ECT0 = ect0
ect1, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
frame.ECT1 = ect1
ecnce, l, err := quicvarint.Parse(b)
if err != nil {
return 0, replaceUnexpectedEOF(err)
}
b = b[l:]
frame.ECNCE = ecnce
}
return startLen - len(b), nil
}
// Append appends an ACK frame.
func (f *AckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0
if hasECN {
b = append(b, byte(FrameTypeAckECN))
} else {
b = append(b, byte(FrameTypeAck))
}
b = quicvarint.Append(b, uint64(f.LargestAcked()))
b = quicvarint.Append(b, encodeAckDelay(f.DelayTime))
numRanges := min(len(f.AckRanges), protocol.MaxNumAckRanges)
b = quicvarint.Append(b, uint64(numRanges-1))
// write the first range
_, firstRange := f.encodeAckRange(0)
b = quicvarint.Append(b, firstRange)
// write all the other range
for i := 1; i < numRanges; i++ {
gap, len := f.encodeAckRange(i)
b = quicvarint.Append(b, gap)
b = quicvarint.Append(b, len)
}
if hasECN {
b = quicvarint.Append(b, f.ECT0)
b = quicvarint.Append(b, f.ECT1)
b = quicvarint.Append(b, f.ECNCE)
}
return b, nil
}
// Length of a written frame
func (f *AckFrame) Length(_ protocol.Version) protocol.ByteCount {
largestAcked := f.AckRanges[0].Largest
// The number of ACK ranges is limited to 64, which guarantees that the
// ACK Range Count value can be encoded in a single byte varint.
length := 1 + quicvarint.Len(uint64(largestAcked)) + quicvarint.Len(encodeAckDelay(f.DelayTime)) + 1
lowestInFirstRange := f.AckRanges[0].Smallest
length += quicvarint.Len(uint64(largestAcked - lowestInFirstRange))
for i := 1; i < min(len(f.AckRanges), protocol.MaxNumAckRanges); i++ {
gap, len := f.encodeAckRange(i)
length += quicvarint.Len(gap)
length += quicvarint.Len(len)
}
if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 {
length += quicvarint.Len(f.ECT0) + quicvarint.Len(f.ECT1) + quicvarint.Len(f.ECNCE)
}
return protocol.ByteCount(length)
}
// Truncate truncates the ACK frame to fit into maxSize,
// and to at most 64 ACK ranges.
// maxSize must be large enough to fit at least one ACK range.
func (f *AckFrame) Truncate(maxSize protocol.ByteCount, _ protocol.Version) {
f.AckRanges = f.AckRanges[:f.numEncodableAckRanges(maxSize)]
}
// gets the number of ACK ranges that can be encoded
// such that the resulting frame is smaller than maxSize
func (f *AckFrame) numEncodableAckRanges(maxSize protocol.ByteCount) int {
// Fast path: Most ACK frames are relatively small, and we don't need to calculate the exact length.
// We just assume the worst case scenario: every varint is encoded to 8 bytes.
// If the result is still smaller than the maximum ACK frame size, the actual ACK frame will definitely fit.
length := 1 + 8 /* largest acked */ + 8 /* delay */ + 1 /* ack range count */ + 8 /* first range */
if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 {
length += 8 + 8 + 8
}
numRanges := min(len(f.AckRanges), protocol.MaxNumAckRanges)
length += 2 * 8 * (numRanges - 1)
if protocol.ByteCount(length) <= maxSize {
return numRanges
}
// Slow path: Calculate the exact length of the ACK frame.
length = 1 + quicvarint.Len(uint64(f.LargestAcked())) + quicvarint.Len(encodeAckDelay(f.DelayTime)) + 1
_, firstRange := f.encodeAckRange(0)
length += quicvarint.Len(firstRange)
if f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0 {
length += quicvarint.Len(f.ECT0) + quicvarint.Len(f.ECT1) + quicvarint.Len(f.ECNCE)
}
for i := 1; i < numRanges; i++ {
gap, l := f.encodeAckRange(i)
rangeLen := quicvarint.Len(gap) + quicvarint.Len(l)
if protocol.ByteCount(length+rangeLen) > maxSize {
// Writing range i would exceed the maximum size,
// so encode one range less than that.
return i
}
length += rangeLen
}
return numRanges
}
func (f *AckFrame) encodeAckRange(i int) (gap, length uint64) {
if i == 0 {
return 0, uint64(f.AckRanges[0].Largest - f.AckRanges[0].Smallest)
}
return uint64(f.AckRanges[i-1].Smallest - f.AckRanges[i].Largest - 2),
uint64(f.AckRanges[i].Largest - f.AckRanges[i].Smallest)
}
// HasMissingRanges returns if this frame reports any missing packets
func (f *AckFrame) HasMissingRanges() bool {
return len(f.AckRanges) > 1
}
func (f *AckFrame) validateAckRanges() bool {
if len(f.AckRanges) == 0 {
return false
}
// check the validity of every single ACK range
for _, ackRange := range f.AckRanges {
if ackRange.Smallest > ackRange.Largest {
return false
}
}
// check the consistency for ACK with multiple ACK ranges
for i, ackRange := range f.AckRanges {
if i == 0 {
continue
}
lastAckRange := f.AckRanges[i-1]
if lastAckRange.Smallest <= ackRange.Smallest {
return false
}
if lastAckRange.Smallest <= ackRange.Largest+1 {
return false
}
}
return true
}
// LargestAcked is the largest acked packet number
func (f *AckFrame) LargestAcked() protocol.PacketNumber {
return f.AckRanges[0].Largest
}
// LowestAcked is the lowest acked packet number
func (f *AckFrame) LowestAcked() protocol.PacketNumber {
return f.AckRanges[len(f.AckRanges)-1].Smallest
}
// AcksPacket determines if this ACK frame acks a certain packet number
func (f *AckFrame) AcksPacket(p protocol.PacketNumber) bool {
if p < f.LowestAcked() || p > f.LargestAcked() {
return false
}
i := sort.Search(len(f.AckRanges), func(i int) bool {
return p >= f.AckRanges[i].Smallest
})
// i will always be < len(f.AckRanges), since we checked above that p is not bigger than the largest acked
return p <= f.AckRanges[i].Largest
}
func (f *AckFrame) Reset() {
f.DelayTime = 0
f.ECT0 = 0
f.ECT1 = 0
f.ECNCE = 0
for _, r := range f.AckRanges {
r.Largest = 0
r.Smallest = 0
}
f.AckRanges = f.AckRanges[:0]
}
func encodeAckDelay(delay time.Duration) uint64 {
return uint64(delay.Nanoseconds() / (1000 * (1 << protocol.AckDelayExponent)))
}
package wire
import (
"math"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
type AckFrequencyFrame struct {
SequenceNumber uint64
AckElicitingThreshold uint64
RequestMaxAckDelay time.Duration
ReorderingThreshold protocol.PacketNumber
}
func parseAckFrequencyFrame(b []byte, _ protocol.Version) (*AckFrequencyFrame, int, error) {
startLen := len(b)
seq, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
aeth, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
mad, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
// prevents overflows if the peer sends a very large value
maxAckDelay := time.Duration(mad) * time.Microsecond
if maxAckDelay < 0 {
maxAckDelay = math.MaxInt64
}
b = b[l:]
rth, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
return &AckFrequencyFrame{
SequenceNumber: seq,
AckElicitingThreshold: aeth,
RequestMaxAckDelay: maxAckDelay,
ReorderingThreshold: protocol.PacketNumber(rth),
}, startLen - len(b), nil
}
func (f *AckFrequencyFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = quicvarint.Append(b, uint64(FrameTypeAckFrequency))
b = quicvarint.Append(b, f.SequenceNumber)
b = quicvarint.Append(b, f.AckElicitingThreshold)
b = quicvarint.Append(b, uint64(f.RequestMaxAckDelay/time.Microsecond))
return quicvarint.Append(b, uint64(f.ReorderingThreshold)), nil
}
func (f *AckFrequencyFrame) Length(_ protocol.Version) protocol.ByteCount {
return protocol.ByteCount(2 + quicvarint.Len(f.SequenceNumber) + quicvarint.Len(f.AckElicitingThreshold) +
quicvarint.Len(uint64(f.RequestMaxAckDelay/time.Microsecond)) + quicvarint.Len(uint64(f.ReorderingThreshold)))
}
package wire
import "github.com/quic-go/quic-go/internal/protocol"
// AckRange is an ACK range
type AckRange struct {
Smallest protocol.PacketNumber
Largest protocol.PacketNumber
}
// Len returns the number of packets contained in this ACK range
func (r AckRange) Len() protocol.PacketNumber {
return r.Largest - r.Smallest + 1
}
package wire
import (
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A ConnectionCloseFrame is a CONNECTION_CLOSE frame
type ConnectionCloseFrame struct {
IsApplicationError bool
ErrorCode uint64
FrameType uint64
ReasonPhrase string
}
func parseConnectionCloseFrame(b []byte, typ FrameType, _ protocol.Version) (*ConnectionCloseFrame, int, error) {
startLen := len(b)
f := &ConnectionCloseFrame{IsApplicationError: typ == FrameTypeApplicationClose}
ec, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
f.ErrorCode = ec
// read the Frame Type, if this is not an application error
if !f.IsApplicationError {
ft, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
f.FrameType = ft
}
var reasonPhraseLen uint64
reasonPhraseLen, l, err = quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if int(reasonPhraseLen) > len(b) {
return nil, 0, io.EOF
}
reasonPhrase := make([]byte, reasonPhraseLen)
copy(reasonPhrase, b)
f.ReasonPhrase = string(reasonPhrase)
return f, startLen - len(b) + int(reasonPhraseLen), nil
}
// Length of a written frame
func (f *ConnectionCloseFrame) Length(protocol.Version) protocol.ByteCount {
length := 1 + protocol.ByteCount(quicvarint.Len(f.ErrorCode)+quicvarint.Len(uint64(len(f.ReasonPhrase)))) + protocol.ByteCount(len(f.ReasonPhrase))
if !f.IsApplicationError {
length += protocol.ByteCount(quicvarint.Len(f.FrameType)) // for the frame type
}
return length
}
func (f *ConnectionCloseFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
if f.IsApplicationError {
b = append(b, byte(FrameTypeApplicationClose))
} else {
b = append(b, byte(FrameTypeConnectionClose))
}
b = quicvarint.Append(b, f.ErrorCode)
if !f.IsApplicationError {
b = quicvarint.Append(b, f.FrameType)
}
b = quicvarint.Append(b, uint64(len(f.ReasonPhrase)))
b = append(b, []byte(f.ReasonPhrase)...)
return b, nil
}
package wire
import (
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A CryptoFrame is a CRYPTO frame
type CryptoFrame struct {
Offset protocol.ByteCount
Data []byte
}
func parseCryptoFrame(b []byte, _ protocol.Version) (*CryptoFrame, int, error) {
startLen := len(b)
frame := &CryptoFrame{}
offset, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
frame.Offset = protocol.ByteCount(offset)
dataLen, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if dataLen > uint64(len(b)) {
return nil, 0, io.EOF
}
if dataLen != 0 {
frame.Data = make([]byte, dataLen)
copy(frame.Data, b)
}
return frame, startLen - len(b) + int(dataLen), nil
}
func (f *CryptoFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeCrypto))
b = quicvarint.Append(b, uint64(f.Offset))
b = quicvarint.Append(b, uint64(len(f.Data)))
b = append(b, f.Data...)
return b, nil
}
// Length of a written frame
func (f *CryptoFrame) Length(_ protocol.Version) protocol.ByteCount {
return protocol.ByteCount(1 + quicvarint.Len(uint64(f.Offset)) + quicvarint.Len(uint64(len(f.Data))) + len(f.Data))
}
// MaxDataLen returns the maximum data length
func (f *CryptoFrame) MaxDataLen(maxSize protocol.ByteCount) protocol.ByteCount {
// pretend that the data size will be 1 bytes
// if it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterwards
headerLen := protocol.ByteCount(1 + quicvarint.Len(uint64(f.Offset)) + 1)
if headerLen > maxSize {
return 0
}
maxDataLen := maxSize - headerLen
if quicvarint.Len(uint64(maxDataLen)) != 1 {
maxDataLen--
}
return maxDataLen
}
// MaybeSplitOffFrame splits a frame such that it is not bigger than n bytes.
// It returns if the frame was actually split.
// The frame might not be split if:
// * the size is large enough to fit the whole frame
// * the size is too small to fit even a 1-byte frame. In that case, the frame returned is nil.
func (f *CryptoFrame) MaybeSplitOffFrame(maxSize protocol.ByteCount, version protocol.Version) (*CryptoFrame, bool /* was splitting required */) {
if f.Length(version) <= maxSize {
return nil, false
}
n := f.MaxDataLen(maxSize)
if n == 0 {
return nil, true
}
newLen := protocol.ByteCount(len(f.Data)) - n
new := &CryptoFrame{}
new.Offset = f.Offset
new.Data = make([]byte, newLen)
// swap the data slices
new.Data, f.Data = f.Data, new.Data
copy(f.Data, new.Data[n:])
new.Data = new.Data[:n]
f.Offset += n
return new, true
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A DataBlockedFrame is a DATA_BLOCKED frame
type DataBlockedFrame struct {
MaximumData protocol.ByteCount
}
func parseDataBlockedFrame(b []byte, _ protocol.Version) (*DataBlockedFrame, int, error) {
offset, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
return &DataBlockedFrame{MaximumData: protocol.ByteCount(offset)}, l, nil
}
func (f *DataBlockedFrame) Append(b []byte, version protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeDataBlocked))
return quicvarint.Append(b, uint64(f.MaximumData)), nil
}
// Length of a written frame
func (f *DataBlockedFrame) Length(version protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaximumData)))
}
package wire
import (
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// MaxDatagramSize is the maximum size of a DATAGRAM frame (RFC 9221).
// By setting it to a large value, we allow all datagrams that fit into a QUIC packet.
// The value is chosen such that it can still be encoded as a 2 byte varint.
// This is a var and not a const so it can be set in tests.
var MaxDatagramSize protocol.ByteCount = 16383
// A DatagramFrame is a DATAGRAM frame
type DatagramFrame struct {
DataLenPresent bool
Data []byte
}
func parseDatagramFrame(b []byte, typ FrameType, _ protocol.Version) (*DatagramFrame, int, error) {
startLen := len(b)
f := &DatagramFrame{}
f.DataLenPresent = uint64(typ)&0x1 > 0
var length uint64
if f.DataLenPresent {
var err error
var l int
length, l, err = quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if length > uint64(len(b)) {
return nil, 0, io.EOF
}
} else {
length = uint64(len(b))
}
f.Data = make([]byte, length)
copy(f.Data, b)
return f, startLen - len(b) + int(length), nil
}
func (f *DatagramFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
typ := uint8(0x30)
if f.DataLenPresent {
typ ^= 0b1
}
b = append(b, typ)
if f.DataLenPresent {
b = quicvarint.Append(b, uint64(len(f.Data)))
}
b = append(b, f.Data...)
return b, nil
}
// MaxDataLen returns the maximum data length
func (f *DatagramFrame) MaxDataLen(maxSize protocol.ByteCount, version protocol.Version) protocol.ByteCount {
headerLen := protocol.ByteCount(1)
if f.DataLenPresent {
// pretend that the data size will be 1 bytes
// if it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterwards
headerLen++
}
if headerLen > maxSize {
return 0
}
maxDataLen := maxSize - headerLen
if f.DataLenPresent && quicvarint.Len(uint64(maxDataLen)) != 1 {
maxDataLen--
}
return maxDataLen
}
// Length of a written frame
func (f *DatagramFrame) Length(_ protocol.Version) protocol.ByteCount {
length := 1 + protocol.ByteCount(len(f.Data))
if f.DataLenPresent {
length += protocol.ByteCount(quicvarint.Len(uint64(len(f.Data))))
}
return length
}
package wire
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/quicvarint"
)
// ErrInvalidReservedBits is returned when the reserved bits are incorrect.
// When this error is returned, parsing continues, and an ExtendedHeader is returned.
// This is necessary because we need to decrypt the packet in that case,
// in order to avoid a timing side-channel.
var ErrInvalidReservedBits = errors.New("invalid reserved bits")
// ExtendedHeader is the header of a QUIC packet.
type ExtendedHeader struct {
Header
typeByte byte
KeyPhase protocol.KeyPhaseBit
PacketNumberLen protocol.PacketNumberLen
PacketNumber protocol.PacketNumber
parsedLen protocol.ByteCount
}
func (h *ExtendedHeader) parse(data []byte) (bool /* reserved bits valid */, error) {
// read the (now unencrypted) first byte
h.typeByte = data[0]
h.PacketNumberLen = protocol.PacketNumberLen(h.typeByte&0x3) + 1
if protocol.ByteCount(len(data)) < h.Header.ParsedLen()+protocol.ByteCount(h.PacketNumberLen) {
return false, io.EOF
}
pn, err := readPacketNumber(data[h.Header.ParsedLen():], h.PacketNumberLen)
if err != nil {
return true, nil
}
h.PacketNumber = pn
reservedBitsValid := h.typeByte&0xc == 0
h.parsedLen = h.Header.ParsedLen() + protocol.ByteCount(h.PacketNumberLen)
return reservedBitsValid, err
}
// Append appends the Header.
func (h *ExtendedHeader) Append(b []byte, v protocol.Version) ([]byte, error) {
if h.DestConnectionID.Len() > protocol.MaxConnIDLen {
return nil, fmt.Errorf("invalid connection ID length: %d bytes", h.DestConnectionID.Len())
}
if h.SrcConnectionID.Len() > protocol.MaxConnIDLen {
return nil, fmt.Errorf("invalid connection ID length: %d bytes", h.SrcConnectionID.Len())
}
var packetType uint8
if v == protocol.Version2 {
switch h.Type {
case protocol.PacketTypeInitial:
packetType = 0b01
case protocol.PacketType0RTT:
packetType = 0b10
case protocol.PacketTypeHandshake:
packetType = 0b11
case protocol.PacketTypeRetry:
packetType = 0b00
}
} else {
switch h.Type {
case protocol.PacketTypeInitial:
packetType = 0b00
case protocol.PacketType0RTT:
packetType = 0b01
case protocol.PacketTypeHandshake:
packetType = 0b10
case protocol.PacketTypeRetry:
packetType = 0b11
}
}
firstByte := 0xc0 | packetType<<4
if h.Type != protocol.PacketTypeRetry {
// Retry packets don't have a packet number
firstByte |= uint8(h.PacketNumberLen - 1)
}
b = append(b, firstByte)
b = append(b, make([]byte, 4)...)
binary.BigEndian.PutUint32(b[len(b)-4:], uint32(h.Version))
b = append(b, uint8(h.DestConnectionID.Len()))
b = append(b, h.DestConnectionID.Bytes()...)
b = append(b, uint8(h.SrcConnectionID.Len()))
b = append(b, h.SrcConnectionID.Bytes()...)
//nolint:exhaustive
switch h.Type {
case protocol.PacketTypeRetry:
b = append(b, h.Token...)
return b, nil
case protocol.PacketTypeInitial:
b = quicvarint.Append(b, uint64(len(h.Token)))
b = append(b, h.Token...)
}
b = quicvarint.AppendWithLen(b, uint64(h.Length), 2)
return appendPacketNumber(b, h.PacketNumber, h.PacketNumberLen)
}
// ParsedLen returns the number of bytes that were consumed when parsing the header
func (h *ExtendedHeader) ParsedLen() protocol.ByteCount {
return h.parsedLen
}
// GetLength determines the length of the Header.
func (h *ExtendedHeader) GetLength(_ protocol.Version) protocol.ByteCount {
length := 1 /* type byte */ + 4 /* version */ + 1 /* dest conn ID len */ + protocol.ByteCount(h.DestConnectionID.Len()) + 1 /* src conn ID len */ + protocol.ByteCount(h.SrcConnectionID.Len()) + protocol.ByteCount(h.PacketNumberLen) + 2 /* length */
if h.Type == protocol.PacketTypeInitial {
length += protocol.ByteCount(quicvarint.Len(uint64(len(h.Token))) + len(h.Token))
}
return length
}
// Log logs the Header
func (h *ExtendedHeader) Log(logger utils.Logger) {
var token string
if h.Type == protocol.PacketTypeInitial || h.Type == protocol.PacketTypeRetry {
if len(h.Token) == 0 {
token = "Token: (empty), "
} else {
token = fmt.Sprintf("Token: %#x, ", h.Token)
}
if h.Type == protocol.PacketTypeRetry {
logger.Debugf("\tLong Header{Type: %s, DestConnectionID: %s, SrcConnectionID: %s, %sVersion: %s}", h.Type, h.DestConnectionID, h.SrcConnectionID, token, h.Version)
return
}
}
logger.Debugf("\tLong Header{Type: %s, DestConnectionID: %s, SrcConnectionID: %s, %sPacketNumber: %d, PacketNumberLen: %d, Length: %d, Version: %s}", h.Type, h.DestConnectionID, h.SrcConnectionID, token, h.PacketNumber, h.PacketNumberLen, h.Length, h.Version)
}
func appendPacketNumber(b []byte, pn protocol.PacketNumber, pnLen protocol.PacketNumberLen) ([]byte, error) {
switch pnLen {
case protocol.PacketNumberLen1:
b = append(b, uint8(pn))
case protocol.PacketNumberLen2:
buf := make([]byte, 2)
binary.BigEndian.PutUint16(buf, uint16(pn))
b = append(b, buf...)
case protocol.PacketNumberLen3:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(pn))
b = append(b, buf[1:]...)
case protocol.PacketNumberLen4:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(pn))
b = append(b, buf...)
default:
return nil, fmt.Errorf("invalid packet number length: %d", pnLen)
}
return b, nil
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
)
// A Frame in QUIC
type Frame interface {
Append(b []byte, version protocol.Version) ([]byte, error)
Length(version protocol.Version) protocol.ByteCount
}
// IsProbingFrame returns true if the frame is a probing frame.
// See section 9.1 of RFC 9000.
func IsProbingFrame(f Frame) bool {
switch f.(type) {
case *PathChallengeFrame, *PathResponseFrame, *NewConnectionIDFrame:
return true
}
return false
}
// IsProbingFrameType returns true if the FrameType is a probing frame.
// See section 9.1 of RFC 9000.
func IsProbingFrameType(f FrameType) bool {
//nolint:exhaustive // PATH_CHALLENGE, PATH_RESPONSE and NEW_CONNECTION_ID are the only probing frames
switch f {
case FrameTypePathChallenge, FrameTypePathResponse, FrameTypeNewConnectionID:
return true
default:
return false
}
}
package wire
import (
"errors"
"fmt"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/quicvarint"
)
var errUnknownFrameType = errors.New("unknown frame type")
// The FrameParser parses QUIC frames, one by one.
type FrameParser struct {
ackDelayExponent uint8
supportsDatagrams bool
supportsResetStreamAt bool
supportsAckFrequency bool
// To avoid allocating when parsing, keep a single ACK frame struct.
// It is used over and over again.
ackFrame *AckFrame
}
// NewFrameParser creates a new frame parser.
func NewFrameParser(supportsDatagrams, supportsResetStreamAt, supportsAckFrequency bool) *FrameParser {
return &FrameParser{
supportsDatagrams: supportsDatagrams,
supportsResetStreamAt: supportsResetStreamAt,
supportsAckFrequency: supportsAckFrequency,
ackFrame: &AckFrame{},
}
}
// ParseType parses the frame type of the next frame.
// It skips over PADDING frames.
func (p *FrameParser) ParseType(b []byte, encLevel protocol.EncryptionLevel) (FrameType, int, error) {
var parsed int
for len(b) != 0 {
typ, l, err := quicvarint.Parse(b)
parsed += l
if err != nil {
return 0, parsed, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
ErrorMessage: err.Error(),
}
}
b = b[l:]
if typ == 0x0 { // skip PADDING frames
continue
}
ft := FrameType(typ)
valid := ft.isValidRFC9000() ||
(p.supportsDatagrams && ft.IsDatagramFrameType()) ||
(p.supportsResetStreamAt && ft == FrameTypeResetStreamAt) ||
(p.supportsAckFrequency && (ft == FrameTypeAckFrequency || ft == FrameTypeImmediateAck))
if !valid {
return 0, parsed, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: typ,
ErrorMessage: errUnknownFrameType.Error(),
}
}
if !ft.isAllowedAtEncLevel(encLevel) {
return 0, parsed, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: typ,
ErrorMessage: fmt.Sprintf("%d not allowed at encryption level %s", ft, encLevel),
}
}
return ft, parsed, nil
}
return 0, parsed, io.EOF
}
func (p *FrameParser) ParseStreamFrame(frameType FrameType, data []byte, v protocol.Version) (*StreamFrame, int, error) {
frame, n, err := ParseStreamFrame(data, frameType, v)
if err != nil {
return nil, n, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: uint64(frameType),
ErrorMessage: err.Error(),
}
}
return frame, n, nil
}
func (p *FrameParser) ParseAckFrame(frameType FrameType, data []byte, encLevel protocol.EncryptionLevel, v protocol.Version) (*AckFrame, int, error) {
ackDelayExponent := p.ackDelayExponent
if encLevel != protocol.Encryption1RTT {
ackDelayExponent = protocol.DefaultAckDelayExponent
}
p.ackFrame.Reset()
l, err := parseAckFrame(p.ackFrame, data, frameType, ackDelayExponent, v)
if err != nil {
return nil, l, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: uint64(frameType),
ErrorMessage: err.Error(),
}
}
return p.ackFrame, l, nil
}
func (p *FrameParser) ParseDatagramFrame(frameType FrameType, data []byte, v protocol.Version) (*DatagramFrame, int, error) {
f, l, err := parseDatagramFrame(data, frameType, v)
if err != nil {
return nil, 0, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: uint64(frameType),
ErrorMessage: err.Error(),
}
}
return f, l, nil
}
// ParseLessCommonFrame parses everything except STREAM, ACK or DATAGRAM.
// These cases should be handled separately for performance reasons.
func (p *FrameParser) ParseLessCommonFrame(frameType FrameType, data []byte, v protocol.Version) (Frame, int, error) {
var frame Frame
var l int
var err error
//nolint:exhaustive // Common frames should already be handled.
switch frameType {
case FrameTypePing:
frame = &PingFrame{}
case FrameTypeResetStream:
frame, l, err = parseResetStreamFrame(data, false, v)
case FrameTypeStopSending:
frame, l, err = parseStopSendingFrame(data, v)
case FrameTypeCrypto:
frame, l, err = parseCryptoFrame(data, v)
case FrameTypeNewToken:
frame, l, err = parseNewTokenFrame(data, v)
case FrameTypeMaxData:
frame, l, err = parseMaxDataFrame(data, v)
case FrameTypeMaxStreamData:
frame, l, err = parseMaxStreamDataFrame(data, v)
case FrameTypeBidiMaxStreams, FrameTypeUniMaxStreams:
frame, l, err = parseMaxStreamsFrame(data, frameType, v)
case FrameTypeDataBlocked:
frame, l, err = parseDataBlockedFrame(data, v)
case FrameTypeStreamDataBlocked:
frame, l, err = parseStreamDataBlockedFrame(data, v)
case FrameTypeBidiStreamBlocked, FrameTypeUniStreamBlocked:
frame, l, err = parseStreamsBlockedFrame(data, frameType, v)
case FrameTypeNewConnectionID:
frame, l, err = parseNewConnectionIDFrame(data, v)
case FrameTypeRetireConnectionID:
frame, l, err = parseRetireConnectionIDFrame(data, v)
case FrameTypePathChallenge:
frame, l, err = parsePathChallengeFrame(data, v)
case FrameTypePathResponse:
frame, l, err = parsePathResponseFrame(data, v)
case FrameTypeConnectionClose, FrameTypeApplicationClose:
frame, l, err = parseConnectionCloseFrame(data, frameType, v)
case FrameTypeHandshakeDone:
frame = &HandshakeDoneFrame{}
case FrameTypeResetStreamAt:
frame, l, err = parseResetStreamFrame(data, true, v)
case FrameTypeAckFrequency:
frame, l, err = parseAckFrequencyFrame(data, v)
case FrameTypeImmediateAck:
frame = &ImmediateAckFrame{}
default:
err = errUnknownFrameType
}
if err != nil {
return frame, l, &qerr.TransportError{
ErrorCode: qerr.FrameEncodingError,
FrameType: uint64(frameType),
ErrorMessage: err.Error(),
}
}
return frame, l, err
}
// SetAckDelayExponent sets the acknowledgment delay exponent (sent in the transport parameters).
// This value is used to scale the ACK Delay field in the ACK frame.
func (p *FrameParser) SetAckDelayExponent(exp uint8) {
p.ackDelayExponent = exp
}
func replaceUnexpectedEOF(e error) error {
if e == io.ErrUnexpectedEOF {
return io.EOF
}
return e
}
package wire
import "github.com/quic-go/quic-go/internal/protocol"
type FrameType uint64
// These constants correspond to those defined in RFC 9000.
// Stream frame types are not listed explicitly here; use FrameType.IsStreamFrameType() to identify them.
const (
FrameTypePing FrameType = 0x1
FrameTypeAck FrameType = 0x2
FrameTypeAckECN FrameType = 0x3
FrameTypeResetStream FrameType = 0x4
FrameTypeStopSending FrameType = 0x5
FrameTypeCrypto FrameType = 0x6
FrameTypeNewToken FrameType = 0x7
FrameTypeMaxData FrameType = 0x10
FrameTypeMaxStreamData FrameType = 0x11
FrameTypeBidiMaxStreams FrameType = 0x12
FrameTypeUniMaxStreams FrameType = 0x13
FrameTypeDataBlocked FrameType = 0x14
FrameTypeStreamDataBlocked FrameType = 0x15
FrameTypeBidiStreamBlocked FrameType = 0x16
FrameTypeUniStreamBlocked FrameType = 0x17
FrameTypeNewConnectionID FrameType = 0x18
FrameTypeRetireConnectionID FrameType = 0x19
FrameTypePathChallenge FrameType = 0x1a
FrameTypePathResponse FrameType = 0x1b
FrameTypeConnectionClose FrameType = 0x1c
FrameTypeApplicationClose FrameType = 0x1d
FrameTypeHandshakeDone FrameType = 0x1e
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/09/
FrameTypeResetStreamAt FrameType = 0x24
// https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/
FrameTypeAckFrequency FrameType = 0xaf
FrameTypeImmediateAck FrameType = 0x1f
FrameTypeDatagramNoLength FrameType = 0x30
FrameTypeDatagramWithLength FrameType = 0x31
)
func (t FrameType) IsStreamFrameType() bool {
return t >= 0x8 && t <= 0xf
}
func (t FrameType) isValidRFC9000() bool {
return t <= 0x1e
}
func (t FrameType) IsAckFrameType() bool {
return t == FrameTypeAck || t == FrameTypeAckECN
}
func (t FrameType) IsDatagramFrameType() bool {
return t == FrameTypeDatagramNoLength || t == FrameTypeDatagramWithLength
}
func (t FrameType) isAllowedAtEncLevel(encLevel protocol.EncryptionLevel) bool {
//nolint:exhaustive
switch encLevel {
case protocol.EncryptionInitial, protocol.EncryptionHandshake:
switch t {
case FrameTypeCrypto, FrameTypeAck, FrameTypeAckECN, FrameTypeConnectionClose, FrameTypePing:
return true
default:
return false
}
case protocol.Encryption0RTT:
switch t {
case FrameTypeCrypto, FrameTypeAck, FrameTypeAckECN, FrameTypeConnectionClose, FrameTypeNewToken, FrameTypePathResponse, FrameTypeRetireConnectionID:
return false
default:
return true
}
case protocol.Encryption1RTT:
return true
default:
panic("unknown encryption level")
}
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
)
// A HandshakeDoneFrame is a HANDSHAKE_DONE frame
type HandshakeDoneFrame struct{}
func (f *HandshakeDoneFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
return append(b, byte(FrameTypeHandshakeDone)), nil
}
// Length of a written frame
func (f *HandshakeDoneFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1
}
package wire
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// ParseConnectionID parses the destination connection ID of a packet.
func ParseConnectionID(data []byte, shortHeaderConnIDLen int) (protocol.ConnectionID, error) {
if len(data) == 0 {
return protocol.ConnectionID{}, io.EOF
}
if !IsLongHeaderPacket(data[0]) {
if len(data) < shortHeaderConnIDLen+1 {
return protocol.ConnectionID{}, io.EOF
}
return protocol.ParseConnectionID(data[1 : 1+shortHeaderConnIDLen]), nil
}
if len(data) < 6 {
return protocol.ConnectionID{}, io.EOF
}
destConnIDLen := int(data[5])
if destConnIDLen > protocol.MaxConnIDLen {
return protocol.ConnectionID{}, protocol.ErrInvalidConnectionIDLen
}
if len(data) < 6+destConnIDLen {
return protocol.ConnectionID{}, io.EOF
}
return protocol.ParseConnectionID(data[6 : 6+destConnIDLen]), nil
}
// ParseArbitraryLenConnectionIDs parses the most general form of a Long Header packet,
// using only the version-independent packet format as described in Section 5.1 of RFC 8999:
// https://datatracker.ietf.org/doc/html/rfc8999#section-5.1.
// This function should only be called on Long Header packets for which we don't support the version.
func ParseArbitraryLenConnectionIDs(data []byte) (bytesParsed int, dest, src protocol.ArbitraryLenConnectionID, _ error) {
startLen := len(data)
if len(data) < 6 {
return 0, nil, nil, io.EOF
}
data = data[5:] // skip first byte and version field
destConnIDLen := data[0]
data = data[1:]
destConnID := make(protocol.ArbitraryLenConnectionID, destConnIDLen)
if len(data) < int(destConnIDLen)+1 {
return 0, nil, nil, io.EOF
}
copy(destConnID, data)
data = data[destConnIDLen:]
srcConnIDLen := data[0]
data = data[1:]
if len(data) < int(srcConnIDLen) {
return 0, nil, nil, io.EOF
}
srcConnID := make(protocol.ArbitraryLenConnectionID, srcConnIDLen)
copy(srcConnID, data)
return startLen - len(data) + int(srcConnIDLen), destConnID, srcConnID, nil
}
func IsPotentialQUICPacket(firstByte byte) bool {
return firstByte&0x40 > 0
}
// IsLongHeaderPacket says if this is a Long Header packet
func IsLongHeaderPacket(firstByte byte) bool {
return firstByte&0x80 > 0
}
// ParseVersion parses the QUIC version.
// It should only be called for Long Header packets (Short Header packets don't contain a version number).
func ParseVersion(data []byte) (protocol.Version, error) {
if len(data) < 5 {
return 0, io.EOF
}
return protocol.Version(binary.BigEndian.Uint32(data[1:5])), nil
}
// IsVersionNegotiationPacket says if this is a version negotiation packet
func IsVersionNegotiationPacket(b []byte) bool {
if len(b) < 5 {
return false
}
return IsLongHeaderPacket(b[0]) && b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0
}
// Is0RTTPacket says if this is a 0-RTT packet.
// A packet sent with a version we don't understand can never be a 0-RTT packet.
func Is0RTTPacket(b []byte) bool {
if len(b) < 5 {
return false
}
if !IsLongHeaderPacket(b[0]) {
return false
}
version := protocol.Version(binary.BigEndian.Uint32(b[1:5]))
//nolint:exhaustive // We only need to test QUIC versions that we support.
switch version {
case protocol.Version1:
return b[0]>>4&0b11 == 0b01
case protocol.Version2:
return b[0]>>4&0b11 == 0b10
default:
return false
}
}
var ErrUnsupportedVersion = errors.New("unsupported version")
// The Header is the version independent part of the header
type Header struct {
typeByte byte
Type protocol.PacketType
Version protocol.Version
SrcConnectionID protocol.ConnectionID
DestConnectionID protocol.ConnectionID
Length protocol.ByteCount
Token []byte
parsedLen protocol.ByteCount // how many bytes were read while parsing this header
}
// ParsePacket parses a long header packet.
// The packet is cut according to the length field.
// If we understand the version, the packet is parsed up unto the packet number.
// Otherwise, only the invariant part of the header is parsed.
func ParsePacket(data []byte) (*Header, []byte, []byte, error) {
if len(data) == 0 || !IsLongHeaderPacket(data[0]) {
return nil, nil, nil, errors.New("not a long header packet")
}
hdr, err := parseHeader(data)
if err != nil {
if errors.Is(err, ErrUnsupportedVersion) {
return hdr, nil, nil, err
}
return nil, nil, nil, err
}
if protocol.ByteCount(len(data)) < hdr.ParsedLen()+hdr.Length {
return nil, nil, nil, fmt.Errorf("packet length (%d bytes) is smaller than the expected length (%d bytes)", len(data)-int(hdr.ParsedLen()), hdr.Length)
}
packetLen := int(hdr.ParsedLen() + hdr.Length)
return hdr, data[:packetLen], data[packetLen:], nil
}
// ParseHeader parses the header:
// * if we understand the version: up to the packet number
// * if not, only the invariant part of the header
func parseHeader(b []byte) (*Header, error) {
if len(b) == 0 {
return nil, io.EOF
}
typeByte := b[0]
h := &Header{typeByte: typeByte}
l, err := h.parseLongHeader(b[1:])
h.parsedLen = protocol.ByteCount(l) + 1
return h, err
}
func (h *Header) parseLongHeader(b []byte) (int, error) {
startLen := len(b)
if len(b) < 5 {
return 0, io.EOF
}
h.Version = protocol.Version(binary.BigEndian.Uint32(b[:4]))
if h.Version != 0 && h.typeByte&0x40 == 0 {
return startLen - len(b), errors.New("not a QUIC packet")
}
destConnIDLen := int(b[4])
if destConnIDLen > protocol.MaxConnIDLen {
return startLen - len(b), protocol.ErrInvalidConnectionIDLen
}
b = b[5:]
if len(b) < destConnIDLen+1 {
return startLen - len(b), io.EOF
}
h.DestConnectionID = protocol.ParseConnectionID(b[:destConnIDLen])
srcConnIDLen := int(b[destConnIDLen])
if srcConnIDLen > protocol.MaxConnIDLen {
return startLen - len(b), protocol.ErrInvalidConnectionIDLen
}
b = b[destConnIDLen+1:]
if len(b) < srcConnIDLen {
return startLen - len(b), io.EOF
}
h.SrcConnectionID = protocol.ParseConnectionID(b[:srcConnIDLen])
b = b[srcConnIDLen:]
if h.Version == 0 { // version negotiation packet
return startLen - len(b), nil
}
// If we don't understand the version, we have no idea how to interpret the rest of the bytes
if !protocol.IsSupportedVersion(protocol.SupportedVersions, h.Version) {
return startLen - len(b), ErrUnsupportedVersion
}
if h.Version == protocol.Version2 {
switch h.typeByte >> 4 & 0b11 {
case 0b00:
h.Type = protocol.PacketTypeRetry
case 0b01:
h.Type = protocol.PacketTypeInitial
case 0b10:
h.Type = protocol.PacketType0RTT
case 0b11:
h.Type = protocol.PacketTypeHandshake
}
} else {
switch h.typeByte >> 4 & 0b11 {
case 0b00:
h.Type = protocol.PacketTypeInitial
case 0b01:
h.Type = protocol.PacketType0RTT
case 0b10:
h.Type = protocol.PacketTypeHandshake
case 0b11:
h.Type = protocol.PacketTypeRetry
}
}
if h.Type == protocol.PacketTypeRetry {
tokenLen := len(b) - 16
if tokenLen <= 0 {
return startLen - len(b), io.EOF
}
h.Token = make([]byte, tokenLen)
copy(h.Token, b[:tokenLen])
return startLen - len(b) + tokenLen + 16, nil
}
if h.Type == protocol.PacketTypeInitial {
tokenLen, n, err := quicvarint.Parse(b)
if err != nil {
return startLen - len(b), err
}
b = b[n:]
if tokenLen > uint64(len(b)) {
return startLen - len(b), io.EOF
}
h.Token = make([]byte, tokenLen)
copy(h.Token, b[:tokenLen])
b = b[tokenLen:]
}
pl, n, err := quicvarint.Parse(b)
if err != nil {
return 0, err
}
h.Length = protocol.ByteCount(pl)
return startLen - len(b) + n, nil
}
// ParsedLen returns the number of bytes that were consumed when parsing the header
func (h *Header) ParsedLen() protocol.ByteCount {
return h.parsedLen
}
// ParseExtended parses the version dependent part of the header.
// The Reader has to be set such that it points to the first byte of the header.
func (h *Header) ParseExtended(data []byte) (*ExtendedHeader, error) {
extHdr := h.toExtendedHeader()
reservedBitsValid, err := extHdr.parse(data)
if err != nil {
return nil, err
}
if !reservedBitsValid {
return extHdr, ErrInvalidReservedBits
}
return extHdr, nil
}
func (h *Header) toExtendedHeader() *ExtendedHeader {
return &ExtendedHeader{Header: *h}
}
// PacketType is the type of the packet, for logging purposes
func (h *Header) PacketType() string {
return h.Type.String()
}
func readPacketNumber(data []byte, pnLen protocol.PacketNumberLen) (protocol.PacketNumber, error) {
var pn protocol.PacketNumber
switch pnLen {
case protocol.PacketNumberLen1:
pn = protocol.PacketNumber(data[0])
case protocol.PacketNumberLen2:
pn = protocol.PacketNumber(binary.BigEndian.Uint16(data[:2]))
case protocol.PacketNumberLen3:
pn = protocol.PacketNumber(uint32(data[2]) + uint32(data[1])<<8 + uint32(data[0])<<16)
case protocol.PacketNumberLen4:
pn = protocol.PacketNumber(binary.BigEndian.Uint32(data[:4]))
default:
return 0, fmt.Errorf("invalid packet number length: %d", pnLen)
}
return pn, nil
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// An ImmediateAckFrame is an IMMEDIATE_ACK frame
type ImmediateAckFrame struct{}
func (f *ImmediateAckFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
return quicvarint.Append(b, uint64(FrameTypeImmediateAck)), nil
}
// Length of a written frame
func (f *ImmediateAckFrame) Length(_ protocol.Version) protocol.ByteCount {
return protocol.ByteCount(quicvarint.Len(uint64(FrameTypeImmediateAck)))
}
package wire
import (
"fmt"
"strings"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
// LogFrame logs a frame, either sent or received
func LogFrame(logger utils.Logger, frame Frame, sent bool) {
if !logger.Debug() {
return
}
dir := "<-"
if sent {
dir = "->"
}
switch f := frame.(type) {
case *CryptoFrame:
dataLen := protocol.ByteCount(len(f.Data))
logger.Debugf("\t%s &wire.CryptoFrame{Offset: %d, Data length: %d, Offset + Data length: %d}", dir, f.Offset, dataLen, f.Offset+dataLen)
case *StreamFrame:
logger.Debugf("\t%s &wire.StreamFrame{StreamID: %d, Fin: %t, Offset: %d, Data length: %d, Offset + Data length: %d}", dir, f.StreamID, f.Fin, f.Offset, f.DataLen(), f.Offset+f.DataLen())
case *ResetStreamFrame:
logger.Debugf("\t%s &wire.ResetStreamFrame{StreamID: %d, ErrorCode: %#x, FinalSize: %d}", dir, f.StreamID, f.ErrorCode, f.FinalSize)
case *AckFrame:
hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0
var ecn string
if hasECN {
ecn = fmt.Sprintf(", ECT0: %d, ECT1: %d, CE: %d", f.ECT0, f.ECT1, f.ECNCE)
}
if len(f.AckRanges) > 1 {
ackRanges := make([]string, len(f.AckRanges))
for i, r := range f.AckRanges {
ackRanges[i] = fmt.Sprintf("{Largest: %d, Smallest: %d}", r.Largest, r.Smallest)
}
logger.Debugf("\t%s &wire.AckFrame{LargestAcked: %d, LowestAcked: %d, AckRanges: {%s}, DelayTime: %s%s}", dir, f.LargestAcked(), f.LowestAcked(), strings.Join(ackRanges, ", "), f.DelayTime.String(), ecn)
} else {
logger.Debugf("\t%s &wire.AckFrame{LargestAcked: %d, LowestAcked: %d, DelayTime: %s%s}", dir, f.LargestAcked(), f.LowestAcked(), f.DelayTime.String(), ecn)
}
case *MaxDataFrame:
logger.Debugf("\t%s &wire.MaxDataFrame{MaximumData: %d}", dir, f.MaximumData)
case *MaxStreamDataFrame:
logger.Debugf("\t%s &wire.MaxStreamDataFrame{StreamID: %d, MaximumStreamData: %d}", dir, f.StreamID, f.MaximumStreamData)
case *DataBlockedFrame:
logger.Debugf("\t%s &wire.DataBlockedFrame{MaximumData: %d}", dir, f.MaximumData)
case *StreamDataBlockedFrame:
logger.Debugf("\t%s &wire.StreamDataBlockedFrame{StreamID: %d, MaximumStreamData: %d}", dir, f.StreamID, f.MaximumStreamData)
case *MaxStreamsFrame:
switch f.Type {
case protocol.StreamTypeUni:
logger.Debugf("\t%s &wire.MaxStreamsFrame{Type: uni, MaxStreamNum: %d}", dir, f.MaxStreamNum)
case protocol.StreamTypeBidi:
logger.Debugf("\t%s &wire.MaxStreamsFrame{Type: bidi, MaxStreamNum: %d}", dir, f.MaxStreamNum)
}
case *StreamsBlockedFrame:
switch f.Type {
case protocol.StreamTypeUni:
logger.Debugf("\t%s &wire.StreamsBlockedFrame{Type: uni, MaxStreams: %d}", dir, f.StreamLimit)
case protocol.StreamTypeBidi:
logger.Debugf("\t%s &wire.StreamsBlockedFrame{Type: bidi, MaxStreams: %d}", dir, f.StreamLimit)
}
case *NewConnectionIDFrame:
logger.Debugf("\t%s &wire.NewConnectionIDFrame{SequenceNumber: %d, RetirePriorTo: %d, ConnectionID: %s, StatelessResetToken: %#x}", dir, f.SequenceNumber, f.RetirePriorTo, f.ConnectionID, f.StatelessResetToken)
case *RetireConnectionIDFrame:
logger.Debugf("\t%s &wire.RetireConnectionIDFrame{SequenceNumber: %d}", dir, f.SequenceNumber)
case *NewTokenFrame:
logger.Debugf("\t%s &wire.NewTokenFrame{Token: %#x}", dir, f.Token)
default:
logger.Debugf("\t%s %#v", dir, frame)
}
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A MaxDataFrame carries flow control information for the connection
type MaxDataFrame struct {
MaximumData protocol.ByteCount
}
// parseMaxDataFrame parses a MAX_DATA frame
func parseMaxDataFrame(b []byte, _ protocol.Version) (*MaxDataFrame, int, error) {
frame := &MaxDataFrame{}
byteOffset, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
frame.MaximumData = protocol.ByteCount(byteOffset)
return frame, l, nil
}
func (f *MaxDataFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeMaxData))
b = quicvarint.Append(b, uint64(f.MaximumData))
return b, nil
}
// Length of a written frame
func (f *MaxDataFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaximumData)))
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A MaxStreamDataFrame is a MAX_STREAM_DATA frame
type MaxStreamDataFrame struct {
StreamID protocol.StreamID
MaximumStreamData protocol.ByteCount
}
func parseMaxStreamDataFrame(b []byte, _ protocol.Version) (*MaxStreamDataFrame, int, error) {
startLen := len(b)
sid, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
offset, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
return &MaxStreamDataFrame{
StreamID: protocol.StreamID(sid),
MaximumStreamData: protocol.ByteCount(offset),
}, startLen - len(b), nil
}
func (f *MaxStreamDataFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeMaxStreamData))
b = quicvarint.Append(b, uint64(f.StreamID))
b = quicvarint.Append(b, uint64(f.MaximumStreamData))
return b, nil
}
// Length of a written frame
func (f *MaxStreamDataFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.MaximumStreamData)))
}
package wire
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A MaxStreamsFrame is a MAX_STREAMS frame
type MaxStreamsFrame struct {
Type protocol.StreamType
MaxStreamNum protocol.StreamNum
}
func parseMaxStreamsFrame(b []byte, typ FrameType, _ protocol.Version) (*MaxStreamsFrame, int, error) {
f := &MaxStreamsFrame{}
//nolint:exhaustive // Function will only be called with BidiMaxStreamsFrameType or UniMaxStreamsFrameType
switch typ {
case FrameTypeBidiMaxStreams:
f.Type = protocol.StreamTypeBidi
case FrameTypeUniMaxStreams:
f.Type = protocol.StreamTypeUni
}
streamID, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
f.MaxStreamNum = protocol.StreamNum(streamID)
if f.MaxStreamNum > protocol.MaxStreamCount {
return nil, 0, fmt.Errorf("%d exceeds the maximum stream count", f.MaxStreamNum)
}
return f, l, nil
}
func (f *MaxStreamsFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
switch f.Type {
case protocol.StreamTypeBidi:
b = append(b, byte(FrameTypeBidiMaxStreams))
case protocol.StreamTypeUni:
b = append(b, byte(FrameTypeUniMaxStreams))
}
b = quicvarint.Append(b, uint64(f.MaxStreamNum))
return b, nil
}
// Length of a written frame
func (f *MaxStreamsFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.MaxStreamNum)))
}
package wire
import (
"errors"
"fmt"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A NewConnectionIDFrame is a NEW_CONNECTION_ID frame
type NewConnectionIDFrame struct {
SequenceNumber uint64
RetirePriorTo uint64
ConnectionID protocol.ConnectionID
StatelessResetToken protocol.StatelessResetToken
}
func parseNewConnectionIDFrame(b []byte, _ protocol.Version) (*NewConnectionIDFrame, int, error) {
startLen := len(b)
seq, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
ret, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if ret > seq {
//nolint:staticcheck // SA1021: Retire Prior To is the name of the field
return nil, 0, fmt.Errorf("Retire Prior To value (%d) larger than Sequence Number (%d)", ret, seq)
}
if len(b) == 0 {
return nil, 0, io.EOF
}
connIDLen := int(b[0])
b = b[1:]
if connIDLen == 0 {
return nil, 0, errors.New("invalid zero-length connection ID")
}
if connIDLen > protocol.MaxConnIDLen {
return nil, 0, protocol.ErrInvalidConnectionIDLen
}
if len(b) < connIDLen {
return nil, 0, io.EOF
}
frame := &NewConnectionIDFrame{
SequenceNumber: seq,
RetirePriorTo: ret,
ConnectionID: protocol.ParseConnectionID(b[:connIDLen]),
}
b = b[connIDLen:]
if len(b) < len(frame.StatelessResetToken) {
return nil, 0, io.EOF
}
copy(frame.StatelessResetToken[:], b)
return frame, startLen - len(b) + len(frame.StatelessResetToken), nil
}
func (f *NewConnectionIDFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeNewConnectionID))
b = quicvarint.Append(b, f.SequenceNumber)
b = quicvarint.Append(b, f.RetirePriorTo)
connIDLen := f.ConnectionID.Len()
if connIDLen > protocol.MaxConnIDLen {
return nil, fmt.Errorf("invalid connection ID length: %d", connIDLen)
}
b = append(b, uint8(connIDLen))
b = append(b, f.ConnectionID.Bytes()...)
b = append(b, f.StatelessResetToken[:]...)
return b, nil
}
// Length of a written frame
func (f *NewConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(f.SequenceNumber)+quicvarint.Len(f.RetirePriorTo)+1 /* connection ID length */ +f.ConnectionID.Len()) + 16
}
package wire
import (
"errors"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A NewTokenFrame is a NEW_TOKEN frame
type NewTokenFrame struct {
Token []byte
}
func parseNewTokenFrame(b []byte, _ protocol.Version) (*NewTokenFrame, int, error) {
tokenLen, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if tokenLen == 0 {
return nil, 0, errors.New("token must not be empty")
}
if uint64(len(b)) < tokenLen {
return nil, 0, io.EOF
}
token := make([]byte, int(tokenLen))
copy(token, b)
return &NewTokenFrame{Token: token}, l + int(tokenLen), nil
}
func (f *NewTokenFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeNewToken))
b = quicvarint.Append(b, uint64(len(f.Token)))
b = append(b, f.Token...)
return b, nil
}
// Length of a written frame
func (f *NewTokenFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(len(f.Token)))+len(f.Token))
}
package wire
import (
"io"
"github.com/quic-go/quic-go/internal/protocol"
)
// A PathChallengeFrame is a PATH_CHALLENGE frame
type PathChallengeFrame struct {
Data [8]byte
}
func parsePathChallengeFrame(b []byte, _ protocol.Version) (*PathChallengeFrame, int, error) {
f := &PathChallengeFrame{}
if len(b) < 8 {
return nil, 0, io.EOF
}
copy(f.Data[:], b)
return f, 8, nil
}
func (f *PathChallengeFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypePathChallenge))
b = append(b, f.Data[:]...)
return b, nil
}
// Length of a written frame
func (f *PathChallengeFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1 + 8
}
package wire
import (
"io"
"github.com/quic-go/quic-go/internal/protocol"
)
// A PathResponseFrame is a PATH_RESPONSE frame
type PathResponseFrame struct {
Data [8]byte
}
func parsePathResponseFrame(b []byte, _ protocol.Version) (*PathResponseFrame, int, error) {
f := &PathResponseFrame{}
if len(b) < 8 {
return nil, 0, io.EOF
}
copy(f.Data[:], b)
return f, 8, nil
}
func (f *PathResponseFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypePathResponse))
b = append(b, f.Data[:]...)
return b, nil
}
// Length of a written frame
func (f *PathResponseFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1 + 8
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
)
// A PingFrame is a PING frame
type PingFrame struct{}
func (f *PingFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
return append(b, byte(FrameTypePing)), nil
}
// Length of a written frame
func (f *PingFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1
}
package wire
import (
"sync"
"github.com/quic-go/quic-go/internal/protocol"
)
var pool sync.Pool
func init() {
pool.New = func() any {
return &StreamFrame{
Data: make([]byte, 0, protocol.MaxPacketBufferSize),
fromPool: true,
}
}
}
func GetStreamFrame() *StreamFrame {
f := pool.Get().(*StreamFrame)
return f
}
func putStreamFrame(f *StreamFrame) {
if !f.fromPool {
return
}
if protocol.ByteCount(cap(f.Data)) != protocol.MaxPacketBufferSize {
panic("wire.PutStreamFrame called with packet of wrong size!")
}
pool.Put(f)
}
package wire
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/quicvarint"
)
// A ResetStreamFrame is a RESET_STREAM or RESET_STREAM_AT frame in QUIC
type ResetStreamFrame struct {
StreamID protocol.StreamID
ErrorCode qerr.StreamErrorCode
FinalSize protocol.ByteCount
ReliableSize protocol.ByteCount
}
func parseResetStreamFrame(b []byte, isResetStreamAt bool, _ protocol.Version) (*ResetStreamFrame, int, error) {
startLen := len(b)
streamID, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
errorCode, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
finalSize, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
var reliableSize uint64
if isResetStreamAt {
reliableSize, l, err = quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
}
if reliableSize > finalSize {
return nil, 0, fmt.Errorf("RESET_STREAM_AT: reliable size can't be larger than final size (%d vs %d)", reliableSize, finalSize)
}
return &ResetStreamFrame{
StreamID: protocol.StreamID(streamID),
ErrorCode: qerr.StreamErrorCode(errorCode),
FinalSize: protocol.ByteCount(finalSize),
ReliableSize: protocol.ByteCount(reliableSize),
}, startLen - len(b), nil
}
func (f *ResetStreamFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
if f.ReliableSize == 0 {
b = quicvarint.Append(b, uint64(FrameTypeResetStream))
} else {
b = quicvarint.Append(b, uint64(FrameTypeResetStreamAt))
}
b = quicvarint.Append(b, uint64(f.StreamID))
b = quicvarint.Append(b, uint64(f.ErrorCode))
b = quicvarint.Append(b, uint64(f.FinalSize))
if f.ReliableSize > 0 {
b = quicvarint.Append(b, uint64(f.ReliableSize))
}
return b, nil
}
// Length of a written frame
func (f *ResetStreamFrame) Length(protocol.Version) protocol.ByteCount {
size := 1 // the frame type for both RESET_STREAM and RESET_STREAM_AT fits into 1 byte
if f.ReliableSize > 0 {
size += quicvarint.Len(uint64(f.ReliableSize))
}
return protocol.ByteCount(size + quicvarint.Len(uint64(f.StreamID)) + quicvarint.Len(uint64(f.ErrorCode)) + quicvarint.Len(uint64(f.FinalSize)))
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A RetireConnectionIDFrame is a RETIRE_CONNECTION_ID frame
type RetireConnectionIDFrame struct {
SequenceNumber uint64
}
func parseRetireConnectionIDFrame(b []byte, _ protocol.Version) (*RetireConnectionIDFrame, int, error) {
seq, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
return &RetireConnectionIDFrame{SequenceNumber: seq}, l, nil
}
func (f *RetireConnectionIDFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeRetireConnectionID))
b = quicvarint.Append(b, f.SequenceNumber)
return b, nil
}
// Length of a written frame
func (f *RetireConnectionIDFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(f.SequenceNumber))
}
package wire
import (
"errors"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
// ParseShortHeader parses a short header packet.
// It must be called after header protection was removed.
// Otherwise, the check for the reserved bits will (most likely) fail.
func ParseShortHeader(data []byte, connIDLen int) (length int, _ protocol.PacketNumber, _ protocol.PacketNumberLen, _ protocol.KeyPhaseBit, _ error) {
if len(data) == 0 {
return 0, 0, 0, 0, io.EOF
}
if data[0]&0x80 > 0 {
return 0, 0, 0, 0, errors.New("not a short header packet")
}
if data[0]&0x40 == 0 {
return 0, 0, 0, 0, errors.New("not a QUIC packet")
}
pnLen := protocol.PacketNumberLen(data[0]&0b11) + 1
if len(data) < 1+int(pnLen)+connIDLen {
return 0, 0, 0, 0, io.EOF
}
pos := 1 + connIDLen
pn, err := readPacketNumber(data[pos:], pnLen)
if err != nil {
return 0, 0, 0, 0, err
}
kp := protocol.KeyPhaseZero
if data[0]&0b100 > 0 {
kp = protocol.KeyPhaseOne
}
if data[0]&0x18 != 0 {
err = ErrInvalidReservedBits
}
return 1 + connIDLen + int(pnLen), pn, pnLen, kp, err
}
// AppendShortHeader writes a short header.
func AppendShortHeader(b []byte, connID protocol.ConnectionID, pn protocol.PacketNumber, pnLen protocol.PacketNumberLen, kp protocol.KeyPhaseBit) ([]byte, error) {
typeByte := 0x40 | uint8(pnLen-1)
if kp == protocol.KeyPhaseOne {
typeByte |= byte(1 << 2)
}
b = append(b, typeByte)
b = append(b, connID.Bytes()...)
return appendPacketNumber(b, pn, pnLen)
}
func ShortHeaderLen(dest protocol.ConnectionID, pnLen protocol.PacketNumberLen) protocol.ByteCount {
return 1 + protocol.ByteCount(dest.Len()) + protocol.ByteCount(pnLen)
}
func LogShortHeader(logger utils.Logger, dest protocol.ConnectionID, pn protocol.PacketNumber, pnLen protocol.PacketNumberLen, kp protocol.KeyPhaseBit) {
logger.Debugf("\tShort Header{DestConnectionID: %s, PacketNumber: %d, PacketNumberLen: %d, KeyPhase: %s}", dest, pn, pnLen, kp)
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/quicvarint"
)
// A StopSendingFrame is a STOP_SENDING frame
type StopSendingFrame struct {
StreamID protocol.StreamID
ErrorCode qerr.StreamErrorCode
}
// parseStopSendingFrame parses a STOP_SENDING frame
func parseStopSendingFrame(b []byte, _ protocol.Version) (*StopSendingFrame, int, error) {
startLen := len(b)
streamID, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
errorCode, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
return &StopSendingFrame{
StreamID: protocol.StreamID(streamID),
ErrorCode: qerr.StreamErrorCode(errorCode),
}, startLen - len(b), nil
}
// Length of a written frame
func (f *StopSendingFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.ErrorCode)))
}
func (f *StopSendingFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, byte(FrameTypeStopSending))
b = quicvarint.Append(b, uint64(f.StreamID))
b = quicvarint.Append(b, uint64(f.ErrorCode))
return b, nil
}
package wire
import (
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A StreamDataBlockedFrame is a STREAM_DATA_BLOCKED frame
type StreamDataBlockedFrame struct {
StreamID protocol.StreamID
MaximumStreamData protocol.ByteCount
}
func parseStreamDataBlockedFrame(b []byte, _ protocol.Version) (*StreamDataBlockedFrame, int, error) {
startLen := len(b)
sid, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
offset, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
return &StreamDataBlockedFrame{
StreamID: protocol.StreamID(sid),
MaximumStreamData: protocol.ByteCount(offset),
}, startLen - len(b) + l, nil
}
func (f *StreamDataBlockedFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
b = append(b, 0x15)
b = quicvarint.Append(b, uint64(f.StreamID))
b = quicvarint.Append(b, uint64(f.MaximumStreamData))
return b, nil
}
// Length of a written frame
func (f *StreamDataBlockedFrame) Length(protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID))+quicvarint.Len(uint64(f.MaximumStreamData)))
}
package wire
import (
"errors"
"io"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A StreamFrame of QUIC
type StreamFrame struct {
StreamID protocol.StreamID
Offset protocol.ByteCount
Data []byte
Fin bool
DataLenPresent bool
fromPool bool
}
func ParseStreamFrame(b []byte, typ FrameType, _ protocol.Version) (*StreamFrame, int, error) {
startLen := len(b)
hasOffset := typ&0b100 > 0
fin := typ&0b1 > 0
hasDataLen := typ&0b10 > 0
streamID, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
var offset uint64
if hasOffset {
offset, l, err = quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
}
var dataLen uint64
if hasDataLen {
var err error
var l int
dataLen, l, err = quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
b = b[l:]
if dataLen > uint64(len(b)) {
return nil, 0, io.EOF
}
} else {
// The rest of the packet is data
dataLen = uint64(len(b))
}
var frame *StreamFrame
if dataLen < protocol.MinStreamFrameBufferSize {
frame = &StreamFrame{}
if dataLen > 0 {
frame.Data = make([]byte, dataLen)
}
} else {
frame = GetStreamFrame()
// The STREAM frame can't be larger than the StreamFrame we obtained from the buffer,
// since those StreamFrames have a buffer length of the maximum packet size.
if dataLen > uint64(cap(frame.Data)) {
return nil, 0, io.EOF
}
frame.Data = frame.Data[:dataLen]
}
frame.StreamID = protocol.StreamID(streamID)
frame.Offset = protocol.ByteCount(offset)
frame.Fin = fin
frame.DataLenPresent = hasDataLen
if dataLen > 0 {
copy(frame.Data, b)
}
if frame.Offset+frame.DataLen() > protocol.MaxByteCount {
return nil, 0, errors.New("stream data overflows maximum offset")
}
return frame, startLen - len(b) + int(dataLen), nil
}
func (f *StreamFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
if len(f.Data) == 0 && !f.Fin {
return nil, errors.New("StreamFrame: attempting to write empty frame without FIN")
}
typ := byte(0x8)
if f.Fin {
typ ^= 0b1
}
hasOffset := f.Offset != 0
if f.DataLenPresent {
typ ^= 0b10
}
if hasOffset {
typ ^= 0b100
}
b = append(b, typ)
b = quicvarint.Append(b, uint64(f.StreamID))
if hasOffset {
b = quicvarint.Append(b, uint64(f.Offset))
}
if f.DataLenPresent {
b = quicvarint.Append(b, uint64(f.DataLen()))
}
b = append(b, f.Data...)
return b, nil
}
// Length returns the total length of the STREAM frame
func (f *StreamFrame) Length(protocol.Version) protocol.ByteCount {
length := 1 + quicvarint.Len(uint64(f.StreamID))
if f.Offset != 0 {
length += quicvarint.Len(uint64(f.Offset))
}
if f.DataLenPresent {
length += quicvarint.Len(uint64(f.DataLen()))
}
return protocol.ByteCount(length) + f.DataLen()
}
// DataLen gives the length of data in bytes
func (f *StreamFrame) DataLen() protocol.ByteCount {
return protocol.ByteCount(len(f.Data))
}
// MaxDataLen returns the maximum data length
// If 0 is returned, writing will fail (a STREAM frame must contain at least 1 byte of data).
func (f *StreamFrame) MaxDataLen(maxSize protocol.ByteCount, _ protocol.Version) protocol.ByteCount {
headerLen := 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamID)))
if f.Offset != 0 {
headerLen += protocol.ByteCount(quicvarint.Len(uint64(f.Offset)))
}
if f.DataLenPresent {
// Pretend that the data size will be 1 byte.
// If it turns out that varint encoding the length will consume 2 bytes, we need to adjust the data length afterward
headerLen++
}
if headerLen > maxSize {
return 0
}
maxDataLen := maxSize - headerLen
if f.DataLenPresent && quicvarint.Len(uint64(maxDataLen)) != 1 {
maxDataLen--
}
return maxDataLen
}
// MaybeSplitOffFrame splits a frame such that it is not bigger than n bytes.
// It returns if the frame was actually split.
// The frame might not be split if:
// * the size is large enough to fit the whole frame
// * the size is too small to fit even a 1-byte frame. In that case, the frame returned is nil.
func (f *StreamFrame) MaybeSplitOffFrame(maxSize protocol.ByteCount, version protocol.Version) (*StreamFrame, bool /* was splitting required */) {
if maxSize >= f.Length(version) {
return nil, false
}
n := f.MaxDataLen(maxSize, version)
if n == 0 {
return nil, true
}
new := GetStreamFrame()
new.StreamID = f.StreamID
new.Offset = f.Offset
new.Fin = false
new.DataLenPresent = f.DataLenPresent
// swap the data slices
new.Data, f.Data = f.Data, new.Data
new.fromPool, f.fromPool = f.fromPool, new.fromPool
f.Data = f.Data[:protocol.ByteCount(len(new.Data))-n]
copy(f.Data, new.Data[n:])
new.Data = new.Data[:n]
f.Offset += n
return new, true
}
func (f *StreamFrame) PutBack() {
putStreamFrame(f)
}
package wire
import (
"fmt"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/quicvarint"
)
// A StreamsBlockedFrame is a STREAMS_BLOCKED frame
type StreamsBlockedFrame struct {
Type protocol.StreamType
StreamLimit protocol.StreamNum
}
func parseStreamsBlockedFrame(b []byte, typ FrameType, _ protocol.Version) (*StreamsBlockedFrame, int, error) {
f := &StreamsBlockedFrame{}
//nolint:exhaustive // This will only be called with a BidiStreamBlockedFrameType or a UniStreamBlockedFrameType.
switch typ {
case FrameTypeBidiStreamBlocked:
f.Type = protocol.StreamTypeBidi
case FrameTypeUniStreamBlocked:
f.Type = protocol.StreamTypeUni
}
streamLimit, l, err := quicvarint.Parse(b)
if err != nil {
return nil, 0, replaceUnexpectedEOF(err)
}
f.StreamLimit = protocol.StreamNum(streamLimit)
if f.StreamLimit > protocol.MaxStreamCount {
return nil, 0, fmt.Errorf("%d exceeds the maximum stream count", f.StreamLimit)
}
return f, l, nil
}
func (f *StreamsBlockedFrame) Append(b []byte, _ protocol.Version) ([]byte, error) {
switch f.Type {
case protocol.StreamTypeBidi:
b = append(b, byte(FrameTypeBidiStreamBlocked))
case protocol.StreamTypeUni:
b = append(b, byte(FrameTypeUniStreamBlocked))
}
b = quicvarint.Append(b, uint64(f.StreamLimit))
return b, nil
}
// Length of a written frame
func (f *StreamsBlockedFrame) Length(_ protocol.Version) protocol.ByteCount {
return 1 + protocol.ByteCount(quicvarint.Len(uint64(f.StreamLimit)))
}
package wire
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net/netip"
"slices"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/quicvarint"
)
// AdditionalTransportParametersClient are additional transport parameters that will be added
// to the client's transport parameters.
// This is not intended for production use, but _only_ to increase the size of the ClientHello beyond
// the usual size of less than 1 MTU.
var AdditionalTransportParametersClient map[uint64][]byte
const transportParameterMarshalingVersion = 1
type transportParameterID uint64
const (
originalDestinationConnectionIDParameterID transportParameterID = 0x0
maxIdleTimeoutParameterID transportParameterID = 0x1
statelessResetTokenParameterID transportParameterID = 0x2
maxUDPPayloadSizeParameterID transportParameterID = 0x3
initialMaxDataParameterID transportParameterID = 0x4
initialMaxStreamDataBidiLocalParameterID transportParameterID = 0x5
initialMaxStreamDataBidiRemoteParameterID transportParameterID = 0x6
initialMaxStreamDataUniParameterID transportParameterID = 0x7
initialMaxStreamsBidiParameterID transportParameterID = 0x8
initialMaxStreamsUniParameterID transportParameterID = 0x9
ackDelayExponentParameterID transportParameterID = 0xa
maxAckDelayParameterID transportParameterID = 0xb
disableActiveMigrationParameterID transportParameterID = 0xc
preferredAddressParameterID transportParameterID = 0xd
activeConnectionIDLimitParameterID transportParameterID = 0xe
initialSourceConnectionIDParameterID transportParameterID = 0xf
retrySourceConnectionIDParameterID transportParameterID = 0x10
// RFC 9221
maxDatagramFrameSizeParameterID transportParameterID = 0x20
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/09/
resetStreamAtParameterID transportParameterID = 0x1d
// https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/07/
// When removing support for this codepoint, increment transportParameterMarshalingVersion
// to prevent 0-RTT resumption with tickets that remember it.
legacyResetStreamAtParameterID transportParameterID = 0x17f7586d2cb571
// https://datatracker.ietf.org/doc/draft-ietf-quic-ack-frequency/11/
minAckDelayParameterID transportParameterID = 0xff04de1b
)
// PreferredAddress is the value encoding in the preferred_address transport parameter
type PreferredAddress struct {
IPv4, IPv6 netip.AddrPort
ConnectionID protocol.ConnectionID
StatelessResetToken protocol.StatelessResetToken
}
// TransportParameters are parameters sent to the peer during the handshake
type TransportParameters struct {
InitialMaxStreamDataBidiLocal protocol.ByteCount
InitialMaxStreamDataBidiRemote protocol.ByteCount
InitialMaxStreamDataUni protocol.ByteCount
InitialMaxData protocol.ByteCount
MaxAckDelay time.Duration
AckDelayExponent uint8
DisableActiveMigration bool
MaxUDPPayloadSize protocol.ByteCount
MaxUniStreamNum protocol.StreamNum
MaxBidiStreamNum protocol.StreamNum
MaxIdleTimeout time.Duration
PreferredAddress *PreferredAddress
OriginalDestinationConnectionID protocol.ConnectionID
InitialSourceConnectionID protocol.ConnectionID
RetrySourceConnectionID *protocol.ConnectionID // use a pointer here to distinguish zero-length connection IDs from missing transport parameters
StatelessResetToken *protocol.StatelessResetToken
ActiveConnectionIDLimit uint64
MaxDatagramFrameSize protocol.ByteCount // RFC 9221
EnableResetStreamAt bool // https://datatracker.ietf.org/doc/draft-ietf-quic-reliable-stream-reset/09/
MinAckDelay *time.Duration
}
// Unmarshal the transport parameters
func (p *TransportParameters) Unmarshal(data []byte, sentBy protocol.Perspective) error {
if err := p.unmarshal(data, sentBy, false); err != nil {
return &qerr.TransportError{
ErrorCode: qerr.TransportParameterError,
ErrorMessage: err.Error(),
}
}
return nil
}
func (p *TransportParameters) unmarshal(b []byte, sentBy protocol.Perspective, fromSessionTicket bool) error {
// needed to check that every parameter is only sent at most once
parameterIDs := make([]transportParameterID, 0, 32)
var (
readOriginalDestinationConnectionID bool
readInitialSourceConnectionID bool
)
p.AckDelayExponent = protocol.DefaultAckDelayExponent
p.MaxAckDelay = protocol.DefaultMaxAckDelay
p.MaxDatagramFrameSize = protocol.InvalidByteCount
p.ActiveConnectionIDLimit = protocol.DefaultActiveConnectionIDLimit
for len(b) > 0 {
paramIDInt, l, err := quicvarint.Parse(b)
if err != nil {
return err
}
paramID := transportParameterID(paramIDInt)
b = b[l:]
paramLen, l, err := quicvarint.Parse(b)
if err != nil {
return err
}
b = b[l:]
if uint64(len(b)) < paramLen {
return fmt.Errorf("remaining length (%d) smaller than parameter length (%d)", len(b), paramLen)
}
parameterIDs = append(parameterIDs, paramID)
switch paramID {
case maxIdleTimeoutParameterID,
maxUDPPayloadSizeParameterID,
initialMaxDataParameterID,
initialMaxStreamDataBidiLocalParameterID,
initialMaxStreamDataBidiRemoteParameterID,
initialMaxStreamDataUniParameterID,
initialMaxStreamsBidiParameterID,
initialMaxStreamsUniParameterID,
maxAckDelayParameterID,
maxDatagramFrameSizeParameterID,
ackDelayExponentParameterID,
activeConnectionIDLimitParameterID,
minAckDelayParameterID:
if err := p.readNumericTransportParameter(b, paramID, int(paramLen)); err != nil {
return err
}
b = b[paramLen:]
case preferredAddressParameterID:
if sentBy == protocol.PerspectiveClient {
return errors.New("client sent a preferred_address")
}
if err := p.readPreferredAddress(b, int(paramLen)); err != nil {
return err
}
b = b[paramLen:]
case disableActiveMigrationParameterID:
if paramLen != 0 {
return fmt.Errorf("wrong length for disable_active_migration: %d (expected empty)", paramLen)
}
p.DisableActiveMigration = true
case statelessResetTokenParameterID:
if sentBy == protocol.PerspectiveClient {
return errors.New("client sent a stateless_reset_token")
}
if paramLen != 16 {
return fmt.Errorf("wrong length for stateless_reset_token: %d (expected 16)", paramLen)
}
var token protocol.StatelessResetToken
if len(b) < len(token) {
return io.EOF
}
copy(token[:], b)
b = b[len(token):]
p.StatelessResetToken = &token
case originalDestinationConnectionIDParameterID:
if sentBy == protocol.PerspectiveClient {
return errors.New("client sent an original_destination_connection_id")
}
if paramLen > protocol.MaxConnIDLen {
return protocol.ErrInvalidConnectionIDLen
}
p.OriginalDestinationConnectionID = protocol.ParseConnectionID(b[:paramLen])
b = b[paramLen:]
readOriginalDestinationConnectionID = true
case initialSourceConnectionIDParameterID:
if paramLen > protocol.MaxConnIDLen {
return protocol.ErrInvalidConnectionIDLen
}
p.InitialSourceConnectionID = protocol.ParseConnectionID(b[:paramLen])
b = b[paramLen:]
readInitialSourceConnectionID = true
case retrySourceConnectionIDParameterID:
if sentBy == protocol.PerspectiveClient {
return errors.New("client sent a retry_source_connection_id")
}
if paramLen > protocol.MaxConnIDLen {
return protocol.ErrInvalidConnectionIDLen
}
connID := protocol.ParseConnectionID(b[:paramLen])
b = b[paramLen:]
p.RetrySourceConnectionID = &connID
case resetStreamAtParameterID, legacyResetStreamAtParameterID:
if paramLen != 0 {
return fmt.Errorf("wrong length for reset_stream_at: %d (expected empty)", paramLen)
}
p.EnableResetStreamAt = true
default:
if fromSessionTicket {
// A ticket might contain a parameter for an extension supported by an older
// version of this endpoint. If we can't parse it, don't resume with it.
return fmt.Errorf("unknown transport parameter %#x in session ticket", paramID)
}
b = b[paramLen:]
}
}
// min_ack_delay must be less or equal to max_ack_delay
if p.MinAckDelay != nil && *p.MinAckDelay > p.MaxAckDelay {
return fmt.Errorf("min_ack_delay (%s) is greater than max_ack_delay (%s)", *p.MinAckDelay, p.MaxAckDelay)
}
if !fromSessionTicket {
if sentBy == protocol.PerspectiveServer && !readOriginalDestinationConnectionID {
return errors.New("missing original_destination_connection_id")
}
if p.MaxUDPPayloadSize == 0 {
p.MaxUDPPayloadSize = protocol.MaxByteCount
}
if !readInitialSourceConnectionID {
return errors.New("missing initial_source_connection_id")
}
}
// check that every transport parameter was sent at most once
slices.Sort(parameterIDs)
for i := range len(parameterIDs) - 1 {
if parameterIDs[i] == parameterIDs[i+1] {
return fmt.Errorf("received duplicate transport parameter %#x", parameterIDs[i])
}
}
return nil
}
func (p *TransportParameters) readPreferredAddress(b []byte, expectedLen int) error {
remainingLen := len(b)
pa := &PreferredAddress{}
if len(b) < 4+2+16+2+1 {
return io.EOF
}
var ipv4 [4]byte
copy(ipv4[:], b[:4])
port4 := binary.BigEndian.Uint16(b[4:])
b = b[4+2:]
if port4 != 0 && ipv4 != [4]byte{} {
pa.IPv4 = netip.AddrPortFrom(netip.AddrFrom4(ipv4), port4)
}
var ipv6 [16]byte
copy(ipv6[:], b[:16])
port6 := binary.BigEndian.Uint16(b[16:])
if port6 != 0 && ipv6 != [16]byte{} {
pa.IPv6 = netip.AddrPortFrom(netip.AddrFrom16(ipv6), port6)
}
b = b[16+2:]
connIDLen := int(b[0])
b = b[1:]
if connIDLen == 0 || connIDLen > protocol.MaxConnIDLen {
return fmt.Errorf("invalid connection ID length: %d", connIDLen)
}
if len(b) < connIDLen+len(pa.StatelessResetToken) {
return io.EOF
}
pa.ConnectionID = protocol.ParseConnectionID(b[:connIDLen])
b = b[connIDLen:]
copy(pa.StatelessResetToken[:], b)
b = b[len(pa.StatelessResetToken):]
if bytesRead := remainingLen - len(b); bytesRead != expectedLen {
return fmt.Errorf("expected preferred_address to be %d long, read %d bytes", expectedLen, bytesRead)
}
p.PreferredAddress = pa
return nil
}
func (p *TransportParameters) readNumericTransportParameter(b []byte, paramID transportParameterID, expectedLen int) error {
val, l, err := quicvarint.Parse(b)
if err != nil {
return fmt.Errorf("error while reading transport parameter %d: %s", paramID, err)
}
if l != expectedLen {
return fmt.Errorf("inconsistent transport parameter length for transport parameter %#x", paramID)
}
//nolint:exhaustive // This only covers the numeric transport parameters.
switch paramID {
case initialMaxStreamDataBidiLocalParameterID:
p.InitialMaxStreamDataBidiLocal = protocol.ByteCount(val)
case initialMaxStreamDataBidiRemoteParameterID:
p.InitialMaxStreamDataBidiRemote = protocol.ByteCount(val)
case initialMaxStreamDataUniParameterID:
p.InitialMaxStreamDataUni = protocol.ByteCount(val)
case initialMaxDataParameterID:
p.InitialMaxData = protocol.ByteCount(val)
case initialMaxStreamsBidiParameterID:
p.MaxBidiStreamNum = protocol.StreamNum(val)
if p.MaxBidiStreamNum > protocol.MaxStreamCount {
return fmt.Errorf("initial_max_streams_bidi too large: %d (maximum %d)", p.MaxBidiStreamNum, protocol.MaxStreamCount)
}
case initialMaxStreamsUniParameterID:
p.MaxUniStreamNum = protocol.StreamNum(val)
if p.MaxUniStreamNum > protocol.MaxStreamCount {
return fmt.Errorf("initial_max_streams_uni too large: %d (maximum %d)", p.MaxUniStreamNum, protocol.MaxStreamCount)
}
case maxIdleTimeoutParameterID:
p.MaxIdleTimeout = max(protocol.MinRemoteIdleTimeout, time.Duration(val)*time.Millisecond)
case maxUDPPayloadSizeParameterID:
if val < 1200 {
return fmt.Errorf("invalid value for max_udp_payload_size: %d (minimum 1200)", val)
}
p.MaxUDPPayloadSize = protocol.ByteCount(val)
case ackDelayExponentParameterID:
if val > protocol.MaxAckDelayExponent {
return fmt.Errorf("invalid value for ack_delay_exponent: %d (maximum %d)", val, protocol.MaxAckDelayExponent)
}
p.AckDelayExponent = uint8(val)
case maxAckDelayParameterID:
if val > uint64(protocol.MaxMaxAckDelay/time.Millisecond) {
return fmt.Errorf("invalid value for max_ack_delay: %dms (maximum %dms)", val, protocol.MaxMaxAckDelay/time.Millisecond)
}
p.MaxAckDelay = time.Duration(val) * time.Millisecond
case activeConnectionIDLimitParameterID:
if val < 2 {
return fmt.Errorf("invalid value for active_connection_id_limit: %d (minimum 2)", val)
}
p.ActiveConnectionIDLimit = val
case maxDatagramFrameSizeParameterID:
p.MaxDatagramFrameSize = protocol.ByteCount(val)
case minAckDelayParameterID:
mad := time.Duration(val) * time.Microsecond
if mad < 0 {
mad = math.MaxInt64
}
p.MinAckDelay = &mad
default:
return fmt.Errorf("TransportParameter BUG: transport parameter %d not found", paramID)
}
return nil
}
// Marshal the transport parameters
func (p *TransportParameters) Marshal(pers protocol.Perspective) []byte {
// Typical Transport Parameters consume around 110 bytes, depending on the exact values,
// especially the lengths of the Connection IDs.
// Allocate 256 bytes, so we won't have to grow the slice in any case.
b := make([]byte, 0, 256)
// add a greased value
random := make([]byte, 18)
rand.Read(random)
b = quicvarint.Append(b, 27+31*uint64(random[0]))
length := random[1] % 16
b = quicvarint.Append(b, uint64(length))
b = append(b, random[2:2+length]...)
// initial_max_stream_data_bidi_local
b = p.marshalVarintParam(b, initialMaxStreamDataBidiLocalParameterID, uint64(p.InitialMaxStreamDataBidiLocal))
// initial_max_stream_data_bidi_remote
b = p.marshalVarintParam(b, initialMaxStreamDataBidiRemoteParameterID, uint64(p.InitialMaxStreamDataBidiRemote))
// initial_max_stream_data_uni
b = p.marshalVarintParam(b, initialMaxStreamDataUniParameterID, uint64(p.InitialMaxStreamDataUni))
// initial_max_data
b = p.marshalVarintParam(b, initialMaxDataParameterID, uint64(p.InitialMaxData))
// initial_max_bidi_streams
b = p.marshalVarintParam(b, initialMaxStreamsBidiParameterID, uint64(p.MaxBidiStreamNum))
// initial_max_uni_streams
b = p.marshalVarintParam(b, initialMaxStreamsUniParameterID, uint64(p.MaxUniStreamNum))
// idle_timeout
b = p.marshalVarintParam(b, maxIdleTimeoutParameterID, uint64(p.MaxIdleTimeout/time.Millisecond))
// max_udp_payload_size
if p.MaxUDPPayloadSize > 0 {
b = p.marshalVarintParam(b, maxUDPPayloadSizeParameterID, uint64(p.MaxUDPPayloadSize))
}
// max_ack_delay
// Only send it if is different from the default value.
if p.MaxAckDelay != protocol.DefaultMaxAckDelay {
b = p.marshalVarintParam(b, maxAckDelayParameterID, uint64(p.MaxAckDelay/time.Millisecond))
}
// ack_delay_exponent
// Only send it if is different from the default value.
if p.AckDelayExponent != protocol.DefaultAckDelayExponent {
b = p.marshalVarintParam(b, ackDelayExponentParameterID, uint64(p.AckDelayExponent))
}
// disable_active_migration
if p.DisableActiveMigration {
b = quicvarint.Append(b, uint64(disableActiveMigrationParameterID))
b = quicvarint.Append(b, 0)
}
if pers == protocol.PerspectiveServer {
// stateless_reset_token
if p.StatelessResetToken != nil {
b = quicvarint.Append(b, uint64(statelessResetTokenParameterID))
b = quicvarint.Append(b, 16)
b = append(b, p.StatelessResetToken[:]...)
}
// original_destination_connection_id
b = quicvarint.Append(b, uint64(originalDestinationConnectionIDParameterID))
b = quicvarint.Append(b, uint64(p.OriginalDestinationConnectionID.Len()))
b = append(b, p.OriginalDestinationConnectionID.Bytes()...)
// preferred_address
if p.PreferredAddress != nil {
b = quicvarint.Append(b, uint64(preferredAddressParameterID))
b = quicvarint.Append(b, 4+2+16+2+1+uint64(p.PreferredAddress.ConnectionID.Len())+16)
if p.PreferredAddress.IPv4.IsValid() {
ipv4 := p.PreferredAddress.IPv4.Addr().As4()
b = append(b, ipv4[:]...)
b = binary.BigEndian.AppendUint16(b, p.PreferredAddress.IPv4.Port())
} else {
b = append(b, make([]byte, 6)...)
}
if p.PreferredAddress.IPv6.IsValid() {
ipv6 := p.PreferredAddress.IPv6.Addr().As16()
b = append(b, ipv6[:]...)
b = binary.BigEndian.AppendUint16(b, p.PreferredAddress.IPv6.Port())
} else {
b = append(b, make([]byte, 18)...)
}
b = append(b, uint8(p.PreferredAddress.ConnectionID.Len()))
b = append(b, p.PreferredAddress.ConnectionID.Bytes()...)
b = append(b, p.PreferredAddress.StatelessResetToken[:]...)
}
}
// active_connection_id_limit
if p.ActiveConnectionIDLimit != protocol.DefaultActiveConnectionIDLimit {
b = p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
}
// initial_source_connection_id
b = quicvarint.Append(b, uint64(initialSourceConnectionIDParameterID))
b = quicvarint.Append(b, uint64(p.InitialSourceConnectionID.Len()))
b = append(b, p.InitialSourceConnectionID.Bytes()...)
// retry_source_connection_id
if pers == protocol.PerspectiveServer && p.RetrySourceConnectionID != nil {
b = quicvarint.Append(b, uint64(retrySourceConnectionIDParameterID))
b = quicvarint.Append(b, uint64(p.RetrySourceConnectionID.Len()))
b = append(b, p.RetrySourceConnectionID.Bytes()...)
}
// QUIC datagrams
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
b = p.marshalVarintParam(b, maxDatagramFrameSizeParameterID, uint64(p.MaxDatagramFrameSize))
}
// QUIC Stream Resets with Partial Delivery
if p.EnableResetStreamAt {
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 0)
}
if p.MinAckDelay != nil {
b = p.marshalVarintParam(b, minAckDelayParameterID, uint64(*p.MinAckDelay/time.Microsecond))
}
if pers == protocol.PerspectiveClient && len(AdditionalTransportParametersClient) > 0 {
for k, v := range AdditionalTransportParametersClient {
b = quicvarint.Append(b, k)
b = quicvarint.Append(b, uint64(len(v)))
b = append(b, v...)
}
}
return b
}
func (p *TransportParameters) marshalVarintParam(b []byte, id transportParameterID, val uint64) []byte {
b = quicvarint.Append(b, uint64(id))
b = quicvarint.Append(b, uint64(quicvarint.Len(val)))
return quicvarint.Append(b, val)
}
// MarshalForSessionTicket marshals the transport parameters we save in the session ticket.
// When sending a 0-RTT enabled TLS session tickets, we need to save the transport parameters.
// The client will remember the transport parameters used in the last session,
// and apply those to the 0-RTT data it sends.
// Saving the transport parameters in the ticket gives the server the option to reject 0-RTT
// if the transport parameters changed.
// Since the session ticket is encrypted, the serialization format is defined by the server.
// For convenience, we use the same format that we also use for sending the transport parameters.
func (p *TransportParameters) MarshalForSessionTicket(b []byte) []byte {
b = quicvarint.Append(b, transportParameterMarshalingVersion)
// initial_max_stream_data_bidi_local
b = p.marshalVarintParam(b, initialMaxStreamDataBidiLocalParameterID, uint64(p.InitialMaxStreamDataBidiLocal))
// initial_max_stream_data_bidi_remote
b = p.marshalVarintParam(b, initialMaxStreamDataBidiRemoteParameterID, uint64(p.InitialMaxStreamDataBidiRemote))
// initial_max_stream_data_uni
b = p.marshalVarintParam(b, initialMaxStreamDataUniParameterID, uint64(p.InitialMaxStreamDataUni))
// initial_max_data
b = p.marshalVarintParam(b, initialMaxDataParameterID, uint64(p.InitialMaxData))
// initial_max_bidi_streams
b = p.marshalVarintParam(b, initialMaxStreamsBidiParameterID, uint64(p.MaxBidiStreamNum))
// initial_max_uni_streams
b = p.marshalVarintParam(b, initialMaxStreamsUniParameterID, uint64(p.MaxUniStreamNum))
// active_connection_id_limit
b = p.marshalVarintParam(b, activeConnectionIDLimitParameterID, p.ActiveConnectionIDLimit)
// max_datagram_frame_size
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
b = p.marshalVarintParam(b, maxDatagramFrameSizeParameterID, uint64(p.MaxDatagramFrameSize))
}
// reset_stream_at
if p.EnableResetStreamAt {
b = quicvarint.Append(b, uint64(resetStreamAtParameterID))
b = quicvarint.Append(b, 0)
}
return b
}
// UnmarshalFromSessionTicket unmarshals transport parameters from a session ticket.
func (p *TransportParameters) UnmarshalFromSessionTicket(b []byte) error {
version, l, err := quicvarint.Parse(b)
if err != nil {
return err
}
if version != transportParameterMarshalingVersion {
return fmt.Errorf("unknown transport parameter marshaling version: %d", version)
}
return p.unmarshal(b[l:], protocol.PerspectiveServer, true)
}
// ValidFor0RTT checks if the transport parameters match those saved in the session ticket.
func (p *TransportParameters) ValidFor0RTT(saved *TransportParameters) bool {
if saved.MaxDatagramFrameSize != protocol.InvalidByteCount && (p.MaxDatagramFrameSize == protocol.InvalidByteCount || p.MaxDatagramFrameSize < saved.MaxDatagramFrameSize) {
return false
}
if saved.EnableResetStreamAt && !p.EnableResetStreamAt {
return false
}
return p.InitialMaxStreamDataBidiLocal >= saved.InitialMaxStreamDataBidiLocal &&
p.InitialMaxStreamDataBidiRemote >= saved.InitialMaxStreamDataBidiRemote &&
p.InitialMaxStreamDataUni >= saved.InitialMaxStreamDataUni &&
p.InitialMaxData >= saved.InitialMaxData &&
p.MaxBidiStreamNum >= saved.MaxBidiStreamNum &&
p.MaxUniStreamNum >= saved.MaxUniStreamNum &&
p.ActiveConnectionIDLimit == saved.ActiveConnectionIDLimit
}
// ValidForUpdate checks that the new transport parameters don't reduce limits after resuming a 0-RTT connection.
// It is only used on the client side.
func (p *TransportParameters) ValidForUpdate(saved *TransportParameters) bool {
if saved.MaxDatagramFrameSize != protocol.InvalidByteCount && (p.MaxDatagramFrameSize == protocol.InvalidByteCount || p.MaxDatagramFrameSize < saved.MaxDatagramFrameSize) {
return false
}
if saved.EnableResetStreamAt && !p.EnableResetStreamAt {
return false
}
return p.ActiveConnectionIDLimit >= saved.ActiveConnectionIDLimit &&
p.InitialMaxData >= saved.InitialMaxData &&
p.InitialMaxStreamDataBidiLocal >= saved.InitialMaxStreamDataBidiLocal &&
p.InitialMaxStreamDataBidiRemote >= saved.InitialMaxStreamDataBidiRemote &&
p.InitialMaxStreamDataUni >= saved.InitialMaxStreamDataUni &&
p.MaxBidiStreamNum >= saved.MaxBidiStreamNum &&
p.MaxUniStreamNum >= saved.MaxUniStreamNum
}
// String returns a string representation, intended for logging.
func (p *TransportParameters) String() string {
logString := "&wire.TransportParameters{OriginalDestinationConnectionID: %s, InitialSourceConnectionID: %s, "
logParams := []any{p.OriginalDestinationConnectionID, p.InitialSourceConnectionID}
if p.RetrySourceConnectionID != nil {
logString += "RetrySourceConnectionID: %s, "
logParams = append(logParams, p.RetrySourceConnectionID)
}
logString += "InitialMaxStreamDataBidiLocal: %d, InitialMaxStreamDataBidiRemote: %d, InitialMaxStreamDataUni: %d, InitialMaxData: %d, MaxBidiStreamNum: %d, MaxUniStreamNum: %d, MaxIdleTimeout: %s, AckDelayExponent: %d, MaxAckDelay: %s, ActiveConnectionIDLimit: %d"
logParams = append(logParams, []any{p.InitialMaxStreamDataBidiLocal, p.InitialMaxStreamDataBidiRemote, p.InitialMaxStreamDataUni, p.InitialMaxData, p.MaxBidiStreamNum, p.MaxUniStreamNum, p.MaxIdleTimeout, p.AckDelayExponent, p.MaxAckDelay, p.ActiveConnectionIDLimit}...)
if p.StatelessResetToken != nil { // the client never sends a stateless reset token
logString += ", StatelessResetToken: %#x"
logParams = append(logParams, *p.StatelessResetToken)
}
if p.MaxDatagramFrameSize != protocol.InvalidByteCount {
logString += ", MaxDatagramFrameSize: %d"
logParams = append(logParams, p.MaxDatagramFrameSize)
}
logString += ", EnableResetStreamAt: %t"
logParams = append(logParams, p.EnableResetStreamAt)
if p.MinAckDelay != nil {
logString += ", MinAckDelay: %s"
logParams = append(logParams, *p.MinAckDelay)
}
logString += "}"
return fmt.Sprintf(logString, logParams...)
}
package wire
import (
"crypto/rand"
"encoding/binary"
"errors"
"github.com/quic-go/quic-go/internal/protocol"
)
// ParseVersionNegotiationPacket parses a Version Negotiation packet.
func ParseVersionNegotiationPacket(b []byte) (dest, src protocol.ArbitraryLenConnectionID, _ []protocol.Version, _ error) {
n, dest, src, err := ParseArbitraryLenConnectionIDs(b)
if err != nil {
return nil, nil, nil, err
}
b = b[n:]
if len(b) == 0 {
//nolint:staticcheck // SA1021: the packet is called Version Negotiation packet
return nil, nil, nil, errors.New("Version Negotiation packet has empty version list")
}
if len(b)%4 != 0 {
//nolint:staticcheck // SA1021: the packet is called Version Negotiation packet
return nil, nil, nil, errors.New("Version Negotiation packet has a version list with an invalid length")
}
versions := make([]protocol.Version, len(b)/4)
for i := 0; len(b) > 0; i++ {
versions[i] = protocol.Version(binary.BigEndian.Uint32(b[:4]))
b = b[4:]
}
return dest, src, versions, nil
}
// ComposeVersionNegotiation composes a Version Negotiation
func ComposeVersionNegotiation(destConnID, srcConnID protocol.ArbitraryLenConnectionID, versions []protocol.Version) []byte {
greasedVersions := protocol.GetGreasedVersions(versions)
expectedLen := 1 /* type byte */ + 4 /* version field */ + 1 /* dest connection ID length field */ + destConnID.Len() + 1 /* src connection ID length field */ + srcConnID.Len() + len(greasedVersions)*4
buf := make([]byte, 1+4 /* type byte and version field */, expectedLen)
_, _ = rand.Read(buf[:1]) // ignore the error here. It is not critical to have perfect random here.
// Setting the "QUIC bit" (0x40) is not required by the RFC,
// but it allows clients to demultiplex QUIC with a long list of other protocols.
// See RFC 9443 and https://mailarchive.ietf.org/arch/msg/quic/oR4kxGKY6mjtPC1CZegY1ED4beg/ for details.
buf[0] |= 0xc0
// The next 4 bytes are left at 0 (version number).
buf = append(buf, uint8(destConnID.Len()))
buf = append(buf, destConnID.Bytes()...)
buf = append(buf, uint8(srcConnID.Len()))
buf = append(buf, srcConnID.Bytes()...)
for _, v := range greasedVersions {
buf = binary.BigEndian.AppendUint32(buf, uint32(v))
}
return buf
}
package quic
import (
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
type mtuDiscoverer interface {
// Start starts the MTU discovery process.
// It's unnecessary to call ShouldSendProbe before that.
Start(now monotime.Time)
ShouldSendProbe(now monotime.Time) bool
CurrentSize() protocol.ByteCount
GetPing(now monotime.Time) (ping ackhandler.Frame, datagramSize protocol.ByteCount)
Reset(now monotime.Time, start, max protocol.ByteCount)
}
const (
// At some point, we have to stop searching for a higher MTU.
// We're happy to send a packet that's 10 bytes smaller than the actual MTU.
maxMTUDiff protocol.ByteCount = 20
// send a probe packet every mtuProbeDelay RTTs
mtuProbeDelay = 5
// Once maxLostMTUProbes MTU probe packets larger than a certain size are lost,
// MTU discovery won't probe for larger MTUs than this size.
// The algorithm used here is resilient to packet loss of (maxLostMTUProbes - 1) packets.
maxLostMTUProbes = 3
)
// The Path MTU is found by sending a larger packet every now and then.
// If the packet is acknowledged, we conclude that the path supports this larger packet size.
// If the packet is lost, this can mean one of two things:
// 1. The path doesn't support this larger packet size, or
// 2. The packet was lost due to packet loss, independent of its size.
// The algorithm used here is resilient to packet loss of (maxLostMTUProbes - 1) packets.
// For simplicty, the following example use maxLostMTUProbes = 2.
//
// Initialization:
// |------------------------------------------------------------------------------|
// min max
//
// The first MTU probe packet will have size (min+max)/2.
// Assume that this packet is acknowledged. We can now move the min marker,
// and continue the search in the resulting interval.
//
// If 1st probe packet acknowledged:
// |---------------------------------------|--------------------------------------|
// min max
//
// If 1st probe packet lost:
// |---------------------------------------|--------------------------------------|
// min lost[0] max
//
// We can't conclude that the path doesn't support this packet size, since the loss of the probe
// packet could have been unrelated to the packet size. A larger probe packet will be sent later on.
// After a loss, the next probe packet has size (min+lost[0])/2.
// Now assume this probe packet is acknowledged:
//
// 2nd probe packet acknowledged:
// |------------------|--------------------|--------------------------------------|
// min lost[0] max
//
// First of all, we conclude that the path supports at least this MTU. That's progress!
// Second, we probe a bit more aggressively with the next probe packet:
// After an acknowledgement, the next probe packet has size (min+max)/2.
// This means we'll send a packet larger than the first probe packet (which was lost).
//
// If 3rd probe packet acknowledged:
// |-------------------------------------------------|----------------------------|
// min max
//
// We can conclude that the loss of the 1st probe packet was not due to its size, and
// continue searching in a much smaller interval now.
//
// If 3rd probe packet lost:
// |------------------|--------------------|---------|----------------------------|
// min lost[0] max
//
// Since in our example numPTOProbes = 2, and we lost 2 packets smaller than max, we
// conclude that this packet size is not supported on the path, and reduce the maximum
// value of the search interval.
//
// MTU discovery concludes once the interval min and max has been narrowed down to maxMTUDiff.
type mtuFinder struct {
lastProbeTime monotime.Time
rttStats *utils.RTTStats
inFlight protocol.ByteCount // the size of the probe packet currently in flight. InvalidByteCount if none is in flight
min protocol.ByteCount
// on initialization, we treat the maximum size as the first "lost" packet
lost [maxLostMTUProbes]protocol.ByteCount
lastProbeWasLost bool
// The generation is used to ignore ACKs / losses for probe packets sent before a reset.
// Resets happen when the connection is migrated to a new path.
// We're therefore not concerned about overflows of this counter.
generation uint8
qlogger qlogwriter.Recorder
}
var _ mtuDiscoverer = &mtuFinder{}
func newMTUDiscoverer(
rttStats *utils.RTTStats,
start, max protocol.ByteCount,
qlogger qlogwriter.Recorder,
) *mtuFinder {
f := &mtuFinder{
inFlight: protocol.InvalidByteCount,
rttStats: rttStats,
qlogger: qlogger,
}
f.init(start, max)
return f
}
func (f *mtuFinder) init(start, max protocol.ByteCount) {
f.min = start
for i := range f.lost {
if i == 0 {
f.lost[i] = max
continue
}
f.lost[i] = protocol.InvalidByteCount
}
}
func (f *mtuFinder) done() bool {
return f.max()-f.min <= maxMTUDiff+1
}
func (f *mtuFinder) max() protocol.ByteCount {
for i, v := range f.lost {
if v == protocol.InvalidByteCount {
return f.lost[i-1]
}
}
return f.lost[len(f.lost)-1]
}
func (f *mtuFinder) Start(now monotime.Time) {
f.lastProbeTime = now // makes sure the first probe packet is not sent immediately
}
func (f *mtuFinder) ShouldSendProbe(now monotime.Time) bool {
if f.lastProbeTime.IsZero() {
return false
}
if f.inFlight != protocol.InvalidByteCount || f.done() {
return false
}
return !now.Before(f.lastProbeTime.Add(mtuProbeDelay * f.rttStats.SmoothedRTT()))
}
func (f *mtuFinder) GetPing(now monotime.Time) (ackhandler.Frame, protocol.ByteCount) {
var size protocol.ByteCount
if f.lastProbeWasLost {
size = (f.min + f.lost[0]) / 2
} else {
size = (f.min + f.max()) / 2
}
f.lastProbeTime = now
f.inFlight = size
return ackhandler.Frame{
Frame: &wire.PingFrame{},
Handler: &mtuFinderAckHandler{mtuFinder: f, generation: f.generation},
}, size
}
func (f *mtuFinder) CurrentSize() protocol.ByteCount {
return f.min
}
func (f *mtuFinder) Reset(now monotime.Time, start, max protocol.ByteCount) {
f.generation++
f.lastProbeTime = now
f.lastProbeWasLost = false
f.inFlight = protocol.InvalidByteCount
f.init(start, max)
}
type mtuFinderAckHandler struct {
*mtuFinder
generation uint8
}
var _ ackhandler.FrameHandler = &mtuFinderAckHandler{}
func (h *mtuFinderAckHandler) OnAcked(wire.Frame) {
if h.generation != h.mtuFinder.generation {
// ACK for probe sent before reset
return
}
size := h.inFlight
if size == protocol.InvalidByteCount {
panic("OnAcked callback called although there's no MTU probe packet in flight")
}
h.inFlight = protocol.InvalidByteCount
h.min = size
h.lastProbeWasLost = false
// remove all values smaller than size from the lost array
var j int
for i, v := range h.lost {
if size < v {
j = i
break
}
}
if j > 0 {
for i := range len(h.lost) {
if i+j < len(h.lost) {
h.lost[i] = h.lost[i+j]
} else {
h.lost[i] = protocol.InvalidByteCount
}
}
}
if h.qlogger != nil {
h.qlogger.RecordEvent(qlog.MTUUpdated{
Value: int(size),
Done: h.done(),
})
}
}
func (h *mtuFinderAckHandler) OnLost(wire.Frame) {
if h.generation != h.mtuFinder.generation {
// probe sent before reset received
return
}
size := h.inFlight
if size == protocol.InvalidByteCount {
panic("OnLost callback called although there's no MTU probe packet in flight")
}
h.lastProbeWasLost = true
h.inFlight = protocol.InvalidByteCount
for i, v := range h.lost {
if size < v {
copy(h.lost[i+1:], h.lost[i:])
h.lost[i] = size
break
}
}
}
package quic
import (
crand "crypto/rand"
"encoding/binary"
"errors"
"fmt"
"math/rand/v2"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
var errNothingToPack = errors.New("nothing to pack")
type packer interface {
PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*coalescedPacket, error)
PackAckOnlyPacket(maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error)
AppendPacket(_ *packetBuffer, maxPacketSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, error)
PackPTOProbePacket(_ protocol.EncryptionLevel, _ protocol.ByteCount, addPingIfEmpty bool, now monotime.Time, v protocol.Version) (*coalescedPacket, error)
PackConnectionClose(*qerr.TransportError, protocol.ByteCount, protocol.Version) (*coalescedPacket, error)
PackApplicationClose(*qerr.ApplicationError, protocol.ByteCount, protocol.Version) (*coalescedPacket, error)
PackPathProbePacket(protocol.ConnectionID, []ackhandler.Frame, protocol.Version) (shortHeaderPacket, *packetBuffer, error)
PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, v protocol.Version) (shortHeaderPacket, *packetBuffer, error)
SetToken([]byte)
}
type sealer interface {
handshake.LongHeaderSealer
}
type payload struct {
streamFrames []ackhandler.StreamFrame
frames []ackhandler.Frame
ack *wire.AckFrame
length protocol.ByteCount
}
type longHeaderPacket struct {
header *wire.ExtendedHeader
ack *wire.AckFrame
frames []ackhandler.Frame
streamFrames []ackhandler.StreamFrame // only used for 0-RTT packets
length protocol.ByteCount
}
type shortHeaderPacket struct {
PacketNumber protocol.PacketNumber
Frames []ackhandler.Frame
StreamFrames []ackhandler.StreamFrame
Ack *wire.AckFrame
Length protocol.ByteCount
IsPathMTUProbePacket bool
IsPathProbePacket bool
// used for logging
DestConnID protocol.ConnectionID
PacketNumberLen protocol.PacketNumberLen
KeyPhase protocol.KeyPhaseBit
}
func (p *shortHeaderPacket) IsAckEliciting() bool { return ackhandler.HasAckElicitingFrames(p.Frames) }
type coalescedPacket struct {
buffer *packetBuffer
longHdrPackets []*longHeaderPacket
shortHdrPacket *shortHeaderPacket
}
// IsOnlyShortHeaderPacket says if this packet only contains a short header packet (and no long header packets).
func (p *coalescedPacket) IsOnlyShortHeaderPacket() bool {
return len(p.longHdrPackets) == 0 && p.shortHdrPacket != nil
}
func (p *longHeaderPacket) EncryptionLevel() protocol.EncryptionLevel {
//nolint:exhaustive // Will never be called for Retry packets (and they don't have encrypted data).
switch p.header.Type {
case protocol.PacketTypeInitial:
return protocol.EncryptionInitial
case protocol.PacketTypeHandshake:
return protocol.EncryptionHandshake
case protocol.PacketType0RTT:
return protocol.Encryption0RTT
default:
panic("can't determine encryption level")
}
}
func (p *longHeaderPacket) IsAckEliciting() bool { return ackhandler.HasAckElicitingFrames(p.frames) }
type packetNumberManager interface {
PeekPacketNumber(protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen)
PopPacketNumber(protocol.EncryptionLevel) protocol.PacketNumber
}
type sealingManager interface {
GetInitialSealer() (handshake.LongHeaderSealer, error)
GetHandshakeSealer() (handshake.LongHeaderSealer, error)
Get0RTTSealer() (handshake.LongHeaderSealer, error)
Get1RTTSealer() (handshake.ShortHeaderSealer, error)
}
type frameSource interface {
HasData() bool
Append([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount, monotime.Time, protocol.Version) ([]ackhandler.Frame, []ackhandler.StreamFrame, protocol.ByteCount)
}
type ackFrameSource interface {
GetAckFrame(_ protocol.EncryptionLevel, now monotime.Time, onlyIfQueued bool) *wire.AckFrame
}
type packetPacker struct {
srcConnID protocol.ConnectionID
getDestConnID func() protocol.ConnectionID
perspective protocol.Perspective
cryptoSetup sealingManager
initialStream *initialCryptoStream
handshakeStream *cryptoStream
token []byte
pnManager packetNumberManager
framer frameSource
acks ackFrameSource
datagramQueue *datagramQueue
retransmissionQueue *retransmissionQueue
rand rand.Rand
numNonAckElicitingAcks int
}
var _ packer = &packetPacker{}
func newPacketPacker(
srcConnID protocol.ConnectionID,
getDestConnID func() protocol.ConnectionID,
initialStream *initialCryptoStream,
handshakeStream *cryptoStream,
packetNumberManager packetNumberManager,
retransmissionQueue *retransmissionQueue,
cryptoSetup sealingManager,
framer frameSource,
acks ackFrameSource,
datagramQueue *datagramQueue,
perspective protocol.Perspective,
) *packetPacker {
var b [16]byte
_, _ = crand.Read(b[:])
return &packetPacker{
cryptoSetup: cryptoSetup,
getDestConnID: getDestConnID,
srcConnID: srcConnID,
initialStream: initialStream,
handshakeStream: handshakeStream,
retransmissionQueue: retransmissionQueue,
datagramQueue: datagramQueue,
perspective: perspective,
framer: framer,
acks: acks,
rand: *rand.New(rand.NewPCG(binary.BigEndian.Uint64(b[:8]), binary.BigEndian.Uint64(b[8:]))),
pnManager: packetNumberManager,
}
}
// PackConnectionClose packs a packet that closes the connection with a transport error.
func (p *packetPacker) PackConnectionClose(e *qerr.TransportError, maxPacketSize protocol.ByteCount, v protocol.Version) (*coalescedPacket, error) {
var reason string
// don't send details of crypto errors
if !e.ErrorCode.IsCryptoError() {
reason = e.ErrorMessage
}
return p.packConnectionClose(false, uint64(e.ErrorCode), e.FrameType, reason, maxPacketSize, v)
}
// PackApplicationClose packs a packet that closes the connection with an application error.
func (p *packetPacker) PackApplicationClose(e *qerr.ApplicationError, maxPacketSize protocol.ByteCount, v protocol.Version) (*coalescedPacket, error) {
return p.packConnectionClose(true, uint64(e.ErrorCode), 0, e.ErrorMessage, maxPacketSize, v)
}
func (p *packetPacker) packConnectionClose(
isApplicationError bool,
errorCode uint64,
frameType uint64,
reason string,
maxPacketSize protocol.ByteCount,
v protocol.Version,
) (*coalescedPacket, error) {
var sealers [4]sealer
var hdrs [3]*wire.ExtendedHeader
var payloads [4]payload
var size protocol.ByteCount
var connID protocol.ConnectionID
var oneRTTPacketNumber protocol.PacketNumber
var oneRTTPacketNumberLen protocol.PacketNumberLen
var keyPhase protocol.KeyPhaseBit // only set for 1-RTT
var numLongHdrPackets uint8
encLevels := [4]protocol.EncryptionLevel{protocol.EncryptionInitial, protocol.EncryptionHandshake, protocol.Encryption0RTT, protocol.Encryption1RTT}
for i, encLevel := range encLevels {
if p.perspective == protocol.PerspectiveServer && encLevel == protocol.Encryption0RTT {
continue
}
ccf := &wire.ConnectionCloseFrame{
IsApplicationError: isApplicationError,
ErrorCode: errorCode,
FrameType: frameType,
ReasonPhrase: reason,
}
// don't send application errors in Initial or Handshake packets
if isApplicationError && (encLevel == protocol.EncryptionInitial || encLevel == protocol.EncryptionHandshake) {
ccf.IsApplicationError = false
ccf.ErrorCode = uint64(qerr.ApplicationErrorErrorCode)
ccf.ReasonPhrase = ""
}
pl := payload{
frames: []ackhandler.Frame{{Frame: ccf}},
length: ccf.Length(v),
}
var sealer sealer
var err error
switch encLevel {
case protocol.EncryptionInitial:
sealer, err = p.cryptoSetup.GetInitialSealer()
case protocol.EncryptionHandshake:
sealer, err = p.cryptoSetup.GetHandshakeSealer()
case protocol.Encryption0RTT:
sealer, err = p.cryptoSetup.Get0RTTSealer()
case protocol.Encryption1RTT:
var s handshake.ShortHeaderSealer
s, err = p.cryptoSetup.Get1RTTSealer()
if err == nil {
keyPhase = s.KeyPhase()
}
sealer = s
}
if err == handshake.ErrKeysNotYetAvailable || err == handshake.ErrKeysDropped {
continue
}
if err != nil {
return nil, err
}
sealers[i] = sealer
var hdr *wire.ExtendedHeader
if encLevel == protocol.Encryption1RTT {
connID = p.getDestConnID()
oneRTTPacketNumber, oneRTTPacketNumberLen = p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
size += p.shortHeaderPacketLength(connID, oneRTTPacketNumberLen, pl)
} else {
hdr = p.getLongHeader(encLevel, v)
hdrs[i] = hdr
size += p.longHeaderPacketLength(hdr, pl, v) + protocol.ByteCount(sealer.Overhead())
numLongHdrPackets++
}
payloads[i] = pl
}
buffer := getPacketBuffer()
packet := &coalescedPacket{
buffer: buffer,
longHdrPackets: make([]*longHeaderPacket, 0, numLongHdrPackets),
}
for i, encLevel := range encLevels {
if sealers[i] == nil {
continue
}
if encLevel == protocol.Encryption1RTT {
shp, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, keyPhase, payloads[i], 0, maxPacketSize, sealers[i], false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shp
} else {
var paddingLen protocol.ByteCount
if encLevel == protocol.EncryptionInitial {
paddingLen = p.initialPaddingLen(payloads[i].frames, size, maxPacketSize)
}
longHdrPacket, err := p.appendLongHeaderPacket(buffer, hdrs[i], payloads[i], paddingLen, encLevel, sealers[i], v)
if err != nil {
return nil, err
}
packet.longHdrPackets = append(packet.longHdrPackets, longHdrPacket)
}
}
return packet, nil
}
// longHeaderPacketLength calculates the length of a serialized long header packet.
// It takes into account that packets that have a tiny payload need to be padded,
// such that len(payload) + packet number len >= 4 + AEAD overhead
func (p *packetPacker) longHeaderPacketLength(hdr *wire.ExtendedHeader, pl payload, v protocol.Version) protocol.ByteCount {
var paddingLen protocol.ByteCount
pnLen := protocol.ByteCount(hdr.PacketNumberLen)
if pl.length < 4-pnLen {
paddingLen = 4 - pnLen - pl.length
}
return hdr.GetLength(v) + pl.length + paddingLen
}
// shortHeaderPacketLength calculates the length of a serialized short header packet.
// It takes into account that packets that have a tiny payload need to be padded,
// such that len(payload) + packet number len >= 4 + AEAD overhead
func (p *packetPacker) shortHeaderPacketLength(connID protocol.ConnectionID, pnLen protocol.PacketNumberLen, pl payload) protocol.ByteCount {
var paddingLen protocol.ByteCount
if pl.length < 4-protocol.ByteCount(pnLen) {
paddingLen = 4 - protocol.ByteCount(pnLen) - pl.length
}
return wire.ShortHeaderLen(connID, pnLen) + pl.length + paddingLen
}
// size is the expected size of the packet, if no padding was applied.
func (p *packetPacker) initialPaddingLen(frames []ackhandler.Frame, currentSize, maxPacketSize protocol.ByteCount) protocol.ByteCount {
// For the server, only ack-eliciting Initial packets need to be padded.
if p.perspective == protocol.PerspectiveServer && !ackhandler.HasAckElicitingFrames(frames) {
return 0
}
if currentSize >= maxPacketSize {
return 0
}
return maxPacketSize - currentSize
}
// PackCoalescedPacket packs a new packet.
// It packs an Initial / Handshake if there is data to send in these packet number spaces.
// It should only be called before the handshake is confirmed.
func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*coalescedPacket, error) {
var (
initialHdr, handshakeHdr, zeroRTTHdr *wire.ExtendedHeader
initialPayload, handshakePayload, zeroRTTPayload, oneRTTPayload payload
oneRTTPacketNumber protocol.PacketNumber
oneRTTPacketNumberLen protocol.PacketNumberLen
)
// Try packing an Initial packet.
initialSealer, err := p.cryptoSetup.GetInitialSealer()
if err != nil && err != handshake.ErrKeysDropped {
return nil, err
}
var size protocol.ByteCount
if initialSealer != nil {
initialHdr, initialPayload = p.maybeGetCryptoPacket(
maxSize-protocol.ByteCount(initialSealer.Overhead()),
protocol.EncryptionInitial,
now,
false,
onlyAck,
v,
)
if initialPayload.length > 0 {
size += p.longHeaderPacketLength(initialHdr, initialPayload, v) + protocol.ByteCount(initialSealer.Overhead())
}
}
// Add a Handshake packet.
var handshakeSealer sealer
if (onlyAck && size == 0) || (!onlyAck && size < maxSize-protocol.MinCoalescedPacketSize) {
var err error
handshakeSealer, err = p.cryptoSetup.GetHandshakeSealer()
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
return nil, err
}
if handshakeSealer != nil {
handshakeHdr, handshakePayload = p.maybeGetCryptoPacket(
maxSize-size-protocol.ByteCount(handshakeSealer.Overhead()),
protocol.EncryptionHandshake,
now,
false,
onlyAck,
v,
)
if handshakePayload.length > 0 {
s := p.longHeaderPacketLength(handshakeHdr, handshakePayload, v) + protocol.ByteCount(handshakeSealer.Overhead())
size += s
}
}
}
// Add a 0-RTT / 1-RTT packet.
var zeroRTTSealer sealer
var oneRTTSealer handshake.ShortHeaderSealer
var connID protocol.ConnectionID
var kp protocol.KeyPhaseBit
if (onlyAck && size == 0) || (!onlyAck && size < maxSize-protocol.MinCoalescedPacketSize) {
var err error
oneRTTSealer, err = p.cryptoSetup.Get1RTTSealer()
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
return nil, err
}
if err == nil { // 1-RTT
kp = oneRTTSealer.KeyPhase()
connID = p.getDestConnID()
oneRTTPacketNumber, oneRTTPacketNumberLen = p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
hdrLen := wire.ShortHeaderLen(connID, oneRTTPacketNumberLen)
oneRTTPayload = p.maybeGetShortHeaderPacket(oneRTTSealer, hdrLen, maxSize-size, onlyAck, now, v)
if oneRTTPayload.length > 0 {
size += p.shortHeaderPacketLength(connID, oneRTTPacketNumberLen, oneRTTPayload) + protocol.ByteCount(oneRTTSealer.Overhead())
}
} else if p.perspective == protocol.PerspectiveClient && !onlyAck { // 0-RTT packets can't contain ACK frames
var err error
zeroRTTSealer, err = p.cryptoSetup.Get0RTTSealer()
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
return nil, err
}
if zeroRTTSealer != nil {
zeroRTTHdr, zeroRTTPayload = p.maybeGetAppDataPacketFor0RTT(zeroRTTSealer, maxSize-size, now, v)
if zeroRTTPayload.length > 0 {
size += p.longHeaderPacketLength(zeroRTTHdr, zeroRTTPayload, v) + protocol.ByteCount(zeroRTTSealer.Overhead())
}
}
}
}
if initialPayload.length == 0 && handshakePayload.length == 0 && zeroRTTPayload.length == 0 && oneRTTPayload.length == 0 {
return nil, nil
}
buffer := getPacketBuffer()
packet := &coalescedPacket{
buffer: buffer,
longHdrPackets: make([]*longHeaderPacket, 0, 3),
}
if initialPayload.length > 0 {
padding := p.initialPaddingLen(initialPayload.frames, size, maxSize)
cont, err := p.appendLongHeaderPacket(buffer, initialHdr, initialPayload, padding, protocol.EncryptionInitial, initialSealer, v)
if err != nil {
return nil, err
}
packet.longHdrPackets = append(packet.longHdrPackets, cont)
}
if handshakePayload.length > 0 {
cont, err := p.appendLongHeaderPacket(buffer, handshakeHdr, handshakePayload, 0, protocol.EncryptionHandshake, handshakeSealer, v)
if err != nil {
return nil, err
}
packet.longHdrPackets = append(packet.longHdrPackets, cont)
}
if zeroRTTPayload.length > 0 {
longHdrPacket, err := p.appendLongHeaderPacket(buffer, zeroRTTHdr, zeroRTTPayload, 0, protocol.Encryption0RTT, zeroRTTSealer, v)
if err != nil {
return nil, err
}
packet.longHdrPackets = append(packet.longHdrPackets, longHdrPacket)
} else if oneRTTPayload.length > 0 {
shp, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, kp, oneRTTPayload, 0, maxSize, oneRTTSealer, false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shp
}
return packet, nil
}
// PackAckOnlyPacket packs a packet containing only an ACK in the application data packet number space.
// It should be called after the handshake is confirmed.
func (p *packetPacker) PackAckOnlyPacket(maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, *packetBuffer, error) {
buf := getPacketBuffer()
packet, err := p.appendPacket(buf, true, maxSize, now, v)
return packet, buf, err
}
// AppendPacket packs a packet in the application data packet number space.
// It should be called after the handshake is confirmed.
func (p *packetPacker) AppendPacket(buf *packetBuffer, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (shortHeaderPacket, error) {
return p.appendPacket(buf, false, maxSize, now, v)
}
func (p *packetPacker) appendPacket(
buf *packetBuffer,
onlyAck bool,
maxPacketSize protocol.ByteCount,
now monotime.Time,
v protocol.Version,
) (shortHeaderPacket, error) {
sealer, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
return shortHeaderPacket{}, err
}
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
connID := p.getDestConnID()
hdrLen := wire.ShortHeaderLen(connID, pnLen)
pl := p.maybeGetShortHeaderPacket(sealer, hdrLen, maxPacketSize, onlyAck, now, v)
if pl.length == 0 {
return shortHeaderPacket{}, errNothingToPack
}
kp := sealer.KeyPhase()
return p.appendShortHeaderPacket(buf, connID, pn, pnLen, kp, pl, 0, maxPacketSize, sealer, false, v)
}
func (p *packetPacker) maybeGetCryptoPacket(
maxPacketSize protocol.ByteCount,
encLevel protocol.EncryptionLevel,
now monotime.Time,
addPingIfEmpty bool,
onlyAck bool,
v protocol.Version,
) (*wire.ExtendedHeader, payload) {
if onlyAck {
if ack := p.acks.GetAckFrame(encLevel, now, true); ack != nil {
hdr := p.getLongHeader(encLevel, v)
maxPacketSize -= hdr.GetLength(v)
ack.Truncate(maxPacketSize, v)
return hdr, payload{ack: ack, length: ack.Length(v)}
}
return nil, payload{length: 0}
}
var hasCryptoData func() bool
var popCryptoFrame func(maxLen protocol.ByteCount) *wire.CryptoFrame
//nolint:exhaustive // Initial and Handshake are the only two encryption levels here.
switch encLevel {
case protocol.EncryptionInitial:
hasCryptoData = p.initialStream.HasData
popCryptoFrame = p.initialStream.PopCryptoFrame
case protocol.EncryptionHandshake:
hasCryptoData = p.handshakeStream.HasData
popCryptoFrame = p.handshakeStream.PopCryptoFrame
}
handler := p.retransmissionQueue.AckHandler(encLevel)
hasRetransmission := p.retransmissionQueue.HasData(encLevel)
ack := p.acks.GetAckFrame(encLevel, now, !hasRetransmission && !hasCryptoData())
var pl payload
if !hasCryptoData() && !hasRetransmission && ack == nil {
if !addPingIfEmpty {
// nothing to send
return nil, payload{}
}
ping := &wire.PingFrame{}
pl.frames = append(pl.frames, ackhandler.Frame{Frame: ping, Handler: emptyHandler{}})
pl.length += ping.Length(v)
}
hdr := p.getLongHeader(encLevel, v)
maxPacketSize -= hdr.GetLength(v)
if ack != nil {
ack.Truncate(maxPacketSize, v)
pl.ack = ack
pl.length = ack.Length(v)
maxPacketSize -= pl.length
}
if hasRetransmission {
for {
frame := p.retransmissionQueue.GetFrame(encLevel, maxPacketSize, v)
if frame == nil {
break
}
pl.frames = append(pl.frames, ackhandler.Frame{
Frame: frame,
Handler: p.retransmissionQueue.AckHandler(encLevel),
})
frameLen := frame.Length(v)
pl.length += frameLen
maxPacketSize -= frameLen
}
return hdr, pl
} else {
for hasCryptoData() {
cf := popCryptoFrame(maxPacketSize)
if cf == nil {
break
}
pl.frames = append(pl.frames, ackhandler.Frame{Frame: cf, Handler: handler})
pl.length += cf.Length(v)
maxPacketSize -= cf.Length(v)
}
}
return hdr, pl
}
func (p *packetPacker) maybeGetAppDataPacketFor0RTT(sealer sealer, maxSize protocol.ByteCount, now monotime.Time, v protocol.Version) (*wire.ExtendedHeader, payload) {
if p.perspective != protocol.PerspectiveClient {
return nil, payload{}
}
hdr := p.getLongHeader(protocol.Encryption0RTT, v)
maxPayloadSize := maxSize - hdr.GetLength(v) - protocol.ByteCount(sealer.Overhead())
return hdr, p.maybeGetAppDataPacket(maxPayloadSize, false, false, now, v)
}
func (p *packetPacker) maybeGetShortHeaderPacket(
sealer handshake.ShortHeaderSealer,
hdrLen, maxPacketSize protocol.ByteCount,
onlyAck bool,
now monotime.Time,
v protocol.Version,
) payload {
maxPayloadSize := maxPacketSize - hdrLen - protocol.ByteCount(sealer.Overhead())
return p.maybeGetAppDataPacket(maxPayloadSize, onlyAck, true, now, v)
}
func (p *packetPacker) maybeGetAppDataPacket(
maxPayloadSize protocol.ByteCount,
onlyAck, ackAllowed bool,
now monotime.Time,
v protocol.Version,
) payload {
pl := p.composeNextPacket(maxPayloadSize, onlyAck, ackAllowed, now, v)
// check if we have anything to send
if len(pl.frames) == 0 && len(pl.streamFrames) == 0 {
if pl.ack == nil {
return payload{}
}
// the packet only contains an ACK
if p.numNonAckElicitingAcks >= protocol.MaxNonAckElicitingAcks {
ping := &wire.PingFrame{}
pl.frames = append(pl.frames, ackhandler.Frame{Frame: ping})
pl.length += ping.Length(v)
p.numNonAckElicitingAcks = 0
} else {
p.numNonAckElicitingAcks++
}
} else {
p.numNonAckElicitingAcks = 0
}
return pl
}
func (p *packetPacker) composeNextPacket(
maxPayloadSize protocol.ByteCount,
onlyAck, ackAllowed bool,
now monotime.Time,
v protocol.Version,
) payload {
if onlyAck {
if ack := p.acks.GetAckFrame(protocol.Encryption1RTT, now, true); ack != nil {
ack.Truncate(maxPayloadSize, v)
return payload{ack: ack, length: ack.Length(v)}
}
return payload{}
}
hasData := p.framer.HasData()
hasRetransmission := p.retransmissionQueue.HasData(protocol.Encryption1RTT)
var pl payload
if ackAllowed {
if ack := p.acks.GetAckFrame(protocol.Encryption1RTT, now, !hasRetransmission && !hasData); ack != nil {
ack.Truncate(maxPayloadSize, v)
pl.ack = ack
pl.length += ack.Length(v)
}
}
if p.datagramQueue != nil {
if f := p.datagramQueue.Peek(); f != nil {
size := f.Length(v)
if size <= maxPayloadSize-pl.length { // DATAGRAM frame fits
pl.frames = append(pl.frames, ackhandler.Frame{Frame: f})
pl.length += size
p.datagramQueue.Pop()
} else if pl.ack == nil {
// The DATAGRAM frame doesn't fit, and the packet doesn't contain an ACK.
// Discard this frame. There's no point in retrying this in the next packet,
// as it's unlikely that the available packet size will increase.
p.datagramQueue.Pop()
}
// If the DATAGRAM frame was too large and the packet contained an ACK, we'll try to send it out later.
}
}
if pl.ack != nil && !hasData && !hasRetransmission {
return pl
}
if hasRetransmission {
for {
remainingLen := maxPayloadSize - pl.length
if remainingLen < protocol.MinStreamFrameSize {
break
}
f := p.retransmissionQueue.GetFrame(protocol.Encryption1RTT, remainingLen, v)
if f == nil {
break
}
pl.frames = append(pl.frames, ackhandler.Frame{Frame: f, Handler: p.retransmissionQueue.AckHandler(protocol.Encryption1RTT)})
pl.length += f.Length(v)
}
}
if hasData {
var lengthAdded protocol.ByteCount
startLen := len(pl.frames)
pl.frames, pl.streamFrames, lengthAdded = p.framer.Append(pl.frames, pl.streamFrames, maxPayloadSize-pl.length, now, v)
pl.length += lengthAdded
// add handlers for the control frames that were added
for i := startLen; i < len(pl.frames); i++ {
if pl.frames[i].Handler != nil {
continue
}
switch pl.frames[i].Frame.(type) {
case *wire.PathChallengeFrame, *wire.PathResponseFrame:
// Path probing is currently not supported, therefore we don't need to set the OnAcked callback yet.
// PATH_CHALLENGE and PATH_RESPONSE are never retransmitted.
default:
// we might be packing a 0-RTT packet, but we need to use the 1-RTT ack handler anyway
pl.frames[i].Handler = p.retransmissionQueue.AckHandler(protocol.Encryption1RTT)
}
}
}
return pl
}
func (p *packetPacker) PackPTOProbePacket(
encLevel protocol.EncryptionLevel,
maxPacketSize protocol.ByteCount,
addPingIfEmpty bool,
now monotime.Time,
v protocol.Version,
) (*coalescedPacket, error) {
if encLevel == protocol.Encryption1RTT {
return p.packPTOProbePacket1RTT(maxPacketSize, addPingIfEmpty, now, v)
}
var sealer handshake.LongHeaderSealer
//nolint:exhaustive // Probe packets are never sent for 0-RTT.
switch encLevel {
case protocol.EncryptionInitial:
var err error
sealer, err = p.cryptoSetup.GetInitialSealer()
if err != nil {
return nil, err
}
case protocol.EncryptionHandshake:
var err error
sealer, err = p.cryptoSetup.GetHandshakeSealer()
if err != nil {
return nil, err
}
default:
panic("unknown encryption level")
}
hdr, pl := p.maybeGetCryptoPacket(
maxPacketSize-protocol.ByteCount(sealer.Overhead()),
encLevel,
now,
addPingIfEmpty,
false,
v,
)
if pl.length == 0 {
return nil, nil
}
buffer := getPacketBuffer()
packet := &coalescedPacket{buffer: buffer}
size := p.longHeaderPacketLength(hdr, pl, v) + protocol.ByteCount(sealer.Overhead())
var padding protocol.ByteCount
if encLevel == protocol.EncryptionInitial {
padding = p.initialPaddingLen(pl.frames, size, maxPacketSize)
}
longHdrPacket, err := p.appendLongHeaderPacket(buffer, hdr, pl, padding, encLevel, sealer, v)
if err != nil {
return nil, err
}
packet.longHdrPackets = []*longHeaderPacket{longHdrPacket}
return packet, nil
}
func (p *packetPacker) packPTOProbePacket1RTT(maxPacketSize protocol.ByteCount, addPingIfEmpty bool, now monotime.Time, v protocol.Version) (*coalescedPacket, error) {
s, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
return nil, err
}
kp := s.KeyPhase()
connID := p.getDestConnID()
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
hdrLen := wire.ShortHeaderLen(connID, pnLen)
pl := p.maybeGetAppDataPacket(maxPacketSize-protocol.ByteCount(s.Overhead())-hdrLen, false, true, now, v)
if pl.length == 0 {
if !addPingIfEmpty {
return nil, nil
}
ping := &wire.PingFrame{}
pl.frames = append(pl.frames, ackhandler.Frame{Frame: ping, Handler: emptyHandler{}})
pl.length += ping.Length(v)
}
buffer := getPacketBuffer()
packet := &coalescedPacket{buffer: buffer}
shp, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, 0, maxPacketSize, s, false, v)
if err != nil {
return nil, err
}
packet.shortHdrPacket = &shp
return packet, nil
}
func (p *packetPacker) PackMTUProbePacket(ping ackhandler.Frame, size protocol.ByteCount, v protocol.Version) (shortHeaderPacket, *packetBuffer, error) {
pl := payload{
frames: []ackhandler.Frame{ping},
length: ping.Frame.Length(v),
}
buffer := getPacketBuffer()
s, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
return shortHeaderPacket{}, nil, err
}
connID := p.getDestConnID()
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
padding := size - p.shortHeaderPacketLength(connID, pnLen, pl) - protocol.ByteCount(s.Overhead())
kp := s.KeyPhase()
packet, err := p.appendShortHeaderPacket(buffer, connID, pn, pnLen, kp, pl, padding, size, s, true, v)
return packet, buffer, err
}
func (p *packetPacker) PackPathProbePacket(connID protocol.ConnectionID, frames []ackhandler.Frame, v protocol.Version) (shortHeaderPacket, *packetBuffer, error) {
pn, pnLen := p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
buf := getPacketBuffer()
s, err := p.cryptoSetup.Get1RTTSealer()
if err != nil {
return shortHeaderPacket{}, nil, err
}
var l protocol.ByteCount
for _, f := range frames {
l += f.Frame.Length(v)
}
payload := payload{
frames: frames,
length: l,
}
padding := protocol.MinInitialPacketSize - p.shortHeaderPacketLength(connID, pnLen, payload) - protocol.ByteCount(s.Overhead())
packet, err := p.appendShortHeaderPacket(buf, connID, pn, pnLen, s.KeyPhase(), payload, padding, protocol.MinInitialPacketSize, s, false, v)
if err != nil {
return shortHeaderPacket{}, nil, err
}
packet.IsPathProbePacket = true
return packet, buf, err
}
func (p *packetPacker) getLongHeader(encLevel protocol.EncryptionLevel, v protocol.Version) *wire.ExtendedHeader {
pn, pnLen := p.pnManager.PeekPacketNumber(encLevel)
hdr := &wire.ExtendedHeader{
PacketNumber: pn,
PacketNumberLen: pnLen,
}
hdr.Version = v
hdr.SrcConnectionID = p.srcConnID
hdr.DestConnectionID = p.getDestConnID()
//nolint:exhaustive // 1-RTT packets are not long header packets.
switch encLevel {
case protocol.EncryptionInitial:
hdr.Type = protocol.PacketTypeInitial
hdr.Token = p.token
case protocol.EncryptionHandshake:
hdr.Type = protocol.PacketTypeHandshake
case protocol.Encryption0RTT:
hdr.Type = protocol.PacketType0RTT
}
return hdr
}
func (p *packetPacker) appendLongHeaderPacket(buffer *packetBuffer, header *wire.ExtendedHeader, pl payload, padding protocol.ByteCount, encLevel protocol.EncryptionLevel, sealer sealer, v protocol.Version) (*longHeaderPacket, error) {
var paddingLen protocol.ByteCount
pnLen := protocol.ByteCount(header.PacketNumberLen)
if pl.length < 4-pnLen {
paddingLen = 4 - pnLen - pl.length
}
paddingLen += padding
header.Length = pnLen + protocol.ByteCount(sealer.Overhead()) + pl.length + paddingLen
startLen := len(buffer.Data)
raw := buffer.Data[startLen:]
raw, err := header.Append(raw, v)
if err != nil {
return nil, err
}
payloadOffset := protocol.ByteCount(len(raw))
raw, err = p.appendPacketPayload(raw, pl, paddingLen, v)
if err != nil {
return nil, err
}
raw = p.encryptPacket(raw, sealer, header.PacketNumber, payloadOffset, pnLen)
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
if pn := p.pnManager.PopPacketNumber(encLevel); pn != header.PacketNumber {
return nil, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, header.PacketNumber)
}
return &longHeaderPacket{
header: header,
ack: pl.ack,
frames: pl.frames,
streamFrames: pl.streamFrames,
length: protocol.ByteCount(len(raw)),
}, nil
}
func (p *packetPacker) appendShortHeaderPacket(
buffer *packetBuffer,
connID protocol.ConnectionID,
pn protocol.PacketNumber,
pnLen protocol.PacketNumberLen,
kp protocol.KeyPhaseBit,
pl payload,
padding, maxPacketSize protocol.ByteCount,
sealer sealer,
isMTUProbePacket bool,
v protocol.Version,
) (shortHeaderPacket, error) {
var paddingLen protocol.ByteCount
if pl.length < 4-protocol.ByteCount(pnLen) {
paddingLen = 4 - protocol.ByteCount(pnLen) - pl.length
}
paddingLen += padding
startLen := len(buffer.Data)
raw := buffer.Data[startLen:]
raw, err := wire.AppendShortHeader(raw, connID, pn, pnLen, kp)
if err != nil {
return shortHeaderPacket{}, err
}
payloadOffset := protocol.ByteCount(len(raw))
raw, err = p.appendPacketPayload(raw, pl, paddingLen, v)
if err != nil {
return shortHeaderPacket{}, err
}
if !isMTUProbePacket {
if size := protocol.ByteCount(len(raw) + sealer.Overhead()); size > maxPacketSize {
return shortHeaderPacket{}, fmt.Errorf("PacketPacker BUG: packet too large (%d bytes, allowed %d bytes)", size, maxPacketSize)
}
}
raw = p.encryptPacket(raw, sealer, pn, payloadOffset, protocol.ByteCount(pnLen))
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
if newPN := p.pnManager.PopPacketNumber(protocol.Encryption1RTT); newPN != pn {
return shortHeaderPacket{}, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, newPN)
}
return shortHeaderPacket{
PacketNumber: pn,
PacketNumberLen: pnLen,
KeyPhase: kp,
StreamFrames: pl.streamFrames,
Frames: pl.frames,
Ack: pl.ack,
Length: protocol.ByteCount(len(raw)),
DestConnID: connID,
IsPathMTUProbePacket: isMTUProbePacket,
}, nil
}
// appendPacketPayload serializes the payload of a packet into the raw byte slice.
// It modifies the order of payload.frames.
func (p *packetPacker) appendPacketPayload(raw []byte, pl payload, paddingLen protocol.ByteCount, v protocol.Version) ([]byte, error) {
payloadOffset := len(raw)
if pl.ack != nil {
var err error
raw, err = pl.ack.Append(raw, v)
if err != nil {
return nil, err
}
}
if paddingLen > 0 {
raw = append(raw, make([]byte, paddingLen)...)
}
// Randomize the order of the control frames.
// This makes sure that the receiver doesn't rely on the order in which frames are packed.
if len(pl.frames) > 1 {
p.rand.Shuffle(len(pl.frames), func(i, j int) { pl.frames[i], pl.frames[j] = pl.frames[j], pl.frames[i] })
}
for _, f := range pl.frames {
var err error
raw, err = f.Frame.Append(raw, v)
if err != nil {
return nil, err
}
}
for _, f := range pl.streamFrames {
var err error
raw, err = f.Frame.Append(raw, v)
if err != nil {
return nil, err
}
}
if payloadSize := protocol.ByteCount(len(raw)-payloadOffset) - paddingLen; payloadSize != pl.length {
return nil, fmt.Errorf("PacketPacker BUG: payload size inconsistent (expected %d, got %d bytes)", pl.length, payloadSize)
}
return raw, nil
}
func (p *packetPacker) encryptPacket(raw []byte, sealer sealer, pn protocol.PacketNumber, payloadOffset, pnLen protocol.ByteCount) []byte {
_ = sealer.Seal(raw[payloadOffset:payloadOffset], raw[payloadOffset:], pn, raw[:payloadOffset])
raw = raw[:len(raw)+sealer.Overhead()]
// apply header protection
pnOffset := payloadOffset - pnLen
sealer.EncryptHeader(raw[pnOffset+4:pnOffset+4+16], &raw[0], raw[pnOffset:payloadOffset])
return raw
}
func (p *packetPacker) SetToken(token []byte) {
p.token = token
}
type emptyHandler struct{}
var _ ackhandler.FrameHandler = emptyHandler{}
func (emptyHandler) OnAcked(wire.Frame) {}
func (emptyHandler) OnLost(wire.Frame) {}
package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
type headerDecryptor interface {
DecryptHeader(sample []byte, firstByte *byte, pnBytes []byte)
}
type headerParseError struct {
err error
}
func (e *headerParseError) Unwrap() error {
return e.err
}
func (e *headerParseError) Error() string {
return e.err.Error()
}
type unpackedPacket struct {
hdr *wire.ExtendedHeader
encryptionLevel protocol.EncryptionLevel
data []byte
}
// The packetUnpacker unpacks QUIC packets.
type packetUnpacker struct {
cs handshake.CryptoSetup
shortHdrConnIDLen int
}
var _ unpacker = &packetUnpacker{}
func newPacketUnpacker(cs handshake.CryptoSetup, shortHdrConnIDLen int) *packetUnpacker {
return &packetUnpacker{
cs: cs,
shortHdrConnIDLen: shortHdrConnIDLen,
}
}
// UnpackLongHeader unpacks a Long Header packet.
// If the reserved bits are invalid, the error is wire.ErrInvalidReservedBits.
// If any other error occurred when parsing the header, the error is of type headerParseError.
// If decrypting the payload fails for any reason, the error is the error returned by the AEAD.
func (u *packetUnpacker) UnpackLongHeader(hdr *wire.Header, data []byte) (*unpackedPacket, error) {
var encLevel protocol.EncryptionLevel
var extHdr *wire.ExtendedHeader
var decrypted []byte
//nolint:exhaustive // Retry packets can't be unpacked.
switch hdr.Type {
case protocol.PacketTypeInitial:
encLevel = protocol.EncryptionInitial
opener, err := u.cs.GetInitialOpener()
if err != nil {
return nil, err
}
extHdr, decrypted, err = u.unpackLongHeaderPacket(opener, hdr, data)
if err != nil {
return nil, err
}
case protocol.PacketTypeHandshake:
encLevel = protocol.EncryptionHandshake
opener, err := u.cs.GetHandshakeOpener()
if err != nil {
return nil, err
}
extHdr, decrypted, err = u.unpackLongHeaderPacket(opener, hdr, data)
if err != nil {
return nil, err
}
case protocol.PacketType0RTT:
encLevel = protocol.Encryption0RTT
opener, err := u.cs.Get0RTTOpener()
if err != nil {
return nil, err
}
extHdr, decrypted, err = u.unpackLongHeaderPacket(opener, hdr, data)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown packet type: %s", hdr.Type)
}
if len(decrypted) == 0 {
return nil, &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "empty packet",
}
}
return &unpackedPacket{
hdr: extHdr,
encryptionLevel: encLevel,
data: decrypted,
}, nil
}
func (u *packetUnpacker) UnpackShortHeader(rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
opener, err := u.cs.Get1RTTOpener()
if err != nil {
return 0, 0, 0, nil, err
}
pn, pnLen, kp, decrypted, err := u.unpackShortHeaderPacket(opener, rcvTime, data)
if err != nil {
return 0, 0, 0, nil, err
}
if len(decrypted) == 0 {
return 0, 0, 0, nil, &qerr.TransportError{
ErrorCode: qerr.ProtocolViolation,
ErrorMessage: "empty packet",
}
}
return pn, pnLen, kp, decrypted, nil
}
func (u *packetUnpacker) unpackLongHeaderPacket(opener handshake.LongHeaderOpener, hdr *wire.Header, data []byte) (*wire.ExtendedHeader, []byte, error) {
extHdr, parseErr := u.unpackLongHeader(opener, hdr, data)
// If the reserved bits are set incorrectly, we still need to continue unpacking.
// This avoids a timing side-channel, which otherwise might allow an attacker
// to gain information about the header encryption.
if parseErr != nil && parseErr != wire.ErrInvalidReservedBits {
return nil, nil, parseErr
}
extHdrLen := extHdr.ParsedLen()
extHdr.PacketNumber = opener.DecodePacketNumber(extHdr.PacketNumber, extHdr.PacketNumberLen)
decrypted, err := opener.Open(data[extHdrLen:extHdrLen], data[extHdrLen:], extHdr.PacketNumber, data[:extHdrLen])
if err != nil {
return nil, nil, err
}
if parseErr != nil {
return nil, nil, parseErr
}
return extHdr, decrypted, nil
}
func (u *packetUnpacker) unpackShortHeaderPacket(opener handshake.ShortHeaderOpener, rcvTime monotime.Time, data []byte) (protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, []byte, error) {
l, pn, pnLen, kp, parseErr := u.unpackShortHeader(opener, data)
// If the reserved bits are set incorrectly, we still need to continue unpacking.
// This avoids a timing side-channel, which otherwise might allow an attacker
// to gain information about the header encryption.
if parseErr != nil && parseErr != wire.ErrInvalidReservedBits {
return 0, 0, 0, nil, &headerParseError{parseErr}
}
pn = opener.DecodePacketNumber(pn, pnLen)
decrypted, err := opener.Open(data[l:l], data[l:], rcvTime, pn, kp, data[:l])
if err != nil {
return 0, 0, 0, nil, err
}
return pn, pnLen, kp, decrypted, parseErr
}
func (u *packetUnpacker) unpackShortHeader(hd headerDecryptor, data []byte) (int, protocol.PacketNumber, protocol.PacketNumberLen, protocol.KeyPhaseBit, error) {
hdrLen := 1 /* first header byte */ + u.shortHdrConnIDLen
if len(data) < hdrLen+4+16 {
return 0, 0, 0, 0, fmt.Errorf("packet too small, expected at least 20 bytes after the header, got %d", len(data)-hdrLen)
}
origPNBytes := make([]byte, 4)
copy(origPNBytes, data[hdrLen:hdrLen+4])
// 2. decrypt the header, assuming a 4 byte packet number
hd.DecryptHeader(
data[hdrLen+4:hdrLen+4+16],
&data[0],
data[hdrLen:hdrLen+4],
)
// 3. parse the header (and learn the actual length of the packet number)
l, pn, pnLen, kp, parseErr := wire.ParseShortHeader(data, u.shortHdrConnIDLen)
if parseErr != nil && parseErr != wire.ErrInvalidReservedBits {
return l, pn, pnLen, kp, parseErr
}
// 4. if the packet number is shorter than 4 bytes, replace the remaining bytes with the copy we saved earlier
if pnLen != protocol.PacketNumberLen4 {
copy(data[hdrLen+int(pnLen):hdrLen+4], origPNBytes[int(pnLen):])
}
return l, pn, pnLen, kp, parseErr
}
// The error is either nil, a wire.ErrInvalidReservedBits or of type headerParseError.
func (u *packetUnpacker) unpackLongHeader(hd headerDecryptor, hdr *wire.Header, data []byte) (*wire.ExtendedHeader, error) {
extHdr, err := unpackLongHeader(hd, hdr, data)
if err != nil && err != wire.ErrInvalidReservedBits {
return nil, &headerParseError{err: err}
}
return extHdr, err
}
func unpackLongHeader(hd headerDecryptor, hdr *wire.Header, data []byte) (*wire.ExtendedHeader, error) {
hdrLen := hdr.ParsedLen()
if protocol.ByteCount(len(data)) < hdrLen+4+16 {
return nil, fmt.Errorf("packet too small, expected at least 20 bytes after the header, got %d", protocol.ByteCount(len(data))-hdrLen)
}
// The packet number can be up to 4 bytes long, but we won't know the length until we decrypt it.
// 1. save a copy of the 4 bytes
origPNBytes := make([]byte, 4)
copy(origPNBytes, data[hdrLen:hdrLen+4])
// 2. decrypt the header, assuming a 4 byte packet number
hd.DecryptHeader(
data[hdrLen+4:hdrLen+4+16],
&data[0],
data[hdrLen:hdrLen+4],
)
// 3. parse the header (and learn the actual length of the packet number)
extHdr, parseErr := hdr.ParseExtended(data)
if parseErr != nil && parseErr != wire.ErrInvalidReservedBits {
return nil, parseErr
}
// 4. if the packet number is shorter than 4 bytes, replace the remaining bytes with the copy we saved earlier
if extHdr.PacketNumberLen != protocol.PacketNumberLen4 {
copy(data[extHdr.ParsedLen():hdrLen+4], origPNBytes[int(extHdr.PacketNumberLen):])
}
return extHdr, parseErr
}
package quic
import (
"crypto/rand"
"net"
"slices"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
)
type pathID int64
const invalidPathID pathID = -1
// Maximum number of paths to keep track of.
// If the peer probes another path (before the pathTimeout of an existing path expires),
// this probing attempt is ignored.
const maxPaths = 3
// If no packet is received for a path for pathTimeout,
// the path can be evicted when the peer probes another path.
// This prevents an attacker from churning through paths by duplicating packets and
// sending them with spoofed source addresses.
const pathTimeout = 5 * time.Second
type path struct {
id pathID
addr net.Addr
lastPacketTime monotime.Time
pathChallenge [8]byte
validated bool
rcvdNonProbing bool
}
type pathManager struct {
nextPathID pathID
// ordered by lastPacketTime, with the most recently used path at the end
paths []*path
getConnID func(pathID) (_ protocol.ConnectionID, ok bool)
retireConnID func(pathID)
logger utils.Logger
}
func newPathManager(
getConnID func(pathID) (_ protocol.ConnectionID, ok bool),
retireConnID func(pathID),
logger utils.Logger,
) *pathManager {
return &pathManager{
paths: make([]*path, 0, maxPaths+1),
getConnID: getConnID,
retireConnID: retireConnID,
logger: logger,
}
}
// Returns a path challenge frame if one should be sent.
// May return nil.
func (pm *pathManager) HandlePacket(
remoteAddr net.Addr,
t monotime.Time,
pathChallenge *wire.PathChallengeFrame, // may be nil if the packet didn't contain a PATH_CHALLENGE
isNonProbing bool,
) (_ protocol.ConnectionID, _ []ackhandler.Frame, shouldSwitch bool) {
var p *path
for i, path := range pm.paths {
if addrsEqual(path.addr, remoteAddr) {
p = path
p.lastPacketTime = t
// already sent a PATH_CHALLENGE for this path
if isNonProbing {
path.rcvdNonProbing = true
}
if pm.logger.Debug() {
pm.logger.Debugf("received packet for path %s that was already probed, validated: %t", remoteAddr, path.validated)
}
shouldSwitch = path.validated && path.rcvdNonProbing
if i != len(pm.paths)-1 {
// move the path to the end of the list
pm.paths = slices.Delete(pm.paths, i, i+1)
pm.paths = append(pm.paths, p)
}
if pathChallenge == nil {
return protocol.ConnectionID{}, nil, shouldSwitch
}
}
}
if len(pm.paths) >= maxPaths {
if pm.paths[0].lastPacketTime.Add(pathTimeout).After(t) {
if pm.logger.Debug() {
pm.logger.Debugf("received packet for previously unseen path %s, but already have %d paths", remoteAddr, len(pm.paths))
}
return protocol.ConnectionID{}, nil, shouldSwitch
}
// evict the oldest path, if the last packet was received more than pathTimeout ago
pm.retireConnID(pm.paths[0].id)
pm.paths = pm.paths[1:]
}
var pathID pathID
if p != nil {
pathID = p.id
} else {
pathID = pm.nextPathID
}
// previously unseen path, initiate path validation by sending a PATH_CHALLENGE
connID, ok := pm.getConnID(pathID)
if !ok {
pm.logger.Debugf("skipping validation of new path %s since no connection ID is available", remoteAddr)
return protocol.ConnectionID{}, nil, shouldSwitch
}
frames := make([]ackhandler.Frame, 0, 2)
if p == nil {
var pathChallengeData [8]byte
rand.Read(pathChallengeData[:])
p = &path{
id: pm.nextPathID,
addr: remoteAddr,
lastPacketTime: t,
rcvdNonProbing: isNonProbing,
pathChallenge: pathChallengeData,
}
pm.nextPathID++
pm.paths = append(pm.paths, p)
frames = append(frames, ackhandler.Frame{
Frame: &wire.PathChallengeFrame{Data: p.pathChallenge},
Handler: (*pathManagerAckHandler)(pm),
})
pm.logger.Debugf("enqueueing PATH_CHALLENGE for new path %s", remoteAddr)
}
if pathChallenge != nil {
frames = append(frames, ackhandler.Frame{
Frame: &wire.PathResponseFrame{Data: pathChallenge.Data},
Handler: (*pathManagerAckHandler)(pm),
})
}
return connID, frames, shouldSwitch
}
func (pm *pathManager) HandlePathResponseFrame(f *wire.PathResponseFrame) {
for _, p := range pm.paths {
if f.Data == p.pathChallenge {
// path validated
p.validated = true
pm.logger.Debugf("path %s validated", p.addr)
break
}
}
}
// SwitchToPath is called when the connection switches to a new path
func (pm *pathManager) SwitchToPath(addr net.Addr) {
// retire all other paths
for _, path := range pm.paths {
if addrsEqual(path.addr, addr) {
pm.logger.Debugf("switching to path %d (%s)", path.id, addr)
continue
}
pm.retireConnID(path.id)
}
clear(pm.paths)
pm.paths = pm.paths[:0]
}
type pathManagerAckHandler pathManager
var _ ackhandler.FrameHandler = &pathManagerAckHandler{}
// Acknowledging the frame doesn't validate the path, only receiving the PATH_RESPONSE does.
func (pm *pathManagerAckHandler) OnAcked(f wire.Frame) {}
func (pm *pathManagerAckHandler) OnLost(f wire.Frame) {
pc, ok := f.(*wire.PathChallengeFrame)
if !ok {
return
}
for i, path := range pm.paths {
if path.pathChallenge == pc.Data {
pm.paths = slices.Delete(pm.paths, i, i+1)
pm.retireConnID(path.id)
break
}
}
}
func addrsEqual(addr1, addr2 net.Addr) bool {
if addr1 == nil || addr2 == nil {
return false
}
a1, ok1 := addr1.(*net.UDPAddr)
a2, ok2 := addr2.(*net.UDPAddr)
if ok1 && ok2 {
return a1.IP.Equal(a2.IP) && a1.Port == a2.Port
}
return addr1.String() == addr2.String()
}
package quic
import (
"context"
"crypto/rand"
"errors"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
var (
// ErrPathClosed is returned when trying to switch to a path that has been closed.
ErrPathClosed = errors.New("path closed")
// ErrPathNotValidated is returned when trying to use a path before path probing has completed.
ErrPathNotValidated = errors.New("path not yet validated")
)
var errPathDoesNotExist = errors.New("path does not exist")
// Path is a network path.
type Path struct {
id pathID
pathManager *pathManagerOutgoing
tr *Transport
initialRTT time.Duration
enablePath func()
validated atomic.Bool
abandon chan struct{}
}
func (p *Path) Probe(ctx context.Context) error {
path := p.pathManager.addPath(p, p.enablePath)
p.pathManager.enqueueProbe(p)
nextProbeDur := p.initialRTT
var timer *time.Timer
var timerChan <-chan time.Time
for {
select {
case <-ctx.Done():
return context.Cause(ctx)
case <-path.Validated():
p.validated.Store(true)
return nil
case <-timerChan:
nextProbeDur *= 2 // exponential backoff
p.pathManager.enqueueProbe(p)
case <-path.ProbeSent():
case <-p.abandon:
return ErrPathClosed
}
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(nextProbeDur)
timerChan = timer.C
}
}
// Switch switches the QUIC connection to this path.
// It immediately stops sending on the old path, and sends on this new path.
func (p *Path) Switch() error {
if err := p.pathManager.switchToPath(p.id); err != nil {
switch {
case errors.Is(err, ErrPathNotValidated):
return err
case errors.Is(err, errPathDoesNotExist) && !p.validated.Load():
select {
case <-p.abandon:
return ErrPathClosed
default:
return ErrPathNotValidated
}
default:
return ErrPathClosed
}
}
return nil
}
// Close abandons a path.
// It is not possible to close the path that’s currently active.
// After closing, it is not possible to probe this path again.
func (p *Path) Close() error {
select {
case <-p.abandon:
return nil
default:
}
if err := p.pathManager.removePath(p.id); err != nil {
return err
}
close(p.abandon)
return nil
}
type pathOutgoing struct {
pathChallenges [][8]byte // length is implicitly limited by exponential backoff
tr *Transport
isValidated bool
probeSent chan struct{} // receives when a PATH_CHALLENGE is sent
validated chan struct{} // closed when the path the corresponding PATH_RESPONSE is received
enablePath func()
}
func (p *pathOutgoing) ProbeSent() <-chan struct{} { return p.probeSent }
func (p *pathOutgoing) Validated() <-chan struct{} { return p.validated }
type pathManagerOutgoing struct {
getConnID func(pathID) (_ protocol.ConnectionID, ok bool)
retireConnID func(pathID)
scheduleSending func()
mx sync.Mutex
activePath pathID
pathsToProbe []pathID
paths map[pathID]*pathOutgoing
nextPathID pathID
pathToSwitchTo *pathOutgoing
}
// newPathManagerOutgoing creates a new pathManagerOutgoing object. This
// function must be side-effect free as it may be called multiple times for a
// single connection.
func newPathManagerOutgoing(
getConnID func(pathID) (_ protocol.ConnectionID, ok bool),
retireConnID func(pathID),
scheduleSending func(),
) *pathManagerOutgoing {
return &pathManagerOutgoing{
activePath: 0, // at initialization time, we're guaranteed to be using the handshake path
nextPathID: 1,
getConnID: getConnID,
retireConnID: retireConnID,
scheduleSending: scheduleSending,
paths: make(map[pathID]*pathOutgoing, 4),
}
}
func (pm *pathManagerOutgoing) addPath(p *Path, enablePath func()) *pathOutgoing {
pm.mx.Lock()
defer pm.mx.Unlock()
// path might already exist, and just being re-probed
if existingPath, ok := pm.paths[p.id]; ok {
existingPath.validated = make(chan struct{})
return existingPath
}
path := &pathOutgoing{
tr: p.tr,
probeSent: make(chan struct{}, 1),
validated: make(chan struct{}),
enablePath: enablePath,
}
pm.paths[p.id] = path
return path
}
func (pm *pathManagerOutgoing) enqueueProbe(p *Path) {
pm.mx.Lock()
pm.pathsToProbe = append(pm.pathsToProbe, p.id)
pm.mx.Unlock()
pm.scheduleSending()
}
func (pm *pathManagerOutgoing) removePath(id pathID) error {
if err := pm.removePathImpl(id); err != nil {
return err
}
pm.scheduleSending()
return nil
}
func (pm *pathManagerOutgoing) removePathImpl(id pathID) error {
pm.mx.Lock()
defer pm.mx.Unlock()
if id == pm.activePath {
return errors.New("cannot close active path")
}
p, ok := pm.paths[id]
if !ok {
return nil
}
if len(p.pathChallenges) > 0 {
pm.retireConnID(id)
}
delete(pm.paths, id)
return nil
}
func (pm *pathManagerOutgoing) switchToPath(id pathID) error {
pm.mx.Lock()
defer pm.mx.Unlock()
p, ok := pm.paths[id]
if !ok {
return errPathDoesNotExist
}
if !p.isValidated {
return ErrPathNotValidated
}
pm.pathToSwitchTo = p
pm.activePath = id
return nil
}
func (pm *pathManagerOutgoing) NewPath(t *Transport, initialRTT time.Duration, enablePath func()) *Path {
pm.mx.Lock()
defer pm.mx.Unlock()
id := pm.nextPathID
pm.nextPathID++
return &Path{
pathManager: pm,
id: id,
tr: t,
enablePath: enablePath,
initialRTT: initialRTT,
abandon: make(chan struct{}),
}
}
func (pm *pathManagerOutgoing) NextPathToProbe() (_ protocol.ConnectionID, _ ackhandler.Frame, _ *Transport, hasPath bool) {
pm.mx.Lock()
defer pm.mx.Unlock()
var p *pathOutgoing
id := invalidPathID
for _, pID := range pm.pathsToProbe {
var ok bool
p, ok = pm.paths[pID]
if ok {
id = pID
break
}
// if the path doesn't exist in the map, it might have been abandoned
pm.pathsToProbe = pm.pathsToProbe[1:]
}
if id == invalidPathID {
return protocol.ConnectionID{}, ackhandler.Frame{}, nil, false
}
connID, ok := pm.getConnID(id)
if !ok {
return protocol.ConnectionID{}, ackhandler.Frame{}, nil, false
}
var b [8]byte
_, _ = rand.Read(b[:])
p.pathChallenges = append(p.pathChallenges, b)
pm.pathsToProbe = pm.pathsToProbe[1:]
p.enablePath()
select {
case p.probeSent <- struct{}{}:
default:
}
frame := ackhandler.Frame{
Frame: &wire.PathChallengeFrame{Data: b},
Handler: (*pathManagerOutgoingAckHandler)(pm),
}
return connID, frame, p.tr, true
}
func (pm *pathManagerOutgoing) HandlePathResponseFrame(f *wire.PathResponseFrame) {
pm.mx.Lock()
defer pm.mx.Unlock()
for _, p := range pm.paths {
if slices.Contains(p.pathChallenges, f.Data) {
// path validated
if !p.isValidated {
// make sure that duplicate PATH_RESPONSE frames are ignored
p.isValidated = true
p.pathChallenges = nil
close(p.validated)
}
break
}
}
}
func (pm *pathManagerOutgoing) ShouldSwitchPath() (*Transport, bool) {
pm.mx.Lock()
defer pm.mx.Unlock()
if pm.pathToSwitchTo == nil {
return nil, false
}
p := pm.pathToSwitchTo
pm.pathToSwitchTo = nil
return p.tr, true
}
type pathManagerOutgoingAckHandler pathManagerOutgoing
var _ ackhandler.FrameHandler = &pathManagerOutgoingAckHandler{}
// OnAcked is called when the PATH_CHALLENGE is acked.
// This doesn't validate the path, only receiving the PATH_RESPONSE does.
func (pm *pathManagerOutgoingAckHandler) OnAcked(wire.Frame) {}
func (pm *pathManagerOutgoingAckHandler) OnLost(wire.Frame) {}
package qlog
import (
"fmt"
"net/netip"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
func milliseconds(dur time.Duration) float64 { return float64(dur.Nanoseconds()) / 1e6 }
type encoderHelper struct {
enc *jsontext.Encoder
err error
}
func (h *encoderHelper) WriteToken(t jsontext.Token) {
if h.err != nil {
return
}
h.err = h.enc.WriteToken(t)
}
type versions []Version
func (v versions) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginArray)
for _, e := range v {
h.WriteToken(jsontext.String(fmt.Sprintf("%x", uint32(e))))
}
h.WriteToken(jsontext.EndArray)
return h.err
}
type RawInfo struct {
Length int // full packet length, including header and AEAD authentication tag
PayloadLength int // length of the packet payload, excluding AEAD tag
}
func (i RawInfo) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Uint(uint64(i.Length)))
if i.PayloadLength != 0 {
h.WriteToken(jsontext.String("payload_length"))
h.WriteToken(jsontext.Uint(uint64(i.PayloadLength)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type PathEndpointInfo struct {
IPv4 netip.AddrPort
IPv6 netip.AddrPort
}
func (p PathEndpointInfo) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if p.IPv4.IsValid() {
h.WriteToken(jsontext.String("ip_v4"))
h.WriteToken(jsontext.String(p.IPv4.Addr().String()))
h.WriteToken(jsontext.String("port_v4"))
h.WriteToken(jsontext.Int(int64(p.IPv4.Port())))
}
if p.IPv6.IsValid() {
h.WriteToken(jsontext.String("ip_v6"))
h.WriteToken(jsontext.String(p.IPv6.Addr().String()))
h.WriteToken(jsontext.String("port_v6"))
h.WriteToken(jsontext.Int(int64(p.IPv6.Port())))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type StartedConnection struct {
Local PathEndpointInfo
Remote PathEndpointInfo
}
func (e StartedConnection) Name() string { return "transport:connection_started" }
func (e StartedConnection) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("local"))
if err := e.Local.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("remote"))
if err := e.Remote.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type VersionInformation struct {
ClientVersions, ServerVersions []Version
ChosenVersion Version
}
func (e VersionInformation) Name() string { return "transport:version_information" }
func (e VersionInformation) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if len(e.ClientVersions) > 0 {
h.WriteToken(jsontext.String("client_versions"))
if err := versions(e.ClientVersions).encode(enc); err != nil {
return err
}
}
if len(e.ServerVersions) > 0 {
h.WriteToken(jsontext.String("server_versions"))
if err := versions(e.ServerVersions).encode(enc); err != nil {
return err
}
}
h.WriteToken(jsontext.String("chosen_version"))
h.WriteToken(jsontext.String(fmt.Sprintf("%x", uint32(e.ChosenVersion))))
h.WriteToken(jsontext.EndObject)
return h.err
}
type ConnectionClosed struct {
Initiator Initiator
ConnectionError *TransportErrorCode
ApplicationError *ApplicationErrorCode
Reason string
Trigger ConnectionCloseTrigger
}
func (e ConnectionClosed) Name() string { return "transport:connection_closed" }
func (e ConnectionClosed) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("initiator"))
h.WriteToken(jsontext.String(string(e.Initiator)))
if e.ConnectionError != nil {
h.WriteToken(jsontext.String("connection_error"))
if e.ConnectionError.IsCryptoError() {
h.WriteToken(jsontext.String(fmt.Sprintf("crypto_error_%#x", uint16(*e.ConnectionError))))
} else {
switch *e.ConnectionError {
case qerr.NoError:
h.WriteToken(jsontext.String("no_error"))
case qerr.InternalError:
h.WriteToken(jsontext.String("internal_error"))
case qerr.ConnectionRefused:
h.WriteToken(jsontext.String("connection_refused"))
case qerr.FlowControlError:
h.WriteToken(jsontext.String("flow_control_error"))
case qerr.StreamLimitError:
h.WriteToken(jsontext.String("stream_limit_error"))
case qerr.StreamStateError:
h.WriteToken(jsontext.String("stream_state_error"))
case qerr.FinalSizeError:
h.WriteToken(jsontext.String("final_size_error"))
case qerr.FrameEncodingError:
h.WriteToken(jsontext.String("frame_encoding_error"))
case qerr.TransportParameterError:
h.WriteToken(jsontext.String("transport_parameter_error"))
case qerr.ConnectionIDLimitError:
h.WriteToken(jsontext.String("connection_id_limit_error"))
case qerr.ProtocolViolation:
h.WriteToken(jsontext.String("protocol_violation"))
case qerr.InvalidToken:
h.WriteToken(jsontext.String("invalid_token"))
case qerr.ApplicationErrorErrorCode:
h.WriteToken(jsontext.String("application_error"))
case qerr.CryptoBufferExceeded:
h.WriteToken(jsontext.String("crypto_buffer_exceeded"))
case qerr.KeyUpdateError:
h.WriteToken(jsontext.String("key_update_error"))
case qerr.AEADLimitReached:
h.WriteToken(jsontext.String("aead_limit_reached"))
case qerr.NoViablePathError:
h.WriteToken(jsontext.String("no_viable_path"))
default:
h.WriteToken(jsontext.String("unknown"))
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.Uint(uint64(*e.ConnectionError)))
}
}
}
if e.ApplicationError != nil {
h.WriteToken(jsontext.String("application_error"))
h.WriteToken(jsontext.String("unknown"))
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.Uint(uint64(*e.ApplicationError)))
}
if e.ConnectionError != nil || e.ApplicationError != nil {
h.WriteToken(jsontext.String("reason"))
h.WriteToken(jsontext.String(e.Reason))
}
if e.Trigger != "" {
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(string(e.Trigger)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type PacketSent struct {
Header PacketHeader
Raw RawInfo
DatagramID DatagramID
Frames []Frame
ECN ECN
IsCoalesced bool
Trigger string
SupportedVersions []Version
}
func (e PacketSent) Name() string { return "transport:packet_sent" }
func (e PacketSent) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
if e.DatagramID != 0 {
h.WriteToken(jsontext.String("datagram_id"))
h.WriteToken(jsontext.Uint(uint64(e.DatagramID)))
}
if len(e.Frames) > 0 {
h.WriteToken(jsontext.String("frames"))
if err := frames(e.Frames).encode(enc); err != nil {
return err
}
}
if e.IsCoalesced {
h.WriteToken(jsontext.String("is_coalesced"))
h.WriteToken(jsontext.True)
}
if e.ECN != ECNUnsupported {
h.WriteToken(jsontext.String("ecn"))
h.WriteToken(jsontext.String(string(e.ECN)))
}
if e.Trigger != "" {
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(e.Trigger))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type PacketReceived struct {
Header PacketHeader
Raw RawInfo
DatagramID DatagramID
Frames []Frame
ECN ECN
IsCoalesced bool
Trigger string
}
func (e PacketReceived) Name() string { return "transport:packet_received" }
func (e PacketReceived) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
if e.DatagramID != 0 {
h.WriteToken(jsontext.String("datagram_id"))
h.WriteToken(jsontext.Uint(uint64(e.DatagramID)))
}
if len(e.Frames) > 0 {
h.WriteToken(jsontext.String("frames"))
if err := frames(e.Frames).encode(enc); err != nil {
return err
}
}
if e.IsCoalesced {
h.WriteToken(jsontext.String("is_coalesced"))
h.WriteToken(jsontext.True)
}
if e.ECN != ECNUnsupported {
h.WriteToken(jsontext.String("ecn"))
h.WriteToken(jsontext.String(string(e.ECN)))
}
if e.Trigger != "" {
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(e.Trigger))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type VersionNegotiationReceived struct {
Header PacketHeaderVersionNegotiation
SupportedVersions []Version
}
func (e VersionNegotiationReceived) Name() string { return "transport:packet_received" }
func (e VersionNegotiationReceived) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("supported_versions"))
if err := versions(e.SupportedVersions).encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type VersionNegotiationSent struct {
Header PacketHeaderVersionNegotiation
SupportedVersions []Version
}
func (e VersionNegotiationSent) Name() string { return "transport:packet_sent" }
func (e VersionNegotiationSent) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("supported_versions"))
if err := versions(e.SupportedVersions).encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type PacketBuffered struct {
Header PacketHeader
Raw RawInfo
DatagramID DatagramID
}
func (e PacketBuffered) Name() string { return "transport:packet_buffered" }
func (e PacketBuffered) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
if e.DatagramID != 0 {
h.WriteToken(jsontext.String("datagram_id"))
h.WriteToken(jsontext.Uint(uint64(e.DatagramID)))
}
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String("keys_unavailable"))
h.WriteToken(jsontext.EndObject)
return h.err
}
// PacketDropped is the transport:packet_dropped event.
type PacketDropped struct {
Header PacketHeader
Raw RawInfo
DatagramID DatagramID
Trigger PacketDropReason
}
func (e PacketDropped) Name() string { return "transport:packet_dropped" }
func (e PacketDropped) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("raw"))
if err := e.Raw.encode(enc); err != nil {
return err
}
if e.DatagramID != 0 {
h.WriteToken(jsontext.String("datagram_id"))
h.WriteToken(jsontext.Uint(uint64(e.DatagramID)))
}
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(string(e.Trigger)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type MTUUpdated struct {
Value int
Done bool
}
func (e MTUUpdated) Name() string { return "recovery:mtu_updated" }
func (e MTUUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("mtu"))
h.WriteToken(jsontext.Uint(uint64(e.Value)))
h.WriteToken(jsontext.String("done"))
h.WriteToken(jsontext.Bool(e.Done))
h.WriteToken(jsontext.EndObject)
return h.err
}
// MetricsUpdated logs RTT and congestion metrics as defined in the
// recovery:metrics_updated event.
// The PTO count is logged via PTOCountUpdated.
type MetricsUpdated struct {
MinRTT time.Duration
SmoothedRTT time.Duration
LatestRTT time.Duration
RTTVariance time.Duration
CongestionWindow int
BytesInFlight int
PacketsInFlight int
}
func (e MetricsUpdated) Name() string { return "recovery:metrics_updated" }
func (e MetricsUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if e.MinRTT != 0 {
h.WriteToken(jsontext.String("min_rtt"))
h.WriteToken(jsontext.Float(milliseconds(e.MinRTT)))
}
if e.SmoothedRTT != 0 {
h.WriteToken(jsontext.String("smoothed_rtt"))
h.WriteToken(jsontext.Float(milliseconds(e.SmoothedRTT)))
}
if e.LatestRTT != 0 {
h.WriteToken(jsontext.String("latest_rtt"))
h.WriteToken(jsontext.Float(milliseconds(e.LatestRTT)))
}
if e.RTTVariance != 0 {
h.WriteToken(jsontext.String("rtt_variance"))
h.WriteToken(jsontext.Float(milliseconds(e.RTTVariance)))
}
if e.CongestionWindow != 0 {
h.WriteToken(jsontext.String("congestion_window"))
h.WriteToken(jsontext.Uint(uint64(e.CongestionWindow)))
}
if e.BytesInFlight != 0 {
h.WriteToken(jsontext.String("bytes_in_flight"))
h.WriteToken(jsontext.Uint(uint64(e.BytesInFlight)))
}
if e.PacketsInFlight != 0 {
h.WriteToken(jsontext.String("packets_in_flight"))
h.WriteToken(jsontext.Uint(uint64(e.PacketsInFlight)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
// PTOCountUpdated logs the pto_count value of the
// recovery:metrics_updated event.
type PTOCountUpdated struct {
PTOCount uint32
}
func (e PTOCountUpdated) Name() string { return "recovery:metrics_updated" }
func (e PTOCountUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("pto_count"))
h.WriteToken(jsontext.Uint(uint64(e.PTOCount)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type PacketLost struct {
Header PacketHeader
Trigger PacketLossReason
}
func (e PacketLost) Name() string { return "recovery:packet_lost" }
func (e PacketLost) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("header"))
if err := e.Header.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(string(e.Trigger)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type SpuriousLoss struct {
EncryptionLevel protocol.EncryptionLevel
PacketNumber protocol.PacketNumber
PacketReordering uint64
TimeReordering time.Duration
}
func (e SpuriousLoss) Name() string { return "recovery:spurious_loss" }
func (e SpuriousLoss) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("packet_number_space"))
h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncryptionLevel)))
h.WriteToken(jsontext.String("packet_number"))
h.WriteToken(jsontext.Uint(uint64(e.PacketNumber)))
h.WriteToken(jsontext.String("reordering_packets"))
h.WriteToken(jsontext.Uint(e.PacketReordering))
h.WriteToken(jsontext.String("reordering_time"))
h.WriteToken(jsontext.Float(milliseconds(e.TimeReordering)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type KeyUpdated struct {
Trigger KeyUpdateTrigger
KeyType KeyType
KeyPhase KeyPhase // only set for 1-RTT keys
// we don't log the keys here, so we don't need `old` and `new`.
}
func (e KeyUpdated) Name() string { return "security:key_updated" }
func (e KeyUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(string(e.Trigger)))
h.WriteToken(jsontext.String("key_type"))
h.WriteToken(jsontext.String(string(e.KeyType)))
if e.KeyType == KeyTypeClient1RTT || e.KeyType == KeyTypeServer1RTT {
h.WriteToken(jsontext.String("key_phase"))
h.WriteToken(jsontext.Uint(uint64(e.KeyPhase)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type KeyDiscarded struct {
KeyType KeyType
KeyPhase KeyPhase // only set for 1-RTT keys
}
func (e KeyDiscarded) Name() string { return "security:key_discarded" }
func (e KeyDiscarded) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if e.KeyType != KeyTypeClient1RTT && e.KeyType != KeyTypeServer1RTT {
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String("tls"))
}
h.WriteToken(jsontext.String("key_type"))
h.WriteToken(jsontext.String(string(e.KeyType)))
if e.KeyType == KeyTypeClient1RTT || e.KeyType == KeyTypeServer1RTT {
h.WriteToken(jsontext.String("key_phase"))
h.WriteToken(jsontext.Uint(uint64(e.KeyPhase)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type ParametersSet struct {
Restore bool
Initiator Initiator
SentBy protocol.Perspective
OriginalDestinationConnectionID protocol.ConnectionID
InitialSourceConnectionID protocol.ConnectionID
RetrySourceConnectionID *protocol.ConnectionID
StatelessResetToken *protocol.StatelessResetToken
DisableActiveMigration bool
MaxIdleTimeout time.Duration
MaxUDPPayloadSize protocol.ByteCount
AckDelayExponent uint8
MaxAckDelay time.Duration
ActiveConnectionIDLimit uint64
InitialMaxData protocol.ByteCount
InitialMaxStreamDataBidiLocal protocol.ByteCount
InitialMaxStreamDataBidiRemote protocol.ByteCount
InitialMaxStreamDataUni protocol.ByteCount
InitialMaxStreamsBidi int64
InitialMaxStreamsUni int64
PreferredAddress *PreferredAddress
MaxDatagramFrameSize protocol.ByteCount
EnableResetStreamAt bool
}
func (e ParametersSet) Name() string {
if e.Restore {
return "transport:parameters_restored"
}
return "transport:parameters_set"
}
func (e ParametersSet) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if !e.Restore {
h.WriteToken(jsontext.String("initiator"))
h.WriteToken(jsontext.String(string(e.Initiator)))
if e.SentBy == protocol.PerspectiveServer {
h.WriteToken(jsontext.String("original_destination_connection_id"))
h.WriteToken(jsontext.String(e.OriginalDestinationConnectionID.String()))
if e.StatelessResetToken != nil {
h.WriteToken(jsontext.String("stateless_reset_token"))
h.WriteToken(jsontext.String(fmt.Sprintf("%x", e.StatelessResetToken[:])))
}
if e.RetrySourceConnectionID != nil {
h.WriteToken(jsontext.String("retry_source_connection_id"))
h.WriteToken(jsontext.String((*e.RetrySourceConnectionID).String()))
}
}
h.WriteToken(jsontext.String("initial_source_connection_id"))
h.WriteToken(jsontext.String(e.InitialSourceConnectionID.String()))
}
h.WriteToken(jsontext.String("disable_active_migration"))
h.WriteToken(jsontext.Bool(e.DisableActiveMigration))
if e.MaxIdleTimeout != 0 {
h.WriteToken(jsontext.String("max_idle_timeout"))
h.WriteToken(jsontext.Float(milliseconds(e.MaxIdleTimeout)))
}
if e.MaxUDPPayloadSize != 0 {
h.WriteToken(jsontext.String("max_udp_payload_size"))
h.WriteToken(jsontext.Int(int64(e.MaxUDPPayloadSize)))
}
if e.AckDelayExponent != 0 {
h.WriteToken(jsontext.String("ack_delay_exponent"))
h.WriteToken(jsontext.Uint(uint64(e.AckDelayExponent)))
}
if e.MaxAckDelay != 0 {
h.WriteToken(jsontext.String("max_ack_delay"))
h.WriteToken(jsontext.Float(milliseconds(e.MaxAckDelay)))
}
if e.ActiveConnectionIDLimit != 0 {
h.WriteToken(jsontext.String("active_connection_id_limit"))
h.WriteToken(jsontext.Uint(e.ActiveConnectionIDLimit))
}
if e.InitialMaxData != 0 {
h.WriteToken(jsontext.String("initial_max_data"))
h.WriteToken(jsontext.Int(int64(e.InitialMaxData)))
}
if e.InitialMaxStreamDataBidiLocal != 0 {
h.WriteToken(jsontext.String("initial_max_stream_data_bidi_local"))
h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiLocal)))
}
if e.InitialMaxStreamDataBidiRemote != 0 {
h.WriteToken(jsontext.String("initial_max_stream_data_bidi_remote"))
h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiRemote)))
}
if e.InitialMaxStreamDataUni != 0 {
h.WriteToken(jsontext.String("initial_max_stream_data_uni"))
h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataUni)))
}
if e.InitialMaxStreamsBidi != 0 {
h.WriteToken(jsontext.String("initial_max_streams_bidi"))
h.WriteToken(jsontext.Int(e.InitialMaxStreamsBidi))
}
if e.InitialMaxStreamsUni != 0 {
h.WriteToken(jsontext.String("initial_max_streams_uni"))
h.WriteToken(jsontext.Int(e.InitialMaxStreamsUni))
}
if e.PreferredAddress != nil {
h.WriteToken(jsontext.String("preferred_address"))
if err := e.PreferredAddress.encode(enc); err != nil {
return err
}
}
if e.MaxDatagramFrameSize != protocol.InvalidByteCount {
h.WriteToken(jsontext.String("max_datagram_frame_size"))
h.WriteToken(jsontext.Int(int64(e.MaxDatagramFrameSize)))
}
if e.EnableResetStreamAt {
h.WriteToken(jsontext.String("reset_stream_at"))
h.WriteToken(jsontext.True)
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type PreferredAddress struct {
IPv4, IPv6 netip.AddrPort
ConnectionID protocol.ConnectionID
StatelessResetToken protocol.StatelessResetToken
}
func (a PreferredAddress) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if a.IPv4.IsValid() {
h.WriteToken(jsontext.String("ip_v4"))
h.WriteToken(jsontext.String(a.IPv4.Addr().String()))
h.WriteToken(jsontext.String("port_v4"))
h.WriteToken(jsontext.Uint(uint64(a.IPv4.Port())))
}
if a.IPv6.IsValid() {
h.WriteToken(jsontext.String("ip_v6"))
h.WriteToken(jsontext.String(a.IPv6.Addr().String()))
h.WriteToken(jsontext.String("port_v6"))
h.WriteToken(jsontext.Uint(uint64(a.IPv6.Port())))
}
h.WriteToken(jsontext.String("connection_id"))
h.WriteToken(jsontext.String(a.ConnectionID.String()))
h.WriteToken(jsontext.String("stateless_reset_token"))
h.WriteToken(jsontext.String(fmt.Sprintf("%x", a.StatelessResetToken)))
h.WriteToken(jsontext.EndObject)
return h.err
}
type LossTimerUpdated struct {
Type LossTimerUpdateType
TimerType TimerType
EncLevel EncryptionLevel
Time time.Time
}
func (e LossTimerUpdated) Name() string { return "recovery:loss_timer_updated" }
func (e LossTimerUpdated) Encode(enc *jsontext.Encoder, t time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("event_type"))
h.WriteToken(jsontext.String(string(e.Type)))
h.WriteToken(jsontext.String("timer_type"))
h.WriteToken(jsontext.String(string(e.TimerType)))
h.WriteToken(jsontext.String("packet_number_space"))
h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncLevel)))
if e.Type == LossTimerUpdateTypeSet {
h.WriteToken(jsontext.String("delta"))
h.WriteToken(jsontext.Float(milliseconds(e.Time.Sub(t))))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type eventLossTimerCanceled struct{}
func (e eventLossTimerCanceled) Name() string { return "recovery:loss_timer_updated" }
func (e eventLossTimerCanceled) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("event_type"))
h.WriteToken(jsontext.String("cancelled"))
h.WriteToken(jsontext.EndObject)
return h.err
}
type CongestionStateUpdated struct {
State CongestionState
}
func (e CongestionStateUpdated) Name() string { return "recovery:congestion_state_updated" }
func (e CongestionStateUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("new"))
h.WriteToken(jsontext.String(e.State.String()))
h.WriteToken(jsontext.EndObject)
return h.err
}
type ECNStateUpdated struct {
State ECNState
Trigger string
}
func (e ECNStateUpdated) Name() string { return "recovery:ecn_state_updated" }
func (e ECNStateUpdated) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("new"))
h.WriteToken(jsontext.String(string(e.State)))
if e.Trigger != "" {
h.WriteToken(jsontext.String("trigger"))
h.WriteToken(jsontext.String(e.Trigger))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type ALPNInformation struct {
ChosenALPN string
}
func (e ALPNInformation) Name() string { return "transport:alpn_information" }
func (e ALPNInformation) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("chosen_alpn"))
h.WriteToken(jsontext.String(e.ChosenALPN))
h.WriteToken(jsontext.EndObject)
return h.err
}
// DebugEvent is a generic event that can be used to log arbitrary messages.
type DebugEvent struct {
EventName string
Message string
}
func (e DebugEvent) Name() string {
if e.EventName == "" {
return "transport:debug"
}
return fmt.Sprintf("transport:%s", e.EventName)
}
func (e DebugEvent) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("message"))
h.WriteToken(jsontext.String(e.Message))
h.WriteToken(jsontext.EndObject)
return h.err
}
package qlog
import (
"encoding/hex"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
type Frame struct {
Frame any
}
type frames []Frame
type (
// An AckFrame is an ACK frame.
AckFrame = wire.AckFrame
// A ConnectionCloseFrame is a CONNECTION_CLOSE frame.
ConnectionCloseFrame = wire.ConnectionCloseFrame
// A DataBlockedFrame is a DATA_BLOCKED frame.
DataBlockedFrame = wire.DataBlockedFrame
// A HandshakeDoneFrame is a HANDSHAKE_DONE frame.
HandshakeDoneFrame = wire.HandshakeDoneFrame
// A MaxDataFrame is a MAX_DATA frame.
MaxDataFrame = wire.MaxDataFrame
// A MaxStreamDataFrame is a MAX_STREAM_DATA frame.
MaxStreamDataFrame = wire.MaxStreamDataFrame
// A MaxStreamsFrame is a MAX_STREAMS_FRAME.
MaxStreamsFrame = wire.MaxStreamsFrame
// A NewConnectionIDFrame is a NEW_CONNECTION_ID frame.
NewConnectionIDFrame = wire.NewConnectionIDFrame
// A NewTokenFrame is a NEW_TOKEN frame.
NewTokenFrame = wire.NewTokenFrame
// A PathChallengeFrame is a PATH_CHALLENGE frame.
PathChallengeFrame = wire.PathChallengeFrame
// A PathResponseFrame is a PATH_RESPONSE frame.
PathResponseFrame = wire.PathResponseFrame
// A PingFrame is a PING frame.
PingFrame = wire.PingFrame
// A ResetStreamFrame is a RESET_STREAM frame.
ResetStreamFrame = wire.ResetStreamFrame
// A RetireConnectionIDFrame is a RETIRE_CONNECTION_ID frame.
RetireConnectionIDFrame = wire.RetireConnectionIDFrame
// A StopSendingFrame is a STOP_SENDING frame.
StopSendingFrame = wire.StopSendingFrame
// A StreamsBlockedFrame is a STREAMS_BLOCKED frame.
StreamsBlockedFrame = wire.StreamsBlockedFrame
// A StreamDataBlockedFrame is a STREAM_DATA_BLOCKED frame.
StreamDataBlockedFrame = wire.StreamDataBlockedFrame
// An AckFrequencyFrame is an ACK_FREQUENCY frame.
AckFrequencyFrame = wire.AckFrequencyFrame
// An ImmediateAckFrame is an IMMEDIATE_ACK frame.
ImmediateAckFrame = wire.ImmediateAckFrame
)
type AckRange = wire.AckRange
// A CryptoFrame is a CRYPTO frame.
type CryptoFrame struct {
Offset int64
Length int64
}
// A StreamFrame is a STREAM frame.
type StreamFrame struct {
StreamID StreamID
Offset int64
Length int64
Fin bool
}
// A DatagramFrame is a DATAGRAM frame.
type DatagramFrame struct {
Length int64
}
func (fs frames) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginArray)
for _, f := range fs {
if err := f.Encode(enc); err != nil {
return err
}
}
h.WriteToken(jsontext.EndArray)
return h.err
}
func (f Frame) Encode(enc *jsontext.Encoder) error {
switch frame := f.Frame.(type) {
case *PingFrame:
return encodePingFrame(enc, frame)
case *AckFrame:
return encodeAckFrame(enc, frame)
case *ResetStreamFrame:
return encodeResetStreamFrame(enc, frame)
case *StopSendingFrame:
return encodeStopSendingFrame(enc, frame)
case *CryptoFrame:
return encodeCryptoFrame(enc, frame)
case *NewTokenFrame:
return encodeNewTokenFrame(enc, frame)
case *StreamFrame:
return encodeStreamFrame(enc, frame)
case *MaxDataFrame:
return encodeMaxDataFrame(enc, frame)
case *MaxStreamDataFrame:
return encodeMaxStreamDataFrame(enc, frame)
case *MaxStreamsFrame:
return encodeMaxStreamsFrame(enc, frame)
case *DataBlockedFrame:
return encodeDataBlockedFrame(enc, frame)
case *StreamDataBlockedFrame:
return encodeStreamDataBlockedFrame(enc, frame)
case *StreamsBlockedFrame:
return encodeStreamsBlockedFrame(enc, frame)
case *NewConnectionIDFrame:
return encodeNewConnectionIDFrame(enc, frame)
case *RetireConnectionIDFrame:
return encodeRetireConnectionIDFrame(enc, frame)
case *PathChallengeFrame:
return encodePathChallengeFrame(enc, frame)
case *PathResponseFrame:
return encodePathResponseFrame(enc, frame)
case *ConnectionCloseFrame:
return encodeConnectionCloseFrame(enc, frame)
case *HandshakeDoneFrame:
return encodeHandshakeDoneFrame(enc, frame)
case *DatagramFrame:
return encodeDatagramFrame(enc, frame)
case *AckFrequencyFrame:
return encodeAckFrequencyFrame(enc, frame)
case *ImmediateAckFrame:
return encodeImmediateAckFrame(enc, frame)
default:
panic("unknown frame type")
}
}
func encodePingFrame(enc *jsontext.Encoder, _ *PingFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("ping"))
h.WriteToken(jsontext.EndObject)
return h.err
}
type ackRanges []wire.AckRange
func (ars ackRanges) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginArray)
for _, r := range ars {
if err := ackRange(r).encode(enc); err != nil {
return err
}
}
h.WriteToken(jsontext.EndArray)
return h.err
}
type ackRange wire.AckRange
func (ar ackRange) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginArray)
h.WriteToken(jsontext.Int(int64(ar.Smallest)))
if ar.Smallest != ar.Largest {
h.WriteToken(jsontext.Int(int64(ar.Largest)))
}
h.WriteToken(jsontext.EndArray)
return h.err
}
func encodeAckFrame(enc *jsontext.Encoder, f *AckFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("ack"))
if f.DelayTime > 0 {
h.WriteToken(jsontext.String("ack_delay"))
h.WriteToken(jsontext.Float(milliseconds(f.DelayTime)))
}
h.WriteToken(jsontext.String("acked_ranges"))
if err := ackRanges(f.AckRanges).encode(enc); err != nil {
return err
}
hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0
if hasECN {
h.WriteToken(jsontext.String("ect0"))
h.WriteToken(jsontext.Uint(f.ECT0))
h.WriteToken(jsontext.String("ect1"))
h.WriteToken(jsontext.Uint(f.ECT1))
h.WriteToken(jsontext.String("ce"))
h.WriteToken(jsontext.Uint(f.ECNCE))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeResetStreamFrame(enc *jsontext.Encoder, f *ResetStreamFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
if f.ReliableSize > 0 {
h.WriteToken(jsontext.String("reset_stream_at"))
} else {
h.WriteToken(jsontext.String("reset_stream"))
}
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Int(int64(f.StreamID)))
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.Int(int64(f.ErrorCode)))
h.WriteToken(jsontext.String("final_size"))
h.WriteToken(jsontext.Int(int64(f.FinalSize)))
if f.ReliableSize > 0 {
h.WriteToken(jsontext.String("reliable_size"))
h.WriteToken(jsontext.Int(int64(f.ReliableSize)))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeStopSendingFrame(enc *jsontext.Encoder, f *StopSendingFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("stop_sending"))
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Int(int64(f.StreamID)))
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.Int(int64(f.ErrorCode)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeCryptoFrame(enc *jsontext.Encoder, f *CryptoFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("crypto"))
h.WriteToken(jsontext.String("offset"))
h.WriteToken(jsontext.Int(f.Offset))
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Int(f.Length))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeNewTokenFrame(enc *jsontext.Encoder, f *NewTokenFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("new_token"))
h.WriteToken(jsontext.String("token"))
if err := (Token{Raw: f.Token}).encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeStreamFrame(enc *jsontext.Encoder, f *StreamFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("stream"))
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Int(int64(f.StreamID)))
h.WriteToken(jsontext.String("offset"))
h.WriteToken(jsontext.Int(f.Offset))
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Int(f.Length))
if f.Fin {
h.WriteToken(jsontext.String("fin"))
h.WriteToken(jsontext.True)
}
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeMaxDataFrame(enc *jsontext.Encoder, f *MaxDataFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("max_data"))
h.WriteToken(jsontext.String("maximum"))
h.WriteToken(jsontext.Int(int64(f.MaximumData)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeMaxStreamDataFrame(enc *jsontext.Encoder, f *MaxStreamDataFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("max_stream_data"))
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Int(int64(f.StreamID)))
h.WriteToken(jsontext.String("maximum"))
h.WriteToken(jsontext.Int(int64(f.MaximumStreamData)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeMaxStreamsFrame(enc *jsontext.Encoder, f *MaxStreamsFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("max_streams"))
h.WriteToken(jsontext.String("stream_type"))
h.WriteToken(jsontext.String(streamType(f.Type).String()))
h.WriteToken(jsontext.String("maximum"))
h.WriteToken(jsontext.Int(int64(f.MaxStreamNum)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeDataBlockedFrame(enc *jsontext.Encoder, f *DataBlockedFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("data_blocked"))
h.WriteToken(jsontext.String("limit"))
h.WriteToken(jsontext.Int(int64(f.MaximumData)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeStreamDataBlockedFrame(enc *jsontext.Encoder, f *StreamDataBlockedFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("stream_data_blocked"))
h.WriteToken(jsontext.String("stream_id"))
h.WriteToken(jsontext.Int(int64(f.StreamID)))
h.WriteToken(jsontext.String("limit"))
h.WriteToken(jsontext.Int(int64(f.MaximumStreamData)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeStreamsBlockedFrame(enc *jsontext.Encoder, f *StreamsBlockedFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("streams_blocked"))
h.WriteToken(jsontext.String("stream_type"))
h.WriteToken(jsontext.String(streamType(f.Type).String()))
h.WriteToken(jsontext.String("limit"))
h.WriteToken(jsontext.Int(int64(f.StreamLimit)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeNewConnectionIDFrame(enc *jsontext.Encoder, f *NewConnectionIDFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("new_connection_id"))
h.WriteToken(jsontext.String("sequence_number"))
h.WriteToken(jsontext.Uint(f.SequenceNumber))
h.WriteToken(jsontext.String("retire_prior_to"))
h.WriteToken(jsontext.Uint(f.RetirePriorTo))
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Int(int64(f.ConnectionID.Len())))
h.WriteToken(jsontext.String("connection_id"))
h.WriteToken(jsontext.String(f.ConnectionID.String()))
h.WriteToken(jsontext.String("stateless_reset_token"))
h.WriteToken(jsontext.String(hex.EncodeToString(f.StatelessResetToken[:])))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeRetireConnectionIDFrame(enc *jsontext.Encoder, f *RetireConnectionIDFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("retire_connection_id"))
h.WriteToken(jsontext.String("sequence_number"))
h.WriteToken(jsontext.Uint(f.SequenceNumber))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodePathChallengeFrame(enc *jsontext.Encoder, f *PathChallengeFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("path_challenge"))
h.WriteToken(jsontext.String("data"))
h.WriteToken(jsontext.String(hex.EncodeToString(f.Data[:])))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodePathResponseFrame(enc *jsontext.Encoder, f *PathResponseFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("path_response"))
h.WriteToken(jsontext.String("data"))
h.WriteToken(jsontext.String(hex.EncodeToString(f.Data[:])))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeConnectionCloseFrame(enc *jsontext.Encoder, f *ConnectionCloseFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("connection_close"))
h.WriteToken(jsontext.String("error_space"))
errorSpace := "transport"
if f.IsApplicationError {
errorSpace = "application"
}
h.WriteToken(jsontext.String(errorSpace))
errName := transportError(f.ErrorCode).String()
if len(errName) > 0 {
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.String(errName))
} else {
h.WriteToken(jsontext.String("error_code"))
h.WriteToken(jsontext.Uint(f.ErrorCode))
}
h.WriteToken(jsontext.String("raw_error_code"))
h.WriteToken(jsontext.Uint(f.ErrorCode))
h.WriteToken(jsontext.String("reason"))
h.WriteToken(jsontext.String(f.ReasonPhrase))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeHandshakeDoneFrame(enc *jsontext.Encoder, _ *HandshakeDoneFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("handshake_done"))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeDatagramFrame(enc *jsontext.Encoder, f *DatagramFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("datagram"))
h.WriteToken(jsontext.String("length"))
h.WriteToken(jsontext.Int(f.Length))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeAckFrequencyFrame(enc *jsontext.Encoder, f *AckFrequencyFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("ack_frequency"))
h.WriteToken(jsontext.String("sequence_number"))
h.WriteToken(jsontext.Uint(f.SequenceNumber))
h.WriteToken(jsontext.String("ack_eliciting_threshold"))
h.WriteToken(jsontext.Uint(f.AckElicitingThreshold))
h.WriteToken(jsontext.String("request_max_ack_delay"))
h.WriteToken(jsontext.Float(milliseconds(f.RequestMaxAckDelay)))
h.WriteToken(jsontext.String("reordering_threshold"))
h.WriteToken(jsontext.Int(int64(f.ReorderingThreshold)))
h.WriteToken(jsontext.EndObject)
return h.err
}
func encodeImmediateAckFrame(enc *jsontext.Encoder, _ *ImmediateAckFrame) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("frame_type"))
h.WriteToken(jsontext.String("immediate_ack"))
h.WriteToken(jsontext.EndObject)
return h.err
}
package qlog
import (
"encoding/hex"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
type Token struct {
Raw []byte
}
func (t Token) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("data"))
h.WriteToken(jsontext.String(hex.EncodeToString(t.Raw)))
h.WriteToken(jsontext.EndObject)
return h.err
}
// PacketHeader is a QUIC packet header.
type PacketHeader struct {
PacketType PacketType
KeyPhaseBit KeyPhaseBit
PacketNumber PacketNumber
Version Version
SrcConnectionID ConnectionID
DestConnectionID ConnectionID
Token *Token
}
func (h PacketHeader) encode(enc *jsontext.Encoder) error {
helper := encoderHelper{enc: enc}
helper.WriteToken(jsontext.BeginObject)
helper.WriteToken(jsontext.String("packet_type"))
helper.WriteToken(jsontext.String(string(h.PacketType)))
if h.PacketType != PacketTypeRetry && h.PacketType != PacketTypeVersionNegotiation && h.PacketType != "" &&
h.PacketNumber != protocol.InvalidPacketNumber {
helper.WriteToken(jsontext.String("packet_number"))
helper.WriteToken(jsontext.Int(int64(h.PacketNumber)))
}
if h.Version != 0 {
helper.WriteToken(jsontext.String("version"))
helper.WriteToken(jsontext.String(version(h.Version).String()))
}
if h.PacketType != PacketType1RTT {
helper.WriteToken(jsontext.String("scil"))
helper.WriteToken(jsontext.Int(int64(h.SrcConnectionID.Len())))
if h.SrcConnectionID.Len() > 0 {
helper.WriteToken(jsontext.String("scid"))
helper.WriteToken(jsontext.String(h.SrcConnectionID.String()))
}
}
helper.WriteToken(jsontext.String("dcil"))
helper.WriteToken(jsontext.Int(int64(h.DestConnectionID.Len())))
if h.DestConnectionID.Len() > 0 {
helper.WriteToken(jsontext.String("dcid"))
helper.WriteToken(jsontext.String(h.DestConnectionID.String()))
}
if h.KeyPhaseBit == KeyPhaseZero || h.KeyPhaseBit == KeyPhaseOne {
helper.WriteToken(jsontext.String("key_phase_bit"))
helper.WriteToken(jsontext.String(h.KeyPhaseBit.String()))
}
if h.Token != nil {
helper.WriteToken(jsontext.String("token"))
if err := h.Token.encode(enc); err != nil {
return err
}
}
helper.WriteToken(jsontext.EndObject)
return helper.err
}
type PacketHeaderVersionNegotiation struct {
SrcConnectionID ArbitraryLenConnectionID
DestConnectionID ArbitraryLenConnectionID
}
func (h PacketHeaderVersionNegotiation) encode(enc *jsontext.Encoder) error {
helper := encoderHelper{enc: enc}
helper.WriteToken(jsontext.BeginObject)
helper.WriteToken(jsontext.String("packet_type"))
helper.WriteToken(jsontext.String("version_negotiation"))
helper.WriteToken(jsontext.String("scil"))
helper.WriteToken(jsontext.Int(int64(h.SrcConnectionID.Len())))
helper.WriteToken(jsontext.String("scid"))
helper.WriteToken(jsontext.String(h.SrcConnectionID.String()))
helper.WriteToken(jsontext.String("dcil"))
helper.WriteToken(jsontext.Int(int64(h.DestConnectionID.Len())))
helper.WriteToken(jsontext.String("dcid"))
helper.WriteToken(jsontext.String(h.DestConnectionID.String()))
helper.WriteToken(jsontext.EndObject)
return helper.err
}
package qlog
import (
"bufio"
"context"
"fmt"
"log"
"os"
"slices"
"strings"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/qlogwriter"
)
// EventSchema is the qlog event schema for QUIC
const EventSchema = "urn:ietf:params:qlog:events:quic-12"
// DefaultConnectionTracer creates a qlog file in the qlog directory specified by the QLOGDIR environment variable.
// File names are <odcid>_<perspective>.sqlog.
// Returns nil if QLOGDIR is not set.
func DefaultConnectionTracer(_ context.Context, isClient bool, connID ConnectionID) qlogwriter.Trace {
return defaultConnectionTracerWithSchemas(isClient, connID, []string{EventSchema})
}
func DefaultConnectionTracerWithSchemas(_ context.Context, isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace {
if !slices.Contains(eventSchemas, EventSchema) {
eventSchemas = append([]string{EventSchema}, eventSchemas...)
}
return defaultConnectionTracerWithSchemas(isClient, connID, eventSchemas)
}
func defaultConnectionTracerWithSchemas(isClient bool, connID ConnectionID, eventSchemas []string) qlogwriter.Trace {
qlogDir := os.Getenv("QLOGDIR")
if qlogDir == "" {
return nil
}
if _, err := os.Stat(qlogDir); os.IsNotExist(err) {
if err := os.MkdirAll(qlogDir, 0o755); err != nil {
log.Fatalf("failed to create qlog dir %s: %v", qlogDir, err)
}
}
label := "server"
if isClient {
label = "client"
}
path := fmt.Sprintf("%s/%s_%s.sqlog", strings.TrimRight(qlogDir, "/"), connID, label)
f, err := os.Create(path)
if err != nil {
log.Printf("Failed to create qlog file %s: %s", path, err.Error())
return nil
}
fileSeq := qlogwriter.NewConnectionFileSeq(
utils.NewBufferedWriteCloser(bufio.NewWriter(f), f),
isClient,
connID,
eventSchemas,
)
go fileSeq.Run()
return fileSeq
}
package qlog
import (
"fmt"
"hash/crc32"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
)
type (
ConnectionID = protocol.ConnectionID
ArbitraryLenConnectionID = protocol.ArbitraryLenConnectionID
Version = protocol.Version
PacketNumber = protocol.PacketNumber
EncryptionLevel = protocol.EncryptionLevel
KeyPhaseBit = protocol.KeyPhaseBit
KeyPhase = protocol.KeyPhase
StreamID = protocol.StreamID
TransportErrorCode = qerr.TransportErrorCode
ApplicationErrorCode = qerr.ApplicationErrorCode
)
const (
// KeyPhaseZero is key phase bit 0
KeyPhaseZero = protocol.KeyPhaseZero
// KeyPhaseOne is key phase bit 1
KeyPhaseOne = protocol.KeyPhaseOne
)
// ECN represents the Explicit Congestion Notification value.
type ECN string
const (
// ECNUnsupported means that no ECN value was set / received
ECNUnsupported ECN = ""
// ECTNot is Not-ECT
ECTNot ECN = "Not-ECT"
// ECT0 is ECT(0)
ECT0 ECN = "ECT(0)"
// ECT1 is ECT(1)
ECT1 ECN = "ECT(1)"
// ECNCE is CE
ECNCE ECN = "CE"
)
type Initiator string
const (
InitiatorLocal Initiator = "local"
InitiatorRemote Initiator = "remote"
)
type streamType protocol.StreamType
func (s streamType) String() string {
switch protocol.StreamType(s) {
case protocol.StreamTypeUni:
return "unidirectional"
case protocol.StreamTypeBidi:
return "bidirectional"
default:
return "unknown stream type"
}
}
type version protocol.Version
func (v version) String() string {
return fmt.Sprintf("%x", uint32(v))
}
func encLevelToPacketNumberSpace(encLevel protocol.EncryptionLevel) string {
switch encLevel {
case protocol.EncryptionInitial:
return "initial"
case protocol.EncryptionHandshake:
return "handshake"
case protocol.Encryption0RTT, protocol.Encryption1RTT:
return "application_data"
default:
return "unknown encryption level"
}
}
// KeyType represents the type of cryptographic key used in QUIC connections.
type KeyType string
const (
// KeyTypeServerInitial represents the server's initial secret key.
KeyTypeServerInitial KeyType = "server_initial_secret"
// KeyTypeClientInitial represents the client's initial secret key.
KeyTypeClientInitial KeyType = "client_initial_secret"
// KeyTypeServerHandshake represents the server's handshake secret key.
KeyTypeServerHandshake KeyType = "server_handshake_secret"
// KeyTypeClientHandshake represents the client's handshake secret key.
KeyTypeClientHandshake KeyType = "client_handshake_secret"
// KeyTypeServer0RTT represents the server's 0-RTT secret key.
KeyTypeServer0RTT KeyType = "server_0rtt_secret"
// KeyTypeClient0RTT represents the client's 0-RTT secret key.
KeyTypeClient0RTT KeyType = "client_0rtt_secret"
// KeyTypeServer1RTT represents the server's 1-RTT secret key.
KeyTypeServer1RTT KeyType = "server_1rtt_secret"
// KeyTypeClient1RTT represents the client's 1-RTT secret key.
KeyTypeClient1RTT KeyType = "client_1rtt_secret"
)
// KeyUpdateTrigger describes what caused a key update event.
type KeyUpdateTrigger string
const (
// KeyUpdateTLS indicates the key update was triggered by TLS.
KeyUpdateTLS KeyUpdateTrigger = "tls"
// KeyUpdateRemote indicates the key update was triggered by the remote peer.
KeyUpdateRemote KeyUpdateTrigger = "remote_update"
// KeyUpdateLocal indicates the key update was triggered locally.
KeyUpdateLocal KeyUpdateTrigger = "local_update"
)
type transportError uint64
func (e transportError) String() string {
switch qerr.TransportErrorCode(e) {
case qerr.NoError:
return "no_error"
case qerr.InternalError:
return "internal_error"
case qerr.ConnectionRefused:
return "connection_refused"
case qerr.FlowControlError:
return "flow_control_error"
case qerr.StreamLimitError:
return "stream_limit_error"
case qerr.StreamStateError:
return "stream_state_error"
case qerr.FinalSizeError:
return "final_size_error"
case qerr.FrameEncodingError:
return "frame_encoding_error"
case qerr.TransportParameterError:
return "transport_parameter_error"
case qerr.ConnectionIDLimitError:
return "connection_id_limit_error"
case qerr.ProtocolViolation:
return "protocol_violation"
case qerr.InvalidToken:
return "invalid_token"
case qerr.ApplicationErrorErrorCode:
return "application_error"
case qerr.CryptoBufferExceeded:
return "crypto_buffer_exceeded"
case qerr.KeyUpdateError:
return "key_update_error"
case qerr.AEADLimitReached:
return "aead_limit_reached"
case qerr.NoViablePathError:
return "no_viable_path"
default:
return ""
}
}
type PacketType string
const (
// PacketTypeInitial represents an Initial packet
PacketTypeInitial PacketType = "initial"
// PacketTypeHandshake represents a Handshake packet
PacketTypeHandshake PacketType = "handshake"
// PacketTypeRetry represents a Retry packet
PacketTypeRetry PacketType = "retry"
// PacketType0RTT represents a 0-RTT packet
PacketType0RTT PacketType = "0RTT"
// PacketTypeVersionNegotiation represents a Version Negotiation packet
PacketTypeVersionNegotiation PacketType = "version_negotiation"
// PacketTypeStatelessReset represents a Stateless Reset packet
PacketTypeStatelessReset PacketType = "stateless_reset"
// PacketType1RTT represents a 1-RTT packet
PacketType1RTT PacketType = "1RTT"
// // PacketTypeNotDetermined represents a packet type that could not be determined
// PacketTypeNotDetermined packetType = ""
)
func EncryptionLevelToPacketType(l EncryptionLevel) PacketType {
switch l {
case protocol.EncryptionInitial:
return PacketTypeInitial
case protocol.EncryptionHandshake:
return PacketTypeHandshake
case protocol.Encryption0RTT:
return PacketType0RTT
case protocol.Encryption1RTT:
return PacketType1RTT
default:
panic(fmt.Sprintf("unknown encryption level: %d", l))
}
}
type PacketLossReason string
const (
// PacketLossReorderingThreshold is used when a packet is declared lost due to reordering threshold
PacketLossReorderingThreshold PacketLossReason = "reordering_threshold"
// PacketLossTimeThreshold is used when a packet is declared lost due to time threshold
PacketLossTimeThreshold PacketLossReason = "time_threshold"
)
type PacketDropReason string
const (
// PacketDropKeyUnavailable is used when a packet is dropped because keys are unavailable
PacketDropKeyUnavailable PacketDropReason = "key_unavailable"
// PacketDropUnknownConnectionID is used when a packet is dropped because the connection ID is unknown
PacketDropUnknownConnectionID PacketDropReason = "unknown_connection_id"
// PacketDropHeaderParseError is used when a packet is dropped because header parsing failed
PacketDropHeaderParseError PacketDropReason = "header_parse_error"
// PacketDropPayloadDecryptError is used when a packet is dropped because decrypting the payload failed
PacketDropPayloadDecryptError PacketDropReason = "payload_decrypt_error"
// PacketDropProtocolViolation is used when a packet is dropped due to a protocol violation
PacketDropProtocolViolation PacketDropReason = "protocol_violation"
// PacketDropDOSPrevention is used when a packet is dropped to mitigate a DoS attack
PacketDropDOSPrevention PacketDropReason = "dos_prevention"
// PacketDropUnsupportedVersion is used when a packet is dropped because the version is not supported
PacketDropUnsupportedVersion PacketDropReason = "unsupported_version"
// PacketDropUnexpectedPacket is used when an unexpected packet is received
PacketDropUnexpectedPacket PacketDropReason = "unexpected_packet"
// PacketDropUnexpectedSourceConnectionID is used when a packet with an unexpected source connection ID is received
PacketDropUnexpectedSourceConnectionID PacketDropReason = "unexpected_source_connection_id"
// PacketDropUnexpectedVersion is used when a packet with an unexpected version is received
PacketDropUnexpectedVersion PacketDropReason = "unexpected_version"
// PacketDropDuplicate is used when a duplicate packet is received
PacketDropDuplicate PacketDropReason = "duplicate"
)
type LossTimerUpdateType string
const (
LossTimerUpdateTypeSet LossTimerUpdateType = "set"
LossTimerUpdateTypeExpired LossTimerUpdateType = "expired"
LossTimerUpdateTypeCancelled LossTimerUpdateType = "cancelled"
)
type TimerType string
const (
// TimerTypeACK represents an ACK timer
TimerTypeACK TimerType = "ack"
// TimerTypePTO represents a PTO (Probe Timeout) timer
TimerTypePTO TimerType = "pto"
// TimerTypePathProbe represents a path probe timer
TimerTypePathProbe TimerType = "path_probe"
)
type CongestionState string
const (
// CongestionStateSlowStart is the slow start phase of Reno / Cubic
CongestionStateSlowStart CongestionState = "slow_start"
// CongestionStateCongestionAvoidance is the congestion avoidance phase of Reno / Cubic
CongestionStateCongestionAvoidance CongestionState = "congestion_avoidance"
// CongestionStateRecovery is the recovery phase of Reno / Cubic
CongestionStateRecovery CongestionState = "recovery"
// CongestionStateApplicationLimited means that the congestion controller is application limited
CongestionStateApplicationLimited CongestionState = "application_limited"
)
func (s CongestionState) String() string {
return string(s)
}
// ECNState is the state of the ECN state machine (see Appendix A.4 of RFC 9000)
type ECNState string
const (
// ECNStateTesting is the testing state
ECNStateTesting ECNState = "testing"
// ECNStateUnknown is the unknown state
ECNStateUnknown ECNState = "unknown"
// ECNStateFailed is the failed state
ECNStateFailed ECNState = "failed"
// ECNStateCapable is the capable state
ECNStateCapable ECNState = "capable"
)
type ConnectionCloseTrigger string
const (
// IdleTimeout indicates the connection was closed due to idle timeout
ConnectionCloseTriggerIdleTimeout ConnectionCloseTrigger = "idle_timeout"
// Application indicates the connection was closed by the application
ConnectionCloseTriggerApplication ConnectionCloseTrigger = "application"
// VersionMismatch indicates the connection was closed due to a QUIC version mismatch
ConnectionCloseTriggerVersionMismatch ConnectionCloseTrigger = "version_mismatch"
// StatelessReset indicates the connection was closed due to receiving a stateless reset from the peer
ConnectionCloseTriggerStatelessReset ConnectionCloseTrigger = "stateless_reset"
)
// DatagramID is a unique identifier for a datagram
type DatagramID uint32
// CalculateDatagramID computes a DatagramID for a given packet
func CalculateDatagramID(packet []byte) DatagramID {
return DatagramID(crc32.ChecksumIEEE(packet))
}
// Package jsontext provides a fast JSON encoder providing only the necessary features
// for qlog encoding. No efforts are made to add any features beyond qlog's requirements.
//
// The API aims to be compatible with the standard library's encoding/json/jsontext package.
package jsontext
import (
"fmt"
"io"
"strconv"
"unsafe"
)
type kind uint8
const (
kindString kind = iota
kindInt
kindUint
kindFloat
kindBool
kindNull
kindObjectStart
kindObjectEnd
kindArrayStart
kindArrayEnd
)
// Token represents a JSON token.
type Token struct {
kind kind
str string
i64 int64
u64 uint64
f64 float64
b bool
}
// String creates a string token.
func String(s string) Token {
return Token{kind: kindString, str: s}
}
// Int creates an int token.
func Int(i int64) Token {
return Token{kind: kindInt, i64: i}
}
// Uint creates a uint token.
func Uint(u uint64) Token {
return Token{kind: kindUint, u64: u}
}
// Float creates a float token.
func Float(f float64) Token {
return Token{kind: kindFloat, f64: f}
}
// Bool creates a bool token.
func Bool(b bool) Token {
return Token{kind: kindBool, b: b}
}
// Null is a null token.
var Null Token = Token{kind: kindNull}
// BeginObject is the begin object token.
var BeginObject Token = Token{kind: kindObjectStart}
// EndObject is the end object token.
var EndObject Token = Token{kind: kindObjectEnd}
// BeginArray is the begin array token.
var BeginArray Token = Token{kind: kindArrayStart}
// EndArray is the end array token.
var EndArray Token = Token{kind: kindArrayEnd}
// True is a true token.
var True Token = Bool(true)
// False is a false token.
var False Token = Bool(false)
var hexDigits = [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
var (
commaByte = []byte(",")
quoteByte = []byte(`"`)
colonByte = []byte(":")
trueByte = []byte("true")
falseByte = []byte("false")
nullByte = []byte("null")
openObjectByte = []byte("{")
closeObjectByte = []byte("}")
openArrayByte = []byte("[")
closeArrayByte = []byte("]")
newlineByte = []byte("\n")
escapeQuote = []byte(`\"`)
escapeBackslash = []byte(`\\`)
escapeBackspace = []byte(`\b`)
escapeFormfeed = []byte(`\f`)
escapeNewline = []byte(`\n`)
escapeCarriage = []byte(`\r`)
escapeTab = []byte(`\t`)
escapeUnicode = []byte(`\u00`)
)
type context struct {
isObject bool
needsComma bool
expectKey bool
}
// Encoder encodes JSON to an io.Writer.
type Encoder struct {
w io.Writer
buf [64]byte // scratch buffer for number formatting
stack []context
}
// NewEncoder creates a new Encoder.
func NewEncoder(w io.Writer) *Encoder {
stack := make([]context, 0, 8)
stack = append(stack, context{isObject: false, needsComma: false, expectKey: false})
return &Encoder{
w: w,
stack: stack,
}
}
// WriteToken writes a token to the encoder.
func (e *Encoder) WriteToken(t Token) error {
if len(e.stack) == 0 {
return fmt.Errorf("empty stack")
}
curr := &e.stack[len(e.stack)-1]
isClosing := t.kind == kindObjectEnd || t.kind == kindArrayEnd
if !isClosing && curr.needsComma {
if _, err := e.w.Write(commaByte); err != nil {
return err
}
curr.needsComma = false
}
var err error
switch t.kind {
case kindString:
data := stringToBytes(t.str)
needsEscape := false
for _, b := range data {
if b == '"' || b == '\\' || b < 0x20 {
needsEscape = true
break
}
}
if !needsEscape {
if _, err = e.w.Write(quoteByte); err != nil {
return err
}
if _, err = e.w.Write(data); err != nil {
return err
}
if _, err = e.w.Write(quoteByte); err != nil {
return err
}
} else {
if _, err = e.w.Write(quoteByte); err != nil {
return err
}
for i := 0; i < len(t.str); i++ {
c := t.str[i]
switch c {
case '"':
if _, err = e.w.Write(escapeQuote); err != nil {
return err
}
case '\\':
if _, err = e.w.Write(escapeBackslash); err != nil {
return err
}
case '\b':
if _, err = e.w.Write(escapeBackspace); err != nil {
return err
}
case '\f':
if _, err = e.w.Write(escapeFormfeed); err != nil {
return err
}
case '\n':
if _, err = e.w.Write(escapeNewline); err != nil {
return err
}
case '\r':
if _, err = e.w.Write(escapeCarriage); err != nil {
return err
}
case '\t':
if _, err = e.w.Write(escapeTab); err != nil {
return err
}
default:
if c < 0x20 {
if _, err = e.w.Write(escapeUnicode); err != nil {
return err
}
if _, err = e.w.Write([]byte{hexDigits[c>>4], hexDigits[c&0xf]}); err != nil {
return err
}
} else {
if _, err = e.w.Write([]byte{c}); err != nil {
return err
}
}
}
}
if _, err = e.w.Write(quoteByte); err != nil {
return err
}
}
if curr.isObject {
if curr.expectKey {
// key
if _, err = e.w.Write(colonByte); err != nil {
return err
}
curr.expectKey = false
return nil // do not call afterValue for keys
} else {
// value
e.afterValue()
}
} else {
e.afterValue()
}
case kindInt:
b := strconv.AppendInt(e.buf[:0], t.i64, 10)
if _, err = e.w.Write(b); err != nil {
return err
}
e.afterValue()
case kindUint:
b := strconv.AppendUint(e.buf[:0], t.u64, 10)
if _, err = e.w.Write(b); err != nil {
return err
}
e.afterValue()
case kindFloat:
b := strconv.AppendFloat(e.buf[:0], t.f64, 'g', -1, 64)
if _, err = e.w.Write(b); err != nil {
return err
}
e.afterValue()
case kindBool:
if t.b {
if _, err = e.w.Write(trueByte); err != nil {
return err
}
} else {
if _, err = e.w.Write(falseByte); err != nil {
return err
}
}
e.afterValue()
case kindNull:
if _, err = e.w.Write(nullByte); err != nil {
return err
}
e.afterValue()
case kindObjectStart:
if _, err = e.w.Write(openObjectByte); err != nil {
return err
}
e.stack = append(e.stack, context{isObject: true, needsComma: false, expectKey: true})
return nil
case kindObjectEnd:
if _, err = e.w.Write(closeObjectByte); err != nil {
return err
}
e.stack = e.stack[:len(e.stack)-1]
e.afterValue()
if len(e.stack) == 1 {
if _, err = e.w.Write(newlineByte); err != nil {
return err
}
}
return nil
case kindArrayStart:
if _, err = e.w.Write(openArrayByte); err != nil {
return err
}
e.stack = append(e.stack, context{isObject: false, needsComma: false, expectKey: false})
return nil
case kindArrayEnd:
if _, err = e.w.Write(closeArrayByte); err != nil {
return err
}
e.stack = e.stack[:len(e.stack)-1]
e.afterValue()
if len(e.stack) == 1 {
if _, err = e.w.Write(newlineByte); err != nil {
return err
}
}
return nil
default:
return fmt.Errorf("unknown token kind")
}
return err
}
// afterValue updates the state after encoding a value
func (e *Encoder) afterValue() {
if len(e.stack) > 1 {
curr := &e.stack[len(e.stack)-1]
curr.needsComma = true
if curr.isObject {
curr.expectKey = true
}
}
}
func stringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
package qlogwriter
import (
"runtime/debug"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
type ConnectionID = protocol.ConnectionID
// Setting of this only works when quic-go is used as a library.
// When building a binary from this repository, the version can be set using the following go build flag:
// -ldflags="-X github.com/quic-go/quic-go/qlogwriter.quicGoVersion=foobar"
var quicGoVersion = "(devel)"
func init() {
if quicGoVersion != "(devel)" { // variable set by ldflags
return
}
info, ok := debug.ReadBuildInfo()
if !ok { // no build info available. This happens when quic-go is not used as a library.
return
}
for _, d := range info.Deps {
if d.Path == "github.com/quic-go/quic-go" {
quicGoVersion = d.Version
if d.Replace != nil {
if len(d.Replace.Version) > 0 {
quicGoVersion = d.Version
} else {
quicGoVersion += " (replaced)"
}
}
break
}
}
}
type encoderHelper struct {
enc *jsontext.Encoder
err error
}
func (h *encoderHelper) WriteToken(t jsontext.Token) {
if h.err != nil {
return
}
h.err = h.enc.WriteToken(t)
}
type traceHeader struct {
VantagePointType string
GroupID *ConnectionID
ReferenceTime time.Time
EventSchemas []string
}
func (l traceHeader) Encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("file_schema"))
h.WriteToken(jsontext.String("urn:ietf:params:qlog:file:sequential"))
h.WriteToken(jsontext.String("serialization_format"))
h.WriteToken(jsontext.String("application/qlog+json-seq"))
h.WriteToken(jsontext.String("title"))
h.WriteToken(jsontext.String("quic-go qlog"))
h.WriteToken(jsontext.String("code_version"))
h.WriteToken(jsontext.String(quicGoVersion))
h.WriteToken(jsontext.String("trace"))
// trace
h.WriteToken(jsontext.BeginObject)
if len(l.EventSchemas) > 0 {
h.WriteToken(jsontext.String("event_schemas"))
h.WriteToken(jsontext.BeginArray)
for _, schema := range l.EventSchemas {
h.WriteToken(jsontext.String(schema))
}
h.WriteToken(jsontext.EndArray)
}
h.WriteToken(jsontext.String("vantage_point"))
// -- vantage_point
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("type"))
h.WriteToken(jsontext.String(l.VantagePointType))
// -- end vantage_point
h.WriteToken(jsontext.EndObject)
h.WriteToken(jsontext.String("common_fields"))
// -- common_fields
h.WriteToken(jsontext.BeginObject)
if l.GroupID != nil {
h.WriteToken(jsontext.String("group_id"))
h.WriteToken(jsontext.String(l.GroupID.String()))
}
h.WriteToken(jsontext.String("reference_time"))
// ---- reference_time
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("clock_type"))
h.WriteToken(jsontext.String("monotonic"))
h.WriteToken(jsontext.String("epoch"))
h.WriteToken(jsontext.String("unknown"))
h.WriteToken(jsontext.String("wall_clock_time"))
h.WriteToken(jsontext.String(l.ReferenceTime.Format(time.RFC3339Nano)))
// ---- end reference_time
h.WriteToken(jsontext.EndObject)
// -- end common_fields
h.WriteToken(jsontext.EndObject)
// end trace
h.WriteToken(jsontext.EndObject)
// The following fields are not required by the qlog draft anymore,
// but qvis still requires them to be present.
h.WriteToken(jsontext.String("qlog_format"))
h.WriteToken(jsontext.String("JSON-SEQ"))
h.WriteToken(jsontext.String("qlog_version"))
h.WriteToken(jsontext.String("0.3"))
h.WriteToken(jsontext.EndObject)
return h.err
}
package qlogwriter
import (
"bytes"
"fmt"
"io"
"log"
"slices"
"sync"
"time"
"github.com/quic-go/quic-go/qlogwriter/jsontext"
)
// Trace represents a qlog trace that can have multiple event producers.
// Each producer can record events to the trace independently.
// When the last producer is closed, the underlying trace is closed as well.
type Trace interface {
// AddProducer creates a new Recorder for this trace.
// Each Recorder can record events independently.
AddProducer() Recorder
// SupportsSchemas returns true if the trace supports the given schema.
SupportsSchemas(schema string) bool
}
// Recorder is used to record events to a qlog trace.
// It is safe for concurrent use by multiple goroutines.
type Recorder interface {
// RecordEvent records a single Event to the trace.
// It must not be called after Close.
RecordEvent(Event)
// Close signals that this producer is done recording events.
// When all producers are closed, the underlying trace is closed.
// It must not be called concurrently with RecordEvent.
io.Closer
}
// Event represents a qlog event that can be encoded to JSON.
// Each event must provide its name and a method to encode itself using a jsontext.Encoder.
type Event interface {
// Name returns the name of the event, as it should appear in the qlog output
Name() string
// Encode writes the event's data to the provided jsontext.Encoder
Encode(encoder *jsontext.Encoder, eventTime time.Time) error
}
// RecordSeparator is the record separator byte for the JSON-SEQ format
const RecordSeparator byte = 0x1e
var recordSeparator = []byte{RecordSeparator}
type event struct {
Time time.Time
Event Event
}
const eventChanSize = 50
// FileSeq represents a qlog trace using the JSON-SEQ format,
// https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-12.html#section-5
// qlog event producers can be created by calling AddProducer.
// The underlying io.WriteCloser is closed when the last producer is removed.
type FileSeq struct {
w io.WriteCloser
enc *jsontext.Encoder
referenceTime time.Time
runStopped chan struct{}
encodeErr error
events chan event
done chan struct{}
mx sync.Mutex
producers int
closed bool
eventSchemas []string
}
var _ Trace = &FileSeq{}
// NewFileSeq creates a new JSON-SEQ qlog trace to log transport events.
func NewFileSeq(w io.WriteCloser) *FileSeq {
return newFileSeq(w, "transport", nil, nil)
}
// NewConnectionFileSeq creates a new qlog trace to log connection events.
func NewConnectionFileSeq(w io.WriteCloser, isClient bool, odcid ConnectionID, eventSchemas []string) *FileSeq {
pers := "server"
if isClient {
pers = "client"
}
return newFileSeq(w, pers, &odcid, eventSchemas)
}
func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID, eventSchemas []string) *FileSeq {
now := time.Now()
buf := &bytes.Buffer{}
enc := jsontext.NewEncoder(buf)
if _, err := buf.Write(recordSeparator); err != nil {
panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
}
if err := (&traceHeader{
VantagePointType: pers,
GroupID: odcid,
ReferenceTime: now,
EventSchemas: eventSchemas,
}).Encode(enc); err != nil {
panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
}
_, encodeErr := w.Write(buf.Bytes())
return &FileSeq{
w: w,
referenceTime: now,
enc: jsontext.NewEncoder(w),
runStopped: make(chan struct{}),
encodeErr: encodeErr,
events: make(chan event, eventChanSize),
done: make(chan struct{}),
eventSchemas: eventSchemas,
}
}
func (t *FileSeq) SupportsSchemas(schema string) bool {
return slices.Contains(t.eventSchemas, schema)
}
func (t *FileSeq) AddProducer() Recorder {
t.mx.Lock()
defer t.mx.Unlock()
if t.closed {
return nil
}
t.producers++
return &Writer{t: t}
}
func (t *FileSeq) record(eventTime time.Time, details Event) {
t.mx.Lock()
if t.closed {
t.mx.Unlock()
return
}
t.mx.Unlock()
t.events <- event{Time: eventTime, Event: details}
}
func (t *FileSeq) Run() {
defer close(t.runStopped)
for {
select {
case <-t.done:
for {
select {
case e := <-t.events:
t.encodeEvent(e)
default:
if t.encodeErr != nil {
log.Printf("exporting qlog failed: %s\n", t.encodeErr)
}
return
}
}
case e := <-t.events:
t.encodeEvent(e)
}
}
}
func (t *FileSeq) encodeEvent(e event) {
if t.encodeErr != nil {
return
}
if _, err := t.w.Write(recordSeparator); err != nil {
t.encodeErr = err
return
}
h := encoderHelper{enc: t.enc}
h.WriteToken(jsontext.BeginObject)
h.WriteToken(jsontext.String("time"))
h.WriteToken(jsontext.Float(float64(e.Time.Sub(t.referenceTime).Nanoseconds()) / 1e6))
h.WriteToken(jsontext.String("name"))
h.WriteToken(jsontext.String(e.Event.Name()))
h.WriteToken(jsontext.String("data"))
if err := e.Event.Encode(t.enc, e.Time); err != nil {
t.encodeErr = err
return
}
h.WriteToken(jsontext.EndObject)
if h.err != nil {
t.encodeErr = h.err
}
}
func (t *FileSeq) removeProducer() {
t.mx.Lock()
t.producers--
last := t.producers == 0
if last {
t.closed = true
}
t.mx.Unlock()
if last {
close(t.done)
<-t.runStopped // wait for Run to drain and exit
_ = t.w.Close()
}
}
type Writer struct {
t *FileSeq
}
func (w *Writer) Close() error {
w.t.removeProducer()
return nil
}
func (w *Writer) RecordEvent(ev Event) {
w.t.record(time.Now(), ev)
}
package quicvarint
import (
"bytes"
"io"
)
// Reader implements both the io.ByteReader and io.Reader interfaces.
type Reader interface {
io.ByteReader
io.Reader
}
var _ Reader = &bytes.Reader{}
// A Peeker can peek bytes without consuming them.
type Peeker interface {
Peek(b []byte) (int, error)
}
// Peek reads a number in the QUIC varint format without consuming bytes.
func Peek(p Peeker) (uint64, error) {
var b [8]byte
// first peek 1 byte to determine the varint length
if _, err := p.Peek(b[:1]); err != nil {
return 0, err
}
l := 1 << (b[0] >> 6) // 1, 2, 4, or 8 bytes
if l == 1 {
return uint64(b[0] & 0b00111111), nil
}
if _, err := p.Peek(b[:l]); err != nil {
return 0, err
}
val, _, err := Parse(b[:l])
return val, err
}
type byteReader struct {
io.Reader
}
var _ Reader = &byteReader{}
// NewReader returns a Reader for r.
// If r already implements both io.ByteReader and io.Reader, NewReader returns r.
// Otherwise, r is wrapped to add the missing interfaces.
func NewReader(r io.Reader) Reader {
if r, ok := r.(Reader); ok {
return r
}
return &byteReader{r}
}
func (r *byteReader) ReadByte() (byte, error) {
var b [1]byte
var n int
var err error
for n == 0 && err == nil {
n, err = r.Read(b[:])
}
if n == 1 && err == io.EOF {
err = nil
}
return b[0], err
}
// Writer implements both the io.ByteWriter and io.Writer interfaces.
type Writer interface {
io.ByteWriter
io.Writer
}
var _ Writer = &bytes.Buffer{}
type byteWriter struct {
io.Writer
}
var _ Writer = &byteWriter{}
// NewWriter returns a Writer for w.
// If w already implements both io.ByteWriter and io.Writer, NewWriter returns w.
// Otherwise, w is wrapped to add the missing interfaces.
func NewWriter(w io.Writer) Writer {
if w, ok := w.(Writer); ok {
return w
}
return &byteWriter{w}
}
func (w *byteWriter) WriteByte(c byte) error {
_, err := w.Write([]byte{c})
return err
}
package quicvarint
import (
"encoding/binary"
"fmt"
"io"
)
// taken from the QUIC draft
const (
// Min is the minimum value allowed for a QUIC varint.
Min = 0
// Max is the maximum allowed value for a QUIC varint (2^62-1).
Max = maxVarInt8
maxVarInt1 = 63
maxVarInt2 = 16383
maxVarInt4 = 1073741823
maxVarInt8 = 4611686018427387903
)
type varintLengthError struct {
Num uint64
}
func (e *varintLengthError) Error() string {
return fmt.Sprintf("value doesn't fit into 62 bits: %d", e.Num)
}
// Read reads a number in the QUIC varint format from r.
func Read(r io.ByteReader) (uint64, error) {
firstByte, err := r.ReadByte()
if err != nil {
return 0, err
}
// the first two bits of the first byte encode the length
l := 1 << ((firstByte & 0xc0) >> 6)
b1 := firstByte & (0xff - 0xc0)
if l == 1 {
return uint64(b1), nil
}
b2, err := r.ReadByte()
if err != nil {
return 0, err
}
if l == 2 {
return uint64(b2) + uint64(b1)<<8, nil
}
b3, err := r.ReadByte()
if err != nil {
return 0, err
}
b4, err := r.ReadByte()
if err != nil {
return 0, err
}
if l == 4 {
return uint64(b4) + uint64(b3)<<8 + uint64(b2)<<16 + uint64(b1)<<24, nil
}
b5, err := r.ReadByte()
if err != nil {
return 0, err
}
b6, err := r.ReadByte()
if err != nil {
return 0, err
}
b7, err := r.ReadByte()
if err != nil {
return 0, err
}
b8, err := r.ReadByte()
if err != nil {
return 0, err
}
return uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, nil
}
// Parse reads a number in the QUIC varint format.
// It returns the number of bytes consumed.
func Parse(b []byte) (uint64 /* value */, int /* bytes consumed */, error) {
if len(b) == 0 {
return 0, 0, io.EOF
}
first := b[0]
switch first >> 6 {
case 0: // 1-byte encoding: 00xxxxxx
return uint64(first & 0b00111111), 1, nil
case 1: // 2-byte encoding: 01xxxxxx
if len(b) < 2 {
return 0, 0, io.ErrUnexpectedEOF
}
return uint64(b[1]) | uint64(first&0b00111111)<<8, 2, nil
case 2: // 4-byte encoding: 10xxxxxx
if len(b) < 4 {
return 0, 0, io.ErrUnexpectedEOF
}
return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(first&0b00111111)<<24, 4, nil
case 3: // 8-byte encoding: 00xxxxxx
if len(b) < 8 {
return 0, 0, io.ErrUnexpectedEOF
}
// binary.BigEndian.Uint64 only reads the first 8 bytes. Passing the full slice avoids slicing overhead.
return binary.BigEndian.Uint64(b) & 0x3fffffffffffffff, 8, nil
}
panic("unreachable")
}
// Append appends i in the QUIC varint format.
func Append(b []byte, i uint64) []byte {
if i <= maxVarInt1 {
return append(b, uint8(i))
}
if i <= maxVarInt2 {
return append(b, []byte{uint8(i>>8) | 0x40, uint8(i)}...)
}
if i <= maxVarInt4 {
return append(b, []byte{uint8(i>>24) | 0x80, uint8(i >> 16), uint8(i >> 8), uint8(i)}...)
}
if i <= maxVarInt8 {
return append(b, []byte{
uint8(i>>56) | 0xc0, uint8(i >> 48), uint8(i >> 40), uint8(i >> 32),
uint8(i >> 24), uint8(i >> 16), uint8(i >> 8), uint8(i),
}...)
}
panic(&varintLengthError{Num: i})
}
// AppendWithLen append i in the QUIC varint format with the desired length.
func AppendWithLen(b []byte, i uint64, length int) []byte {
if length != 1 && length != 2 && length != 4 && length != 8 {
panic("invalid varint length")
}
l := Len(i)
if l == length {
return Append(b, i)
}
if l > length {
panic(fmt.Sprintf("cannot encode %d in %d bytes", i, length))
}
switch length {
case 2:
b = append(b, 0b01000000)
case 4:
b = append(b, 0b10000000)
case 8:
b = append(b, 0b11000000)
}
for range length - l - 1 {
b = append(b, 0)
}
for j := range l {
b = append(b, uint8(i>>(8*(l-1-j))))
}
return b
}
// Len determines the number of bytes that will be needed to write the number i.
//
//gcassert:inline
func Len(i uint64) int {
if i <= maxVarInt1 {
return 1
}
if i <= maxVarInt2 {
return 2
}
if i <= maxVarInt4 {
return 4
}
if i <= maxVarInt8 {
return 8
}
// Don't use a fmt.Sprintf here to format the error message.
// The function would then exceed the inlining budget.
panic(&varintLengthError{Num: i})
}
package quic
import (
"context"
"fmt"
"io"
"sync"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
// A ReceiveStream is a unidirectional Receive Stream.
type ReceiveStream struct {
mutex sync.Mutex
streamID protocol.StreamID
sender streamSender
frameQueue *frameSorter
finalOffset protocol.ByteCount
finalSizeChan chan struct{} // closed when the final size is known
currentFrame []byte
currentFrameDone func()
readPosInFrame int
currentFrameIsLast bool // is the currentFrame the last frame on this stream
queuedStopSending bool
queuedMaxStreamData bool
// Set once we read the io.EOF or the cancellation error.
// Note that for local cancellations, this doesn't necessarily mean that we know the final offset yet.
errorRead bool
completed bool // set once we've called streamSender.onStreamCompleted
cancelledRemotely bool
cancelledLocally bool
cancelErr *StreamError
closeForShutdownErr error
readPos protocol.ByteCount
reliableSize protocol.ByteCount
readChan chan struct{}
readOnce chan struct{} // cap: 1, to protect against concurrent use of Read
deadline monotime.Time
flowController *streamFlowController
}
var (
_ streamControlFrameGetter = &ReceiveStream{}
_ receiveStreamFrameHandler = &ReceiveStream{}
)
func newReceiveStream(
streamID protocol.StreamID,
sender streamSender,
flowController *streamFlowController,
) *ReceiveStream {
return &ReceiveStream{
streamID: streamID,
sender: sender,
flowController: flowController,
frameQueue: newFrameSorter(),
readChan: make(chan struct{}, 1),
readOnce: make(chan struct{}, 1),
finalOffset: protocol.MaxByteCount,
finalSizeChan: make(chan struct{}),
}
}
// StreamID returns the stream ID.
func (s *ReceiveStream) StreamID() StreamID {
return s.streamID
}
// WaitForReceiveFinalSize waits until the receive stream's final size is known.
// The final size is learned from a FIN or RESET_STREAM frame.
// Most applications don't need this. It is mainly useful for protocol layers
// that need exact stream final sizes, such as WebTransport flow control accounting.
func (s *ReceiveStream) WaitForReceiveFinalSize(ctx context.Context) (int64, error) {
for {
s.mutex.Lock()
size := s.finalOffset
closeErr := s.closeForShutdownErr
finalSizeChan := s.finalSizeChan
s.mutex.Unlock()
if size != protocol.MaxByteCount {
return int64(size), nil
}
if closeErr != nil {
return 0, closeErr
}
select {
case <-finalSizeChan:
case <-ctx.Done():
return 0, context.Cause(ctx)
}
}
}
// Read reads data from the stream.
// Read can be made to time out using [ReceiveStream.SetReadDeadline].
// If the stream was canceled, the error is a [StreamError].
func (s *ReceiveStream) Read(p []byte) (int, error) {
// Concurrent use of Read is not permitted (and doesn't make any sense),
// but sometimes people do it anyway.
// Make sure that we only execute one call at any given time to avoid hard to debug failures.
s.readOnce <- struct{}{}
defer func() { <-s.readOnce }()
s.mutex.Lock()
queuedStreamWindowUpdate, queuedConnWindowUpdate, n, err := s.readImpl(p)
completed := s.isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
if queuedStreamWindowUpdate {
s.sender.onHasStreamControlFrame(s.streamID, s)
}
if queuedConnWindowUpdate {
s.sender.onHasConnectionData()
}
return n, err
}
func (s *ReceiveStream) isNewlyCompleted() bool {
if s.completed {
return false
}
// We need to know the final offset (either via FIN or RESET_STREAM) for flow control accounting.
if s.finalOffset == protocol.MaxByteCount {
return false
}
// We're done with the stream if it was cancelled locally...
if s.cancelledLocally {
s.completed = true
return true
}
// ... or if the error (either io.EOF or the reset error) was read
if s.errorRead {
s.completed = true
return true
}
return false
}
func (s *ReceiveStream) readImpl(p []byte) (hasStreamWindowUpdate bool, hasConnWindowUpdate bool, _ int, _ error) {
if s.currentFrameIsLast && s.currentFrame == nil {
s.errorRead = true
return false, false, 0, io.EOF
}
if s.cancelledLocally || s.isRemoteCancellationEffective() {
s.errorRead = true
return false, false, 0, s.cancelErr
}
if s.closeForShutdownErr != nil {
return false, false, 0, s.closeForShutdownErr
}
var bytesRead int
var deadlineTimer *time.Timer
for bytesRead < len(p) {
if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) {
s.dequeueNextFrame()
}
if s.currentFrame == nil && bytesRead > 0 {
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.closeForShutdownErr
}
for {
// Stop waiting on errors
if s.closeForShutdownErr != nil {
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.closeForShutdownErr
}
if s.cancelledLocally || s.isRemoteCancellationEffective() {
s.errorRead = true
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.cancelErr
}
deadline := s.deadline
if !deadline.IsZero() && !monotime.Now().Before(deadline) {
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, errDeadline
}
if s.currentFrame != nil || s.currentFrameIsLast {
break
}
s.mutex.Unlock()
if deadline.IsZero() {
<-s.readChan
} else {
if deadlineTimer == nil {
deadlineTimer = time.NewTimer(monotime.Until(deadline))
defer deadlineTimer.Stop()
} else {
deadlineTimer.Reset(monotime.Until(deadline))
}
select {
case <-s.readChan:
case <-deadlineTimer.C:
}
}
s.mutex.Lock()
s.dequeueNextFrame()
}
if bytesRead > len(p) {
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, fmt.Errorf("BUG: bytesRead (%d) > len(p) (%d) in stream.Read", bytesRead, len(p))
}
if s.readPosInFrame > len(s.currentFrame) {
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, fmt.Errorf("BUG: readPosInFrame (%d) > frame.DataLen (%d) in stream.Read", s.readPosInFrame, len(s.currentFrame))
}
m := copy(p[bytesRead:], s.currentFrame[s.readPosInFrame:])
// when a RESET_STREAM was received, the flow controller was already
// informed about the final offset for this stream
if !s.isRemoteCancellationEffective() {
hasStream, hasConn := s.flowController.AddBytesRead(protocol.ByteCount(m))
if hasStream {
s.queuedMaxStreamData = true
hasStreamWindowUpdate = true
}
if hasConn {
hasConnWindowUpdate = true
}
}
s.readPosInFrame += m
s.readPos += protocol.ByteCount(m)
bytesRead += m
if s.isRemoteCancellationEffective() {
s.flowController.Abandon()
}
if s.readPosInFrame >= len(s.currentFrame) && s.currentFrameIsLast {
s.currentFrame = nil
if s.currentFrameDone != nil {
s.currentFrameDone()
}
s.errorRead = true
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, io.EOF
}
}
if s.isRemoteCancellationEffective() {
s.errorRead = true
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, s.cancelErr
}
return hasStreamWindowUpdate, hasConnWindowUpdate, bytesRead, nil
}
// isRemoteCancellationEffective returns whether the stream was cancelled remotely
// and all reliable data has been read.
func (s *ReceiveStream) isRemoteCancellationEffective() bool {
return s.cancelledRemotely && s.readPos >= s.reliableSize
}
// Peek fills b with stream data, without consuming the stream data.
// It blocks until len(b) bytes are available, or an error occurs.
// It respects the stream deadline set by SetReadDeadline.
// If the stream ends before len(b) bytes are available,
// it returns the number of bytes peeked along with io.EOF.
func (s *ReceiveStream) Peek(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
// prevent concurrent use with Read
s.readOnce <- struct{}{}
defer func() { <-s.readOnce }()
return s.peekImpl(b)
}
func (s *ReceiveStream) peekImpl(b []byte) (int, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
var deadlineTimer *time.Timer
for {
if s.currentFrameIsLast && s.currentFrame == nil {
return 0, io.EOF
}
if s.cancelledLocally || s.isRemoteCancellationEffective() {
return 0, s.cancelErr
}
if s.closeForShutdownErr != nil {
return 0, s.closeForShutdownErr
}
deadline := s.deadline
if !deadline.IsZero() && !monotime.Now().Before(deadline) {
return 0, errDeadline
}
if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) {
s.dequeueNextFrame()
}
if s.currentFrame != nil && s.readPosInFrame < len(s.currentFrame) {
availableInCurrentFrame := len(s.currentFrame) - s.readPosInFrame
if availableInCurrentFrame >= len(b) {
copy(b, s.currentFrame[s.readPosInFrame:])
return len(b), nil
}
offset := s.readPos + protocol.ByteCount(availableInCurrentFrame)
// First peek, then copy.
// This avoids copying data if there's not enough data in the queue.
if err := s.frameQueue.Peek(offset, b[availableInCurrentFrame:]); err == nil {
copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:])
return len(b), nil
}
if s.currentFrameIsLast {
copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:])
return availableInCurrentFrame, io.EOF
}
// If the stream was remotely cancelled and the request extends beyond the reliable size,
// return the data available with the cancel error (once it's all received).
if s.cancelledRemotely && s.readPos+protocol.ByteCount(len(b)) > s.reliableSize {
total := int(s.reliableSize - s.readPos)
needed := total - availableInCurrentFrame
// only return once all available data is contiguous
if needed <= 0 || s.frameQueue.Peek(offset, b[availableInCurrentFrame:total]) == nil {
copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:])
return total, s.cancelErr
}
}
// If the request extends beyond the stream's final offset,
// return the data available with EOF (once it's all received).
if s.readPos+protocol.ByteCount(len(b)) > s.finalOffset {
total := int(s.finalOffset - s.readPos)
needed := total - availableInCurrentFrame
// only return once all available data is contiguous
if needed <= 0 || s.frameQueue.Peek(offset, b[availableInCurrentFrame:total]) == nil {
copy(b[:availableInCurrentFrame], s.currentFrame[s.readPosInFrame:])
return total, io.EOF
}
}
}
if s.currentFrameIsLast || s.readPos >= s.finalOffset {
return 0, io.EOF
}
s.mutex.Unlock()
if deadline.IsZero() {
<-s.readChan
} else {
if deadlineTimer == nil {
deadlineTimer = time.NewTimer(monotime.Until(deadline))
defer deadlineTimer.Stop()
} else {
deadlineTimer.Reset(monotime.Until(deadline))
}
select {
case <-s.readChan:
case <-deadlineTimer.C:
}
}
s.mutex.Lock()
if s.currentFrame == nil || s.readPosInFrame >= len(s.currentFrame) {
s.dequeueNextFrame()
}
}
}
func (s *ReceiveStream) dequeueNextFrame() {
var offset protocol.ByteCount
// We're done with the last frame. Release the buffer.
if s.currentFrameDone != nil {
s.currentFrameDone()
}
offset, s.currentFrame, s.currentFrameDone = s.frameQueue.Pop()
s.currentFrameIsLast = offset+protocol.ByteCount(len(s.currentFrame)) >= s.finalOffset && !s.cancelledRemotely
s.readPosInFrame = 0
}
// CancelRead aborts receiving on this stream.
// It instructs the peer to stop transmitting stream data.
// Read will unblock immediately, and future Read calls will fail.
// When called multiple times or after reading the io.EOF it is a no-op.
func (s *ReceiveStream) CancelRead(errorCode StreamErrorCode) {
s.mutex.Lock()
queuedNewControlFrame := s.cancelReadImpl(errorCode)
completed := s.isNewlyCompleted()
s.mutex.Unlock()
if queuedNewControlFrame {
s.sender.onHasStreamControlFrame(s.streamID, s)
}
if completed {
s.flowController.Abandon()
s.sender.onStreamCompleted(s.streamID)
}
}
func (s *ReceiveStream) cancelReadImpl(errorCode qerr.StreamErrorCode) (queuedNewControlFrame bool) {
if s.cancelledLocally { // duplicate call to CancelRead
return false
}
if s.closeForShutdownErr != nil {
return false
}
s.cancelledLocally = true
if s.errorRead || s.cancelledRemotely {
return false
}
s.queuedStopSending = true
s.cancelErr = &StreamError{StreamID: s.streamID, ErrorCode: errorCode, Remote: false}
s.signalRead()
return true
}
func (s *ReceiveStream) handleStreamFrame(frame *wire.StreamFrame, now monotime.Time) error {
s.mutex.Lock()
err := s.handleStreamFrameImpl(frame, now)
completed := s.isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.flowController.Abandon()
s.sender.onStreamCompleted(s.streamID)
}
return err
}
func (s *ReceiveStream) handleStreamFrameImpl(frame *wire.StreamFrame, now monotime.Time) error {
maxOffset := frame.Offset + frame.DataLen()
if err := s.flowController.UpdateHighestReceived(maxOffset, frame.Fin, now); err != nil {
return err
}
if frame.Fin {
s.setFinalOffset(maxOffset)
}
if s.cancelledLocally {
return nil
}
if err := s.frameQueue.Push(frame.Data, frame.Offset, frame.PutBack); err != nil {
return err
}
s.signalRead()
return nil
}
func (s *ReceiveStream) handleResetStreamFrame(frame *wire.ResetStreamFrame, now monotime.Time) error {
s.mutex.Lock()
err := s.handleResetStreamFrameImpl(frame, now)
completed := s.isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
return err
}
func (s *ReceiveStream) handleResetStreamFrameImpl(frame *wire.ResetStreamFrame, now monotime.Time) error {
if s.closeForShutdownErr != nil {
return nil
}
if err := s.flowController.UpdateHighestReceived(frame.FinalSize, true, now); err != nil {
return err
}
s.setFinalOffset(frame.FinalSize)
// senders are allowed to reduce the reliable size, but frames might have been reordered
if (!s.cancelledRemotely && s.reliableSize == 0) || frame.ReliableSize < s.reliableSize {
s.reliableSize = frame.ReliableSize
}
if s.readPos >= s.reliableSize {
// calling Abandon multiple times is a no-op
s.flowController.Abandon()
}
// ignore duplicate RESET_STREAM frames for this stream (after checking their final offset)
if s.cancelledRemotely {
return nil
}
// don't save the error if the RESET_STREAM frames was received after CancelRead was called
if s.cancelledLocally {
return nil
}
s.cancelledRemotely = true
s.cancelErr = &StreamError{StreamID: s.streamID, ErrorCode: frame.ErrorCode, Remote: true}
s.signalRead()
return nil
}
func (s *ReceiveStream) setFinalOffset(offset protocol.ByteCount) {
s.finalOffset = offset
s.closeFinalSizeChan()
}
func (s *ReceiveStream) getControlFrame(now monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
s.mutex.Lock()
defer s.mutex.Unlock()
if !s.queuedStopSending && !s.queuedMaxStreamData {
return ackhandler.Frame{}, false, false
}
if s.queuedStopSending {
s.queuedStopSending = false
return ackhandler.Frame{
Frame: &wire.StopSendingFrame{StreamID: s.streamID, ErrorCode: s.cancelErr.ErrorCode},
}, true, s.queuedMaxStreamData
}
s.queuedMaxStreamData = false
return ackhandler.Frame{
Frame: &wire.MaxStreamDataFrame{
StreamID: s.streamID,
MaximumStreamData: s.flowController.GetWindowUpdate(now),
},
}, true, false
}
// SetReadDeadline sets the deadline for future Read calls and
// any currently-blocked Read call.
// A zero value for t means Read will not time out.
func (s *ReceiveStream) SetReadDeadline(t time.Time) error {
s.mutex.Lock()
s.deadline = monotime.FromTime(t)
s.mutex.Unlock()
s.signalRead()
return nil
}
// CloseForShutdown closes a stream abruptly.
// It makes Read unblock (and return the error) immediately.
// The peer will NOT be informed about this: the stream is closed without sending a FIN or RESET.
func (s *ReceiveStream) closeForShutdown(err error) {
s.mutex.Lock()
s.closeForShutdownErr = err
s.closeFinalSizeChan()
s.mutex.Unlock()
s.signalRead()
}
func (s *ReceiveStream) closeFinalSizeChan() {
if s.finalSizeChan != nil {
close(s.finalSizeChan)
s.finalSizeChan = nil
}
}
// signalRead performs a non-blocking send on the readChan
func (s *ReceiveStream) signalRead() {
select {
case s.readChan <- struct{}{}:
default:
}
}
package quic
import (
"fmt"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
type framesToRetransmit struct {
crypto []*wire.CryptoFrame
other []wire.Frame
}
type retransmissionQueue struct {
initial *framesToRetransmit
handshake *framesToRetransmit
appData framesToRetransmit
}
func newRetransmissionQueue() *retransmissionQueue {
return &retransmissionQueue{
initial: &framesToRetransmit{},
handshake: &framesToRetransmit{},
}
}
func (q *retransmissionQueue) addInitial(f wire.Frame) {
if q.initial == nil {
return
}
if cf, ok := f.(*wire.CryptoFrame); ok {
q.initial.crypto = append(q.initial.crypto, cf)
return
}
q.initial.other = append(q.initial.other, f)
}
func (q *retransmissionQueue) addHandshake(f wire.Frame) {
if q.handshake == nil {
return
}
if cf, ok := f.(*wire.CryptoFrame); ok {
q.handshake.crypto = append(q.handshake.crypto, cf)
return
}
q.handshake.other = append(q.handshake.other, f)
}
func (q *retransmissionQueue) addAppData(f wire.Frame) {
switch f := f.(type) {
case *wire.StreamFrame:
panic("STREAM frames are handled with their respective streams.")
case *wire.CryptoFrame:
q.appData.crypto = append(q.appData.crypto, f)
default:
q.appData.other = append(q.appData.other, f)
}
}
func (q *retransmissionQueue) HasData(encLevel protocol.EncryptionLevel) bool {
//nolint:exhaustive // 0-RTT data is retransmitted in 1-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
return q.initial != nil &&
(len(q.initial.crypto) > 0 || len(q.initial.other) > 0)
case protocol.EncryptionHandshake:
return q.handshake != nil &&
(len(q.handshake.crypto) > 0 || len(q.handshake.other) > 0)
case protocol.Encryption1RTT:
return len(q.appData.crypto) > 0 || len(q.appData.other) > 0
}
return false
}
func (q *retransmissionQueue) GetFrame(encLevel protocol.EncryptionLevel, maxLen protocol.ByteCount, v protocol.Version) wire.Frame {
var r *framesToRetransmit
//nolint:exhaustive // 0-RTT data is retransmitted in 1-RTT packets.
switch encLevel {
case protocol.EncryptionInitial:
r = q.initial
case protocol.EncryptionHandshake:
r = q.handshake
case protocol.Encryption1RTT:
r = &q.appData
}
if r == nil {
return nil
}
if len(r.crypto) > 0 {
f := r.crypto[0]
newFrame, needsSplit := f.MaybeSplitOffFrame(maxLen, v)
if newFrame == nil && !needsSplit { // the whole frame fits
r.crypto = r.crypto[1:]
return f
}
if newFrame != nil { // frame was split. Leave the original frame in the queue.
return newFrame
}
}
if len(r.other) == 0 {
return nil
}
f := r.other[0]
if f.Length(v) > maxLen {
return nil
}
r.other = r.other[1:]
return f
}
func (q *retransmissionQueue) DropPackets(encLevel protocol.EncryptionLevel) {
//nolint:exhaustive // Can only drop Initial and Handshake packet number space.
switch encLevel {
case protocol.EncryptionInitial:
q.initial = nil
case protocol.EncryptionHandshake:
q.handshake = nil
default:
panic(fmt.Sprintf("unexpected encryption level: %s", encLevel))
}
}
func (q *retransmissionQueue) AckHandler(encLevel protocol.EncryptionLevel) ackhandler.FrameHandler {
switch encLevel {
case protocol.EncryptionInitial:
return (*retransmissionQueueInitialAckHandler)(q)
case protocol.EncryptionHandshake:
return (*retransmissionQueueHandshakeAckHandler)(q)
case protocol.Encryption0RTT, protocol.Encryption1RTT:
return (*retransmissionQueueAppDataAckHandler)(q)
}
return nil
}
type retransmissionQueueInitialAckHandler retransmissionQueue
func (q *retransmissionQueueInitialAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueInitialAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addInitial(f)
}
type retransmissionQueueHandshakeAckHandler retransmissionQueue
func (q *retransmissionQueueHandshakeAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueHandshakeAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addHandshake(f)
}
type retransmissionQueueAppDataAckHandler retransmissionQueue
func (q *retransmissionQueueAppDataAckHandler) OnAcked(wire.Frame) {}
func (q *retransmissionQueueAppDataAckHandler) OnLost(f wire.Frame) {
(*retransmissionQueue)(q).addAppData(f)
}
package quic
import (
"net"
"sync/atomic"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
// A sendConn allows sending using a simple Write() on a non-connected packet conn.
type sendConn interface {
Write(b []byte, gsoSize uint16, ecn protocol.ECN) error
WriteTo([]byte, net.Addr, packetInfo) error
Close() error
LocalAddr() net.Addr
RemoteAddr() net.Addr
ChangeRemoteAddr(addr net.Addr, info packetInfo)
capabilities() connCapabilities
}
type remoteAddrInfo struct {
addr net.Addr
oob []byte
}
type sconn struct {
rawConn
localAddr net.Addr
remoteAddrInfo atomic.Pointer[remoteAddrInfo]
logger utils.Logger
// If GSO enabled, and we receive a GSO error for this remote address, GSO is disabled.
gotGSOError bool
// Used to catch the error sometimes returned by the first sendmsg call on Linux,
// see https://github.com/golang/go/issues/63322.
wroteFirstPacket bool
}
var _ sendConn = &sconn{}
func newSendConn(c rawConn, remote net.Addr, info packetInfo, logger utils.Logger) *sconn {
localAddr := c.LocalAddr()
if info.addr.IsValid() {
if udpAddr, ok := localAddr.(*net.UDPAddr); ok {
addrCopy := *udpAddr
addrCopy.IP = info.addr.AsSlice()
localAddr = &addrCopy
}
}
oob := info.OOB()
// increase oob slice capacity, so we can add the UDP_SEGMENT and ECN control messages without allocating
l := len(oob)
oob = append(oob, make([]byte, 64)...)[:l]
sc := &sconn{
rawConn: c,
localAddr: localAddr,
logger: logger,
}
sc.remoteAddrInfo.Store(&remoteAddrInfo{
addr: remote,
oob: oob,
})
return sc
}
func (c *sconn) Write(p []byte, gsoSize uint16, ecn protocol.ECN) error {
ai := c.remoteAddrInfo.Load()
err := c.writePacket(p, ai.addr, ai.oob, gsoSize, ecn)
if err != nil && isGSOError(err) {
// disable GSO for future calls
c.gotGSOError = true
if c.logger.Debug() {
c.logger.Debugf("GSO failed when sending to %s", ai.addr)
}
// send out the packets one by one
for len(p) > 0 {
l := min(len(p), int(gsoSize))
if err := c.writePacket(p[:l], ai.addr, ai.oob, 0, ecn); err != nil {
return err
}
p = p[l:]
}
return nil
}
return err
}
func (c *sconn) writePacket(p []byte, addr net.Addr, oob []byte, gsoSize uint16, ecn protocol.ECN) error {
_, err := c.WritePacket(p, addr, oob, gsoSize, ecn)
if err != nil && !c.wroteFirstPacket && isPermissionError(err) {
_, err = c.WritePacket(p, addr, oob, gsoSize, ecn)
}
c.wroteFirstPacket = true
return err
}
func (c *sconn) WriteTo(b []byte, addr net.Addr, info packetInfo) error {
_, err := c.WritePacket(b, addr, info.OOB(), 0, protocol.ECNUnsupported)
return err
}
func (c *sconn) capabilities() connCapabilities {
capabilities := c.rawConn.capabilities()
if capabilities.GSO {
capabilities.GSO = !c.gotGSOError
}
return capabilities
}
func (c *sconn) ChangeRemoteAddr(addr net.Addr, info packetInfo) {
c.remoteAddrInfo.Store(&remoteAddrInfo{
addr: addr,
oob: info.OOB(),
})
}
func (c *sconn) RemoteAddr() net.Addr { return c.remoteAddrInfo.Load().addr }
func (c *sconn) LocalAddr() net.Addr { return c.localAddr }
package quic
import (
"net"
"github.com/quic-go/quic-go/internal/protocol"
)
type sender interface {
Send(p *packetBuffer, gsoSize uint16, ecn protocol.ECN)
SendProbe(*packetBuffer, net.Addr, packetInfo)
Run() error
WouldBlock() bool
Available() <-chan struct{}
Close()
}
type queueEntry struct {
buf *packetBuffer
gsoSize uint16
ecn protocol.ECN
}
type sendQueue struct {
queue chan queueEntry
closeCalled chan struct{} // runStopped when Close() is called
runStopped chan struct{} // runStopped when the run loop returns
available chan struct{}
conn sendConn
}
var _ sender = &sendQueue{}
const sendQueueCapacity = 8
func newSendQueue(conn sendConn) sender {
return &sendQueue{
conn: conn,
runStopped: make(chan struct{}),
closeCalled: make(chan struct{}),
available: make(chan struct{}, 1),
queue: make(chan queueEntry, sendQueueCapacity),
}
}
// Send sends out a packet. It's guaranteed to not block.
// Callers need to make sure that there's actually space in the send queue by calling WouldBlock.
// Otherwise Send will panic.
func (h *sendQueue) Send(p *packetBuffer, gsoSize uint16, ecn protocol.ECN) {
select {
case h.queue <- queueEntry{buf: p, gsoSize: gsoSize, ecn: ecn}:
// clear available channel if we've reached capacity
if len(h.queue) == sendQueueCapacity {
select {
case <-h.available:
default:
}
}
case <-h.runStopped:
default:
panic("sendQueue.Send would have blocked")
}
}
func (h *sendQueue) SendProbe(p *packetBuffer, addr net.Addr, info packetInfo) {
h.conn.WriteTo(p.Data, addr, info)
}
func (h *sendQueue) WouldBlock() bool {
return len(h.queue) == sendQueueCapacity
}
func (h *sendQueue) Available() <-chan struct{} {
return h.available
}
func (h *sendQueue) Run() error {
defer close(h.runStopped)
var shouldClose bool
for {
if shouldClose && len(h.queue) == 0 {
return nil
}
select {
case <-h.closeCalled:
h.closeCalled = nil // prevent this case from being selected again
// make sure that all queued packets are actually sent out
shouldClose = true
case e := <-h.queue:
if err := h.conn.Write(e.buf.Data, e.gsoSize, e.ecn); err != nil {
// This additional check enables:
// 1. Checking for "datagram too large" message from the kernel, as such,
// 2. Path MTU discovery,and
// 3. Eventual detection of loss PingFrame.
if !isSendMsgSizeErr(err) {
return err
}
}
e.buf.Release()
select {
case h.available <- struct{}{}:
default:
}
}
}
}
func (h *sendQueue) Close() {
close(h.closeCalled)
// wait until the run loop returned
<-h.runStopped
}
package quic
import (
"context"
"fmt"
"sync"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
// A SendStream is a unidirectional Send Stream.
type SendStream struct {
mutex sync.Mutex
numOutstandingFrames int64 // outstanding STREAM and RESET_STREAM frames
retransmissionQueue []*wire.StreamFrame
ctx context.Context
ctxCancel context.CancelCauseFunc
streamID protocol.StreamID
sender streamSender
// reliableSize is the portion of the stream that needs to be transmitted reliably,
// even if the stream is cancelled.
// This requires the peer to support RESET_STREAM_AT.
// This value should not be accessed directly, but only through the reliableOffset method.
// This method returns 0 if the peer doesn't support the RESET_STREAM_AT extension.
reliableSize protocol.ByteCount
writeOffset protocol.ByteCount
shutdownErr error
resetErr *StreamError
queuedResetStreamFrame *wire.ResetStreamFrame
dataForWriting []byte // during a Write() call, this slice is the part of p that still needs to be sent out
nextFrame *wire.StreamFrame
// set if flow control credit for nextFrame was already consumed
nextFrameReserved bool
supportsResetStreamAt bool
finishedWriting bool // set once Close() is called
finSent bool // set when a STREAM_FRAME with FIN bit has been sent
// Set when the application knows about the cancellation.
// This can happen because the application called CancelWrite,
// or because Write returned the error (for remote cancellations).
cancellationFlagged bool
completed bool // set when this stream has been reported to the streamSender as completed
writeChan chan struct{}
writeOnce chan struct{}
deadline monotime.Time
flowController *streamFlowController
}
var (
_ streamControlFrameGetter = &SendStream{}
_ outgoingStream = &SendStream{}
_ sendStreamFrameHandler = &SendStream{}
)
func newSendStream(
ctx context.Context,
streamID protocol.StreamID,
sender streamSender,
flowController *streamFlowController,
supportsResetStreamAt bool,
) *SendStream {
s := &SendStream{
streamID: streamID,
sender: sender,
flowController: flowController,
writeChan: make(chan struct{}, 1),
writeOnce: make(chan struct{}, 1), // cap: 1, to protect against concurrent use of Write
supportsResetStreamAt: supportsResetStreamAt,
}
s.ctx, s.ctxCancel = context.WithCancelCause(ctx)
return s
}
// StreamID returns the stream ID.
func (s *SendStream) StreamID() StreamID {
return s.streamID // same for receiveStream and sendStream
}
// Write writes data to the stream.
// Write can be made to time out using [SendStream.SetWriteDeadline].
// If the stream was canceled, the error is a [StreamError].
func (s *SendStream) Write(p []byte) (int, error) {
// Concurrent use of Write is not permitted (and doesn't make any sense),
// but sometimes people do it anyway.
// Make sure that we only execute one call at any given time to avoid hard to debug failures.
s.writeOnce <- struct{}{}
defer func() { <-s.writeOnce }()
isNewlyCompleted, n, err := s.write(p)
if isNewlyCompleted {
s.sender.onStreamCompleted(s.streamID)
}
return n, err
}
// TryWriteAll writes data to the stream if it can be queued immediately.
// It doesn't block for flow control credit and doesn't respect the write deadline.
// If the entire slice can't be queued immediately, it queues nothing and returns [ErrWouldBlock].
func (s *SendStream) TryWriteAll(p []byte) error {
select {
case s.writeOnce <- struct{}{}:
defer func() { <-s.writeOnce }()
default:
return ErrWouldBlock
}
isNewlyCompleted, hasData, err := s.tryWriteAll(p)
if isNewlyCompleted {
s.sender.onStreamCompleted(s.streamID)
}
if hasData {
s.sender.onHasStreamData(s.streamID, s)
}
return err
}
func (s *SendStream) tryWriteAll(p []byte) (bool /* is newly completed */, bool /* has data */, error) {
// This might wait briefly while a packet is dequeuing stream data.
s.mutex.Lock()
defer s.mutex.Unlock()
if s.resetErr != nil {
s.cancellationFlagged = true
return s.isNewlyCompleted(), false, s.resetErr
}
if s.shutdownErr != nil {
return false, false, s.shutdownErr
}
if s.finishedWriting {
return false, false, fmt.Errorf("write on closed stream %d", s.streamID)
}
if len(p) == 0 {
return false, false, nil
}
bytesToReserve := protocol.ByteCount(len(p))
if s.nextFrame != nil && !s.nextFrameReserved {
bytesToReserve += s.nextFrame.DataLen()
}
if !s.flowController.TryAddBytesSent(bytesToReserve) {
return false, false, ErrWouldBlock
}
if s.nextFrame == nil {
s.nextFrame = wire.GetStreamFrame()
s.nextFrame.Offset = s.writeOffset
s.nextFrame.StreamID = s.streamID
s.nextFrame.DataLenPresent = true
s.nextFrame.Data = s.nextFrame.Data[:0]
}
l := len(s.nextFrame.Data)
if l+len(p) > cap(s.nextFrame.Data) {
// Pooled STREAM frames must keep their packet-sized buffer.
// Use a non-pooled frame when the queued data grows beyond that.
nextFrame := &wire.StreamFrame{
StreamID: s.streamID,
Offset: s.nextFrame.Offset,
DataLenPresent: true,
Data: make([]byte, l+len(p)),
}
copy(nextFrame.Data, s.nextFrame.Data)
s.nextFrame.PutBack()
s.nextFrame = nextFrame
} else {
s.nextFrame.Data = s.nextFrame.Data[:l+len(p)]
}
copy(s.nextFrame.Data[l:], p)
s.nextFrameReserved = true
return false, true, nil
}
func (s *SendStream) write(p []byte) (bool /* is newly completed */, int, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.resetErr != nil {
s.cancellationFlagged = true
return s.isNewlyCompleted(), 0, s.resetErr
}
if s.shutdownErr != nil {
return false, 0, s.shutdownErr
}
if s.finishedWriting {
return false, 0, fmt.Errorf("write on closed stream %d", s.streamID)
}
if !s.deadline.IsZero() && !monotime.Now().Before(s.deadline) {
return false, 0, errDeadline
}
if len(p) == 0 {
return false, 0, nil
}
s.dataForWriting = p
var (
deadlineTimer *time.Timer
bytesWritten int
notifiedSender bool
)
for {
var copied bool
var deadline monotime.Time
// As soon as dataForWriting becomes smaller than a certain size x, we copy all the data to a STREAM frame (s.nextFrame),
// which can then be popped the next time we assemble a packet.
// This allows us to return Write() when all data but x bytes have been sent out.
// When the user now calls Close(), this is much more likely to happen before we popped that last STREAM frame,
// allowing us to set the FIN bit on that frame (instead of sending an empty STREAM frame with FIN).
if s.canBufferStreamFrame() && len(s.dataForWriting) > 0 {
if s.nextFrame == nil {
f := wire.GetStreamFrame()
f.Offset = s.writeOffset
f.StreamID = s.streamID
f.DataLenPresent = true
f.Data = f.Data[:len(s.dataForWriting)]
copy(f.Data, s.dataForWriting)
s.nextFrame = f
} else {
l := len(s.nextFrame.Data)
s.nextFrame.Data = s.nextFrame.Data[:l+len(s.dataForWriting)]
copy(s.nextFrame.Data[l:], s.dataForWriting)
}
s.dataForWriting = nil
bytesWritten = len(p)
copied = true
} else {
bytesWritten = len(p) - len(s.dataForWriting)
deadline = s.deadline
if !deadline.IsZero() {
if !monotime.Now().Before(deadline) {
s.dataForWriting = nil
return false, bytesWritten, errDeadline
}
if deadlineTimer == nil {
deadlineTimer = time.NewTimer(monotime.Until(deadline))
defer deadlineTimer.Stop()
} else {
deadlineTimer.Reset(monotime.Until(deadline))
}
}
if s.dataForWriting == nil || s.shutdownErr != nil || s.resetErr != nil {
break
}
}
s.mutex.Unlock()
if !notifiedSender {
s.sender.onHasStreamData(s.streamID, s) // must be called without holding the mutex
notifiedSender = true
}
if copied {
s.mutex.Lock()
break
}
if deadline.IsZero() {
<-s.writeChan
} else {
select {
case <-s.writeChan:
case <-deadlineTimer.C:
}
}
s.mutex.Lock()
}
if bytesWritten == len(p) {
return false, bytesWritten, nil
}
if s.shutdownErr != nil {
return false, bytesWritten, s.shutdownErr
}
if s.resetErr != nil {
s.cancellationFlagged = true
return s.isNewlyCompleted(), bytesWritten, s.resetErr
}
return false, bytesWritten, nil
}
func (s *SendStream) canBufferStreamFrame() bool {
if s.nextFrameReserved {
return false
}
var l protocol.ByteCount
if s.nextFrame != nil {
l = s.nextFrame.DataLen()
}
return l+protocol.ByteCount(len(s.dataForWriting)) <= protocol.MaxPacketBufferSize
}
// popStreamFrame returns the next STREAM frame that is supposed to be sent on this stream
// maxBytes is the maximum length this frame (including frame header) will have.
func (s *SendStream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.Version) (_ ackhandler.StreamFrame, _ *wire.StreamDataBlockedFrame, hasMore bool) {
s.mutex.Lock()
f, blocked, hasMoreData := s.popNewOrRetransmittedStreamFrame(maxBytes, v)
if f != nil {
s.numOutstandingFrames++
}
s.mutex.Unlock()
if f == nil {
return ackhandler.StreamFrame{}, blocked, hasMoreData
}
return ackhandler.StreamFrame{
Frame: f,
Handler: (*sendStreamAckHandler)(s),
}, blocked, hasMoreData
}
func (s *SendStream) popNewOrRetransmittedStreamFrame(maxBytes protocol.ByteCount, v protocol.Version) (_ *wire.StreamFrame, _ *wire.StreamDataBlockedFrame, hasMoreData bool) {
if s.shutdownErr != nil {
return nil, nil, false
}
if s.resetErr != nil {
reliableOffset := s.reliableOffset()
if reliableOffset == 0 || (s.writeOffset >= reliableOffset && len(s.retransmissionQueue) == 0) {
return nil, nil, false
}
}
if len(s.retransmissionQueue) > 0 {
f, hasMoreRetransmissions := s.maybeGetRetransmission(maxBytes, v)
if f != nil || hasMoreRetransmissions {
if f == nil {
return nil, nil, true
}
// We always claim that we have more data to send.
// This might be incorrect, in which case there'll be a spurious call to popStreamFrame in the future.
return f, nil, true
}
}
if len(s.dataForWriting) == 0 && s.nextFrame == nil {
if s.finishedWriting && !s.finSent {
s.finSent = true
return &wire.StreamFrame{
StreamID: s.streamID,
Offset: s.writeOffset,
DataLenPresent: true,
Fin: true,
}, nil, false
}
return nil, nil, false
}
// if the stream is canceled, only data up to the reliable size needs to be sent
reliableOffset := s.reliableOffset()
var maxDataLen protocol.ByteCount
if s.nextFrameReserved {
maxDataLen = s.nextFrame.DataLen()
} else {
maxDataLen = s.flowController.SendWindowSize()
}
if s.resetErr != nil && reliableOffset > 0 {
maxDataLen = min(maxDataLen, reliableOffset-s.writeOffset)
}
if s.nextFrame != nil {
maxDataLen = min(maxDataLen, s.nextFrame.MaxDataLen(maxBytes, v), s.nextFrame.DataLen())
} else {
f := wire.StreamFrame{
StreamID: s.streamID,
Offset: s.writeOffset,
DataLenPresent: true,
}
maxDataLen = min(maxDataLen, f.MaxDataLen(maxBytes, v), protocol.ByteCount(len(s.dataForWriting)))
}
if maxDataLen == 0 {
return nil, nil, true
}
if !s.nextFrameReserved && !s.flowController.TryAddBytesSent(maxDataLen) {
return nil, nil, true
}
f, hasMoreData := s.popNewStreamFrame(maxDataLen)
if f.DataLen() > 0 {
s.writeOffset += f.DataLen()
}
if s.resetErr != nil && s.writeOffset >= reliableOffset {
hasMoreData = false
}
var blocked *wire.StreamDataBlockedFrame
if f.DataLen() > 0 {
if isBlocked, offset := s.flowController.isNewlyBlocked(); isBlocked {
blocked = &wire.StreamDataBlockedFrame{StreamID: s.streamID, MaximumStreamData: offset}
}
}
f.Fin = s.finishedWriting && s.dataForWriting == nil && s.nextFrame == nil && !s.finSent
if f.Fin {
s.finSent = true
}
return f, blocked, hasMoreData
}
// popNewStreamFrame returns a new STREAM frame to send for this stream
// hasMoreData says if there's more data to send, *not* taking into account the reliable size
func (s *SendStream) popNewStreamFrame(maxDataLen protocol.ByteCount) (_ *wire.StreamFrame, hasMoreData bool) {
if s.nextFrame != nil {
nextFrame := s.nextFrame
nextFrameReserved := s.nextFrameReserved
s.nextFrame = nil
s.nextFrameReserved = false
if nextFrame.DataLen() > maxDataLen {
if nextFrame.DataLen()-maxDataLen > protocol.MaxPacketBufferSize {
s.nextFrame = &wire.StreamFrame{
Data: make([]byte, nextFrame.DataLen()-maxDataLen),
}
} else {
s.nextFrame = wire.GetStreamFrame()
s.nextFrame.Data = s.nextFrame.Data[:nextFrame.DataLen()-maxDataLen]
}
s.nextFrame.StreamID = s.streamID
s.nextFrame.Offset = s.writeOffset + maxDataLen
s.nextFrame.DataLenPresent = true
copy(s.nextFrame.Data, nextFrame.Data[maxDataLen:])
nextFrame.Data = nextFrame.Data[:maxDataLen]
s.nextFrameReserved = nextFrameReserved
} else {
s.signalWrite()
}
return nextFrame, s.nextFrame != nil || s.dataForWriting != nil
}
f := wire.GetStreamFrame()
f.Fin = false
f.StreamID = s.streamID
f.Offset = s.writeOffset
f.DataLenPresent = true
f.Data = f.Data[:0]
s.getDataForWriting(f, maxDataLen)
return f, s.dataForWriting != nil || s.nextFrame != nil || s.finishedWriting
}
func (s *SendStream) maybeGetRetransmission(maxBytes protocol.ByteCount, v protocol.Version) (*wire.StreamFrame, bool /* has more retransmissions */) {
f := s.retransmissionQueue[0]
newFrame, needsSplit := f.MaybeSplitOffFrame(maxBytes, v)
if needsSplit {
return newFrame, true
}
s.retransmissionQueue = s.retransmissionQueue[1:]
return f, len(s.retransmissionQueue) > 0
}
func (s *SendStream) getDataForWriting(f *wire.StreamFrame, maxBytes protocol.ByteCount) {
if protocol.ByteCount(len(s.dataForWriting)) <= maxBytes {
f.Data = f.Data[:len(s.dataForWriting)]
copy(f.Data, s.dataForWriting)
s.dataForWriting = nil
s.signalWrite()
return
}
f.Data = f.Data[:maxBytes]
copy(f.Data, s.dataForWriting)
s.dataForWriting = s.dataForWriting[maxBytes:]
if s.canBufferStreamFrame() {
s.signalWrite()
}
}
func (s *SendStream) isNewlyCompleted() bool {
if s.completed {
return false
}
if s.nextFrame != nil && s.nextFrame.DataLen() > 0 {
return false
}
// We need to keep the stream around until all frames have been sent and acknowledged.
if s.numOutstandingFrames > 0 || len(s.retransmissionQueue) > 0 || s.queuedResetStreamFrame != nil {
return false
}
// The stream is completed if we sent the FIN.
if s.finSent {
s.completed = true
return true
}
// The stream is also completed if:
// 1. the application called CancelWrite, or
// 2. we received a STOP_SENDING, and
// * the application consumed the error via Write, or
// * the application called Close
if s.resetErr != nil && (s.cancellationFlagged || s.finishedWriting) {
s.completed = true
return true
}
return false
}
// Close closes the write-direction of the stream.
// Future calls to Write are not permitted after calling Close.
// It must not be called concurrently with Write.
// It must not be called after calling CancelWrite.
func (s *SendStream) Close() error {
s.mutex.Lock()
if s.shutdownErr != nil || s.finishedWriting {
s.mutex.Unlock()
return nil
}
s.finishedWriting = true
cancelled := s.resetErr != nil
if cancelled {
s.cancellationFlagged = true
}
completed := s.isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
if cancelled {
return fmt.Errorf("close called for canceled stream %d", s.streamID)
}
s.sender.onHasStreamData(s.streamID, s) // need to send the FIN, must be called without holding the mutex
s.ctxCancel(nil)
return nil
}
// SetReliableBoundary marks the data written to this stream so far as reliable.
// It is valid to call this function multiple times, thereby increasing the reliable size.
// It only has an effect if the peer enabled support for the RESET_STREAM_AT extension,
// otherwise, it is a no-op.
func (s *SendStream) SetReliableBoundary() {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.nextFrame != nil {
s.reliableSize = max(s.reliableSize, s.writeOffset+s.nextFrame.DataLen())
} else {
s.reliableSize = max(s.reliableSize, s.writeOffset)
}
}
// returnFramesToPool returns all queued frames to the sync.Pool
func (s *SendStream) returnFramesToPool() {
for _, f := range s.retransmissionQueue {
f.PutBack()
}
clear(s.retransmissionQueue)
s.retransmissionQueue = nil
if s.nextFrame != nil {
s.nextFrame.PutBack()
s.nextFrame = nil
}
s.nextFrameReserved = false
}
// CancelWrite aborts sending on this stream.
// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.
// Write will unblock immediately, and future calls to Write will fail.
// When called multiple times it is a no-op.
// When called after Close, it aborts reliable delivery of outstanding stream data.
// Note that there is no guarantee if the peer will receive the FIN or the cancellation error first.
func (s *SendStream) CancelWrite(errorCode StreamErrorCode) {
s.mutex.Lock()
if s.shutdownErr != nil {
s.mutex.Unlock()
return
}
s.cancellationFlagged = true
if s.resetErr != nil {
completed := s.isNewlyCompleted()
s.mutex.Unlock()
// The user has called CancelWrite. If the previous cancellation was because of a
// STOP_SENDING, we don't need to flag the error to the user anymore.
if completed {
s.sender.onStreamCompleted(s.streamID)
}
return
}
s.resetErr = &StreamError{StreamID: s.streamID, ErrorCode: errorCode, Remote: false}
s.ctxCancel(s.resetErr)
reliableOffset := s.reliableOffset()
finalSize := max(s.writeOffset, reliableOffset)
if s.nextFrameReserved && s.nextFrame != nil {
finalSize = max(finalSize, s.nextFrame.Offset+s.nextFrame.DataLen())
}
if reliableOffset == 0 {
s.numOutstandingFrames = 0
s.returnFramesToPool()
}
s.queuedResetStreamFrame = &wire.ResetStreamFrame{
StreamID: s.streamID,
FinalSize: finalSize,
ErrorCode: errorCode,
// if the peer doesn't support the extension, the reliable offset will always be 0
ReliableSize: reliableOffset,
}
if reliableOffset > 0 {
if s.nextFrame != nil {
if s.nextFrame.Offset >= reliableOffset {
s.nextFrame.PutBack()
s.nextFrame = nil
s.nextFrameReserved = false
} else if s.nextFrame.Offset+s.nextFrame.DataLen() > reliableOffset {
s.nextFrame.Data = s.nextFrame.Data[:reliableOffset-s.nextFrame.Offset]
}
}
if len(s.retransmissionQueue) > 0 {
retransmissionQueue := make([]*wire.StreamFrame, 0, len(s.retransmissionQueue))
for _, f := range s.retransmissionQueue {
if f.Offset >= reliableOffset {
f.PutBack()
continue
}
if f.Offset+f.DataLen() <= reliableOffset {
retransmissionQueue = append(retransmissionQueue, f)
} else {
f.Data = f.Data[:reliableOffset-f.Offset]
retransmissionQueue = append(retransmissionQueue, f)
}
}
s.retransmissionQueue = retransmissionQueue
}
}
s.mutex.Unlock()
s.signalWrite()
s.sender.onHasStreamControlFrame(s.streamID, s)
}
func (s *SendStream) enableResetStreamAt() {
s.mutex.Lock()
s.supportsResetStreamAt = true
s.mutex.Unlock()
}
func (s *SendStream) updateSendWindow(limit protocol.ByteCount) {
s.mutex.Lock()
updated := s.flowController.UpdateSendWindow(limit)
if !updated { // duplicate or reordered MAX_STREAM_DATA frame
s.mutex.Unlock()
return
}
hasStreamData := s.dataForWriting != nil || s.nextFrame != nil
s.mutex.Unlock()
if hasStreamData {
s.sender.onHasStreamData(s.streamID, s)
}
}
func (s *SendStream) handleStopSendingFrame(f *wire.StopSendingFrame) {
s.mutex.Lock()
if s.shutdownErr != nil {
s.mutex.Unlock()
return
}
// If the stream was already cancelled (either locally, or due to a previous STOP_SENDING frame),
// there's nothing else to do.
if s.resetErr != nil && s.reliableOffset() == 0 {
s.mutex.Unlock()
return
}
// if the peer stopped reading from the stream, there's no need to transmit any data reliably
s.reliableSize = 0
s.numOutstandingFrames = 0
finalSize := s.writeOffset
if s.nextFrameReserved && s.nextFrame != nil {
finalSize = max(finalSize, s.nextFrame.Offset+s.nextFrame.DataLen())
}
s.returnFramesToPool()
if s.resetErr == nil {
s.resetErr = &StreamError{StreamID: s.streamID, ErrorCode: f.ErrorCode, Remote: true}
s.ctxCancel(s.resetErr)
}
s.queuedResetStreamFrame = &wire.ResetStreamFrame{
StreamID: s.streamID,
FinalSize: finalSize,
ErrorCode: s.resetErr.ErrorCode,
}
s.mutex.Unlock()
s.signalWrite()
s.sender.onHasStreamControlFrame(s.streamID, s)
}
func (s *SendStream) getControlFrame(monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.queuedResetStreamFrame == nil {
return ackhandler.Frame{}, false, false
}
s.numOutstandingFrames++
f := ackhandler.Frame{
Frame: s.queuedResetStreamFrame,
Handler: (*sendStreamResetStreamHandler)(s),
}
s.queuedResetStreamFrame = nil
return f, true, false
}
func (s *SendStream) reliableOffset() protocol.ByteCount {
if !s.supportsResetStreamAt {
return 0
}
return s.reliableSize
}
// The Context is canceled as soon as the write-side of the stream is closed.
// This happens when Close() or CancelWrite() is called, or when the peer
// cancels the read-side of their stream.
// The cancellation cause is set to the error that caused the stream to
// close, or `context.Canceled` in case the stream is closed without error.
func (s *SendStream) Context() context.Context {
return s.ctx
}
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some data was successfully written.
// A zero value for t means Write will not time out.
func (s *SendStream) SetWriteDeadline(t time.Time) error {
s.mutex.Lock()
s.deadline = monotime.FromTime(t)
s.mutex.Unlock()
s.signalWrite()
return nil
}
// CloseForShutdown closes a stream abruptly.
// It makes Write unblock (and return the error) immediately.
// The peer will NOT be informed about this: the stream is closed without sending a FIN or RST.
func (s *SendStream) closeForShutdown(err error) {
s.mutex.Lock()
if s.shutdownErr == nil && !s.finishedWriting {
s.shutdownErr = err
s.returnFramesToPool()
}
s.mutex.Unlock()
s.ctxCancel(err)
s.signalWrite()
}
// signalWrite performs a non-blocking send on the writeChan
func (s *SendStream) signalWrite() {
select {
case s.writeChan <- struct{}{}:
default:
}
}
type sendStreamAckHandler SendStream
var _ ackhandler.FrameHandler = &sendStreamAckHandler{}
func (s *sendStreamAckHandler) OnAcked(f wire.Frame) {
sf := f.(*wire.StreamFrame)
sf.PutBack()
s.mutex.Lock()
if s.resetErr != nil && (*SendStream)(s).reliableOffset() == 0 {
s.mutex.Unlock()
return
}
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
completed := (*SendStream)(s).isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
}
func (s *sendStreamAckHandler) OnLost(f wire.Frame) {
sf := f.(*wire.StreamFrame)
s.mutex.Lock()
// If the reliable size was 0 when the stream was cancelled,
// the number of outstanding frames was immediately set to 0, and the retransmission queue was dropped.
if s.resetErr != nil && (*SendStream)(s).reliableOffset() == 0 {
// Return the frame to pool since it won't be retransmitted
sf.PutBack()
s.mutex.Unlock()
return
}
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
if s.resetErr != nil && (*SendStream)(s).reliableOffset() > 0 {
// If the stream was reset, and this frame is beyond the reliable offset,
// it doesn't need to be retransmitted.
if sf.Offset >= (*SendStream)(s).reliableOffset() {
sf.PutBack()
// If this frame was the last one tracked, losing it might cause the stream to be completed.
completed := (*SendStream)(s).isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
return
}
// If the payload of the frame extends beyond the reliable size,
// truncate the frame to the reliable size.
if sf.Offset+sf.DataLen() > (*SendStream)(s).reliableOffset() {
sf.Data = sf.Data[:(*SendStream)(s).reliableOffset()-sf.Offset]
}
}
sf.DataLenPresent = true
s.retransmissionQueue = append(s.retransmissionQueue, sf)
s.mutex.Unlock()
s.sender.onHasStreamData(s.streamID, (*SendStream)(s))
}
type sendStreamResetStreamHandler SendStream
var _ ackhandler.FrameHandler = &sendStreamResetStreamHandler{}
func (s *sendStreamResetStreamHandler) OnAcked(f wire.Frame) {
rsf := f.(*wire.ResetStreamFrame)
s.mutex.Lock()
// If the peer sent a STOP_SENDING after we sent a RESET_STREAM_AT frame,
// we sent 1. reduced the reliable size to 0 and 2. sent a RESET_STREAM frame.
// In this case, we don't care about the acknowledgment of this frame.
if rsf.ReliableSize != (*SendStream)(s).reliableOffset() {
s.mutex.Unlock()
return
}
s.numOutstandingFrames--
if s.numOutstandingFrames < 0 {
panic("numOutStandingFrames negative")
}
completed := (*SendStream)(s).isNewlyCompleted()
s.mutex.Unlock()
if completed {
s.sender.onStreamCompleted(s.streamID)
}
}
func (s *sendStreamResetStreamHandler) OnLost(f wire.Frame) {
rsf := f.(*wire.ResetStreamFrame)
s.mutex.Lock()
// If the peer sent a STOP_SENDING after we sent a RESET_STREAM_AT frame,
// we sent 1. reduced the reliable size to 0 and 2. sent a RESET_STREAM frame.
// In this case, the loss of the RESET_STREAM_AT frame can be ignored.
if rsf.ReliableSize != (*SendStream)(s).reliableOffset() {
s.mutex.Unlock()
return
}
s.queuedResetStreamFrame = rsf
s.numOutstandingFrames--
s.mutex.Unlock()
s.sender.onHasStreamControlFrame(s.streamID, (*SendStream)(s))
}
package quic
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/quic-go/quic-go/internal/handshake"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
// ErrServerClosed is returned by the [Listener] or [EarlyListener]'s Accept method after a call to Close.
var ErrServerClosed = errServerClosed{}
type errServerClosed struct{}
func (errServerClosed) Error() string { return "quic: server closed" }
func (errServerClosed) Unwrap() error { return net.ErrClosed }
// packetHandler handles packets
type packetHandler interface {
handlePacket(receivedPacket)
destroy(error)
closeWithTransportError(qerr.TransportErrorCode)
}
type zeroRTTQueue struct {
packets []receivedPacket
expiration monotime.Time
}
type rejectedPacket struct {
receivedPacket
hdr *wire.Header
}
// A Listener of QUIC
type baseServer struct {
tr *packetHandlerMap
disableVersionNegotiation bool
acceptEarlyConns bool
tlsConf *tls.Config
config *Config
conn rawConn
tokenGenerator *handshake.TokenGenerator
maxTokenAge time.Duration
connIDGenerator ConnectionIDGenerator
statelessResetter *statelessResetter
onClose func()
receivedPackets chan receivedPacket
nextZeroRTTCleanup monotime.Time
zeroRTTQueues map[protocol.ConnectionID]*zeroRTTQueue // only initialized if acceptEarlyConns == true
connContext func(context.Context, *ClientInfo) (context.Context, error)
// set as a member, so they can be set in the tests
newConn func(
context.Context,
context.CancelCauseFunc,
sendConn,
connRunner,
protocol.ConnectionID, /* original dest connection ID */
*protocol.ConnectionID, /* retry src connection ID */
protocol.ConnectionID, /* client dest connection ID */
protocol.ConnectionID, /* destination connection ID */
protocol.ConnectionID, /* source connection ID */
ConnectionIDGenerator,
*statelessResetter,
*Config,
*tls.Config,
*handshake.TokenGenerator,
bool, /* client address validated by an address validation token */
time.Duration,
qlogwriter.Trace,
utils.Logger,
protocol.Version,
) *wrappedConn
closeMx sync.Mutex
// errorChan is closed when Close is called. This has two effects:
// 1. it cancels handshakes that are still in flight (using CONNECTION_REFUSED) errors
// 2. it stops handling of packets passed to this server
errorChan chan struct{}
// acceptChan is closed when Close returns.
// This only happens once all handshake in flight have either completed and canceled.
// Calls to Accept will first drain the queue of connections that have completed the handshake,
// and then return ErrServerClosed.
stopAccepting chan struct{}
closeErr error
running chan struct{} // closed as soon as run() returns
versionNegotiationQueue chan receivedPacket
invalidTokenQueue chan rejectedPacket
connectionRefusedQueue chan rejectedPacket
retryQueue chan rejectedPacket
handshakingCount sync.WaitGroup
verifySourceAddress func(net.Addr) bool
connQueue chan *Conn
qlogger qlogwriter.Recorder
logger utils.Logger
}
// A Listener listens for incoming QUIC connections.
// It returns connections once the handshake has completed.
type Listener struct {
baseServer *baseServer
}
// Accept returns new connections. It should be called in a loop.
func (l *Listener) Accept(ctx context.Context) (*Conn, error) {
return l.baseServer.Accept(ctx)
}
// Close closes the listener.
// Accept will return [ErrServerClosed] as soon as all connections in the accept queue have been accepted.
// QUIC handshakes that are still in flight will be rejected with a CONNECTION_REFUSED error.
// Already established (accepted) connections will be unaffected.
func (l *Listener) Close() error {
return l.baseServer.Close()
}
// Addr returns the local network address that the server is listening on.
func (l *Listener) Addr() net.Addr {
return l.baseServer.Addr()
}
// An EarlyListener listens for incoming QUIC connections, and returns them before the handshake completes.
// For connections that don't use 0-RTT, this allows the server to send 0.5-RTT data.
// This data is encrypted with forward-secure keys, however, the client's identity has not yet been verified.
// For connection using 0-RTT, this allows the server to accept and respond to streams that the client opened in the
// 0-RTT data it sent. Note that at this point during the handshake, the live-ness of the
// client has not yet been confirmed, and the 0-RTT data could have been replayed by an attacker.
type EarlyListener struct {
baseServer *baseServer
}
// Accept returns a new connections. It should be called in a loop.
func (l *EarlyListener) Accept(ctx context.Context) (*Conn, error) {
conn, err := l.baseServer.accept(ctx)
if err != nil {
return nil, err
}
return conn, nil
}
// Close closes the listener.
// Accept will return [ErrServerClosed] as soon as all connections in the accept queue have been accepted.
// Early connections that are still in flight will be rejected with a CONNECTION_REFUSED error.
// Already established (accepted) connections will be unaffected.
func (l *EarlyListener) Close() error {
return l.baseServer.Close()
}
// Addr returns the local network addr that the server is listening on.
func (l *EarlyListener) Addr() net.Addr {
return l.baseServer.Addr()
}
// ListenAddr creates a QUIC server listening on a given address.
// See [Listen] for more details.
func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (*Listener, error) {
conn, err := listenUDP(addr)
if err != nil {
return nil, err
}
return (&Transport{
Conn: conn,
createdConn: true,
isSingleUse: true,
}).Listen(tlsConf, config)
}
// ListenAddrEarly works like [ListenAddr], but it returns connections before the handshake completes.
func ListenAddrEarly(addr string, tlsConf *tls.Config, config *Config) (*EarlyListener, error) {
conn, err := listenUDP(addr)
if err != nil {
return nil, err
}
return (&Transport{
Conn: conn,
createdConn: true,
isSingleUse: true,
}).ListenEarly(tlsConf, config)
}
func listenUDP(addr string) (*net.UDPConn, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
return net.ListenUDP("udp", udpAddr)
}
// Listen listens for QUIC connections on a given net.PacketConn.
// If the PacketConn satisfies the [OOBCapablePacketConn] interface (as a [net.UDPConn] does),
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
// will be used instead of ReadFrom and WriteTo to read/write packets.
// A single net.PacketConn can only be used for a single call to Listen.
//
// The tls.Config must not be nil and must contain a certificate configuration.
// Furthermore, it must define an application control (using [NextProtos]).
// The quic.Config may be nil, in that case the default values will be used.
//
// This is a convenience function. More advanced use cases should instantiate a [Transport],
// which offers configuration options for a more fine-grained control of the connection establishment,
// including reusing the underlying UDP socket for outgoing QUIC connections.
// When closing a listener created with Listen, all established QUIC connections will be closed immediately.
func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*Listener, error) {
tr := &Transport{Conn: conn, isSingleUse: true}
return tr.Listen(tlsConf, config)
}
// ListenEarly works like [Listen], but it returns connections before the handshake completes.
func ListenEarly(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*EarlyListener, error) {
tr := &Transport{Conn: conn, isSingleUse: true}
return tr.ListenEarly(tlsConf, config)
}
func newServer(
conn rawConn,
tr *packetHandlerMap,
connIDGenerator ConnectionIDGenerator,
statelessResetter *statelessResetter,
connContext func(context.Context, *ClientInfo) (context.Context, error),
tlsConf *tls.Config,
config *Config,
qlogger qlogwriter.Recorder,
onClose func(),
tokenGeneratorKey TokenGeneratorKey,
maxTokenAge time.Duration,
verifySourceAddress func(net.Addr) bool,
disableVersionNegotiation bool,
acceptEarly bool,
) *baseServer {
s := &baseServer{
conn: conn,
connContext: connContext,
tr: tr,
tlsConf: tlsConf,
config: config,
tokenGenerator: handshake.NewTokenGenerator(tokenGeneratorKey),
maxTokenAge: maxTokenAge,
verifySourceAddress: verifySourceAddress,
connIDGenerator: connIDGenerator,
statelessResetter: statelessResetter,
connQueue: make(chan *Conn, protocol.MaxAcceptQueueSize),
errorChan: make(chan struct{}),
stopAccepting: make(chan struct{}),
running: make(chan struct{}),
receivedPackets: make(chan receivedPacket, protocol.MaxServerUnprocessedPackets),
versionNegotiationQueue: make(chan receivedPacket, 4),
invalidTokenQueue: make(chan rejectedPacket, 4),
connectionRefusedQueue: make(chan rejectedPacket, 4),
retryQueue: make(chan rejectedPacket, 8),
newConn: newConnection,
qlogger: qlogger,
logger: utils.DefaultLogger.WithPrefix("server"),
acceptEarlyConns: acceptEarly,
disableVersionNegotiation: disableVersionNegotiation,
onClose: onClose,
}
if acceptEarly {
s.zeroRTTQueues = map[protocol.ConnectionID]*zeroRTTQueue{}
}
go s.run()
go s.runSendQueue()
s.logger.Debugf("Listening for %s connections on %s", conn.LocalAddr().Network(), conn.LocalAddr().String())
return s
}
func (s *baseServer) run() {
defer close(s.running)
for {
select {
case <-s.errorChan:
return
default:
}
select {
case <-s.errorChan:
return
case p := <-s.receivedPackets:
if bufferStillInUse := s.handlePacketImpl(p); !bufferStillInUse {
p.buffer.Release()
}
}
}
}
func (s *baseServer) runSendQueue() {
for {
select {
case <-s.running:
return
case p := <-s.versionNegotiationQueue:
s.maybeSendVersionNegotiationPacket(p)
case p := <-s.invalidTokenQueue:
s.maybeSendInvalidToken(p)
case p := <-s.connectionRefusedQueue:
s.sendConnectionRefused(p)
case p := <-s.retryQueue:
s.sendRetry(p)
}
}
}
// Accept returns connections that already completed the handshake.
// It is only valid if acceptEarlyConns is false.
func (s *baseServer) Accept(ctx context.Context) (*Conn, error) {
return s.accept(ctx)
}
func (s *baseServer) accept(ctx context.Context) (*Conn, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case conn := <-s.connQueue:
return conn, nil
case <-s.stopAccepting:
// first drain the queue
select {
case conn := <-s.connQueue:
return conn, nil
default:
}
return nil, s.closeErr
}
}
func (s *baseServer) Close() error {
s.close(ErrServerClosed, false)
return nil
}
// close closes the server. The Transport mutex must not be held while calling this method.
// This method closes any handshaking connections which requires the tranpsort mutex.
func (s *baseServer) close(e error, transportClose bool) {
s.closeMx.Lock()
if s.closeErr != nil {
s.closeMx.Unlock()
return
}
s.closeErr = e
close(s.errorChan)
<-s.running
s.closeMx.Unlock()
if !transportClose {
s.onClose()
}
// wait until all handshakes in flight have terminated
s.handshakingCount.Wait()
close(s.stopAccepting)
if transportClose {
// if the transport is closing, drain the connQueue. All connections in the queue
// will be closed by the transport.
for {
select {
case <-s.connQueue:
default:
return
}
}
}
}
// Addr returns the server's network address
func (s *baseServer) Addr() net.Addr {
return s.conn.LocalAddr()
}
func (s *baseServer) handlePacket(p receivedPacket) {
select {
case s.receivedPackets <- p:
case <-s.errorChan:
return
default:
s.logger.Debugf("Dropping packet from %s (%d bytes). Server receive queue full.", p.remoteAddr, p.Size())
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropDOSPrevention,
})
}
}
}
func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer still in use? */ {
if !s.nextZeroRTTCleanup.IsZero() && p.rcvTime.After(s.nextZeroRTTCleanup) {
defer s.cleanupZeroRTTQueues(p.rcvTime)
}
if wire.IsVersionNegotiationPacket(p.data) {
s.logger.Debugf("Dropping Version Negotiation packet.")
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{PacketType: qlog.PacketTypeVersionNegotiation},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
// Short header packets should never end up here in the first place
if !wire.IsLongHeaderPacket(p.data[0]) {
panic(fmt.Sprintf("misrouted packet: %#v", p.data))
}
v, err := wire.ParseVersion(p.data)
// drop the packet if we failed to parse the protocol version
if err != nil {
s.logger.Debugf("Dropping a packet with an unknown version")
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
// send a Version Negotiation Packet if the client is speaking a different protocol version
if !protocol.IsSupportedVersion(s.config.Versions, v) {
if s.disableVersionNegotiation {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{Version: v},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedVersion,
})
}
return false
}
if p.Size() < protocol.MinUnknownVersionPacketSize {
s.logger.Debugf("Dropping a packet with an unsupported version number %d that is too small (%d bytes)", v, p.Size())
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{Version: v},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
return s.enqueueVersionNegotiationPacket(p)
}
if wire.Is0RTTPacket(p.data) {
if !s.acceptEarlyConns {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
return s.handle0RTTPacket(p)
}
// If we're creating a new connection, the packet will be passed to the connection.
// The header will then be parsed again.
hdr, _, _, err := wire.ParsePacket(p.data)
if err != nil {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropHeaderParseError,
})
}
s.logger.Debugf("Error parsing packet: %s", err)
return false
}
if hdr.Type == protocol.PacketTypeInitial && p.Size() < protocol.MinInitialPacketSize {
s.logger.Debugf("Dropping a packet that is too small to be a valid Initial (%d bytes)", p.Size())
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
if hdr.Type != protocol.PacketTypeInitial {
// Drop long header packets.
// There's little point in sending a Stateless Reset, since the client
// might not have received the token yet.
s.logger.Debugf("Dropping long header packet of type %s (%d bytes)", hdr.Type, len(p.data))
if s.qlogger != nil {
var pt qlog.PacketType
switch hdr.Type {
case protocol.PacketTypeInitial:
pt = qlog.PacketTypeInitial
case protocol.PacketTypeHandshake:
pt = qlog.PacketTypeHandshake
case protocol.PacketType0RTT:
pt = qlog.PacketType0RTT
case protocol.PacketTypeRetry:
pt = qlog.PacketTypeRetry
}
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: pt,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return false
}
s.logger.Debugf("<- Received Initial packet.")
if err := s.handleInitialImpl(p, hdr); err != nil {
s.logger.Errorf("Error occurred handling initial packet: %s", err)
}
// Don't put the packet buffer back.
// handleInitialImpl deals with the buffer.
return true
}
func (s *baseServer) handle0RTTPacket(p receivedPacket) bool {
connID, err := wire.ParseConnectionID(p.data, 0)
if err != nil {
if s.qlogger != nil {
v, _ := wire.ParseVersion(p.data)
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropHeaderParseError,
})
}
return false
}
// check again if we might have a connection now
if handler, ok := s.tr.Get(connID); ok {
handler.handlePacket(p)
return true
}
if q, ok := s.zeroRTTQueues[connID]; ok {
if len(q.packets) >= protocol.Max0RTTQueueLen {
if s.qlogger != nil {
v, _ := wire.ParseVersion(p.data)
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropDOSPrevention,
})
}
return false
}
q.packets = append(q.packets, p)
return true
}
if len(s.zeroRTTQueues) >= protocol.Max0RTTQueues {
if s.qlogger != nil {
v, _ := wire.ParseVersion(p.data)
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropDOSPrevention,
})
}
return false
}
queue := &zeroRTTQueue{packets: make([]receivedPacket, 1, 8)}
queue.packets[0] = p
expiration := p.rcvTime.Add(protocol.Max0RTTQueueingDuration)
queue.expiration = expiration
if s.nextZeroRTTCleanup.IsZero() || s.nextZeroRTTCleanup.After(expiration) {
s.nextZeroRTTCleanup = expiration
}
s.zeroRTTQueues[connID] = queue
return true
}
func (s *baseServer) cleanupZeroRTTQueues(now monotime.Time) {
// Iterate over all queues to find those that are expired.
// This is ok since we're placing a pretty low limit on the number of queues.
var nextCleanup monotime.Time
for connID, q := range s.zeroRTTQueues {
if q.expiration.After(now) {
if nextCleanup.IsZero() || nextCleanup.After(q.expiration) {
nextCleanup = q.expiration
}
continue
}
for _, p := range q.packets {
if s.qlogger != nil {
v, _ := wire.ParseVersion(p.data)
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketType0RTT,
PacketNumber: protocol.InvalidPacketNumber,
Version: v,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropDOSPrevention,
})
}
p.buffer.Release()
}
delete(s.zeroRTTQueues, connID)
if s.logger.Debug() {
s.logger.Debugf("Removing 0-RTT queue for %s.", connID)
}
}
s.nextZeroRTTCleanup = nextCleanup
}
// validateToken returns false if:
// - address is invalid
// - token is expired
// - token is null
func (s *baseServer) validateToken(token *handshake.Token, addr net.Addr) bool {
if token == nil {
return false
}
if !token.ValidateRemoteAddr(addr) {
return false
}
if !token.IsRetryToken && time.Since(token.SentTime) > s.maxTokenAge {
return false
}
if token.IsRetryToken && time.Since(token.SentTime) > s.config.maxRetryTokenAge() {
return false
}
return true
}
func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error {
if len(hdr.Token) == 0 && hdr.DestConnectionID.Len() < protocol.MinConnectionIDLenInitial {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
PacketNumber: protocol.InvalidPacketNumber,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
p.buffer.Release()
return errors.New("too short connection ID")
}
// The server queues packets for a while, and we might already have established a connection by now.
// This results in a second check in the connection map.
// That's ok since it's not the hot path (it's only taken by some Initial and 0-RTT packets).
if handler, ok := s.tr.Get(hdr.DestConnectionID); ok {
handler.handlePacket(p)
return nil
}
var (
token *handshake.Token
retrySrcConnID *protocol.ConnectionID
clientAddrVerified bool
)
origDestConnID := hdr.DestConnectionID
if len(hdr.Token) > 0 {
tok, err := s.tokenGenerator.DecodeToken(hdr.Token)
if err == nil {
if tok.IsRetryToken {
origDestConnID = tok.OriginalDestConnectionID
retrySrcConnID = &tok.RetrySrcConnectionID
}
token = tok
}
}
if token != nil {
clientAddrVerified = s.validateToken(token, p.remoteAddr)
if !clientAddrVerified {
// For invalid and expired non-retry tokens, we don't send an INVALID_TOKEN error.
// We just ignore them, and act as if there was no token on this packet at all.
// This also means we might send a Retry later.
if !token.IsRetryToken {
token = nil
} else {
// For Retry tokens, we send an INVALID_ERROR if
// * the token is too old, or
// * the token is invalid, in case of a retry token.
select {
case s.invalidTokenQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out the INVALID_TOKEN packets fast enough
p.buffer.Release()
}
return nil
}
}
}
if token == nil && s.verifySourceAddress != nil && s.verifySourceAddress(p.remoteAddr) {
// Retry invalidates all 0-RTT packets sent.
delete(s.zeroRTTQueues, hdr.DestConnectionID)
select {
case s.retryQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out Retry packets fast enough
p.buffer.Release()
}
return nil
}
// restore RTT from token
var rtt time.Duration
if token != nil && !token.IsRetryToken {
rtt = token.RTT
}
config := s.config
clientInfo := &ClientInfo{
RemoteAddr: p.remoteAddr,
AddrVerified: clientAddrVerified,
}
if s.config.GetConfigForClient != nil {
conf, err := s.config.GetConfigForClient(clientInfo)
if err != nil {
s.logger.Debugf("Rejecting new connection due to GetConfigForClient callback")
s.refuseNewConn(p, hdr)
return nil
}
config = populateConfig(conf)
}
var conn *wrappedConn
var cancel context.CancelCauseFunc
ctx, cancel1 := context.WithCancelCause(context.Background())
if s.connContext != nil {
var err error
ctx, err = s.connContext(ctx, clientInfo)
if err != nil {
cancel1(err)
s.logger.Debugf("Rejecting new connection due to ConnContext callback: %s", err)
s.refuseNewConn(p, hdr)
return nil
}
if ctx == nil {
panic("quic: ConnContext returned nil")
}
// There's no guarantee that the application returns a context
// that's derived from the context we passed into ConnContext.
// We need to make sure that both contexts are cancelled.
var cancel2 context.CancelCauseFunc
ctx, cancel2 = context.WithCancelCause(ctx)
cancel = func(cause error) {
cancel1(cause)
cancel2(cause)
}
} else {
cancel = cancel1
}
var qlogTrace qlogwriter.Trace
if config.Tracer != nil {
// Use the same connection ID that is passed to the client's GetLogWriter callback.
connID := hdr.DestConnectionID
if origDestConnID.Len() > 0 {
connID = origDestConnID
}
qlogTrace = config.Tracer(ctx, false, connID)
}
connID, err := s.connIDGenerator.GenerateConnectionID()
if err != nil {
return err
}
s.logger.Debugf("Changing connection ID to %s.", connID)
conn = s.newConn(
ctx,
cancel,
newSendConn(s.conn, p.remoteAddr, p.info, s.logger),
s.tr,
origDestConnID,
retrySrcConnID,
hdr.DestConnectionID,
hdr.SrcConnectionID,
connID,
s.connIDGenerator,
s.statelessResetter,
config,
s.tlsConf,
s.tokenGenerator,
clientAddrVerified,
rtt,
qlogTrace,
s.logger,
hdr.Version,
)
conn.handlePacket(p)
// Adding the connection will fail if the client's chosen Destination Connection ID is already in use.
// This is very unlikely: Even if an attacker chooses a connection ID that's already in use,
// under normal circumstances the packet would just be routed to that connection.
// The only time this collision will occur if we receive the two Initial packets at the same time.
if added := s.tr.AddWithConnID(hdr.DestConnectionID, connID, conn); !added {
delete(s.zeroRTTQueues, hdr.DestConnectionID)
conn.closeWithTransportError(ConnectionRefused)
return nil
}
// Pass queued 0-RTT to the newly established connection.
if q, ok := s.zeroRTTQueues[hdr.DestConnectionID]; ok {
for _, p := range q.packets {
conn.handlePacket(p)
}
delete(s.zeroRTTQueues, hdr.DestConnectionID)
}
s.handshakingCount.Go(func() { s.handleNewConn(conn) })
go conn.run()
return nil
}
func (s *baseServer) refuseNewConn(p receivedPacket, hdr *wire.Header) {
delete(s.zeroRTTQueues, hdr.DestConnectionID)
select {
case s.connectionRefusedQueue <- rejectedPacket{receivedPacket: p, hdr: hdr}:
default:
// drop packet if we can't send out the CONNECTION_REFUSED fast enough
p.buffer.Release()
}
}
func (s *baseServer) handleNewConn(conn *wrappedConn) {
if s.acceptEarlyConns {
// wait until the early connection is ready, the handshake fails, or the server is closed
select {
case <-s.errorChan:
conn.closeWithTransportError(ConnectionRefused)
return
case <-conn.Context().Done():
return
case <-conn.earlyConnReady():
}
} else {
// wait until the handshake completes, fails, or the server is closed
select {
case <-s.errorChan:
conn.closeWithTransportError(ConnectionRefused)
return
case <-conn.Context().Done():
return
case <-conn.HandshakeComplete():
}
}
select {
case s.connQueue <- conn.Conn:
default:
conn.closeWithTransportError(ConnectionRefused)
}
}
func (s *baseServer) sendRetry(p rejectedPacket) {
if err := s.sendRetryPacket(p); err != nil {
s.logger.Debugf("Error sending Retry packet: %s", err)
}
}
func (s *baseServer) sendRetryPacket(p rejectedPacket) error {
hdr := p.hdr
// Log the Initial packet now.
// If no Retry is sent, the packet will be logged by the connection.
(&wire.ExtendedHeader{Header: *hdr}).Log(s.logger)
srcConnID, err := s.connIDGenerator.GenerateConnectionID()
if err != nil {
return err
}
token, err := s.tokenGenerator.NewRetryToken(p.remoteAddr, hdr.DestConnectionID, srcConnID)
if err != nil {
return err
}
replyHdr := &wire.ExtendedHeader{}
replyHdr.Type = protocol.PacketTypeRetry
replyHdr.Version = hdr.Version
replyHdr.SrcConnectionID = srcConnID
replyHdr.DestConnectionID = hdr.SrcConnectionID
replyHdr.Token = token
if s.logger.Debug() {
s.logger.Debugf("Changing connection ID to %s.", srcConnID)
s.logger.Debugf("-> Sending Retry")
replyHdr.Log(s.logger)
}
buf := getPacketBuffer()
defer buf.Release()
buf.Data, err = replyHdr.Append(buf.Data, hdr.Version)
if err != nil {
return err
}
// append the Retry integrity tag
tag := handshake.GetRetryIntegrityTag(buf.Data, hdr.DestConnectionID, hdr.Version)
buf.Data = append(buf.Data, tag[:]...)
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketSent{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeRetry,
SrcConnectionID: replyHdr.SrcConnectionID,
DestConnectionID: replyHdr.DestConnectionID,
Version: replyHdr.Version,
Token: &qlog.Token{Raw: token},
},
Raw: qlog.RawInfo{
Length: len(buf.Data),
PayloadLength: int(replyHdr.Length),
},
})
}
_, err = s.conn.WritePacket(buf.Data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported)
return err
}
func (s *baseServer) maybeSendInvalidToken(p rejectedPacket) {
defer p.buffer.Release()
// Only send INVALID_TOKEN if we can unprotect the packet.
// This makes sure that we won't send it for packets that were corrupted.
hdr := p.hdr
sealer, opener := handshake.NewInitialAEAD(hdr.DestConnectionID, protocol.PerspectiveServer, hdr.Version)
data := p.data[:hdr.ParsedLen()+hdr.Length]
extHdr, err := unpackLongHeader(opener, hdr, data)
// Only send INVALID_TOKEN if we can unprotect the packet.
// This makes sure that we won't send it for packets that were corrupted.
if err != nil {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
PacketNumber: protocol.InvalidPacketNumber,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropHeaderParseError,
})
}
return
}
hdrLen := extHdr.ParsedLen()
if _, err := opener.Open(data[hdrLen:hdrLen], data[hdrLen:], extHdr.PacketNumber, data[:hdrLen]); err != nil {
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
PacketNumber: protocol.InvalidPacketNumber,
Version: hdr.Version,
},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropPayloadDecryptError,
})
}
return
}
if s.logger.Debug() {
s.logger.Debugf("Client sent an invalid retry token. Sending INVALID_TOKEN to %s.", p.remoteAddr)
}
if err := s.sendError(p.remoteAddr, hdr, sealer, InvalidToken, p.info); err != nil {
s.logger.Debugf("Error sending INVALID_TOKEN error: %s", err)
}
}
func (s *baseServer) sendConnectionRefused(p rejectedPacket) {
defer p.buffer.Release()
sealer, _ := handshake.NewInitialAEAD(p.hdr.DestConnectionID, protocol.PerspectiveServer, p.hdr.Version)
if err := s.sendError(p.remoteAddr, p.hdr, sealer, ConnectionRefused, p.info); err != nil {
s.logger.Debugf("Error sending CONNECTION_REFUSED error: %s", err)
}
}
// sendError sends the error as a response to the packet received with header hdr
func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer handshake.LongHeaderSealer, errorCode qerr.TransportErrorCode, info packetInfo) error {
b := getPacketBuffer()
defer b.Release()
ccf := &wire.ConnectionCloseFrame{ErrorCode: uint64(errorCode)}
replyHdr := &wire.ExtendedHeader{}
replyHdr.Type = protocol.PacketTypeInitial
replyHdr.Version = hdr.Version
replyHdr.SrcConnectionID = hdr.DestConnectionID
replyHdr.DestConnectionID = hdr.SrcConnectionID
replyHdr.PacketNumberLen = protocol.PacketNumberLen4
replyHdr.Length = 4 /* packet number len */ + ccf.Length(hdr.Version) + protocol.ByteCount(sealer.Overhead())
var err error
b.Data, err = replyHdr.Append(b.Data, hdr.Version)
if err != nil {
return err
}
payloadOffset := len(b.Data)
b.Data, err = ccf.Append(b.Data, hdr.Version)
if err != nil {
return err
}
_ = sealer.Seal(b.Data[payloadOffset:payloadOffset], b.Data[payloadOffset:], replyHdr.PacketNumber, b.Data[:payloadOffset])
b.Data = b.Data[0 : len(b.Data)+sealer.Overhead()]
pnOffset := payloadOffset - int(replyHdr.PacketNumberLen)
sealer.EncryptHeader(
b.Data[pnOffset+4:pnOffset+4+16],
&b.Data[0],
b.Data[pnOffset:payloadOffset],
)
replyHdr.Log(s.logger)
wire.LogFrame(s.logger, ccf, true)
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketSent{
Header: qlog.PacketHeader{
PacketType: qlog.PacketTypeInitial,
SrcConnectionID: replyHdr.SrcConnectionID,
DestConnectionID: replyHdr.DestConnectionID,
PacketNumber: replyHdr.PacketNumber,
Version: replyHdr.Version,
},
Raw: qlog.RawInfo{
Length: len(b.Data),
PayloadLength: int(replyHdr.Length),
},
Frames: []qlog.Frame{{Frame: ccf}},
})
}
_, err = s.conn.WritePacket(b.Data, remoteAddr, info.OOB(), 0, protocol.ECNUnsupported)
return err
}
func (s *baseServer) enqueueVersionNegotiationPacket(p receivedPacket) (bufferInUse bool) {
select {
case s.versionNegotiationQueue <- p:
return true
default:
// it's fine to not send version negotiation packets when we are busy
}
return false
}
func (s *baseServer) maybeSendVersionNegotiationPacket(p receivedPacket) {
defer p.buffer.Release()
v, err := wire.ParseVersion(p.data)
if err != nil {
s.logger.Debugf("failed to parse version for sending version negotiation packet: %s", err)
return
}
_, src, dest, err := wire.ParseArbitraryLenConnectionIDs(p.data)
if err != nil { // should never happen
s.logger.Debugf("Dropping a packet with an unknown version for which we failed to parse connection IDs")
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnexpectedPacket,
})
}
return
}
s.logger.Debugf("Client offered version %s, sending Version Negotiation", v)
data := wire.ComposeVersionNegotiation(dest, src, s.config.Versions)
if s.qlogger != nil {
s.qlogger.RecordEvent(qlog.VersionNegotiationSent{
Header: qlog.PacketHeaderVersionNegotiation{
SrcConnectionID: src,
DestConnectionID: dest,
},
SupportedVersions: s.config.Versions,
})
}
if _, err := s.conn.WritePacket(data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported); err != nil {
s.logger.Debugf("Error sending Version Negotiation: %s", err)
}
}
package quic
import (
"encoding/binary"
"errors"
"io"
)
const (
extTypeSNI = 0
extTypeECH = 0xfe0d
)
// findSNIAndECH parses the given byte slice as a ClientHello, and locates:
// - the position and length of the Server Name Indication (SNI) extension,
// - the position of the Encrypted Client Hello (ECH) extension.
// If no SNI extension is found, it returns -1 for the SNI position.
// If no ECH extension is found, it returns -1 for the ECH position.
func findSNIAndECH(data []byte) (sniPos, sniLen, echPos int, err error) {
if len(data) < 4 {
return 0, 0, 0, io.ErrUnexpectedEOF
}
if data[0] != 1 {
return 0, 0, 0, errors.New("not a ClientHello")
}
handshakeLen := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
if len(data) != 4+handshakeLen {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos := 4
// Skip protocol version (2 bytes)
if parsePos+2 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos += 2
// skip random (32 bytes)
if parsePos+32 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos += 32
// session ID
if parsePos+1 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
sessionIDLen := int(data[parsePos])
parsePos++
if parsePos+sessionIDLen > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos += sessionIDLen
// cipher suites
if parsePos+2 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
cipherSuitesLen := int(binary.BigEndian.Uint16(data[parsePos:]))
parsePos += 2
if parsePos+cipherSuitesLen > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos += cipherSuitesLen
// compression methods
if parsePos+1 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
compressionMethodsLen := int(data[parsePos])
parsePos++
if parsePos+compressionMethodsLen > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
parsePos += compressionMethodsLen
// extensions
if parsePos+2 > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
extensionsLen := int(binary.BigEndian.Uint16(data[parsePos:]))
parsePos += 2
if parsePos+extensionsLen > len(data) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
extensionsStart := parsePos
extensions := data[extensionsStart : extensionsStart+extensionsLen]
// parse extensions
var extPos int
sniPos = -1
echPos = -1
for extPos+4 <= extensionsLen {
extType := binary.BigEndian.Uint16(extensions[extPos:])
extLen := int(binary.BigEndian.Uint16(extensions[extPos+2:]))
if extPos+4+extLen > extensionsLen {
return 0, 0, 0, io.ErrUnexpectedEOF
}
switch extType {
case extTypeSNI:
if sniPos != -1 {
return 0, 0, 0, errors.New("multiple SNI extensions")
}
sniData := extensions[extPos+4 : extPos+4+extLen]
if len(sniData) < 2 {
return 0, 0, 0, io.ErrUnexpectedEOF
}
nameListLen := int(binary.BigEndian.Uint16(sniData))
if len(sniData) != 2+nameListLen {
return 0, 0, 0, io.ErrUnexpectedEOF
}
listPos := 2
for listPos+3 <= nameListLen+2 {
nameType := sniData[listPos]
sniLen = int(binary.BigEndian.Uint16(sniData[listPos+1:]))
if listPos+3+sniLen > len(sniData) {
return 0, 0, 0, io.ErrUnexpectedEOF
}
if nameType == 0 { // host_name
sniPos = extensionsStart + extPos + 4 + listPos + 3
break // stop after first host_name
}
listPos += 3 + sniLen
}
if sniPos == 0 {
return 0, 0, 0, errors.New("SNI host_name not found")
}
case extTypeECH:
if echPos != -1 {
return 0, 0, 0, errors.New("multiple ECH extensions")
}
echPos = extensionsStart + extPos
}
extPos += 4 + extLen
if sniPos != -1 && echPos != -1 {
break
}
}
return sniPos, sniLen, echPos, nil
}
package quic
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"hash"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
)
type statelessResetter struct {
mx sync.Mutex
h hash.Hash
}
// newStatelessRetter creates a new stateless reset generator.
// It is valid to use a nil key. In that case, a random key will be used.
// This makes is impossible for on-path attackers to shut down established connections.
func newStatelessResetter(key *StatelessResetKey) *statelessResetter {
var h hash.Hash
if key != nil {
h = hmac.New(sha256.New, key[:])
} else {
b := make([]byte, 32)
_, _ = rand.Read(b)
h = hmac.New(sha256.New, b)
}
return &statelessResetter{h: h}
}
func (r *statelessResetter) GetStatelessResetToken(connID protocol.ConnectionID) protocol.StatelessResetToken {
r.mx.Lock()
defer r.mx.Unlock()
var token protocol.StatelessResetToken
r.h.Write(connID.Bytes())
copy(token[:], r.h.Sum(nil))
r.h.Reset()
return token
}
package quic
import (
"context"
"net"
"os"
"sync"
"time"
"github.com/quic-go/quic-go/internal/ackhandler"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/wire"
)
type deadlineError struct{}
func (deadlineError) Error() string { return "deadline exceeded" }
func (deadlineError) Temporary() bool { return true }
func (deadlineError) Timeout() bool { return true }
func (deadlineError) Unwrap() error { return os.ErrDeadlineExceeded }
var errDeadline net.Error = &deadlineError{}
// The streamSender is notified by the stream about various events.
type streamSender interface {
onHasConnectionData()
onHasStreamData(protocol.StreamID, *SendStream)
onHasStreamControlFrame(protocol.StreamID, streamControlFrameGetter)
// must be called without holding the mutex that is acquired by closeForShutdown
onStreamCompleted(protocol.StreamID)
}
// Each of the both stream halves gets its own uniStreamSender.
// This is necessary in order to keep track when both halves have been completed.
type uniStreamSender struct {
streamSender
onStreamCompletedImpl func()
onHasStreamControlFrameImpl func(protocol.StreamID, streamControlFrameGetter)
}
func (s *uniStreamSender) onHasStreamData(id protocol.StreamID, str *SendStream) {
s.streamSender.onHasStreamData(id, str)
}
func (s *uniStreamSender) onStreamCompleted(protocol.StreamID) { s.onStreamCompletedImpl() }
func (s *uniStreamSender) onHasStreamControlFrame(id protocol.StreamID, str streamControlFrameGetter) {
s.onHasStreamControlFrameImpl(id, str)
}
var _ streamSender = &uniStreamSender{}
type Stream struct {
receiveStr *ReceiveStream
sendStr *SendStream
completedMutex sync.Mutex
sender streamSender
receiveStreamCompleted bool
sendStreamCompleted bool
}
var (
_ outgoingStream = &Stream{}
_ sendStreamFrameHandler = &Stream{}
_ receiveStreamFrameHandler = &Stream{}
)
// newStream creates a new Stream
func newStream(
ctx context.Context,
streamID protocol.StreamID,
sender streamSender,
flowController *streamFlowController,
supportsResetStreamAt bool,
) *Stream {
s := &Stream{sender: sender}
senderForSendStream := &uniStreamSender{
streamSender: sender,
onStreamCompletedImpl: func() {
s.completedMutex.Lock()
s.sendStreamCompleted = true
s.checkIfCompleted()
s.completedMutex.Unlock()
},
onHasStreamControlFrameImpl: func(id protocol.StreamID, str streamControlFrameGetter) {
sender.onHasStreamControlFrame(streamID, s)
},
}
s.sendStr = newSendStream(ctx, streamID, senderForSendStream, flowController, supportsResetStreamAt)
senderForReceiveStream := &uniStreamSender{
streamSender: sender,
onStreamCompletedImpl: func() {
s.completedMutex.Lock()
s.receiveStreamCompleted = true
s.checkIfCompleted()
s.completedMutex.Unlock()
},
onHasStreamControlFrameImpl: func(id protocol.StreamID, str streamControlFrameGetter) {
sender.onHasStreamControlFrame(streamID, s)
},
}
s.receiveStr = newReceiveStream(streamID, senderForReceiveStream, flowController)
return s
}
// StreamID returns the stream ID.
func (s *Stream) StreamID() StreamID {
// the result is same for receiveStream and sendStream
return s.sendStr.StreamID()
}
// Read reads data from the stream.
// Read can be made to time out using [Stream.SetReadDeadline] and [Stream.SetDeadline].
// If the stream was canceled, the error is a [StreamError].
func (s *Stream) Read(p []byte) (int, error) {
return s.receiveStr.Read(p)
}
// Peek fills b with stream data, without consuming the stream data.
// It blocks until len(b) bytes are available, or an error occurs.
// It respects the stream deadline set by SetReadDeadline.
// If the stream ends before len(b) bytes are available,
// it returns the number of bytes peeked along with io.EOF.
func (s *Stream) Peek(b []byte) (int, error) {
return s.receiveStr.Peek(b)
}
// Write writes data to the stream.
// Write can be made to time out using [Stream.SetWriteDeadline] or [Stream.SetDeadline].
// If the stream was canceled, the error is a [StreamError].
func (s *Stream) Write(p []byte) (int, error) {
return s.sendStr.Write(p)
}
// TryWriteAll writes data to the stream if it can be queued immediately.
// See [SendStream.TryWriteAll] for more details.
func (s *Stream) TryWriteAll(p []byte) error {
return s.sendStr.TryWriteAll(p)
}
// SetReliableBoundary marks the data written to this stream so far as reliable.
// It is valid to call this function multiple times, thereby increasing the reliable size.
// It only has an effect if the peer enabled support for the RESET_STREAM_AT extension,
// otherwise, it is a no-op.
func (s *Stream) SetReliableBoundary() {
s.sendStr.SetReliableBoundary()
}
// CancelWrite aborts sending on this stream.
// See [SendStream.CancelWrite] for more details.
func (s *Stream) CancelWrite(errorCode StreamErrorCode) {
s.sendStr.CancelWrite(errorCode)
}
// CancelRead aborts receiving on this stream.
// See [ReceiveStream.CancelRead] for more details.
func (s *Stream) CancelRead(errorCode StreamErrorCode) {
s.receiveStr.CancelRead(errorCode)
}
// WaitForReceiveFinalSize waits until the receive side's final size is known.
// See [ReceiveStream.WaitForReceiveFinalSize] for more details.
// Most applications don't need this. It is mainly useful for protocol layers
// that need exact stream final sizes, such as WebTransport flow control accounting.
func (s *Stream) WaitForReceiveFinalSize(ctx context.Context) (int64, error) {
return s.receiveStr.WaitForReceiveFinalSize(ctx)
}
// The Context is canceled as soon as the write-side of the stream is closed.
// See [SendStream.Context] for more details.
func (s *Stream) Context() context.Context {
return s.sendStr.Context()
}
// Close closes the send-direction of the stream.
// It does not close the receive-direction of the stream.
func (s *Stream) Close() error {
return s.sendStr.Close()
}
func (s *Stream) handleResetStreamFrame(frame *wire.ResetStreamFrame, rcvTime monotime.Time) error {
return s.receiveStr.handleResetStreamFrame(frame, rcvTime)
}
func (s *Stream) handleStreamFrame(frame *wire.StreamFrame, rcvTime monotime.Time) error {
return s.receiveStr.handleStreamFrame(frame, rcvTime)
}
func (s *Stream) handleStopSendingFrame(frame *wire.StopSendingFrame) {
s.sendStr.handleStopSendingFrame(frame)
}
func (s *Stream) updateSendWindow(limit protocol.ByteCount) {
s.sendStr.updateSendWindow(limit)
}
func (s *Stream) enableResetStreamAt() {
s.sendStr.enableResetStreamAt()
}
func (s *Stream) popStreamFrame(maxBytes protocol.ByteCount, v protocol.Version) (_ ackhandler.StreamFrame, _ *wire.StreamDataBlockedFrame, hasMore bool) {
return s.sendStr.popStreamFrame(maxBytes, v)
}
func (s *Stream) getControlFrame(now monotime.Time) (_ ackhandler.Frame, ok, hasMore bool) {
f, ok, _ := s.sendStr.getControlFrame(now)
if ok {
return f, true, true
}
return s.receiveStr.getControlFrame(now)
}
// SetReadDeadline sets the deadline for future Read calls.
// See [ReceiveStream.SetReadDeadline] for more details.
func (s *Stream) SetReadDeadline(t time.Time) error {
return s.receiveStr.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for future Write calls.
// See [SendStream.SetWriteDeadline] for more details.
func (s *Stream) SetWriteDeadline(t time.Time) error {
return s.sendStr.SetWriteDeadline(t)
}
// SetDeadline sets the read and write deadlines associated with the stream.
// It is equivalent to calling both SetReadDeadline and SetWriteDeadline.
func (s *Stream) SetDeadline(t time.Time) error {
_ = s.receiveStr.SetReadDeadline(t) // SetReadDeadline never errors
_ = s.sendStr.SetWriteDeadline(t) // SetWriteDeadline never errors
return nil
}
// CloseForShutdown closes a stream abruptly.
// It makes Read and Write unblock (and return the error) immediately.
// The peer will NOT be informed about this: the stream is closed without sending a FIN or RST.
func (s *Stream) closeForShutdown(err error) {
s.sendStr.closeForShutdown(err)
s.receiveStr.closeForShutdown(err)
}
// checkIfCompleted is called from the uniStreamSender, when one of the stream halves is completed.
// It makes sure that the onStreamCompleted callback is only called if both receive and send side have completed.
func (s *Stream) checkIfCompleted() {
if s.sendStreamCompleted && s.receiveStreamCompleted {
s.sender.onStreamCompleted(s.StreamID())
}
}
package quic
import (
"context"
"fmt"
"sync"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
// StreamLimitReachedError is returned from Conn.OpenStream and Conn.OpenUniStream
// when it is not possible to open a new stream because the number of opens streams reached
// the peer's stream limit.
type StreamLimitReachedError struct{}
func (e StreamLimitReachedError) Error() string { return "too many open streams" }
type streamsMap struct {
ctx context.Context // not used for cancellations, but carries the values associated with the connection
perspective protocol.Perspective
maxIncomingBidiStreams uint64
maxIncomingUniStreams uint64
sender streamSender
queueControlFrame func(wire.Frame)
newFlowController func(protocol.StreamID) *streamFlowController
mutex sync.Mutex
outgoingBidiStreams *outgoingStreamsMap[*Stream]
outgoingUniStreams *outgoingStreamsMap[*SendStream]
incomingBidiStreams *incomingStreamsMap[*Stream]
incomingUniStreams *incomingStreamsMap[*ReceiveStream]
reset bool
supportsResetStreamAt bool
}
func newStreamsMap(
ctx context.Context,
sender streamSender,
queueControlFrame func(wire.Frame),
newFlowController func(protocol.StreamID) *streamFlowController,
maxIncomingBidiStreams uint64,
maxIncomingUniStreams uint64,
perspective protocol.Perspective,
) *streamsMap {
m := &streamsMap{
ctx: ctx,
perspective: perspective,
queueControlFrame: queueControlFrame,
newFlowController: newFlowController,
maxIncomingBidiStreams: maxIncomingBidiStreams,
maxIncomingUniStreams: maxIncomingUniStreams,
sender: sender,
}
m.initMaps()
return m
}
func (m *streamsMap) initMaps() {
m.outgoingBidiStreams = newOutgoingStreamsMap(
protocol.StreamTypeBidi,
func(id protocol.StreamID) *Stream {
return newStream(m.ctx, id, m.sender, m.newFlowController(id), m.supportsResetStreamAt)
},
m.queueControlFrame,
m.perspective,
)
m.incomingBidiStreams = newIncomingStreamsMap(
protocol.StreamTypeBidi,
func(id protocol.StreamID) *Stream {
return newStream(m.ctx, id, m.sender, m.newFlowController(id), m.supportsResetStreamAt)
},
m.maxIncomingBidiStreams,
m.queueControlFrame,
m.perspective,
)
m.outgoingUniStreams = newOutgoingStreamsMap(
protocol.StreamTypeUni,
func(id protocol.StreamID) *SendStream {
return newSendStream(m.ctx, id, m.sender, m.newFlowController(id), m.supportsResetStreamAt)
},
m.queueControlFrame,
m.perspective,
)
m.incomingUniStreams = newIncomingStreamsMap(
protocol.StreamTypeUni,
func(id protocol.StreamID) *ReceiveStream {
return newReceiveStream(id, m.sender, m.newFlowController(id))
},
m.maxIncomingUniStreams,
m.queueControlFrame,
m.perspective,
)
}
func (m *streamsMap) OpenStream() (*Stream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.outgoingBidiStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.OpenStream()
}
func (m *streamsMap) OpenStreamSync(ctx context.Context) (*Stream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.outgoingBidiStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.OpenStreamSync(ctx)
}
func (m *streamsMap) OpenUniStream() (*SendStream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.outgoingUniStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.OpenStream()
}
func (m *streamsMap) OpenUniStreamSync(ctx context.Context) (*SendStream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.outgoingUniStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.OpenStreamSync(ctx)
}
func (m *streamsMap) AcceptStream(ctx context.Context) (*Stream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.incomingBidiStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.AcceptStream(ctx)
}
func (m *streamsMap) AcceptUniStream(ctx context.Context) (*ReceiveStream, error) {
m.mutex.Lock()
reset := m.reset
mm := m.incomingUniStreams
m.mutex.Unlock()
if reset {
return nil, Err0RTTRejected
}
return mm.AcceptStream(ctx)
}
func (m *streamsMap) DeleteStream(id protocol.StreamID) error {
switch protocol.StreamTypeOf(id) {
case protocol.StreamTypeUni:
if protocol.StreamInitiator(id) == m.perspective {
return m.outgoingUniStreams.DeleteStream(id)
}
return m.incomingUniStreams.DeleteStream(id)
case protocol.StreamTypeBidi:
if protocol.StreamInitiator(id) == m.perspective {
return m.outgoingBidiStreams.DeleteStream(id)
}
return m.incomingBidiStreams.DeleteStream(id)
}
panic("")
}
func (m *streamsMap) HandleMaxStreamsFrame(f *wire.MaxStreamsFrame) {
switch f.Type {
case protocol.StreamTypeUni:
m.outgoingUniStreams.SetMaxStream(f.MaxStreamNum.StreamID(protocol.StreamTypeUni, m.perspective))
case protocol.StreamTypeBidi:
m.outgoingBidiStreams.SetMaxStream(f.MaxStreamNum.StreamID(protocol.StreamTypeBidi, m.perspective))
}
}
type sendStreamFrameHandler interface {
updateSendWindow(protocol.ByteCount)
handleStopSendingFrame(*wire.StopSendingFrame)
}
func (m *streamsMap) getSendStream(id protocol.StreamID) (sendStreamFrameHandler, error) {
switch protocol.StreamTypeOf(id) {
case protocol.StreamTypeUni:
if protocol.StreamInitiator(id) != m.perspective {
// an outgoing unidirectional stream is a send stream, not a receive stream
return nil, &qerr.TransportError{
ErrorCode: qerr.StreamStateError,
ErrorMessage: fmt.Sprintf("invalid frame for send stream %d", id),
}
}
str, err := m.outgoingUniStreams.GetStream(id)
if str == nil || err != nil {
return nil, err
}
return str, nil
case protocol.StreamTypeBidi:
if protocol.StreamInitiator(id) == m.perspective {
str, err := m.outgoingBidiStreams.GetStream(id)
if str == nil || err != nil {
return nil, err
}
return str, nil
}
str, err := m.incomingBidiStreams.GetOrOpenStream(id)
if str == nil || err != nil {
return nil, err
}
return str, nil
}
panic("unreachable")
}
func (m *streamsMap) HandleMaxStreamDataFrame(f *wire.MaxStreamDataFrame) error {
str, err := m.getSendStream(f.StreamID)
if err != nil {
return err
}
if str == nil { // stream already deleted
return nil
}
str.updateSendWindow(f.MaximumStreamData)
return nil
}
func (m *streamsMap) HandleStopSendingFrame(f *wire.StopSendingFrame) error {
str, err := m.getSendStream(f.StreamID)
if err != nil {
return err
}
if str == nil { // stream already deleted
return nil
}
str.handleStopSendingFrame(f)
return nil
}
type receiveStreamFrameHandler interface {
handleResetStreamFrame(*wire.ResetStreamFrame, monotime.Time) error
handleStreamFrame(*wire.StreamFrame, monotime.Time) error
}
func (m *streamsMap) getReceiveStream(id protocol.StreamID) (receiveStreamFrameHandler, error) {
switch protocol.StreamTypeOf(id) {
case protocol.StreamTypeUni:
// an outgoing unidirectional stream is a send stream, not a receive stream
if protocol.StreamInitiator(id) == m.perspective {
return nil, &qerr.TransportError{
ErrorCode: qerr.StreamStateError,
ErrorMessage: fmt.Sprintf("invalid frame for receive stream %d", id),
}
}
str, err := m.incomingUniStreams.GetOrOpenStream(id)
if err != nil || str == nil {
return nil, err
}
return str, nil
case protocol.StreamTypeBidi:
var str *Stream
var err error
if protocol.StreamInitiator(id) == m.perspective {
str, err = m.outgoingBidiStreams.GetStream(id)
} else {
str, err = m.incomingBidiStreams.GetOrOpenStream(id)
}
if str == nil || err != nil {
return nil, err
}
return str, nil
}
panic("unreachable")
}
func (m *streamsMap) HandleStreamDataBlockedFrame(f *wire.StreamDataBlockedFrame) error {
if _, err := m.getReceiveStream(f.StreamID); err != nil {
return err
}
// We don't need to do anything in response to a STREAM_DATA_BLOCKED frame,
// but we need to make sure that the stream ID is valid.
return nil // we don't need to do anything in response to a STREAM_DATA_BLOCKED frame
}
func (m *streamsMap) HandleResetStreamFrame(f *wire.ResetStreamFrame, rcvTime monotime.Time) error {
str, err := m.getReceiveStream(f.StreamID)
if err != nil {
return err
}
if str == nil { // stream already deleted
return nil
}
return str.handleResetStreamFrame(f, rcvTime)
}
func (m *streamsMap) HandleStreamFrame(f *wire.StreamFrame, rcvTime monotime.Time) error {
str, err := m.getReceiveStream(f.StreamID)
if err != nil {
return err
}
if str == nil { // stream already deleted
return nil
}
return str.handleStreamFrame(f, rcvTime)
}
func (m *streamsMap) HandleTransportParameters(p *wire.TransportParameters) {
m.supportsResetStreamAt = p.EnableResetStreamAt
if p.EnableResetStreamAt {
m.outgoingBidiStreams.EnableResetStreamAt()
m.outgoingUniStreams.EnableResetStreamAt()
}
m.outgoingBidiStreams.UpdateSendWindow(p.InitialMaxStreamDataBidiRemote)
m.outgoingBidiStreams.SetMaxStream(p.MaxBidiStreamNum.StreamID(protocol.StreamTypeBidi, m.perspective))
m.outgoingUniStreams.UpdateSendWindow(p.InitialMaxStreamDataUni)
m.outgoingUniStreams.SetMaxStream(p.MaxUniStreamNum.StreamID(protocol.StreamTypeUni, m.perspective))
}
func (m *streamsMap) CloseWithError(err error) {
m.outgoingBidiStreams.CloseWithError(err)
m.outgoingUniStreams.CloseWithError(err)
m.incomingBidiStreams.CloseWithError(err)
m.incomingUniStreams.CloseWithError(err)
}
// ResetFor0RTT resets is used when 0-RTT is rejected. In that case, the streams maps are
// 1. closed with an Err0RTTRejected, making calls to Open{Uni}Stream{Sync} / Accept{Uni}Stream return that error.
// 2. reset to their initial state, such that we can immediately process new incoming stream data.
// Afterwards, calls to Open{Uni}Stream{Sync} / Accept{Uni}Stream will continue to return the error,
// until UseResetMaps() has been called.
func (m *streamsMap) ResetFor0RTT() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.reset = true
m.CloseWithError(Err0RTTRejected)
m.supportsResetStreamAt = false
m.initMaps()
}
func (m *streamsMap) UseResetMaps() {
m.mutex.Lock()
m.reset = false
m.mutex.Unlock()
}
package quic
import (
"context"
"fmt"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
type incomingStream interface {
closeForShutdown(error)
}
// When a stream is deleted before it was accepted, we can't delete it from the map immediately.
// We need to wait until the application accepts it, and delete it then.
type incomingStreamEntry[T incomingStream] struct {
stream T
shouldDelete bool
}
type incomingStreamsMap[T incomingStream] struct {
mutex sync.RWMutex
newStreamChan chan struct{}
streamType protocol.StreamType
streams map[protocol.StreamID]incomingStreamEntry[T]
nextStreamToAccept protocol.StreamID // the next stream that will be returned by AcceptStream()
nextStreamToOpen protocol.StreamID // the highest stream that the peer opened
maxStream protocol.StreamID // the highest stream that the peer is allowed to open
maxNumStreams uint64 // maximum number of streams
newStream func(protocol.StreamID) T
queueMaxStreamID func(*wire.MaxStreamsFrame)
closeErr error
}
func newIncomingStreamsMap[T incomingStream](
streamType protocol.StreamType,
newStream func(protocol.StreamID) T,
maxStreams uint64,
queueControlFrame func(wire.Frame),
pers protocol.Perspective,
) *incomingStreamsMap[T] {
var nextStreamToAccept protocol.StreamID
switch {
case streamType == protocol.StreamTypeBidi && pers == protocol.PerspectiveServer:
nextStreamToAccept = protocol.FirstIncomingBidiStreamServer
case streamType == protocol.StreamTypeBidi && pers == protocol.PerspectiveClient:
nextStreamToAccept = protocol.FirstIncomingBidiStreamClient
case streamType == protocol.StreamTypeUni && pers == protocol.PerspectiveServer:
nextStreamToAccept = protocol.FirstIncomingUniStreamServer
case streamType == protocol.StreamTypeUni && pers == protocol.PerspectiveClient:
nextStreamToAccept = protocol.FirstIncomingUniStreamClient
}
return &incomingStreamsMap[T]{
newStreamChan: make(chan struct{}, 1),
streamType: streamType,
streams: make(map[protocol.StreamID]incomingStreamEntry[T]),
maxStream: protocol.StreamNum(maxStreams).StreamID(streamType, pers.Opposite()),
maxNumStreams: maxStreams,
newStream: newStream,
nextStreamToOpen: nextStreamToAccept,
nextStreamToAccept: nextStreamToAccept,
queueMaxStreamID: func(f *wire.MaxStreamsFrame) { queueControlFrame(f) },
}
}
func (m *incomingStreamsMap[T]) AcceptStream(ctx context.Context) (T, error) {
// drain the newStreamChan, so we don't check the map twice if the stream doesn't exist
select {
case <-m.newStreamChan:
default:
}
m.mutex.Lock()
var id protocol.StreamID
var entry incomingStreamEntry[T]
for {
id = m.nextStreamToAccept
if m.closeErr != nil {
m.mutex.Unlock()
return *new(T), m.closeErr
}
var ok bool
entry, ok = m.streams[id]
if ok {
break
}
m.mutex.Unlock()
select {
case <-ctx.Done():
return *new(T), ctx.Err()
case <-m.newStreamChan:
}
m.mutex.Lock()
}
m.nextStreamToAccept += 4
// If this stream was completed before being accepted, we can delete it now.
if entry.shouldDelete {
if err := m.deleteStream(id); err != nil {
m.mutex.Unlock()
return *new(T), err
}
}
m.mutex.Unlock()
return entry.stream, nil
}
func (m *incomingStreamsMap[T]) GetOrOpenStream(id protocol.StreamID) (T, error) {
m.mutex.RLock()
if id > m.maxStream {
m.mutex.RUnlock()
return *new(T), &qerr.TransportError{
ErrorCode: qerr.StreamLimitError,
ErrorMessage: fmt.Sprintf("peer tried to open stream %d (current limit: %d)", id, m.maxStream),
}
}
// if the num is smaller than the highest we accepted
// * this stream exists in the map, and we can return it, or
// * this stream was already closed, then we can return the nil
if id < m.nextStreamToOpen {
var s T
// If the stream was already queued for deletion, and is just waiting to be accepted, don't return it.
if entry, ok := m.streams[id]; ok && !entry.shouldDelete {
s = entry.stream
}
m.mutex.RUnlock()
return s, nil
}
m.mutex.RUnlock()
m.mutex.Lock()
// no need to check the two error conditions from above again
// * maxStream can only increase, so if the id was valid before, it definitely is valid now
// * highestStream is only modified by this function
for newNum := m.nextStreamToOpen; newNum <= id; newNum += 4 {
m.streams[newNum] = incomingStreamEntry[T]{stream: m.newStream(newNum)}
select {
case m.newStreamChan <- struct{}{}:
default:
}
}
m.nextStreamToOpen = id + 4
entry := m.streams[id]
m.mutex.Unlock()
return entry.stream, nil
}
func (m *incomingStreamsMap[T]) DeleteStream(id protocol.StreamID) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if err := m.deleteStream(id); err != nil {
return &qerr.TransportError{
ErrorCode: qerr.StreamStateError,
ErrorMessage: err.Error(),
}
}
return nil
}
func (m *incomingStreamsMap[T]) deleteStream(id protocol.StreamID) error {
if _, ok := m.streams[id]; !ok {
return fmt.Errorf("tried to delete unknown incoming stream %d", id)
}
// Don't delete this stream yet, if it was not yet accepted.
// Just save it to streamsToDelete map, to make sure it is deleted as soon as it gets accepted.
if id >= m.nextStreamToAccept {
entry, ok := m.streams[id]
if ok && entry.shouldDelete {
return fmt.Errorf("tried to delete incoming stream %d multiple times", id)
}
entry.shouldDelete = true
m.streams[id] = entry // can't assign to struct in map, so we need to reassign
return nil
}
delete(m.streams, id)
// queue a MAX_STREAM_ID frame, giving the peer the option to open a new stream
if m.maxNumStreams > uint64(len(m.streams)) {
maxStream := m.nextStreamToOpen + 4*protocol.StreamID(m.maxNumStreams-uint64(len(m.streams))-1)
// never send a value larger than the maximum value for a stream number
if maxStream <= protocol.MaxStreamID {
m.maxStream = maxStream
m.queueMaxStreamID(&wire.MaxStreamsFrame{
Type: m.streamType,
MaxStreamNum: m.maxStream.StreamNum(),
})
}
}
return nil
}
func (m *incomingStreamsMap[T]) CloseWithError(err error) {
m.mutex.Lock()
m.closeErr = err
for _, entry := range m.streams {
entry.stream.closeForShutdown(err)
}
m.mutex.Unlock()
close(m.newStreamChan)
}
package quic
import (
"context"
"fmt"
"slices"
"sync"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/internal/wire"
)
type outgoingStream interface {
updateSendWindow(protocol.ByteCount)
enableResetStreamAt()
closeForShutdown(error)
}
type outgoingStreamsMap[T outgoingStream] struct {
mutex sync.RWMutex
streamType protocol.StreamType
streams map[protocol.StreamID]T
openQueue []chan struct{}
nextStream protocol.StreamID // stream ID of the stream returned by OpenStream(Sync)
maxStream protocol.StreamID // the maximum stream ID we're allowed to open
blockedSent bool // was a STREAMS_BLOCKED sent for the current maxStream
newStream func(protocol.StreamID) T
queueStreamIDBlocked func(*wire.StreamsBlockedFrame)
closeErr error
}
func newOutgoingStreamsMap[T outgoingStream](
streamType protocol.StreamType,
newStream func(protocol.StreamID) T,
queueControlFrame func(wire.Frame),
pers protocol.Perspective,
) *outgoingStreamsMap[T] {
var nextStream protocol.StreamID
switch {
case streamType == protocol.StreamTypeBidi && pers == protocol.PerspectiveServer:
nextStream = protocol.FirstOutgoingBidiStreamServer
case streamType == protocol.StreamTypeBidi && pers == protocol.PerspectiveClient:
nextStream = protocol.FirstOutgoingBidiStreamClient
case streamType == protocol.StreamTypeUni && pers == protocol.PerspectiveServer:
nextStream = protocol.FirstOutgoingUniStreamServer
case streamType == protocol.StreamTypeUni && pers == protocol.PerspectiveClient:
nextStream = protocol.FirstOutgoingUniStreamClient
}
return &outgoingStreamsMap[T]{
streamType: streamType,
streams: make(map[protocol.StreamID]T),
maxStream: protocol.InvalidStreamNum,
nextStream: nextStream,
newStream: newStream,
queueStreamIDBlocked: func(f *wire.StreamsBlockedFrame) { queueControlFrame(f) },
}
}
func (m *outgoingStreamsMap[T]) OpenStream() (T, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.closeErr != nil {
return *new(T), m.closeErr
}
// if there are OpenStreamSync calls waiting, return an error here
if len(m.openQueue) > 0 || m.nextStream > m.maxStream {
m.maybeSendBlockedFrame()
return *new(T), &StreamLimitReachedError{}
}
return m.openStream(), nil
}
func (m *outgoingStreamsMap[T]) OpenStreamSync(ctx context.Context) (T, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.closeErr != nil {
return *new(T), m.closeErr
}
if err := ctx.Err(); err != nil {
return *new(T), err
}
if len(m.openQueue) == 0 && m.nextStream <= m.maxStream {
return m.openStream(), nil
}
waitChan := make(chan struct{}, 1)
m.openQueue = append(m.openQueue, waitChan)
m.maybeSendBlockedFrame()
for {
m.mutex.Unlock()
select {
case <-ctx.Done():
m.mutex.Lock()
m.openQueue = slices.DeleteFunc(m.openQueue, func(c chan struct{}) bool {
return c == waitChan
})
// If we just received a MAX_STREAMS frame, this might have been the next stream
// that could be opened. Make sure we unblock the next OpenStreamSync call.
m.maybeUnblockOpenSync()
return *new(T), ctx.Err()
case <-waitChan:
}
m.mutex.Lock()
if m.closeErr != nil {
return *new(T), m.closeErr
}
if err := ctx.Err(); err != nil {
m.openQueue = slices.DeleteFunc(m.openQueue, func(c chan struct{}) bool {
return c == waitChan
})
m.maybeUnblockOpenSync()
return *new(T), err
}
if m.nextStream > m.maxStream {
// no stream available. Continue waiting
continue
}
str := m.openStream()
m.openQueue = m.openQueue[1:]
m.maybeUnblockOpenSync()
return str, nil
}
}
func (m *outgoingStreamsMap[T]) openStream() T {
s := m.newStream(m.nextStream)
m.streams[m.nextStream] = s
m.nextStream += 4
return s
}
// maybeSendBlockedFrame queues a STREAMS_BLOCKED frame for the current stream offset,
// if we haven't sent one for this offset yet
func (m *outgoingStreamsMap[T]) maybeSendBlockedFrame() {
if m.blockedSent {
return
}
var streamLimit protocol.StreamNum
if m.maxStream != protocol.InvalidStreamID {
streamLimit = m.maxStream.StreamNum()
}
m.queueStreamIDBlocked(&wire.StreamsBlockedFrame{
Type: m.streamType,
StreamLimit: streamLimit,
})
m.blockedSent = true
}
func (m *outgoingStreamsMap[T]) GetStream(id protocol.StreamID) (T, error) {
m.mutex.RLock()
if id >= m.nextStream {
m.mutex.RUnlock()
return *new(T), &qerr.TransportError{
ErrorCode: qerr.StreamStateError,
ErrorMessage: fmt.Sprintf("peer attempted to open stream %d", id),
}
}
s := m.streams[id]
m.mutex.RUnlock()
return s, nil
}
func (m *outgoingStreamsMap[T]) DeleteStream(id protocol.StreamID) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, ok := m.streams[id]; !ok {
return &qerr.TransportError{
ErrorCode: qerr.StreamStateError,
ErrorMessage: fmt.Sprintf("tried to delete unknown outgoing stream %d", id),
}
}
delete(m.streams, id)
return nil
}
func (m *outgoingStreamsMap[T]) SetMaxStream(id protocol.StreamID) {
m.mutex.Lock()
defer m.mutex.Unlock()
if id <= m.maxStream {
return
}
m.maxStream = id
m.blockedSent = false
if m.maxStream < m.nextStream-4+4*protocol.StreamID(len(m.openQueue)) {
m.maybeSendBlockedFrame()
}
m.maybeUnblockOpenSync()
}
// UpdateSendWindow is called when the peer's transport parameters are received.
// Only in the case of a 0-RTT handshake will we have open streams at this point.
// We might need to update the send window, in case the server increased it.
func (m *outgoingStreamsMap[T]) UpdateSendWindow(limit protocol.ByteCount) {
m.mutex.Lock()
for _, str := range m.streams {
str.updateSendWindow(limit)
}
m.mutex.Unlock()
}
func (m *outgoingStreamsMap[T]) EnableResetStreamAt() {
m.mutex.Lock()
for _, str := range m.streams {
str.enableResetStreamAt()
}
m.mutex.Unlock()
}
// unblockOpenSync unblocks the next OpenStreamSync go-routine to open a new stream
func (m *outgoingStreamsMap[T]) maybeUnblockOpenSync() {
if len(m.openQueue) == 0 {
return
}
if m.nextStream > m.maxStream {
return
}
// unblockOpenSync is called both from OpenStreamSync and from SetMaxStream.
// It's sufficient to only unblock OpenStreamSync once.
select {
case m.openQueue[0] <- struct{}{}:
default:
}
}
func (m *outgoingStreamsMap[T]) CloseWithError(err error) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.closeErr = err
for _, str := range m.streams {
str.closeForShutdown(err)
}
for _, c := range m.openQueue {
if c != nil {
close(c)
}
}
m.openQueue = nil
}
package quic
import (
"io"
"log"
"net"
"os"
"strconv"
"strings"
"syscall"
"time"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
type connCapabilities struct {
// This connection has the Don't Fragment (DF) bit set.
// This means it makes to run DPLPMTUD.
DF bool
// GSO (Generic Segmentation Offload) supported
GSO bool
// ECN (Explicit Congestion Notifications) supported
ECN bool
}
// rawConn is a connection that allow reading of a receivedPackeh.
type rawConn interface {
ReadPacket() (receivedPacket, error)
// WritePacket writes a packet on the wire.
// gsoSize is the size of a single packet, or 0 to disable GSO.
// It is invalid to set gsoSize if capabilities.GSO is not set.
WritePacket(b []byte, addr net.Addr, packetInfoOOB []byte, gsoSize uint16, ecn protocol.ECN) (int, error)
LocalAddr() net.Addr
SetReadDeadline(time.Time) error
io.Closer
capabilities() connCapabilities
}
// OOBCapablePacketConn is a connection that allows the reading of ECN bits from the IP header.
// If the PacketConn passed to the [Transport] satisfies this interface, quic-go will use it.
// In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets.
type OOBCapablePacketConn interface {
net.PacketConn
SyscallConn() (syscall.RawConn, error)
SetReadBuffer(int) error
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}
var _ OOBCapablePacketConn = &net.UDPConn{}
func wrapConn(pc net.PacketConn) (rawConn, error) {
if err := setReceiveBuffer(pc); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
setBufferWarningOnce.Do(func() {
if disable, _ := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING")); disable {
return
}
log.Printf("%s. See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.", err)
})
}
}
if err := setSendBuffer(pc); err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
setBufferWarningOnce.Do(func() {
if disable, _ := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING")); disable {
return
}
log.Printf("%s. See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.", err)
})
}
}
conn, ok := pc.(interface {
SyscallConn() (syscall.RawConn, error)
})
var supportsDF bool
if ok {
rawConn, err := conn.SyscallConn()
if err != nil {
return nil, err
}
// only set DF on UDP sockets
if _, ok := pc.LocalAddr().(*net.UDPAddr); ok {
var err error
supportsDF, err = setDF(rawConn)
if err != nil {
return nil, err
}
}
}
c, ok := pc.(OOBCapablePacketConn)
if !ok {
utils.DefaultLogger.Infof("PacketConn is not a net.UDPConn. Disabling optimizations possible on UDP connections.")
return &basicConn{PacketConn: pc, supportsDF: supportsDF}, nil
}
return newConn(c, supportsDF)
}
// The basicConn is the most trivial implementation of a rawConn.
// It reads a single packet from the underlying net.PacketConn.
// It is used when
// * the net.PacketConn is not a OOBCapablePacketConn, and
// * when the OS doesn't support OOB.
type basicConn struct {
net.PacketConn
supportsDF bool
}
var _ rawConn = &basicConn{}
func (c *basicConn) ReadPacket() (receivedPacket, error) {
buffer := getPacketBuffer()
// The packet size should not exceed protocol.MaxPacketBufferSize bytes
// If it does, we only read a truncated packet, which will then end up undecryptable
buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize]
n, addr, err := c.ReadFrom(buffer.Data)
if err != nil {
return receivedPacket{}, err
}
return receivedPacket{
remoteAddr: addr,
rcvTime: monotime.Now(),
data: buffer.Data[:n],
buffer: buffer,
}, nil
}
func (c *basicConn) WritePacket(b []byte, addr net.Addr, _ []byte, gsoSize uint16, ecn protocol.ECN) (n int, err error) {
if gsoSize != 0 {
panic("cannot use GSO with a basicConn")
}
if ecn != protocol.ECNUnsupported {
panic("cannot use ECN with a basicConn")
}
return c.WriteTo(b, addr)
}
func (c *basicConn) capabilities() connCapabilities { return connCapabilities{DF: c.supportsDF} }
package quic
import (
"errors"
"fmt"
"net"
"syscall"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
//go:generate sh -c "echo '// Code generated by go generate. DO NOT EDIT.\n// Source: sys_conn_buffers.go\n' > sys_conn_buffers_write.go && sed -e 's/SetReadBuffer/SetWriteBuffer/g' -e 's/setReceiveBuffer/setSendBuffer/g' -e 's/inspectReadBuffer/inspectWriteBuffer/g' -e 's/protocol\\.DesiredReceiveBufferSize/protocol\\.DesiredSendBufferSize/g' -e 's/forceSetReceiveBuffer/forceSetSendBuffer/g' -e 's/receive buffer/send buffer/g' sys_conn_buffers.go | sed '/^\\/\\/go:generate/d' >> sys_conn_buffers_write.go"
func setReceiveBuffer(c net.PacketConn) error {
conn, ok := c.(interface{ SetReadBuffer(int) error })
if !ok {
return errors.New("connection doesn't allow setting of receive buffer size. Not a *net.UDPConn?")
}
var syscallConn syscall.RawConn
if sc, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
}); ok {
var err error
syscallConn, err = sc.SyscallConn()
if err != nil {
syscallConn = nil
}
}
// The connection has a SetReadBuffer method, but we couldn't obtain a syscall.RawConn.
// This shouldn't happen for a net.UDPConn, but is possible if the connection just implements the
// net.PacketConn interface and the SetReadBuffer method.
// We have no way of checking if increasing the buffer size actually worked.
if syscallConn == nil {
return conn.SetReadBuffer(protocol.DesiredReceiveBufferSize)
}
size, err := inspectReadBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
if size >= protocol.DesiredReceiveBufferSize {
utils.DefaultLogger.Debugf("Conn has receive buffer of %d kiB (wanted: at least %d kiB)", size/1024, protocol.DesiredReceiveBufferSize/1024)
return nil
}
// Ignore the error. We check if we succeeded by querying the buffer size afterward.
_ = conn.SetReadBuffer(protocol.DesiredReceiveBufferSize)
newSize, err := inspectReadBuffer(syscallConn)
if newSize < protocol.DesiredReceiveBufferSize {
// Try again with RCVBUFFORCE on Linux
_ = forceSetReceiveBuffer(syscallConn, protocol.DesiredReceiveBufferSize)
newSize, err = inspectReadBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
}
if err != nil {
return fmt.Errorf("failed to determine receive buffer size: %w", err)
}
if newSize == size {
return fmt.Errorf("failed to increase receive buffer size (wanted: %d kiB, got %d kiB)", protocol.DesiredReceiveBufferSize/1024, newSize/1024)
}
if newSize < protocol.DesiredReceiveBufferSize {
return fmt.Errorf("failed to sufficiently increase receive buffer size (was: %d kiB, wanted: %d kiB, got: %d kiB)", size/1024, protocol.DesiredReceiveBufferSize/1024, newSize/1024)
}
utils.DefaultLogger.Debugf("Increased receive buffer size to %d kiB", newSize/1024)
return nil
}
// Code generated by go generate. DO NOT EDIT.
// Source: sys_conn_buffers.go
package quic
import (
"errors"
"fmt"
"net"
"syscall"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
func setSendBuffer(c net.PacketConn) error {
conn, ok := c.(interface{ SetWriteBuffer(int) error })
if !ok {
return errors.New("connection doesn't allow setting of send buffer size. Not a *net.UDPConn?")
}
var syscallConn syscall.RawConn
if sc, ok := c.(interface {
SyscallConn() (syscall.RawConn, error)
}); ok {
var err error
syscallConn, err = sc.SyscallConn()
if err != nil {
syscallConn = nil
}
}
// The connection has a SetWriteBuffer method, but we couldn't obtain a syscall.RawConn.
// This shouldn't happen for a net.UDPConn, but is possible if the connection just implements the
// net.PacketConn interface and the SetWriteBuffer method.
// We have no way of checking if increasing the buffer size actually worked.
if syscallConn == nil {
return conn.SetWriteBuffer(protocol.DesiredSendBufferSize)
}
size, err := inspectWriteBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
if size >= protocol.DesiredSendBufferSize {
utils.DefaultLogger.Debugf("Conn has send buffer of %d kiB (wanted: at least %d kiB)", size/1024, protocol.DesiredSendBufferSize/1024)
return nil
}
// Ignore the error. We check if we succeeded by querying the buffer size afterward.
_ = conn.SetWriteBuffer(protocol.DesiredSendBufferSize)
newSize, err := inspectWriteBuffer(syscallConn)
if newSize < protocol.DesiredSendBufferSize {
// Try again with RCVBUFFORCE on Linux
_ = forceSetSendBuffer(syscallConn, protocol.DesiredSendBufferSize)
newSize, err = inspectWriteBuffer(syscallConn)
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
}
if err != nil {
return fmt.Errorf("failed to determine send buffer size: %w", err)
}
if newSize == size {
return fmt.Errorf("failed to increase send buffer size (wanted: %d kiB, got %d kiB)", protocol.DesiredSendBufferSize/1024, newSize/1024)
}
if newSize < protocol.DesiredSendBufferSize {
return fmt.Errorf("failed to sufficiently increase send buffer size (was: %d kiB, wanted: %d kiB, got: %d kiB)", size/1024, protocol.DesiredSendBufferSize/1024, newSize/1024)
}
utils.DefaultLogger.Debugf("Increased send buffer size to %d kiB", newSize/1024)
return nil
}
//go:build linux
package quic
import (
"errors"
"syscall"
"golang.org/x/sys/unix"
"github.com/quic-go/quic-go/internal/utils"
)
func setDF(rawConn syscall.RawConn) (bool, error) {
// Enabling IP_MTU_DISCOVER will force the kernel to return "sendto: message too long"
// and the datagram will not be fragmented
var errDFIPv4, errDFIPv6 error
if err := rawConn.Control(func(fd uintptr) {
errDFIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_MTU_DISCOVER, unix.IP_PMTUDISC_PROBE)
errDFIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_MTU_DISCOVER, unix.IPV6_PMTUDISC_PROBE)
}); err != nil {
return false, err
}
switch {
case errDFIPv4 == nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv4 and IPv6.")
case errDFIPv4 == nil && errDFIPv6 != nil:
utils.DefaultLogger.Debugf("Setting DF for IPv4.")
case errDFIPv4 != nil && errDFIPv6 == nil:
utils.DefaultLogger.Debugf("Setting DF for IPv6.")
case errDFIPv4 != nil && errDFIPv6 != nil:
return false, errors.New("setting DF failed for both IPv4 and IPv6")
}
return true, nil
}
func isSendMsgSizeErr(err error) bool {
// https://man7.org/linux/man-pages/man7/udp.7.html
return errors.Is(err, unix.EMSGSIZE)
}
func isRecvMsgSizeErr(error) bool { return false }
//go:build linux
package quic
import (
"encoding/binary"
"errors"
"net/netip"
"os"
"strconv"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
const (
msgTypeIPTOS = unix.IP_TOS
ipv4PKTINFO = unix.IP_PKTINFO
)
const ecnIPv4DataLen = 1
const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
var kernelVersionMajor int
func init() {
kernelVersionMajor, _ = kernelVersion()
}
func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
var serr error
if err := c.Control(func(fd uintptr) {
serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
}); err != nil {
return err
}
return serr
}
func forceSetSendBuffer(c syscall.RawConn, bytes int) error {
var serr error
if err := c.Control(func(fd uintptr) {
serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
}); err != nil {
return err
}
return serr
}
func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
if len(body) != 12 {
return netip.Addr{}, 0, false
}
return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.NativeEndian.Uint32(body), true
}
// isGSOEnabled tests if the kernel supports GSO.
// Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
func isGSOEnabled(conn syscall.RawConn) bool {
if kernelVersionMajor < 5 {
return false
}
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
if err == nil && disabled {
return false
}
var serr error
if err := conn.Control(func(fd uintptr) {
_, serr = unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
}); err != nil {
return false
}
return serr == nil
}
func appendUDPSegmentSizeMsg(b []byte, size uint16) []byte {
startLen := len(b)
const dataLen = 2 // payload is a uint16
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_UDP
h.Type = unix.UDP_SEGMENT
h.SetLen(unix.CmsgLen(dataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
*(*uint16)(unsafe.Pointer(&b[offset])) = size
return b
}
func isGSOError(err error) bool {
var serr *os.SyscallError
if errors.As(err, &serr) {
// EIO is returned by udp_send_skb() if the device driver does not have tx checksums enabled,
// which is a hard requirement of UDP_SEGMENT. See:
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
return serr.Err == unix.EIO
}
return false
}
// The first sendmsg call on a new UDP socket sometimes errors on Linux.
// It's not clear why this happens.
// See https://github.com/golang/go/issues/63322.
func isPermissionError(err error) bool {
var serr *os.SyscallError
if errors.As(err, &serr) {
return serr.Syscall == "sendmsg" && serr.Err == unix.EPERM
}
return false
}
func isECNEnabled() bool {
return kernelVersionMajor >= 5 && !isECNDisabledUsingEnv()
}
// kernelVersion returns major and minor kernel version numbers, parsed from
// the syscall.Uname's Release field, or 0, 0 if the version can't be obtained
// or parsed.
//
// copied from the standard library's internal/syscall/unix/kernel_version_linux.go
func kernelVersion() (major, minor int) {
var uname syscall.Utsname
if err := syscall.Uname(&uname); err != nil {
return
}
var (
values [2]int
value, vi int
)
for _, c := range uname.Release {
if '0' <= c && c <= '9' {
value = (value * 10) + int(c-'0')
} else {
// Note that we're assuming N.N.N here.
// If we see anything else, we are likely to mis-parse it.
values[vi] = value
vi++
if vi >= len(values) {
break
}
value = 0
}
}
return values[0], values[1]
}
//go:build darwin || linux || freebsd
package quic
import (
"encoding/binary"
"errors"
"log"
"net"
"net/netip"
"os"
"strconv"
"sync"
"syscall"
"unsafe"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
const (
ecnMask = 0x3
oobBufferSize = 128
)
// Contrary to what the naming suggests, the ipv{4,6}.Message is not dependent on the IP version.
// They're both just aliases for x/net/internal/socket.Message.
// This means we can use this struct to read from a socket that receives both IPv4 and IPv6 messages.
var _ ipv4.Message = ipv6.Message{}
type batchConn interface {
ReadBatch(ms []ipv4.Message, flags int) (int, error)
}
func inspectReadBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := c.Control(func(fd uintptr) {
size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF)
}); err != nil {
return 0, err
}
return size, serr
}
func inspectWriteBuffer(c syscall.RawConn) (int, error) {
var size int
var serr error
if err := c.Control(func(fd uintptr) {
size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF)
}); err != nil {
return 0, err
}
return size, serr
}
func isECNDisabledUsingEnv() bool {
disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_ECN"))
return err == nil && disabled
}
type oobConn struct {
OOBCapablePacketConn
batchConn batchConn
readPos uint8
// Packets received from the kernel, but not yet returned by ReadPacket().
messages []ipv4.Message
buffers [batchSize]*packetBuffer
cap connCapabilities
}
var _ rawConn = &oobConn{}
func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) {
rawConn, err := c.SyscallConn()
if err != nil {
return nil, err
}
var needsPacketInfo bool
if udpAddr, ok := c.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP.IsUnspecified() {
needsPacketInfo = true
}
// We don't know if this a IPv4-only, IPv6-only or a IPv4-and-IPv6 connection.
// Try enabling receiving of ECN and packet info for both IP versions.
// We expect at least one of those syscalls to succeed.
var errECNIPv4, errECNIPv6, errPIIPv4, errPIIPv6 error
if err := rawConn.Control(func(fd uintptr) {
errECNIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
errECNIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)
if needsPacketInfo {
errPIIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, ipv4PKTINFO, 1)
errPIIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1)
}
}); err != nil {
return nil, err
}
switch {
case errECNIPv4 == nil && errECNIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4 and IPv6.")
case errECNIPv4 == nil && errECNIPv6 != nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4.")
case errECNIPv4 != nil && errECNIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv6.")
case errECNIPv4 != nil && errECNIPv6 != nil:
return nil, errors.New("activating ECN failed for both IPv4 and IPv6")
}
if needsPacketInfo {
switch {
case errPIIPv4 == nil && errPIIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of packet info for IPv4 and IPv6.")
case errPIIPv4 == nil && errPIIPv6 != nil:
utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv4.")
case errPIIPv4 != nil && errPIIPv6 == nil:
utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv6.")
case errPIIPv4 != nil && errPIIPv6 != nil:
return nil, errors.New("activating packet info failed for both IPv4 and IPv6")
}
}
// Allows callers to pass in a connection that already satisfies batchConn interface
// to make use of the optimisation. Otherwise, ipv4.NewPacketConn would unwrap the file descriptor
// via SyscallConn(), and read it that way, which might not be what the caller wants.
var bc batchConn
if ibc, ok := c.(batchConn); ok {
bc = ibc
} else {
bc = ipv4.NewPacketConn(c)
}
msgs := make([]ipv4.Message, batchSize)
for i := range msgs {
// preallocate the [][]byte
msgs[i].Buffers = make([][]byte, 1)
}
oobConn := &oobConn{
OOBCapablePacketConn: c,
batchConn: bc,
messages: msgs,
readPos: batchSize,
cap: connCapabilities{
DF: supportsDF,
GSO: isGSOEnabled(rawConn),
ECN: isECNEnabled(),
},
}
for i := range batchSize {
oobConn.messages[i].OOB = make([]byte, oobBufferSize)
}
return oobConn, nil
}
var invalidCmsgOnceV4, invalidCmsgOnceV6 sync.Once
func (c *oobConn) ReadPacket() (receivedPacket, error) {
if len(c.messages) == int(c.readPos) { // all messages read. Read the next batch of messages.
c.messages = c.messages[:batchSize]
// replace buffers data buffers up to the packet that has been consumed during the last ReadBatch call
for i := uint8(0); i < c.readPos; i++ {
buffer := getPacketBuffer()
buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize]
c.buffers[i] = buffer
c.messages[i].Buffers[0] = c.buffers[i].Data
}
c.readPos = 0
n, err := c.batchConn.ReadBatch(c.messages, 0)
if n == 0 || err != nil {
return receivedPacket{}, err
}
c.messages = c.messages[:n]
}
msg := c.messages[c.readPos]
buffer := c.buffers[c.readPos]
c.readPos++
data := msg.OOB[:msg.NN]
p := receivedPacket{
remoteAddr: msg.Addr,
rcvTime: monotime.Now(),
data: msg.Buffers[0][:msg.N],
buffer: buffer,
}
for len(data) > 0 {
hdr, body, remainder, err := unix.ParseOneSocketControlMessage(data)
if err != nil {
return receivedPacket{}, err
}
if hdr.Level == unix.IPPROTO_IP {
switch hdr.Type {
case msgTypeIPTOS:
if len(body) != 1 {
return receivedPacket{}, errors.New("invalid IPTOS size")
}
p.ecn = protocol.ParseECNHeaderBits(body[0] & ecnMask)
case ipv4PKTINFO:
ip, ifIndex, ok := parseIPv4PktInfo(body)
if ok {
p.info.addr = ip
p.info.ifIndex = ifIndex
} else {
invalidCmsgOnceV4.Do(func() {
log.Printf("Received invalid IPv4 packet info control message: %+x. "+
"This should never occur, please open a new issue and include details about the architecture.", body)
})
}
}
}
if hdr.Level == unix.IPPROTO_IPV6 {
switch hdr.Type {
case unix.IPV6_TCLASS:
if len(body) != 4 {
return receivedPacket{}, errors.New("invalid IPV6_TCLASS size")
}
bits := uint8(binary.NativeEndian.Uint32(body)) & ecnMask
p.ecn = protocol.ParseECNHeaderBits(bits)
case unix.IPV6_PKTINFO:
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
if len(body) == 20 {
p.info.addr = netip.AddrFrom16(*(*[16]byte)(body[:16])).Unmap()
p.info.ifIndex = binary.NativeEndian.Uint32(body[16:])
} else {
invalidCmsgOnceV6.Do(func() {
log.Printf("Received invalid IPv6 packet info control message: %+x. "+
"This should never occur, please open a new issue and include details about the architecture.", body)
})
}
}
}
data = remainder
}
return p, nil
}
// WritePacket writes a new packet.
func (c *oobConn) WritePacket(b []byte, addr net.Addr, packetInfoOOB []byte, gsoSize uint16, ecn protocol.ECN) (int, error) {
oob := packetInfoOOB
if gsoSize > 0 {
if !c.capabilities().GSO {
panic("GSO disabled")
}
oob = appendUDPSegmentSizeMsg(oob, gsoSize)
}
if ecn != protocol.ECNUnsupported {
if !c.capabilities().ECN {
panic("tried to send an ECN-marked packet although ECN is disabled")
}
if remoteUDPAddr, ok := addr.(*net.UDPAddr); ok {
if remoteUDPAddr.IP.To4() != nil {
oob = appendIPv4ECNMsg(oob, ecn)
} else {
oob = appendIPv6ECNMsg(oob, ecn)
}
}
}
n, _, err := c.WriteMsgUDP(b, oob, addr.(*net.UDPAddr))
return n, err
}
func (c *oobConn) capabilities() connCapabilities {
return c.cap
}
type packetInfo struct {
addr netip.Addr
ifIndex uint32
}
func (info *packetInfo) OOB() []byte {
if info == nil {
return nil
}
if info.addr.Is4() {
ip := info.addr.As4()
// struct in_pktinfo {
// unsigned int ipi_ifindex; /* Interface index */
// struct in_addr ipi_spec_dst; /* Local address */
// struct in_addr ipi_addr; /* Header Destination address */
// };
cm := ipv4.ControlMessage{
Src: ip[:],
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
} else if info.addr.Is6() {
ip := info.addr.As16()
// struct in6_pktinfo {
// struct in6_addr ipi6_addr; /* src/dst IPv6 address */
// unsigned int ipi6_ifindex; /* send/recv interface index */
// };
cm := ipv6.ControlMessage{
Src: ip[:],
IfIndex: int(info.ifIndex),
}
return cm.Marshal()
}
return nil
}
func appendIPv4ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
b = append(b, make([]byte, unix.CmsgSpace(ecnIPv4DataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IP
h.Type = unix.IP_TOS
h.SetLen(unix.CmsgLen(ecnIPv4DataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
b[offset] = val.ToHeaderBits()
return b
}
func appendIPv6ECNMsg(b []byte, val protocol.ECN) []byte {
startLen := len(b)
const dataLen = 4
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
h.Level = syscall.IPPROTO_IPV6
h.Type = unix.IPV6_TCLASS
h.SetLen(unix.CmsgLen(dataLen))
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
offset := startLen + unix.CmsgSpace(0)
binary.NativeEndian.PutUint32(b[offset:offset+dataLen], uint32(val.ToHeaderBits()))
return b
}
package events
import (
"reflect"
"slices"
"sync"
"time"
"github.com/quic-go/quic-go/qlogwriter"
)
// Event is a recorded event with the event time.
type Event struct {
Time time.Time
Event qlogwriter.Event
}
// Trace is a qlog.Trace that returns a qlog recorder.
type Trace struct {
Recorder qlogwriter.Recorder
}
var _ qlogwriter.Trace = &Trace{}
func (t *Trace) AddProducer() qlogwriter.Recorder {
return t.Recorder
}
func (t *Trace) SupportsSchemas(string) bool {
return true
}
// Recorder is a qlog.Recorder that records events.
// Events can be retrieved using the Events method.
type Recorder struct {
mx sync.Mutex
events []Event
}
var _ qlogwriter.Recorder = &Recorder{}
// Events returns all recorded events.
// If filter is provided, only events of the given type(s) are returned.
func (r *Recorder) RecordEvent(ev qlogwriter.Event) {
r.mx.Lock()
r.events = append(r.events, Event{Time: time.Now(), Event: ev})
r.mx.Unlock()
}
// Events returns all recorded events, including the event time.
// If filter is provided, only events of the given type(s) are returned.
func (r *Recorder) Events(filter ...qlogwriter.Event) []qlogwriter.Event {
eventsWithTime := r.EventsWithTime(filter...)
events := make([]qlogwriter.Event, 0, len(eventsWithTime))
for _, ev := range eventsWithTime {
events = append(events, ev.Event)
}
return events
}
func (r *Recorder) EventsWithTime(filter ...qlogwriter.Event) []Event {
r.mx.Lock()
events := r.events
r.mx.Unlock()
if len(filter) == 0 {
return events
}
// Some events have the same name when serialized, but use different structs.
// We therefore need to filter by type, and can't use the event name.
filterTypes := make([]reflect.Type, 0, len(filter))
for _, f := range filter {
filterTypes = append(filterTypes, reflect.TypeOf(f))
}
var filtered []Event
for _, ev := range events {
eventType := reflect.TypeOf(ev.Event)
if slices.Contains(filterTypes, eventType) {
filtered = append(filtered, ev)
}
}
return filtered
}
// Clear clears the recorded events.
func (r *Recorder) Clear() {
r.mx.Lock()
r.events = nil
r.mx.Unlock()
}
func (r *Recorder) Close() error { return nil }
package simnet
import (
"container/heap"
"sync"
"time"
)
// queue is a priority queue that delivers packets at their scheduled delivery time
type queue struct {
mu sync.Mutex
packets packetHeap
newPacket chan struct{}
closed bool
pushCount int
}
func newQueue() *queue {
q := &queue{
newPacket: make(chan struct{}, 1),
}
heap.Init(&q.packets)
return q
}
// Enqueue adds a packet to the queue
func (q *queue) Enqueue(p *packetWithDeliveryTime) {
q.mu.Lock()
defer q.mu.Unlock()
if q.closed {
return
}
q.pushCount++
heap.Push(&q.packets, packetWithDeliveryTimeAndOrder{packetWithDeliveryTime: p, count: q.pushCount})
// Signal that a new packet arrived (non-blocking)
select {
case q.newPacket <- struct{}{}:
default:
}
}
// Dequeue removes and returns the next packet when it's ready for delivery
// This blocks until a packet is available AND its delivery time has been reached
// Uses a timer that can be reset if a packet with earlier delivery time arrives
func (q *queue) Dequeue() (*packetWithDeliveryTime, bool) {
timer := time.NewTimer(time.Hour)
timer.Stop()
for {
q.mu.Lock()
if q.closed {
q.mu.Unlock()
timer.Stop()
return nil, false
}
if len(q.packets) == 0 {
// no packets, wait for one to arrive
q.mu.Unlock()
<-q.newPacket
timer.Stop()
continue
}
earliest := q.packets[0]
earliestTime := earliest.DeliveryTime
now := time.Now()
if now.Before(earliestTime) {
// not ready yet, wait until delivery time or new packet
waitDuration := earliestTime.Sub(now)
timer.Reset(waitDuration)
q.mu.Unlock()
select {
case <-timer.C:
continue
case <-q.newPacket:
// new packet arrived, might have earlier delivery time
timer.Stop()
continue
}
}
// Packet is ready, remove from queue and return it
po := heap.Pop(&q.packets).(packetWithDeliveryTimeAndOrder)
p := po.packetWithDeliveryTime
q.mu.Unlock()
return p, true
}
}
// Close closes the queue
func (q *queue) Close() {
q.mu.Lock()
defer q.mu.Unlock()
q.closed = true
close(q.newPacket)
}
type packetWithDeliveryTimeAndOrder struct {
count int
*packetWithDeliveryTime
}
// packetHeap implements heap.Interface ordered by packet delivery time.
type packetHeap []packetWithDeliveryTimeAndOrder
func (h packetHeap) Len() int { return len(h) }
func (h packetHeap) Less(i, j int) bool {
return (h[i].DeliveryTime.Before(h[j].DeliveryTime) || h[i].DeliveryTime.Equal(h[j].DeliveryTime) && h[i].count < h[j].count)
}
func (h packetHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *packetHeap) Push(x any) {
*h = append(*h, x.(packetWithDeliveryTimeAndOrder))
}
func (h *packetHeap) Pop() any {
old := *h
n := len(old)
item := old[n-1]
*h = old[:n-1]
return item
}
package simnet
import (
"errors"
"net"
"net/netip"
"sync"
"time"
)
type ipPortKey struct {
ip string
port uint16
isUDP bool
}
func (k *ipPortKey) FromNetAddr(addr net.Addr) error {
switch addr := addr.(type) {
case *net.UDPAddr:
*k = ipPortKey{
ip: string(addr.IP),
port: uint16(addr.Port),
isUDP: true,
}
return nil
case *net.TCPAddr:
*k = ipPortKey{
ip: string(addr.IP),
port: uint16(addr.Port),
isUDP: false,
}
return nil
default:
ip, err := netip.ParseAddrPort(addr.String())
if err != nil {
return err
}
*k = ipPortKey{
ip: string(ip.Addr().AsSlice()),
port: ip.Port(),
isUDP: addr.Network() == "udp",
}
return nil
}
}
type addrMap[V any] struct {
mu sync.Mutex
nodes map[ipPortKey]V
}
func (m *addrMap[V]) Get(addr net.Addr) (V, bool) {
m.mu.Lock()
defer m.mu.Unlock()
var v V
if len(m.nodes) == 0 {
return v, false
}
var k ipPortKey
if err := k.FromNetAddr(addr); err != nil {
return v, false
}
v, ok := m.nodes[k]
return v, ok
}
func (m *addrMap[V]) Set(addr net.Addr, v V) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.nodes == nil {
m.nodes = make(map[ipPortKey]V)
}
var k ipPortKey
if err := k.FromNetAddr(addr); err != nil {
return err
}
m.nodes[k] = v
return nil
}
func (m *addrMap[V]) Delete(addr net.Addr) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.nodes == nil {
m.nodes = make(map[ipPortKey]V)
}
var k ipPortKey
if err := k.FromNetAddr(addr); err != nil {
return err
}
delete(m.nodes, k)
return nil
}
// PerfectRouter is a router that has no latency or jitter and can route to
// every node
type PerfectRouter struct {
nodes addrMap[PacketReceiver]
}
// SendPacket implements Router.
func (r *PerfectRouter) SendPacket(p Packet) error {
conn, ok := r.nodes.Get(p.To)
if !ok {
return errors.New("unknown destination")
}
conn.RecvPacket(p)
return nil
}
func (r *PerfectRouter) AddNode(addr net.Addr, conn PacketReceiver) {
r.nodes.Set(addr, conn)
}
func (r *PerfectRouter) RemoveNode(addr net.Addr) {
r.nodes.Delete(addr)
}
var _ Router = &PerfectRouter{}
type DelayedPacketReceiver struct {
inner PacketReceiver
delay time.Duration
}
func (r *DelayedPacketReceiver) RecvPacket(p Packet) {
time.AfterFunc(r.delay, func() { r.inner.RecvPacket(p) })
}
type FixedLatencyRouter struct {
PerfectRouter
latency time.Duration
}
func (r *FixedLatencyRouter) SendPacket(p Packet) error {
return r.PerfectRouter.SendPacket(p)
}
func (r *FixedLatencyRouter) AddNode(addr net.Addr, conn PacketReceiver) {
r.PerfectRouter.AddNode(addr, &DelayedPacketReceiver{
inner: conn,
delay: r.latency,
})
}
var _ Router = &FixedLatencyRouter{}
package simnet
import (
"errors"
"net"
"slices"
"sync"
"sync/atomic"
"time"
)
var ErrDeadlineExceeded = errors.New("deadline exceeded")
type PacketReceiver interface {
RecvPacket(p Packet)
}
// Router handles routing of packets between simulated connections.
// Implementations are responsible for delivering packets to their destinations.
type Router interface {
SendPacket(p Packet) error
AddNode(addr net.Addr, receiver PacketReceiver)
}
type Packet struct {
To net.Addr
From net.Addr
Data []byte
}
// SimConn is a simulated network connection that implements net.PacketConn.
// It provides packet-based communication through a Router for testing and
// simulation purposes. All send/recv operations are handled through the
// Router's packet delivery mechanism.
type SimConn struct {
mu sync.Mutex
closed bool
closedChan chan struct{}
deadlineUpdated chan struct{}
packetsSent atomic.Uint64
packetsRcvd atomic.Uint64
bytesSent atomic.Int64
bytesRcvd atomic.Int64
router Router
myAddr *net.UDPAddr
myLocalAddr net.Addr
packetsToRead chan Packet
// Controls whether to block when receiving packets if our buffer is full.
// If false, drops packets.
recvBackPressure bool
readDeadline time.Time
writeDeadline time.Time
}
var _ net.PacketConn = &SimConn{}
// NewSimConn creates a new simulated connection that drops packets if the
// receive buffer is full.
func NewSimConn(addr *net.UDPAddr, rtr Router) *SimConn {
return newSimConn(addr, rtr, false)
}
// NewBlockingSimConn creates a new simulated connection that blocks if the
// receive buffer is full. Does not drop packets.
func NewBlockingSimConn(addr *net.UDPAddr, rtr Router) *SimConn {
return newSimConn(addr, rtr, true)
}
func newSimConn(addr *net.UDPAddr, rtr Router, block bool) *SimConn {
c := &SimConn{
recvBackPressure: block,
router: rtr,
myAddr: addr,
packetsToRead: make(chan Packet, 32),
closedChan: make(chan struct{}),
deadlineUpdated: make(chan struct{}, 1),
}
rtr.AddNode(addr, c)
return c
}
type ConnStats struct {
BytesSent int
BytesRcvd int
PacketsSent int
PacketsRcvd int
}
func (c *SimConn) Stats() ConnStats {
return ConnStats{
BytesSent: int(c.bytesSent.Load()),
BytesRcvd: int(c.bytesRcvd.Load()),
PacketsSent: int(c.packetsSent.Load()),
PacketsRcvd: int(c.packetsRcvd.Load()),
}
}
// SetReadBuffer only exists to quell the warning message from quic-go
func (c *SimConn) SetReadBuffer(n int) error {
return nil
}
// SetWriteBuffer only exists to quell the warning message from quic-go
func (c *SimConn) SetWriteBuffer(n int) error {
return nil
}
func (c *SimConn) RecvPacket(p Packet) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return
}
c.mu.Unlock()
c.packetsRcvd.Add(1)
c.bytesRcvd.Add(int64(len(p.Data)))
if c.recvBackPressure {
select {
case c.packetsToRead <- p:
case <-c.closedChan:
// if the connection is closed, drop the packet
return
}
} else {
select {
case c.packetsToRead <- p:
default:
// drop the packet if the channel is full
}
}
}
func (c *SimConn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return nil
}
c.closed = true
close(c.closedChan)
return nil
}
func (c *SimConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return 0, nil, net.ErrClosed
}
deadline := c.readDeadline
c.mu.Unlock()
if !deadline.IsZero() && !time.Now().Before(deadline) {
return 0, nil, ErrDeadlineExceeded
}
var pkt Packet
var deadlineTimer <-chan time.Time
if !deadline.IsZero() {
deadlineTimer = time.After(time.Until(deadline))
}
select {
case pkt = <-c.packetsToRead:
case <-c.closedChan:
return 0, nil, net.ErrClosed
case <-c.deadlineUpdated:
return c.ReadFrom(p)
case <-deadlineTimer:
return 0, nil, ErrDeadlineExceeded
}
n = copy(p, pkt.Data)
// if the provided buffer is not enough to read the whole packet, we drop
// the rest of the data. this is similar to what `recvfrom` does on Linux
// and macOS.
return n, pkt.From, nil
}
func (c *SimConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return 0, net.ErrClosed
}
deadline := c.writeDeadline
c.mu.Unlock()
if !deadline.IsZero() && !time.Now().Before(deadline) {
return 0, ErrDeadlineExceeded
}
c.packetsSent.Add(1)
c.bytesSent.Add(int64(len(p)))
pkt := Packet{
From: c.myAddr,
To: addr,
Data: slices.Clone(p),
}
return len(p), c.router.SendPacket(pkt)
}
func (c *SimConn) UnicastAddr() net.Addr {
return c.myAddr
}
func (c *SimConn) LocalAddr() net.Addr {
if c.myLocalAddr != nil {
return c.myLocalAddr
}
return c.myAddr
}
func (c *SimConn) SetDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.readDeadline = t
c.writeDeadline = t
select {
case c.deadlineUpdated <- struct{}{}:
default:
}
return nil
}
func (c *SimConn) SetReadDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.readDeadline = t
select {
case c.deadlineUpdated <- struct{}{}:
default:
}
return nil
}
func (c *SimConn) SetWriteDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writeDeadline = t
return nil
}
package simnet
import (
"net"
"sync"
"time"
)
// packetWithDeliveryTime holds a packet along with its scheduled delivery time
type packetWithDeliveryTime struct {
Packet
DeliveryTime time.Time
}
// LinkSettings defines the network characteristics for a simulated link direction
type LinkSettings struct {
// MTU (Maximum Transmission Unit) specifies the maximum packet size in bytes
MTU int
}
// SimulatedLink simulates a bidirectional network link with variable latency and MTU constraints
type SimulatedLink struct {
// Internal state for lifecycle management
wg sync.WaitGroup
// Queues for packet delivery timing
downstreamQueue *queue
upstreamQueue *queue
// Configuration for link characteristics
UplinkSettings LinkSettings
DownlinkSettings LinkSettings
// Latency specifies a fixed network delay for downlink packets
// If both Latency and LatencyFunc are set, LatencyFunc takes precedence
Latency time.Duration
// LatencyFunc computes the network delay for each downlink packet
// This allows variable latency based on packet source/destination
// If nil, Latency field is used instead
LatencyFunc func(Packet) time.Duration
// Packet routing interfaces
UploadPacket Router
downloadPacket PacketReceiver
}
func (l *SimulatedLink) AddNode(addr net.Addr, receiver PacketReceiver) {
l.downloadPacket = receiver
}
func (l *SimulatedLink) Start() {
if l.downloadPacket == nil {
panic("SimulatedLink.Start() called without having added a packet receiver")
}
// Sane defaults
if l.DownlinkSettings.MTU == 0 {
l.DownlinkSettings.MTU = 1400
}
if l.UplinkSettings.MTU == 0 {
l.UplinkSettings.MTU = 1400
}
l.downstreamQueue = newQueue()
l.upstreamQueue = newQueue()
l.wg.Go(func() { l.backgroundDownlink() })
l.wg.Go(func() { l.backgroundUplink() })
}
func (l *SimulatedLink) Close() error {
l.downstreamQueue.Close()
l.upstreamQueue.Close()
l.wg.Wait()
return nil
}
func (l *SimulatedLink) backgroundDownlink() {
for {
// Dequeue a packet (this will block until packet is ready for delivery)
// Dequeue() returns false when the queue is closed
p, ok := l.downstreamQueue.Dequeue()
if !ok {
return
}
// Deliver the packet
l.downloadPacket.RecvPacket(p.Packet)
}
}
func (l *SimulatedLink) backgroundUplink() {
for {
// Dequeue a packet (this will block until packet is ready for delivery)
// Dequeue() returns false when the queue is closed
p, ok := l.upstreamQueue.Dequeue()
if !ok {
return
}
// Deliver the packet
_ = l.UploadPacket.SendPacket(p.Packet)
}
}
func (l *SimulatedLink) SendPacket(p Packet) error {
if len(p.Data) > l.UplinkSettings.MTU {
// Drop packet if it's too large
return nil
}
// Uplink has no latency - packets are delivered immediately
deliveryTime := time.Now()
// Enqueue packet with delivery time
l.upstreamQueue.Enqueue(&packetWithDeliveryTime{
Packet: p,
DeliveryTime: deliveryTime,
})
return nil
}
func (l *SimulatedLink) RecvPacket(p Packet) {
if len(p.Data) > l.DownlinkSettings.MTU {
// Drop packet if it's too large
return
}
// Calculate delivery time based on downlink latency
var latency time.Duration
if l.LatencyFunc != nil {
latency = l.LatencyFunc(p)
} else {
latency = l.Latency
}
deliveryTime := time.Now().Add(latency)
// Enqueue packet with delivery time
l.downstreamQueue.Enqueue(&packetWithDeliveryTime{
Packet: p,
DeliveryTime: deliveryTime,
})
}
package simnet
import (
"errors"
"fmt"
"net"
"time"
)
// Simnet is a simulated network that manages connections between nodes
// with configurable network conditions.
type Simnet struct {
Router Router
links []*SimulatedLink
}
// NodeBiDiLinkSettings defines the bidirectional link settings for a network node.
// It specifies separate configurations for downlink (incoming) and uplink (outgoing)
// traffic, allowing asymmetric network conditions to be simulated.
type NodeBiDiLinkSettings struct {
// Downlink configures the settings for incoming traffic to this node
Downlink LinkSettings
// Uplink configures the settings for outgoing traffic from this node
Uplink LinkSettings
// Latency specifies a fixed network delay for downlink packets only
// If both Latency and LatencyFunc are set, LatencyFunc takes precedence
Latency time.Duration
// LatencyFunc computes the network delay for each downlink packet
// This allows variable latency based on packet source/destination
// If nil, Latency field is used instead
LatencyFunc func(Packet) time.Duration
}
func (n *Simnet) Start() error {
for _, link := range n.links {
link.Start()
}
return nil
}
func (n *Simnet) Close() error {
var errs error
for _, link := range n.links {
err := link.Close()
if err != nil {
errs = errors.Join(errs, err)
}
}
if errs != nil {
return fmt.Errorf("failed to close some links: %w", errs)
}
return nil
}
func (n *Simnet) NewEndpoint(addr *net.UDPAddr, linkSettings NodeBiDiLinkSettings) *SimConn {
link := &SimulatedLink{
DownlinkSettings: linkSettings.Downlink,
UplinkSettings: linkSettings.Uplink,
Latency: linkSettings.Latency,
LatencyFunc: linkSettings.LatencyFunc,
UploadPacket: n.Router,
}
c := NewBlockingSimConn(addr, link)
n.links = append(n.links, link)
n.Router.AddNode(addr, link)
return c
}
package quic
import (
"sync"
list "github.com/quic-go/quic-go/internal/utils/linkedlist"
)
type singleOriginTokenStore struct {
tokens []*ClientToken
len int
p int
}
func newSingleOriginTokenStore(size int) *singleOriginTokenStore {
return &singleOriginTokenStore{tokens: make([]*ClientToken, size)}
}
func (s *singleOriginTokenStore) Add(token *ClientToken) {
s.tokens[s.p] = token
s.p = s.index(s.p + 1)
s.len = min(s.len+1, len(s.tokens))
}
func (s *singleOriginTokenStore) Pop() *ClientToken {
s.p = s.index(s.p - 1)
token := s.tokens[s.p]
s.tokens[s.p] = nil
s.len = max(s.len-1, 0)
return token
}
func (s *singleOriginTokenStore) Len() int {
return s.len
}
func (s *singleOriginTokenStore) index(i int) int {
mod := len(s.tokens)
return (i + mod) % mod
}
type lruTokenStoreEntry struct {
key string
cache *singleOriginTokenStore
}
type lruTokenStore struct {
mutex sync.Mutex
m map[string]*list.Element[*lruTokenStoreEntry]
q *list.List[*lruTokenStoreEntry]
capacity int
singleOriginSize int
}
var _ TokenStore = &lruTokenStore{}
// NewLRUTokenStore creates a new LRU cache for tokens received by the client.
// maxOrigins specifies how many origins this cache is saving tokens for.
// tokensPerOrigin specifies the maximum number of tokens per origin.
func NewLRUTokenStore(maxOrigins, tokensPerOrigin int) TokenStore {
return &lruTokenStore{
m: make(map[string]*list.Element[*lruTokenStoreEntry]),
q: list.New[*lruTokenStoreEntry](),
capacity: maxOrigins,
singleOriginSize: tokensPerOrigin,
}
}
func (s *lruTokenStore) Put(key string, token *ClientToken) {
s.mutex.Lock()
defer s.mutex.Unlock()
if el, ok := s.m[key]; ok {
entry := el.Value
entry.cache.Add(token)
s.q.MoveToFront(el)
return
}
if s.q.Len() < s.capacity {
entry := &lruTokenStoreEntry{
key: key,
cache: newSingleOriginTokenStore(s.singleOriginSize),
}
entry.cache.Add(token)
s.m[key] = s.q.PushFront(entry)
return
}
elem := s.q.Back()
entry := elem.Value
delete(s.m, entry.key)
entry.key = key
entry.cache = newSingleOriginTokenStore(s.singleOriginSize)
entry.cache.Add(token)
s.q.MoveToFront(elem)
s.m[key] = elem
}
func (s *lruTokenStore) Pop(key string) *ClientToken {
s.mutex.Lock()
defer s.mutex.Unlock()
var token *ClientToken
if el, ok := s.m[key]; ok {
s.q.MoveToFront(el)
cache := el.Value.cache
token = cache.Pop()
if cache.Len() == 0 {
s.q.Remove(el)
delete(s.m, key)
}
}
return token
}
package quic
import (
"context"
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/internal/wire"
"github.com/quic-go/quic-go/qlog"
"github.com/quic-go/quic-go/qlogwriter"
)
// ErrTransportClosed is returned by the [Transport]'s Listen or Dial method after it was closed.
var ErrTransportClosed = &errTransportClosed{}
type errTransportClosed struct {
err error
}
func (e *errTransportClosed) Unwrap() []error { return []error{net.ErrClosed, e.err} }
func (e *errTransportClosed) Error() string {
if e.err == nil {
return "quic: transport closed"
}
return fmt.Sprintf("quic: transport closed: %s", e.err)
}
func (e *errTransportClosed) Is(target error) bool {
_, ok := target.(*errTransportClosed)
return ok
}
var errListenerAlreadySet = errors.New("listener already set")
type closePacket struct {
payload []byte
addr net.Addr
info packetInfo
}
// The Transport is the central point to manage incoming and outgoing QUIC connections.
// QUIC demultiplexes connections based on their QUIC Connection IDs, not based on the 4-tuple.
// This means that a single UDP socket can be used for listening for incoming connections, as well as
// for dialing an arbitrary number of outgoing connections.
// A Transport handles a single net.PacketConn, and offers a range of configuration options
// compared to the simple helper functions like [Listen] and [Dial] that this package provides.
type Transport struct {
// A single net.PacketConn can only be handled by one Transport.
// Bad things will happen if passed to multiple Transports.
//
// A number of optimizations will be enabled if the connections implements the OOBCapablePacketConn interface,
// as a *net.UDPConn does.
// 1. It enables the Don't Fragment (DF) bit on the IP header.
// This is required to run DPLPMTUD (Path MTU Discovery, RFC 8899).
// 2. It enables reading of the ECN bits from the IP header.
// This allows the remote node to speed up its loss detection and recovery.
// 3. It uses batched syscalls (recvmmsg) to more efficiently receive packets from the socket.
// 4. It uses Generic Segmentation Offload (GSO) to efficiently send batches of packets (on Linux).
//
// After passing the connection to the Transport, it's invalid to call ReadFrom or WriteTo on the connection.
Conn net.PacketConn
// The length of the connection ID in bytes.
// It can be any value between 1 and 20.
// Due to the increased risk of collisions, it is not recommended to use connection IDs shorter than 4 bytes.
// If unset, a 4 byte connection ID will be used.
ConnectionIDLength int
// Use for generating new connection IDs.
// This allows the application to control of the connection IDs used,
// which allows routing / load balancing based on connection IDs.
// All Connection IDs returned by the ConnectionIDGenerator MUST
// have the same length.
ConnectionIDGenerator ConnectionIDGenerator
// The StatelessResetKey is used to generate stateless reset tokens.
// If no key is configured, sending of stateless resets is disabled.
// It is highly recommended to configure a stateless reset key, as stateless resets
// allow the peer to quickly recover from crashes and reboots of this node.
// See section 10.3 of RFC 9000 for details.
StatelessResetKey *StatelessResetKey
// The TokenGeneratorKey is used to encrypt session resumption tokens.
// If no key is configured, a random key will be generated.
// If multiple servers are authoritative for the same domain, they should use the same key,
// see section 8.1.3 of RFC 9000 for details.
TokenGeneratorKey *TokenGeneratorKey
// MaxTokenAge is the maximum age of the resumption token presented during the handshake.
// These tokens allow skipping address resumption when resuming a QUIC connection,
// and are especially useful when using 0-RTT.
// If not set, it defaults to 24 hours.
// See section 8.1.3 of RFC 9000 for details.
MaxTokenAge time.Duration
// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
// This can be useful if version information is exchanged out-of-band.
// It has no effect for clients.
DisableVersionNegotiationPackets bool
// VerifySourceAddress decides if a connection attempt originating from unvalidated source
// addresses first needs to go through source address validation using QUIC's Retry mechanism,
// as described in RFC 9000 section 8.1.2.
// Note that the address passed to this callback is unvalidated, and might be spoofed in case
// of an attack.
// Validating the source address adds one additional network roundtrip to the handshake,
// and should therefore only be used if a suspiciously high number of incoming connection is recorded.
// For most use cases, wrapping the Allow function of a rate.Limiter will be a reasonable
// implementation of this callback (negating its return value).
VerifySourceAddress func(net.Addr) bool
// ConnContext is called when the server accepts a new connection. To reject a connection return
// a non-nil error.
// The context is closed when the connection is closed, or when the handshake fails for any reason.
// The context returned from the callback is used to derive every other context used during the
// lifetime of the connection:
// * the context passed to crypto/tls (and used on the tls.ClientHelloInfo)
// * the context used in Config.QlogTrace
// * the context returned from Conn.Context
// * the context returned from SendStream.Context
// It is not used for dialed connections.
ConnContext func(context.Context, *ClientInfo) (context.Context, error)
// A Tracer traces events that don't belong to a single QUIC connection.
// Recorder.Close is called when the transport is closed.
Tracer qlogwriter.Recorder
mutex sync.Mutex
handlers map[protocol.ConnectionID]packetHandler
resetTokens map[protocol.StatelessResetToken]packetHandler
initOnce sync.Once
initErr error
// If no ConnectionIDGenerator is set, this is the ConnectionIDLength.
connIDLen int
// Set in init.
// If no ConnectionIDGenerator is set, this is set to a default.
connIDGenerator ConnectionIDGenerator
statelessResetter *statelessResetter
server *baseServer
conn rawConn
closeQueue chan closePacket
statelessResetQueue chan receivedPacket
listening chan struct{} // is closed when listen returns
closeErr error
createdConn bool
isSingleUse bool // was created for a single server or client, i.e. by calling quic.Listen or quic.Dial
readingNonQUICPackets atomic.Bool
nonQUICPackets chan receivedPacket
logger utils.Logger
}
// Listen starts listening for incoming QUIC connections.
// There can only be a single listener on any net.PacketConn.
// Listen may only be called again after the current listener was closed.
func (t *Transport) Listen(tlsConf *tls.Config, conf *Config) (*Listener, error) {
s, err := t.createServer(tlsConf, conf, false)
if err != nil {
return nil, err
}
return &Listener{baseServer: s}, nil
}
// ListenEarly starts listening for incoming QUIC connections.
// There can only be a single listener on any net.PacketConn.
// ListenEarly may only be called again after the current listener was closed.
func (t *Transport) ListenEarly(tlsConf *tls.Config, conf *Config) (*EarlyListener, error) {
s, err := t.createServer(tlsConf, conf, true)
if err != nil {
return nil, err
}
return &EarlyListener{baseServer: s}, nil
}
func (t *Transport) createServer(tlsConf *tls.Config, conf *Config, allow0RTT bool) (*baseServer, error) {
if tlsConf == nil {
return nil, errors.New("quic: tls.Config not set")
}
if err := validateConfig(conf); err != nil {
return nil, err
}
t.mutex.Lock()
defer t.mutex.Unlock()
if t.closeErr != nil {
return nil, t.closeErr
}
if t.server != nil {
return nil, errListenerAlreadySet
}
conf = populateConfig(conf)
if err := t.init(false); err != nil {
return nil, err
}
maxTokenAge := t.MaxTokenAge
if maxTokenAge == 0 {
maxTokenAge = 24 * time.Hour
}
s := newServer(
t.conn,
(*packetHandlerMap)(t),
t.connIDGenerator,
t.statelessResetter,
t.ConnContext,
tlsConf,
conf,
t.Tracer,
t.closeServer,
*t.TokenGeneratorKey,
maxTokenAge,
t.VerifySourceAddress,
t.DisableVersionNegotiationPackets,
allow0RTT,
)
t.server = s
return s, nil
}
// Dial dials a new connection to a remote host (not using 0-RTT).
func (t *Transport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (*Conn, error) {
return t.dial(ctx, addr, "", tlsConf, conf, false)
}
// DialEarly dials a new connection, attempting to use 0-RTT if possible.
func (t *Transport) DialEarly(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (*Conn, error) {
return t.dial(ctx, addr, "", tlsConf, conf, true)
}
func (t *Transport) dial(ctx context.Context, addr net.Addr, host string, tlsConf *tls.Config, conf *Config, use0RTT bool) (*Conn, error) {
if err := t.init(t.isSingleUse); err != nil {
return nil, err
}
if err := validateConfig(conf); err != nil {
return nil, err
}
conf = populateConfig(conf)
tlsConf = tlsConf.Clone()
setTLSConfigServerName(tlsConf, addr, host)
return t.doDial(ctx,
newSendConn(t.conn, addr, packetInfo{}, utils.DefaultLogger),
tlsConf,
conf,
0,
false,
use0RTT,
conf.Versions[0],
)
}
func (t *Transport) doDial(
ctx context.Context,
sendConn sendConn,
tlsConf *tls.Config,
config *Config,
initialPacketNumber protocol.PacketNumber,
hasNegotiatedVersion bool,
use0RTT bool,
version protocol.Version,
) (*Conn, error) {
srcConnID, err := t.connIDGenerator.GenerateConnectionID()
if err != nil {
return nil, err
}
destConnID, err := generateConnectionIDForInitial()
if err != nil {
return nil, err
}
t.mutex.Lock()
if t.closeErr != nil {
t.mutex.Unlock()
return nil, t.closeErr
}
var qlogTrace qlogwriter.Trace
if config.Tracer != nil {
qlogTrace = config.Tracer(ctx, true, destConnID)
}
logger := utils.DefaultLogger.WithPrefix("client")
logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", tlsConf.ServerName, sendConn.LocalAddr(), sendConn.RemoteAddr(), srcConnID, destConnID, version)
conn := newClientConnection(
context.WithoutCancel(ctx),
sendConn,
(*packetHandlerMap)(t),
destConnID,
srcConnID,
t.connIDGenerator,
t.statelessResetter,
config,
tlsConf,
initialPacketNumber,
use0RTT,
hasNegotiatedVersion,
qlogTrace,
logger,
version,
)
t.handlers[srcConnID] = conn
t.mutex.Unlock()
// The error channel needs to be buffered, as the run loop will continue running
// after doDial returns (if the handshake is successful).
// Similarly, the recreateChan needs to be buffered; in case a different case is selected.
errChan := make(chan error, 1)
recreateChan := make(chan errCloseForRecreating, 1)
go func() {
err := conn.run()
var recreateErr *errCloseForRecreating
if errors.As(err, &recreateErr) {
recreateChan <- *recreateErr
return
}
if t.isSingleUse {
t.Close()
}
errChan <- err
}()
// Only set when we're using 0-RTT.
// Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever.
var earlyConnChan <-chan struct{}
if use0RTT {
earlyConnChan = conn.earlyConnReady()
}
select {
case <-ctx.Done():
conn.destroy(nil)
// wait until the Go routine that called Conn.run() returns
select {
case <-errChan:
case <-recreateChan:
}
return nil, context.Cause(ctx)
case params := <-recreateChan:
return t.doDial(ctx,
sendConn,
tlsConf,
config,
params.nextPacketNumber,
true,
use0RTT,
params.nextVersion,
)
case err := <-errChan:
return nil, err
case <-earlyConnChan:
// ready to send 0-RTT data
return conn.Conn, nil
case <-conn.HandshakeComplete():
// handshake successfully completed
return conn.Conn, nil
}
}
func (t *Transport) init(allowZeroLengthConnIDs bool) error {
t.initOnce.Do(func() {
var conn rawConn
if c, ok := t.Conn.(rawConn); ok {
conn = c
} else {
var err error
conn, err = wrapConn(t.Conn)
if err != nil {
t.initErr = err
return
}
}
t.logger = utils.DefaultLogger // TODO: make this configurable
t.conn = conn
t.handlers = make(map[protocol.ConnectionID]packetHandler)
t.resetTokens = make(map[protocol.StatelessResetToken]packetHandler)
t.listening = make(chan struct{})
t.closeQueue = make(chan closePacket, 4)
t.statelessResetQueue = make(chan receivedPacket, 4)
if t.TokenGeneratorKey == nil {
var key TokenGeneratorKey
if _, err := rand.Read(key[:]); err != nil {
t.initErr = err
return
}
t.TokenGeneratorKey = &key
}
if t.ConnectionIDGenerator != nil {
t.connIDGenerator = t.ConnectionIDGenerator
t.connIDLen = t.ConnectionIDGenerator.ConnectionIDLen()
} else {
connIDLen := t.ConnectionIDLength
if t.ConnectionIDLength == 0 && !allowZeroLengthConnIDs {
connIDLen = protocol.DefaultConnectionIDLength
}
t.connIDLen = connIDLen
t.connIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: t.connIDLen}
}
t.statelessResetter = newStatelessResetter(t.StatelessResetKey)
go func() {
defer close(t.listening)
t.listen(conn)
if t.createdConn {
conn.Close()
}
}()
go t.runSendQueue()
})
return t.initErr
}
// WriteTo sends a packet on the underlying connection.
func (t *Transport) WriteTo(b []byte, addr net.Addr) (int, error) {
if err := t.init(false); err != nil {
return 0, err
}
return t.conn.WritePacket(b, addr, nil, 0, protocol.ECNUnsupported)
}
func (t *Transport) runSendQueue() {
for {
select {
case <-t.listening:
return
case p := <-t.closeQueue:
t.conn.WritePacket(p.payload, p.addr, p.info.OOB(), 0, protocol.ECNUnsupported)
case p := <-t.statelessResetQueue:
t.sendStatelessReset(p)
}
}
}
// Close stops listening for UDP datagrams on the Transport.Conn.
// It abruptly terminates all existing connections, without sending a CONNECTION_CLOSE
// to the peers. It is the application's responsibility to cleanly terminate existing
// connections prior to calling Close.
//
// If a server was started, it will be closed as well.
// It is not possible to start any new server or dial new connections after that.
func (t *Transport) Close() error {
// avoid race condition if the transport is currently being initialized
t.init(false)
t.close(nil)
if t.createdConn {
if err := t.Conn.Close(); err != nil {
return err
}
} else if t.conn != nil {
t.conn.SetReadDeadline(time.Now())
defer func() { t.conn.SetReadDeadline(time.Time{}) }()
}
if t.listening != nil {
<-t.listening // wait until listening returns
}
return nil
}
func (t *Transport) closeServer() {
t.mutex.Lock()
defer t.mutex.Unlock()
t.server = nil
if t.isSingleUse {
t.closeErr = ErrServerClosed
}
if len(t.handlers) == 0 {
t.maybeStopListening()
}
}
func (t *Transport) close(e error) {
t.mutex.Lock()
if t.closeErr != nil {
t.mutex.Unlock()
return
}
e = &errTransportClosed{err: e}
t.closeErr = e
server := t.server
t.server = nil
if server != nil {
t.mutex.Unlock()
server.close(e, true)
t.mutex.Lock()
}
// Close existing connections
var wg sync.WaitGroup
for _, handler := range t.handlers {
wg.Go(func() { handler.destroy(e) })
}
t.mutex.Unlock() // closing connections requires releasing transport mutex
wg.Wait()
if t.Tracer != nil {
t.Tracer.Close()
}
}
// only print warnings about the UDP receive buffer size once
var setBufferWarningOnce sync.Once
func (t *Transport) listen(conn rawConn) {
for {
p, err := conn.ReadPacket()
//nolint:staticcheck // SA1019 ignore this!
// TODO: This code is used to ignore wsa errors on Windows.
// Since net.Error.Temporary is deprecated as of Go 1.18, we should find a better solution.
// See https://github.com/quic-go/quic-go/issues/1737 for details.
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
t.mutex.Lock()
closed := t.closeErr != nil
t.mutex.Unlock()
if closed {
return
}
t.logger.Debugf("Temporary error reading from conn: %w", err)
continue
}
if err != nil {
// Windows returns an error when receiving a UDP datagram that doesn't fit into the provided buffer.
if isRecvMsgSizeErr(err) {
continue
}
t.close(err)
return
}
t.handlePacket(p)
}
}
func (t *Transport) maybeStopListening() {
if t.isSingleUse && t.closeErr != nil {
t.conn.SetReadDeadline(time.Now())
}
}
func (t *Transport) handlePacket(p receivedPacket) {
if len(p.data) == 0 {
return
}
if !wire.IsPotentialQUICPacket(p.data[0]) && !wire.IsLongHeaderPacket(p.data[0]) {
t.handleNonQUICPacket(p)
return
}
connID, err := wire.ParseConnectionID(p.data, t.connIDLen)
if err != nil {
t.logger.Debugf("error parsing connection ID on packet from %s: %s", p.remoteAddr, err)
if t.Tracer != nil {
t.Tracer.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropHeaderParseError,
})
}
p.buffer.MaybeRelease()
return
}
// If there's a connection associated with the connection ID, pass the packet there.
if handler, ok := (*packetHandlerMap)(t).Get(connID); ok {
handler.handlePacket(p)
return
}
// RFC 9000 section 10.3.1 requires that the stateless reset detection logic is run for both
// packets that cannot be associated with any connections, and for packets that can't be decrypted.
// We deviate from the RFC and ignore the latter: If a packet's connection ID is associated with an
// existing connection, it is dropped there if if it can't be decrypted.
// Stateless resets use random connection IDs, and at reasonable connection ID lengths collisions are
// exceedingly rare. In the unlikely event that a stateless reset is misrouted to an existing connection,
// it is to be expected that the next stateless reset will be correctly detected.
if isStatelessReset := t.maybeHandleStatelessReset(p.data); isStatelessReset {
return
}
if !wire.IsLongHeaderPacket(p.data[0]) {
if statelessResetQueued := t.maybeSendStatelessReset(p); !statelessResetQueued {
if t.Tracer != nil {
t.Tracer.RecordEvent(qlog.PacketDropped{
Header: qlog.PacketHeader{PacketType: qlog.PacketType1RTT},
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnknownConnectionID,
})
}
p.buffer.Release()
}
return
}
t.mutex.Lock()
defer t.mutex.Unlock()
if t.server == nil { // no server set
t.logger.Debugf("received a packet with an unexpected connection ID %s", connID)
if t.Tracer != nil {
t.Tracer.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropUnknownConnectionID,
})
}
p.buffer.MaybeRelease()
return
}
t.server.handlePacket(p)
}
func (t *Transport) maybeSendStatelessReset(p receivedPacket) (statelessResetQueued bool) {
if t.StatelessResetKey == nil {
return false
}
// Don't send a stateless reset in response to very small packets.
// This includes packets that could be stateless resets.
if len(p.data) <= protocol.MinStatelessResetSize {
return false
}
select {
case t.statelessResetQueue <- p:
return true
default:
// it's fine to not send a stateless reset when we're busy
return false
}
}
func (t *Transport) sendStatelessReset(p receivedPacket) {
defer p.buffer.Release()
connID, err := wire.ParseConnectionID(p.data, t.connIDLen)
if err != nil {
t.logger.Errorf("error parsing connection ID on packet from %s: %s", p.remoteAddr, err)
return
}
token := t.statelessResetter.GetStatelessResetToken(connID)
t.logger.Debugf("Sending stateless reset to %s (connection ID: %s). Token: %#x", p.remoteAddr, connID, token)
data := make([]byte, protocol.MinStatelessResetSize-16, protocol.MinStatelessResetSize)
rand.Read(data)
data[0] = (data[0] & 0x7f) | 0x40
data = append(data, token[:]...)
if _, err := t.conn.WritePacket(data, p.remoteAddr, p.info.OOB(), 0, protocol.ECNUnsupported); err != nil {
t.logger.Debugf("Error sending Stateless Reset to %s: %s", p.remoteAddr, err)
}
}
func (t *Transport) maybeHandleStatelessReset(data []byte) bool {
// stateless resets are always short header packets
if wire.IsLongHeaderPacket(data[0]) {
return false
}
if len(data) < 17 /* type byte + 16 bytes for the reset token */ {
return false
}
token := protocol.StatelessResetToken(data[len(data)-16:])
t.mutex.Lock()
conn, ok := t.resetTokens[token]
t.mutex.Unlock()
if ok {
t.logger.Debugf("Received a stateless reset with token %#x. Closing connection.", token)
go conn.destroy(&StatelessResetError{})
return true
}
return false
}
func (t *Transport) handleNonQUICPacket(p receivedPacket) {
// Strictly speaking, this is racy,
// but we only care about receiving packets at some point after ReadNonQUICPacket has been called.
if !t.readingNonQUICPackets.Load() {
return
}
select {
case t.nonQUICPackets <- p:
default:
if t.Tracer != nil {
t.Tracer.RecordEvent(qlog.PacketDropped{
Raw: qlog.RawInfo{Length: int(p.Size())},
Trigger: qlog.PacketDropDOSPrevention,
})
}
}
}
const maxQueuedNonQUICPackets = 32
// ReadNonQUICPacket reads non-QUIC packets received on the underlying connection.
// The detection logic is very simple: Any packet that has the first and second bit of the packet set to 0.
// Note that this is stricter than the detection logic defined in RFC 9443.
func (t *Transport) ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error) {
if err := t.init(false); err != nil {
return 0, nil, err
}
if !t.readingNonQUICPackets.Load() {
t.nonQUICPackets = make(chan receivedPacket, maxQueuedNonQUICPackets)
t.readingNonQUICPackets.Store(true)
}
select {
case <-ctx.Done():
return 0, nil, ctx.Err()
case p := <-t.nonQUICPackets:
n := copy(b, p.data)
return n, p.remoteAddr, nil
case <-t.listening:
return 0, nil, errors.New("closed")
}
}
func setTLSConfigServerName(tlsConf *tls.Config, addr net.Addr, host string) {
// If no ServerName is set, infer the ServerName from the host we're connecting to.
if tlsConf.ServerName != "" {
return
}
if host == "" {
if udpAddr, ok := addr.(*net.UDPAddr); ok {
tlsConf.ServerName = udpAddr.IP.String()
return
}
}
h, _, err := net.SplitHostPort(host)
if err != nil { // This happens if the host doesn't contain a port number.
tlsConf.ServerName = host
return
}
tlsConf.ServerName = h
}
type packetHandlerMap Transport
var _ connRunner = &packetHandlerMap{}
func (h *packetHandlerMap) Add(id protocol.ConnectionID, handler packetHandler) bool /* was added */ {
h.mutex.Lock()
defer h.mutex.Unlock()
if _, ok := h.handlers[id]; ok {
h.logger.Debugf("Not adding connection ID %s, as it already exists.", id)
return false
}
h.handlers[id] = handler
h.logger.Debugf("Adding connection ID %s.", id)
return true
}
func (h *packetHandlerMap) Get(connID protocol.ConnectionID) (packetHandler, bool) {
h.mutex.Lock()
defer h.mutex.Unlock()
handler, ok := h.handlers[connID]
return handler, ok
}
func (h *packetHandlerMap) AddResetToken(token protocol.StatelessResetToken, handler packetHandler) {
h.mutex.Lock()
h.resetTokens[token] = handler
h.mutex.Unlock()
}
func (h *packetHandlerMap) RemoveResetToken(token protocol.StatelessResetToken) {
h.mutex.Lock()
delete(h.resetTokens, token)
h.mutex.Unlock()
}
func (h *packetHandlerMap) AddWithConnID(clientDestConnID, newConnID protocol.ConnectionID, handler packetHandler) bool {
h.mutex.Lock()
defer h.mutex.Unlock()
if _, ok := h.handlers[clientDestConnID]; ok {
h.logger.Debugf("Not adding connection ID %s for a new connection, as it already exists.", clientDestConnID)
return false
}
h.handlers[clientDestConnID] = handler
h.handlers[newConnID] = handler
h.logger.Debugf("Adding connection IDs %s and %s for a new connection.", clientDestConnID, newConnID)
return true
}
func (h *packetHandlerMap) Remove(id protocol.ConnectionID) {
h.mutex.Lock()
delete(h.handlers, id)
h.mutex.Unlock()
h.logger.Debugf("Removing connection ID %s.", id)
}
// ReplaceWithClosed is called when a connection is closed.
// Depending on which side closed the connection, we need to:
// * remote close: absorb delayed packets
// * local close: retransmit the CONNECTION_CLOSE packet, in case it was lost
func (h *packetHandlerMap) ReplaceWithClosed(ids []protocol.ConnectionID, connClosePacket []byte, expiry time.Duration) {
var handler packetHandler
if connClosePacket != nil {
handler = newClosedLocalConn(
func(addr net.Addr, info packetInfo) {
select {
case h.closeQueue <- closePacket{payload: connClosePacket, addr: addr, info: info}:
default:
// We're backlogged.
// Just drop the packet, sending CONNECTION_CLOSE copies is best effort anyway.
}
},
h.logger,
)
} else {
handler = newClosedRemoteConn()
}
h.mutex.Lock()
for _, id := range ids {
h.handlers[id] = handler
}
h.mutex.Unlock()
h.logger.Debugf("Replacing connection for connection IDs %s with a closed connection.", ids)
time.AfterFunc(expiry, func() {
h.mutex.Lock()
for _, id := range ids {
delete(h.handlers, id)
}
if len(h.handlers) == 0 {
t := (*Transport)(h)
t.maybeStopListening()
}
h.mutex.Unlock()
h.logger.Debugf("Removing connection IDs %s for a closed connection after it has been retired.", ids)
})
}