forked from quic-go/quic-go
* ackhandler: check for missing packets below reordering treshold No functional change expected. With the Acknowledgement Frequency extension, the reordering threshold will become configurable. With this change, it will be easy to use the peer-requested value instead of the predefined constant. * improve documentation * call HighestMissingUpTo in randomized test
229 lines
6.7 KiB
Go
229 lines
6.7 KiB
Go
package ackhandler
|
|
|
|
import (
|
|
"fmt"
|
|
"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"
|
|
)
|
|
|
|
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
|
|
ack.AckRanges = h.packetHistory.AppendAckRanges(ack.AckRanges)
|
|
|
|
h.lastAck = ack
|
|
h.hasNewAck = false
|
|
return ack
|
|
}
|
|
|
|
func (h *receivedPacketTracker) IsPotentiallyDuplicate(pn protocol.PacketNumber) bool {
|
|
return h.packetHistory.IsPotentiallyDuplicate(pn)
|
|
}
|
|
|
|
// number of ack-eliciting packets received before sending an ACK
|
|
const packetsBeforeAck = 2
|
|
|
|
// The appDataReceivedPacketTracker tracks packets received in the Application Data packet number space.
|
|
// It waits until at least 2 packets were received before queueing an ACK, or until the max_ack_delay was reached.
|
|
type appDataReceivedPacketTracker struct {
|
|
receivedPacketTracker
|
|
|
|
largestObservedRcvdTime time.Time
|
|
|
|
largestObserved protocol.PacketNumber
|
|
ignoreBelow protocol.PacketNumber
|
|
|
|
maxAckDelay time.Duration
|
|
ackQueued bool // true if we need send a new ACK
|
|
|
|
ackElicitingPacketsReceivedSinceLastAck int
|
|
ackAlarm time.Time
|
|
|
|
logger utils.Logger
|
|
}
|
|
|
|
func newAppDataReceivedPacketTracker(logger utils.Logger) *appDataReceivedPacketTracker {
|
|
h := &appDataReceivedPacketTracker{
|
|
receivedPacketTracker: *newReceivedPacketTracker(),
|
|
maxAckDelay: protocol.MaxAckDelay,
|
|
logger: logger,
|
|
}
|
|
return h
|
|
}
|
|
|
|
func (h *appDataReceivedPacketTracker) ReceivedPacket(pn protocol.PacketNumber, ecn protocol.ECN, rcvTime time.Time, ackEliciting bool) error {
|
|
if err := h.receivedPacketTracker.ReceivedPacket(pn, ecn, ackEliciting); err != nil {
|
|
return err
|
|
}
|
|
if pn >= h.largestObserved {
|
|
h.largestObserved = pn
|
|
h.largestObservedRcvdTime = rcvTime
|
|
}
|
|
if !ackEliciting {
|
|
return nil
|
|
}
|
|
h.ackElicitingPacketsReceivedSinceLastAck++
|
|
isMissing := h.isMissing(pn)
|
|
if !h.ackQueued && h.shouldQueueACK(pn, ecn, isMissing) {
|
|
h.ackQueued = true
|
|
h.ackAlarm = time.Time{} // cancel the ack alarm
|
|
}
|
|
if !h.ackQueued {
|
|
// No ACK queued, but we'll need to acknowledge the packet after max_ack_delay.
|
|
h.ackAlarm = rcvTime.Add(h.maxAckDelay)
|
|
if h.logger.Debug() {
|
|
h.logger.Debugf("\tSetting ACK timer to max ack delay: %s", h.maxAckDelay)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IgnoreBelow sets a lower limit for acknowledging packets.
|
|
// Packets with packet numbers smaller than p will not be acked.
|
|
func (h *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.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 {
|
|
// always acknowledge the first packet
|
|
if h.lastAck == nil {
|
|
h.logger.Debugf("\tQueueing ACK because the first packet should be acknowledged.")
|
|
return true
|
|
}
|
|
|
|
// 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 time.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 = time.Time{}
|
|
h.ackElicitingPacketsReceivedSinceLastAck = 0
|
|
return ack
|
|
}
|
|
|
|
func (h *appDataReceivedPacketTracker) GetAlarmTimeout() time.Time { return h.ackAlarm }
|