Files
golang.net/quic/sent_packet.go
Damien Neil 3e7a445bf4 quic: skip packet numbers for optimistic ack defense
An "optimistic ACK attack" involves an attacker sending ACKs
for packets it hasn't received, causing the victim's
congestion controller to improperly send at a higher rate.

The standard defense against this attack is to skip the occasional
packet number, and to close the connection with an error if the
peer ACKs an unsent packet.

Implement this defense, increasing the gap between skipped
packet numbers as a connection's lifetime grows and correspondingly
the amount of work required on the part of the attacker.

Change-Id: I01f44f13367821b86af6535ffb69d380e2b4d7b7
Reviewed-on: https://go-review.googlesource.com/c/net/+/664298
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
2025-04-10 11:38:36 -07:00

120 lines
3.0 KiB
Go

// Copyright 2023 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 quic
import (
"sync"
"time"
"golang.org/x/net/internal/quic/quicwire"
)
// A sentPacket tracks state related to an in-flight packet we sent,
// to be committed when the peer acks it or resent if the packet is lost.
type sentPacket struct {
num packetNumber
size int // size in bytes
time time.Time // time sent
ptype packetType
state sentPacketState
ackEliciting bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.4.1
inFlight bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.6.1
// Frames sent in the packet.
//
// This is an abbreviated version of the packet payload, containing only the information
// we need to process an ack for or loss of this packet.
// For example, a CRYPTO frame is recorded as the frame type (0x06), offset, and length,
// but does not include the sent data.
//
// This buffer is written by packetWriter.append* and read by Conn.handleAckOrLoss.
b []byte
n int // read offset into b
}
type sentPacketState uint8
const (
sentPacketSent = sentPacketState(iota) // sent but neither acked nor lost
sentPacketAcked // acked
sentPacketLost // declared lost
sentPacketUnsent // never sent
)
var sentPool = sync.Pool{
New: func() any {
return &sentPacket{}
},
}
func newSentPacket() *sentPacket {
sent := sentPool.Get().(*sentPacket)
sent.reset()
return sent
}
// recycle returns a sentPacket to the pool.
func (sent *sentPacket) recycle() {
sentPool.Put(sent)
}
func (sent *sentPacket) reset() {
*sent = sentPacket{
b: sent.b[:0],
}
}
// markAckEliciting marks the packet as containing an ack-eliciting frame.
func (sent *sentPacket) markAckEliciting() {
sent.ackEliciting = true
sent.inFlight = true
}
// The append* methods record information about frames in the packet.
func (sent *sentPacket) appendNonAckElicitingFrame(frameType byte) {
sent.b = append(sent.b, frameType)
}
func (sent *sentPacket) appendAckElicitingFrame(frameType byte) {
sent.ackEliciting = true
sent.inFlight = true
sent.b = append(sent.b, frameType)
}
func (sent *sentPacket) appendInt(v uint64) {
sent.b = quicwire.AppendVarint(sent.b, v)
}
func (sent *sentPacket) appendOffAndSize(start int64, size int) {
sent.b = quicwire.AppendVarint(sent.b, uint64(start))
sent.b = quicwire.AppendVarint(sent.b, uint64(size))
}
// The next* methods read back information about frames in the packet.
func (sent *sentPacket) next() (frameType byte) {
f := sent.b[sent.n]
sent.n++
return f
}
func (sent *sentPacket) nextInt() uint64 {
v, n := quicwire.ConsumeVarint(sent.b[sent.n:])
sent.n += n
return v
}
func (sent *sentPacket) nextRange() (start, end int64) {
start = int64(sent.nextInt())
end = start + int64(sent.nextInt())
return start, end
}
func (sent *sentPacket) done() bool {
return sent.n == len(sent.b)
}