ackhandler: implement timer logic for path probe packets (#4940)

* remove unused bool return value from sentPacketHandler.getPTOTimeAndSpace

* ackhandler: implement timer logic for path probing packets

Path probe packets are treated differently from regular packets: The new
path might have a vastly different RTT than the original path.

Path probe packets are declared lost 1s after they are sent. This value
can be reduced, once implement proper retransmission logic for lost path
probes.

* ackhandler: declare path probes lost on OnLossDetectionTimeout
This commit is contained in:
Marten Seemann 2025-01-28 06:10:44 +01:00 committed by GitHub
parent 6b9921bbfc
commit 108b6603c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 344 additions and 76 deletions

View file

@ -2194,7 +2194,7 @@ func (s *connection) registerPackedShortHeaderPacket(p shortHeaderPacket, ecn pr
if p.Ack != nil { if p.Ack != nil {
largestAcked = p.Ack.LargestAcked() largestAcked = p.Ack.LargestAcked()
} }
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket) s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket, false)
s.connIDManager.SentPacket() s.connIDManager.SentPacket()
} }
@ -2208,7 +2208,7 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, ecn prot
if p.ack != nil { if p.ack != nil {
largestAcked = p.ack.LargestAcked() largestAcked = p.ack.LargestAcked()
} }
s.sentPacketHandler.SentPacket(now, p.header.PacketNumber, largestAcked, p.streamFrames, p.frames, p.EncryptionLevel(), ecn, p.length, false) s.sentPacketHandler.SentPacket(now, p.header.PacketNumber, largestAcked, p.streamFrames, p.frames, p.EncryptionLevel(), ecn, p.length, false, false)
if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake && if s.perspective == protocol.PerspectiveClient && p.EncryptionLevel() == protocol.EncryptionHandshake &&
!s.droppedInitialKeys { !s.droppedInitialKeys {
// On the client side, Initial keys are dropped as soon as the first Handshake packet is sent. // On the client side, Initial keys are dropped as soon as the first Handshake packet is sent.
@ -2226,7 +2226,7 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, ecn prot
if p.Ack != nil { if p.Ack != nil {
largestAcked = p.Ack.LargestAcked() largestAcked = p.Ack.LargestAcked()
} }
s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket) s.sentPacketHandler.SentPacket(now, p.PacketNumber, largestAcked, p.StreamFrames, p.Frames, protocol.Encryption1RTT, ecn, p.Length, p.IsPathMTUProbePacket, false)
} }
s.connIDManager.SentPacket() s.connIDManager.SentPacket()
s.sendQueue.Send(packet.buffer, 0, ecn) s.sendQueue.Send(packet.buffer, 0, ecn)

View file

@ -1635,15 +1635,15 @@ func TestConnectionPacketPacing(t *testing.T) {
gomock.InOrder( gomock.InOrder(
// 1. allow 2 packets to be sent // 1. allow 2 packets to be sent
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny),
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()), sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()),
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny),
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()), sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()),
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited),
// 2. become pacing limited for 25ms // 2. become pacing limited for 25ms
sph.EXPECT().TimeUntilSend().DoAndReturn(func() time.Time { return time.Now().Add(step) }), sph.EXPECT().TimeUntilSend().DoAndReturn(func() time.Time { return time.Now().Add(step) }),
// 3. send another packet // 3. send another packet
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny),
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()), sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()),
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited),
// 4. become pacing limited for 25ms... // 4. become pacing limited for 25ms...
sph.EXPECT().TimeUntilSend().DoAndReturn(func() time.Time { return time.Now().Add(step) }), sph.EXPECT().TimeUntilSend().DoAndReturn(func() time.Time { return time.Now().Add(step) }),
@ -1652,7 +1652,7 @@ func TestConnectionPacketPacing(t *testing.T) {
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited), sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendPacingLimited),
// 5. stop the test by becoming pacing limited forever // 5. stop the test by becoming pacing limited forever
sph.EXPECT().TimeUntilSend().Return(time.Now().Add(time.Hour)), sph.EXPECT().TimeUntilSend().Return(time.Now().Add(time.Hour)),
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()), sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()),
) )
sph.EXPECT().ECNMode(gomock.Any()).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).AnyTimes()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
@ -1743,7 +1743,7 @@ func TestConnectionIdleTimeout(t *testing.T) {
sph.EXPECT().GetLossDetectionTimeout().AnyTimes() sph.EXPECT().GetLossDetectionTimeout().AnyTimes()
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
sph.EXPECT().ECNMode(gomock.Any()).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).AnyTimes()
var lastSendTime time.Time var lastSendTime time.Time
tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
@ -1890,7 +1890,7 @@ func TestConnectionACKTimer(t *testing.T) {
sph.EXPECT().GetLossDetectionTimeout().AnyTimes() sph.EXPECT().GetLossDetectionTimeout().AnyTimes()
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
sph.EXPECT().ECNMode(gomock.Any()).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).AnyTimes()
rph.EXPECT().GetAlarmTimeout().Return(time.Now().Add(time.Hour)) rph.EXPECT().GetAlarmTimeout().Return(time.Now().Add(time.Hour))
tc.sendConn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() tc.sendConn.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@ -1960,7 +1960,7 @@ func TestConnectionGSOBatch(t *testing.T) {
// allow packets to be sent // allow packets to be sent
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes() sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes() sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes()
sph.EXPECT().ECNMode(gomock.Any()).Return(protocol.ECT1).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).Return(protocol.ECT1).AnyTimes()
@ -2019,7 +2019,7 @@ func TestConnectionGSOBatchPacketSize(t *testing.T) {
// allow packets to be sent // allow packets to be sent
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes() sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes() sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes()
sph.EXPECT().ECNMode(gomock.Any()).Return(protocol.ECT1).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).Return(protocol.ECT1).AnyTimes()
@ -2099,7 +2099,7 @@ func TestConnectionGSOBatchECN(t *testing.T) {
ecnMode := protocol.ECT1 ecnMode := protocol.ECT1
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes() sph.EXPECT().TimeUntilSend().Return(time.Time{}).AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes() sph.EXPECT().GetLossDetectionTimeout().Return(time.Time{}).AnyTimes()
sph.EXPECT().ECNMode(gomock.Any()).DoAndReturn(func(bool) protocol.ECN { return ecnMode }).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).DoAndReturn(func(bool) protocol.ECN { return ecnMode }).AnyTimes()
@ -2201,7 +2201,7 @@ func testConnectionPTOProbePackets(t *testing.T, encLevel protocol.EncryptionLev
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendNone) sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendNone)
sph.EXPECT().ECNMode(gomock.Any()) sph.EXPECT().ECNMode(gomock.Any())
sph.EXPECT().QueueProbePacket(encLevel).Return(false) sph.EXPECT().QueueProbePacket(encLevel).Return(false)
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
tc.packer.EXPECT().MaybePackPTOProbePacket(encLevel, gomock.Any(), gomock.Any(), protocol.Version1).DoAndReturn( tc.packer.EXPECT().MaybePackPTOProbePacket(encLevel, gomock.Any(), gomock.Any(), protocol.Version1).DoAndReturn(
func(encLevel protocol.EncryptionLevel, maxSize protocol.ByteCount, t time.Time, version protocol.Version) (*coalescedPacket, error) { func(encLevel protocol.EncryptionLevel, maxSize protocol.ByteCount, t time.Time, version protocol.Version) (*coalescedPacket, error) {
@ -2254,7 +2254,7 @@ func TestConnectionCongestionControl(t *testing.T) {
sph.EXPECT().ECNMode(true).AnyTimes() sph.EXPECT().ECNMode(true).AnyTimes()
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).Times(2) sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).Times(2)
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAck).MaxTimes(1) sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAck).MaxTimes(1)
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2) sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(2)
// Since we're already sending out packets, we don't expect any calls to PackAckOnlyPacket // Since we're already sending out packets, we don't expect any calls to PackAckOnlyPacket
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
@ -2347,7 +2347,7 @@ func testConnectionSendQueue(t *testing.T, enableGSO bool) {
}, },
) )
sph.EXPECT().GetLossDetectionTimeout().AnyTimes() sph.EXPECT().GetLossDetectionTimeout().AnyTimes()
sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) sph.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes() sph.EXPECT().SendMode(gomock.Any()).Return(ackhandler.SendAny).AnyTimes()
sph.EXPECT().ECNMode(gomock.Any()).AnyTimes() sph.EXPECT().ECNMode(gomock.Any()).AnyTimes()
tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return( tc.packer.EXPECT().AppendPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(

View file

@ -10,7 +10,7 @@ import (
// SentPacketHandler handles ACKs received for outgoing packets // SentPacketHandler handles ACKs received for outgoing packets
type SentPacketHandler interface { type SentPacketHandler interface {
// SentPacket may modify the packet // SentPacket may modify the packet
SentPacket(t time.Time, pn, largestAcked protocol.PacketNumber, streamFrames []StreamFrame, frames []Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket bool) SentPacket(t time.Time, pn, largestAcked protocol.PacketNumber, streamFrames []StreamFrame, frames []Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket, isPathProbePacket bool)
// ReceivedAck processes an ACK frame. // ReceivedAck processes an ACK frame.
// It does not store a copy of the frame. // It does not store a copy of the frame.
ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* 1-RTT packet acked */, error) ReceivedAck(f *wire.AckFrame, encLevel protocol.EncryptionLevel, rcvTime time.Time) (bool /* 1-RTT packet acked */, error)
@ -34,6 +34,8 @@ type SentPacketHandler interface {
GetLossDetectionTimeout() time.Time GetLossDetectionTimeout() time.Time
OnLossDetectionTimeout(now time.Time) error OnLossDetectionTimeout(now time.Time) error
MigratedPath(now time.Time, initialMaxPacketSize protocol.ByteCount)
} }
type sentPacketTracker interface { type sentPacketTracker interface {

View file

@ -27,6 +27,9 @@ const (
maxPTODuration = 60 * time.Second maxPTODuration = 60 * time.Second
) )
// Path probe packets are declared lost after this time.
const pathProbePacketLossTimeout = time.Second
type packetNumberSpace struct { type packetNumberSpace struct {
history sentPacketHistory history sentPacketHistory
pns packetNumberGenerator pns packetNumberGenerator
@ -249,11 +252,12 @@ func (h *sentPacketHandler) SentPacket(
ecn protocol.ECN, ecn protocol.ECN,
size protocol.ByteCount, size protocol.ByteCount,
isPathMTUProbePacket bool, isPathMTUProbePacket bool,
isPathProbePacket bool,
) { ) {
h.bytesSent += size h.bytesSent += size
pnSpace := h.getPacketNumberSpace(encLevel) pnSpace := h.getPacketNumberSpace(encLevel)
if h.logger.Debug() && pnSpace.history.HasOutstandingPackets() { if h.logger.Debug() && (pnSpace.history.HasOutstandingPackets() || pnSpace.history.HasOutstandingPathProbes()) {
for p := max(0, pnSpace.largestSent+1); p < pn; p++ { for p := max(0, pnSpace.largestSent+1); p < pn; p++ {
h.logger.Debugf("Skipping packet number %d", p) h.logger.Debugf("Skipping packet number %d", p)
} }
@ -262,6 +266,18 @@ func (h *sentPacketHandler) SentPacket(
pnSpace.largestSent = pn pnSpace.largestSent = pn
isAckEliciting := len(streamFrames) > 0 || len(frames) > 0 isAckEliciting := len(streamFrames) > 0 || len(frames) > 0
if isPathProbePacket {
p := getPacket()
p.SendTime = t
p.PacketNumber = pn
p.EncryptionLevel = encLevel
p.Length = size
p.Frames = frames
p.isPathProbePacket = true
pnSpace.history.SentPathProbePacket(p)
h.setLossDetectionTimer(t)
return
}
if isAckEliciting { if isAckEliciting {
pnSpace.lastAckElicitingPacketTime = t pnSpace.lastAckElicitingPacketTime = t
h.bytesInFlight += size h.bytesInFlight += size
@ -341,7 +357,7 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
} }
// update the RTT, if the largest acked is newly acknowledged // update the RTT, if the largest acked is newly acknowledged
if len(ackedPackets) > 0 { if len(ackedPackets) > 0 {
if p := ackedPackets[len(ackedPackets)-1]; p.PacketNumber == ack.LargestAcked() { if p := ackedPackets[len(ackedPackets)-1]; p.PacketNumber == ack.LargestAcked() && !p.isPathProbePacket {
// don't use the ack delay for Initial and Handshake packets // don't use the ack delay for Initial and Handshake packets
var ackDelay time.Duration var ackDelay time.Duration
if encLevel == protocol.Encryption1RTT { if encLevel == protocol.Encryption1RTT {
@ -366,6 +382,9 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
pnSpace.largestAcked = max(pnSpace.largestAcked, largestAcked) pnSpace.largestAcked = max(pnSpace.largestAcked, largestAcked)
h.detectLostPackets(rcvTime, encLevel) h.detectLostPackets(rcvTime, encLevel)
if encLevel == protocol.Encryption1RTT {
h.detectLostPathProbes(rcvTime)
}
var acked1RTTPacket bool var acked1RTTPacket bool
for _, p := range ackedPackets { for _, p := range ackedPackets {
if p.includedInBytesInFlight && !p.declaredLost { if p.includedInBytesInFlight && !p.declaredLost {
@ -375,7 +394,9 @@ func (h *sentPacketHandler) ReceivedAck(ack *wire.AckFrame, encLevel protocol.En
acked1RTTPacket = true acked1RTTPacket = true
} }
h.removeFromBytesInFlight(p) h.removeFromBytesInFlight(p)
putPacket(p) if !p.isPathProbePacket {
putPacket(p)
}
} }
// After this point, we must not use ackedPackets any longer! // After this point, we must not use ackedPackets any longer!
// We've already returned the buffers. // We've already returned the buffers.
@ -411,11 +432,11 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL
largestAcked := ack.LargestAcked() largestAcked := ack.LargestAcked()
var processErr error var processErr error
pnSpace.history.Iterate(func(p *packet) bool { pnSpace.history.Iterate(func(p *packet) bool {
// Ignore packets below the lowest acked // ignore packets below the lowest acked
if p.PacketNumber < lowestAcked { if p.PacketNumber < lowestAcked {
return true return true
} }
// Break after largest acked is reached // break after largest acked is reached
if p.PacketNumber > largestAcked { if p.PacketNumber > largestAcked {
return false return false
} }
@ -443,7 +464,15 @@ func (h *sentPacketHandler) detectAndRemoveAckedPackets(ack *wire.AckFrame, encL
} }
return false return false
} }
h.ackedPackets = append(h.ackedPackets, p) if p.isPathProbePacket {
probePacket := pnSpace.history.RemovePathProbe(p.PacketNumber)
if probePacket == nil {
panic(fmt.Sprintf("path probe doesn't exist: %d", p.PacketNumber))
}
h.ackedPackets = append(h.ackedPackets, probePacket)
} else {
h.ackedPackets = append(h.ackedPackets, p)
}
return true return true
}) })
if h.logger.Debug() && len(h.ackedPackets) > 0 { if h.logger.Debug() && len(h.ackedPackets) > 0 {
@ -510,7 +539,7 @@ func (h *sentPacketHandler) getScaledPTO(includeMaxAckDelay bool) time.Duration
} }
// same logic as getLossTimeAndSpace, but for lastAckElicitingPacketTime instead of lossTime // same logic as getLossTimeAndSpace, but for lastAckElicitingPacketTime instead of lossTime
func (h *sentPacketHandler) getPTOTimeAndSpace(now time.Time) (pto time.Time, encLevel protocol.EncryptionLevel, ok bool) { func (h *sentPacketHandler) getPTOTimeAndSpace(now time.Time) (pto time.Time, encLevel protocol.EncryptionLevel) {
// We only send application data probe packets once the handshake is confirmed, // 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. // because before that, we don't have the keys to decrypt ACKs sent in 1-RTT packets.
if !h.handshakeConfirmed && !h.hasOutstandingCryptoPackets() { if !h.handshakeConfirmed && !h.hasOutstandingCryptoPackets() {
@ -519,32 +548,35 @@ func (h *sentPacketHandler) getPTOTimeAndSpace(now time.Time) (pto time.Time, en
} }
t := now.Add(h.getScaledPTO(false)) t := now.Add(h.getScaledPTO(false))
if h.initialPackets != nil { if h.initialPackets != nil {
return t, protocol.EncryptionInitial, true return t, protocol.EncryptionInitial
} }
return t, protocol.EncryptionHandshake, true return t, protocol.EncryptionHandshake
} }
if h.initialPackets != nil { if h.initialPackets != nil && h.initialPackets.history.HasOutstandingPackets() &&
!h.initialPackets.lastAckElicitingPacketTime.IsZero() {
encLevel = protocol.EncryptionInitial encLevel = protocol.EncryptionInitial
if t := h.initialPackets.lastAckElicitingPacketTime; !t.IsZero() { if t := h.initialPackets.lastAckElicitingPacketTime; !t.IsZero() {
pto = t.Add(h.getScaledPTO(false)) pto = t.Add(h.getScaledPTO(false))
} }
} }
if h.handshakePackets != nil && !h.handshakePackets.lastAckElicitingPacketTime.IsZero() { if h.handshakePackets != nil && h.handshakePackets.history.HasOutstandingPackets() &&
!h.handshakePackets.lastAckElicitingPacketTime.IsZero() {
t := h.handshakePackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(false)) t := h.handshakePackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(false))
if pto.IsZero() || (!t.IsZero() && t.Before(pto)) { if pto.IsZero() || (!t.IsZero() && t.Before(pto)) {
pto = t pto = t
encLevel = protocol.EncryptionHandshake encLevel = protocol.EncryptionHandshake
} }
} }
if h.handshakeConfirmed && !h.appDataPackets.lastAckElicitingPacketTime.IsZero() { if h.handshakeConfirmed && h.appDataPackets.history.HasOutstandingPackets() &&
!h.appDataPackets.lastAckElicitingPacketTime.IsZero() {
t := h.appDataPackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(true)) t := h.appDataPackets.lastAckElicitingPacketTime.Add(h.getScaledPTO(true))
if pto.IsZero() || (!t.IsZero() && t.Before(pto)) { if pto.IsZero() || (!t.IsZero() && t.Before(pto)) {
pto = t pto = t
encLevel = protocol.Encryption1RTT encLevel = protocol.Encryption1RTT
} }
} }
return pto, encLevel, true return pto, encLevel
} }
func (h *sentPacketHandler) hasOutstandingCryptoPackets() bool { func (h *sentPacketHandler) hasOutstandingCryptoPackets() bool {
@ -576,8 +608,8 @@ func (h *sentPacketHandler) setLossDetectionTimer(now time.Time) {
func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer { func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer {
// cancel the alarm if no packets are outstanding // cancel the alarm if no packets are outstanding
if h.peerCompletedAddressValidation && if h.peerCompletedAddressValidation && !h.hasOutstandingCryptoPackets() &&
!h.hasOutstandingCryptoPackets() && !h.appDataPackets.history.HasOutstandingPackets() { !h.appDataPackets.history.HasOutstandingPackets() && !h.appDataPackets.history.HasOutstandingPathProbes() {
return alarmTimer{} return alarmTimer{}
} }
@ -586,24 +618,58 @@ func (h *sentPacketHandler) lossDetectionTime(now time.Time) alarmTimer {
return alarmTimer{} return alarmTimer{}
} }
var pathProbeLossTime time.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 // early retransmit timer or time loss detection
lossTime, encLevel := h.getLossTimeAndSpace() lossTime, encLevel := h.getLossTimeAndSpace()
if !lossTime.IsZero() { if !lossTime.IsZero() && (pathProbeLossTime.IsZero() || lossTime.Before(pathProbeLossTime)) {
return alarmTimer{ return alarmTimer{
Time: lossTime, Time: lossTime,
TimerType: logging.TimerTypeACK, TimerType: logging.TimerTypeACK,
EncryptionLevel: encLevel, EncryptionLevel: encLevel,
} }
} }
ptoTime, encLevel := h.getPTOTimeAndSpace(now)
ptoTime, encLevel, ok := h.getPTOTimeAndSpace(now) if !ptoTime.IsZero() && (pathProbeLossTime.IsZero() || ptoTime.Before(pathProbeLossTime)) {
if !ok { return alarmTimer{
return alarmTimer{} Time: ptoTime,
TimerType: logging.TimerTypePTO,
EncryptionLevel: encLevel,
}
} }
return alarmTimer{ if !pathProbeLossTime.IsZero() {
Time: ptoTime, return alarmTimer{
TimerType: logging.TimerTypePTO, Time: pathProbeLossTime,
EncryptionLevel: encLevel, TimerType: logging.TimerTypePathProbe,
EncryptionLevel: encLevel,
}
}
return alarmTimer{}
}
func (h *sentPacketHandler) detectLostPathProbes(now time.Time) {
if !h.appDataPackets.history.HasOutstandingPathProbes() {
return
}
lossTime := now.Add(-pathProbePacketLossTimeout)
// RemovePathProbe cannot be called while iterating.
var lostPathProbes []*packet
h.appDataPackets.history.IteratePathProbes(func(p *packet) bool {
if !p.SendTime.After(lossTime) {
lostPathProbes = append(lostPathProbes, p)
}
return true
})
for _, p := range lostPathProbes {
for _, f := range p.Frames {
f.Handler.OnLost(f.Frame)
}
h.appDataPackets.history.RemovePathProbe(p.PacketNumber)
} }
} }
@ -626,10 +692,11 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
return false return false
} }
isRegularPacket := !p.skippedPacket && !p.isPathProbePacket
var packetLost bool var packetLost bool
if !p.SendTime.After(lostSendTime) { if !p.SendTime.After(lostSendTime) {
packetLost = true packetLost = true
if !p.skippedPacket { if isRegularPacket {
if h.logger.Debug() { if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (time threshold)", p.PacketNumber) h.logger.Debugf("\tlost packet %d (time threshold)", p.PacketNumber)
} }
@ -639,7 +706,7 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
} }
} else if pnSpace.largestAcked >= p.PacketNumber+packetThreshold { } else if pnSpace.largestAcked >= p.PacketNumber+packetThreshold {
packetLost = true packetLost = true
if !p.skippedPacket { if isRegularPacket {
if h.logger.Debug() { if h.logger.Debug() {
h.logger.Debugf("\tlost packet %d (reordering threshold)", p.PacketNumber) h.logger.Debugf("\tlost packet %d (reordering threshold)", p.PacketNumber)
} }
@ -657,7 +724,7 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
} }
if packetLost { if packetLost {
pnSpace.history.DeclareLost(p.PacketNumber) pnSpace.history.DeclareLost(p.PacketNumber)
if !p.skippedPacket { if isRegularPacket {
// the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted // the bytes in flight need to be reduced no matter if the frames in this packet will be retransmitted
h.removeFromBytesInFlight(p) h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p) h.queueFramesForRetransmission(p)
@ -675,6 +742,11 @@ func (h *sentPacketHandler) detectLostPackets(now time.Time, encLevel protocol.E
func (h *sentPacketHandler) OnLossDetectionTimeout(now time.Time) error { func (h *sentPacketHandler) OnLossDetectionTimeout(now time.Time) error {
defer h.setLossDetectionTimer(now) defer h.setLossDetectionTimer(now)
if h.handshakeConfirmed {
h.detectLostPathProbes(now)
}
earliestLossTime, encLevel := h.getLossTimeAndSpace() earliestLossTime, encLevel := h.getLossTimeAndSpace()
if !earliestLossTime.IsZero() { if !earliestLossTime.IsZero() {
if h.logger.Debug() { if h.logger.Debug() {
@ -706,11 +778,12 @@ func (h *sentPacketHandler) OnLossDetectionTimeout(now time.Time) error {
return nil return nil
} }
_, encLevel, ok := h.getPTOTimeAndSpace(now) ptoTime, encLevel := h.getPTOTimeAndSpace(now)
if !ok { if ptoTime.IsZero() {
return nil return nil
} }
if ps := h.getPacketNumberSpace(encLevel); !ps.history.HasOutstandingPackets() && !h.peerCompletedAddressValidation { ps := h.getPacketNumberSpace(encLevel)
if !ps.history.HasOutstandingPackets() && !ps.history.HasOutstandingPathProbes() && !h.peerCompletedAddressValidation {
return nil return nil
} }
h.ptoCount++ h.ptoCount++
@ -917,3 +990,27 @@ func (h *sentPacketHandler) ResetForRetry(now time.Time) {
} }
h.ptoCount = 0 h.ptoCount = 0
} }
func (h *sentPacketHandler) MigratedPath(now time.Time, initialMaxDatagramSize protocol.ByteCount) {
h.rttStats.ResetForPathMigration()
h.appDataPackets.history.Iterate(func(p *packet) bool {
h.appDataPackets.history.DeclareLost(p.PacketNumber)
if !p.skippedPacket && !p.isPathProbePacket {
h.removeFromBytesInFlight(p)
h.queueFramesForRetransmission(p)
}
return true
})
h.appDataPackets.history.IteratePathProbes(func(p *packet) bool {
h.appDataPackets.history.RemovePathProbe(p.PacketNumber)
return true
})
h.congestion = congestion.NewCubicSender(
congestion.DefaultClock{},
h.rttStats,
initialMaxDatagramSize,
true, // use Reno
h.tracer,
)
h.setLossDetectionTimer(now)
}

View file

@ -117,7 +117,7 @@ func testSentPacketHandlerSendAndAcknowledge(t *testing.T, encLevel protocol.Enc
e = protocol.Encryption0RTT e = protocol.Encryption0RTT
} }
pn := sph.PopPacketNumber(e) pn := sph.PopPacketNumber(e)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, e, protocol.ECNNon, 1200, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, e, protocol.ECNNon, 1200, false, false)
pns = append(pns, pn) pns = append(pns, pn)
} }
@ -172,7 +172,7 @@ func TestSentPacketHandlerAcknowledgeSkippedPacket(t *testing.T) {
if pn >= 1e6 { if pn >= 1e6 {
t.Fatal("expected a skipped packet number") t.Fatal("expected a skipped packet number")
} }
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.Encryption1RTT, protocol.ECNNon, 1200, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.Encryption1RTT, protocol.ECNNon, 1200, false, false)
lastPN = pn lastPN = pn
if skippedPN != protocol.InvalidPacketNumber { if skippedPN != protocol.InvalidPacketNumber {
break break
@ -216,7 +216,7 @@ func testSentPacketHandlerRTTs(t *testing.T, encLevel protocol.EncryptionLevel,
sendPacket := func(ti time.Time) protocol.PacketNumber { sendPacket := func(ti time.Time) protocol.PacketNumber {
pn := sph.PopPacketNumber(encLevel) pn := sph.PopPacketNumber(encLevel)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, encLevel, protocol.ECNNon, 1200, false) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, encLevel, protocol.ECNNon, 1200, false, false)
return pn return pn
} }
@ -318,7 +318,7 @@ func testSentPacketHandlerAmplificationLimitServer(t *testing.T, addressValidate
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
require.Equal(t, SendAny, sph.SendMode(time.Now())) require.Equal(t, SendAny, sph.SendMode(time.Now()))
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 999, false) sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 999, false, false)
if i != 3 { if i != 3 {
require.NotZero(t, sph.GetLossDetectionTimeout()) require.NotZero(t, sph.GetLossDetectionTimeout())
} }
@ -333,7 +333,7 @@ func testSentPacketHandlerAmplificationLimitServer(t *testing.T, addressValidate
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
require.Equal(t, SendAny, sph.SendMode(time.Now())) require.Equal(t, SendAny, sph.SendMode(time.Now()))
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false) sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false, false)
} }
require.Equal(t, SendNone, sph.SendMode(time.Now())) require.Equal(t, SendNone, sph.SendMode(time.Now()))
require.Zero(t, sph.GetLossDetectionTimeout()) require.Zero(t, sph.GetLossDetectionTimeout())
@ -373,7 +373,7 @@ func testSentPacketHandlerAmplificationLimitClient(t *testing.T, dropHandshake b
require.Equal(t, SendAny, sph.SendMode(time.Now())) require.Equal(t, SendAny, sph.SendMode(time.Now()))
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 999, false) sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionInitial, protocol.ECNNon, 999, false, false)
// it's not surprising that the loss detection timer is set, as this packet might be lost... // it's not surprising that the loss detection timer is set, as this packet might be lost...
require.NotZero(t, sph.GetLossDetectionTimeout()) require.NotZero(t, sph.GetLossDetectionTimeout())
// ... but it's still set after receiving an ACK for this packet, // ... but it's still set after receiving an ACK for this packet,
@ -405,7 +405,7 @@ func testSentPacketHandlerAmplificationLimitClient(t *testing.T, dropHandshake b
// receiving an ACK for a handshake packet shows that the server completed address validation // receiving an ACK for a handshake packet shows that the server completed address validation
pn = sph.PopPacketNumber(protocol.EncryptionHandshake) pn = sph.PopPacketNumber(protocol.EncryptionHandshake)
sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionHandshake, protocol.ECNNon, 999, false) sph.SentPacket(time.Now(), pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, protocol.EncryptionHandshake, protocol.ECNNon, 999, false, false)
require.NotZero(t, sph.GetLossDetectionTimeout()) require.NotZero(t, sph.GetLossDetectionTimeout())
_, err = sph.ReceivedAck(&wire.AckFrame{AckRanges: ackRanges(pn)}, protocol.EncryptionHandshake, time.Now()) _, err = sph.ReceivedAck(&wire.AckFrame{AckRanges: ackRanges(pn)}, protocol.EncryptionHandshake, time.Now())
require.NoError(t, err) require.NoError(t, err)
@ -428,7 +428,7 @@ func TestSentPacketHandlerDelayBasedLossDetection(t *testing.T) {
var packets packetTracker var packets packetTracker
sendPacket := func(ti time.Time, isPathMTUProbePacket bool) protocol.PacketNumber { sendPacket := func(ti time.Time, isPathMTUProbePacket bool) protocol.PacketNumber {
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, isPathMTUProbePacket) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, isPathMTUProbePacket, false)
return pn return pn
} }
@ -482,7 +482,7 @@ func TestSentPacketHandlerPacketBasedLossDetection(t *testing.T) {
var pns []protocol.PacketNumber var pns []protocol.PacketNumber
for range 5 { for range 5 {
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false, false)
pns = append(pns, pn) pns = append(pns, pn)
} }
@ -551,9 +551,9 @@ func testSentPacketHandlerPTO(t *testing.T, encLevel protocol.EncryptionLevel, p
pn := sph.PopPacketNumber(encLevel) pn := sph.PopPacketNumber(encLevel)
if ackEliciting { if ackEliciting {
tr.EXPECT().SetLossTimer(logging.TimerTypePTO, encLevel, gomock.Any()) tr.EXPECT().SetLossTimer(logging.TimerTypePTO, encLevel, gomock.Any())
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, encLevel, protocol.ECNNon, 1000, false) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, encLevel, protocol.ECNNon, 1000, false, false)
} else { } else {
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, nil, encLevel, protocol.ECNNon, 1000, true) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, nil, encLevel, protocol.ECNNon, 1000, true, false)
} }
return pn return pn
} }
@ -682,7 +682,7 @@ func TestSentPacketHandlerPacketNumberSpacesPTO(t *testing.T) {
sendPacket := func(ti time.Time, encLevel protocol.EncryptionLevel) protocol.PacketNumber { sendPacket := func(ti time.Time, encLevel protocol.EncryptionLevel) protocol.PacketNumber {
pn := sph.PopPacketNumber(encLevel) pn := sph.PopPacketNumber(encLevel)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, encLevel, protocol.ECNNon, 1000, false) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{{Frame: &wire.PingFrame{}}}, encLevel, protocol.ECNNon, 1000, false, false)
return pn return pn
} }
@ -779,7 +779,7 @@ func TestSentPacketHandler0RTT(t *testing.T) {
} else { } else {
frames = []Frame{{Frame: &wire.PingFrame{}}} frames = []Frame{{Frame: &wire.PingFrame{}}}
} }
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, frames, encLevel, protocol.ECNNon, 1000, false) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, frames, encLevel, protocol.ECNNon, 1000, false, false)
return pn return pn
} }
@ -836,7 +836,7 @@ func TestSentPacketHandlerCongestion(t *testing.T) {
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
bytesInFlight += 1000 bytesInFlight += 1000
cong.EXPECT().OnPacketSent(now, bytesInFlight, pn, protocol.ByteCount(1000), true) cong.EXPECT().OnPacketSent(now, bytesInFlight, pn, protocol.ByteCount(1000), true)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, i == 1) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, i == 1, false)
pns = append(pns, pn) pns = append(pns, pn)
sendTimes = append(sendTimes, now) sendTimes = append(sendTimes, now)
now = now.Add(100 * time.Millisecond) now = now.Add(100 * time.Millisecond)
@ -889,7 +889,7 @@ func TestSentPacketHandlerCongestion(t *testing.T) {
now = timeout.Add(100 * time.Millisecond) now = timeout.Add(100 * time.Millisecond)
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
cong.EXPECT().OnPacketSent(now, protocol.ByteCount(2000), pn, protocol.ByteCount(1000), true) cong.EXPECT().OnPacketSent(now, protocol.ByteCount(2000), pn, protocol.ByteCount(1000), true)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false, false)
} }
func TestSentPacketHandlerRetry(t *testing.T) { func TestSentPacketHandlerRetry(t *testing.T) {
@ -925,12 +925,12 @@ func testSentPacketHandlerRetry(t *testing.T, rtt, expectedRTT time.Duration) {
for range 2 { for range 2 {
pn := sph.PopPacketNumber(protocol.EncryptionInitial) pn := sph.PopPacketNumber(protocol.EncryptionInitial)
initialPNs = append(initialPNs, pn) initialPNs = append(initialPNs, pn)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{initialPackets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{initialPackets.NewPingFrame(pn)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false, false)
now = now.Add(100 * time.Millisecond) now = now.Add(100 * time.Millisecond)
pn = sph.PopPacketNumber(protocol.Encryption0RTT) pn = sph.PopPacketNumber(protocol.Encryption0RTT)
appDataPNs = append(appDataPNs, pn) appDataPNs = append(appDataPNs, pn)
sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{appDataPackets.NewPingFrame(pn)}, protocol.Encryption0RTT, protocol.ECNNon, 1000, false) sph.SentPacket(now, pn, protocol.InvalidPacketNumber, nil, []Frame{appDataPackets.NewPingFrame(pn)}, protocol.Encryption0RTT, protocol.ECNNon, 1000, false, false)
now = now.Add(100 * time.Millisecond) now = now.Add(100 * time.Millisecond)
} }
require.Equal(t, protocol.ByteCount(4000), sph.getBytesInFlight()) require.Equal(t, protocol.ByteCount(4000), sph.getBytesInFlight())
@ -972,7 +972,7 @@ func TestSentPacketHandlerRetryAfterPTO(t *testing.T) {
start := time.Now() start := time.Now()
now := start now := start
pn1 := sph.PopPacketNumber(protocol.EncryptionInitial) pn1 := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(now, pn1, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn1)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false) sph.SentPacket(now, pn1, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn1)}, protocol.EncryptionInitial, protocol.ECNNon, 1000, false, false)
timeout := sph.GetLossDetectionTimeout() timeout := sph.GetLossDetectionTimeout()
require.NotZero(t, timeout) require.NotZero(t, timeout)
@ -983,7 +983,7 @@ func TestSentPacketHandlerRetryAfterPTO(t *testing.T) {
// send a retransmission for the first packet // send a retransmission for the first packet
now = timeout.Add(100 * time.Millisecond) now = timeout.Add(100 * time.Millisecond)
pn2 := sph.PopPacketNumber(protocol.EncryptionInitial) pn2 := sph.PopPacketNumber(protocol.EncryptionInitial)
sph.SentPacket(now, pn2, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn2)}, protocol.EncryptionInitial, protocol.ECNNon, 900, false) sph.SentPacket(now, pn2, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn2)}, protocol.EncryptionInitial, protocol.ECNNon, 900, false, false)
const rtt = time.Second const rtt = time.Second
sph.ResetForRetry(now.Add(rtt)) sph.ResetForRetry(now.Add(rtt))
@ -1014,15 +1014,15 @@ func TestSentPacketHandlerECN(t *testing.T) {
sph.congestion = cong sph.congestion = cong
// ECN marks on non-1-RTT packets are ignored // ECN marks on non-1-RTT packets are ignored
sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.EncryptionInitial), protocol.InvalidPacketNumber, nil, nil, protocol.EncryptionInitial, protocol.ECT1, 1200, false) sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.EncryptionInitial), protocol.InvalidPacketNumber, nil, nil, protocol.EncryptionInitial, protocol.ECT1, 1200, false, false)
sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.EncryptionHandshake), protocol.InvalidPacketNumber, nil, nil, protocol.EncryptionHandshake, protocol.ECT0, 1200, false) sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.EncryptionHandshake), protocol.InvalidPacketNumber, nil, nil, protocol.EncryptionHandshake, protocol.ECT0, 1200, false, false)
sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.Encryption0RTT), protocol.InvalidPacketNumber, nil, nil, protocol.Encryption0RTT, protocol.ECNCE, 1200, false) sph.SentPacket(time.Now(), sph.PopPacketNumber(protocol.Encryption0RTT), protocol.InvalidPacketNumber, nil, nil, protocol.Encryption0RTT, protocol.ECNCE, 1200, false, false)
var packets packetTracker var packets packetTracker
sendPacket := func(ti time.Time, ecn protocol.ECN) protocol.PacketNumber { sendPacket := func(ti time.Time, ecn protocol.ECN) protocol.PacketNumber {
pn := sph.PopPacketNumber(protocol.Encryption1RTT) pn := sph.PopPacketNumber(protocol.Encryption1RTT)
ecnHandler.EXPECT().SentPacket(pn, ecn) ecnHandler.EXPECT().SentPacket(pn, ecn)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.Encryption1RTT, ecn, 1200, false) sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.Encryption1RTT, ecn, 1200, false, false)
return pn return pn
} }
@ -1096,3 +1096,131 @@ func TestSentPacketHandlerECN(t *testing.T) {
_, err = sph.ReceivedAck(&wire.AckFrame{AckRanges: ackRanges(pns[0])}, protocol.Encryption1RTT, now.Add(100*time.Millisecond)) _, err = sph.ReceivedAck(&wire.AckFrame{AckRanges: ackRanges(pns[0])}, protocol.Encryption1RTT, now.Add(100*time.Millisecond))
require.NoError(t, err) require.NoError(t, err)
} }
func TestSentPacketHandlerPathProbe(t *testing.T) {
const rtt = 10 * time.Millisecond // RTT of the original path
var rttStats utils.RTTStats
rttStats.UpdateRTT(rtt, 0)
sph := newSentPacketHandler(
0,
1200,
&rttStats,
true,
false,
protocol.PerspectiveClient,
nil,
utils.DefaultLogger,
)
sph.DropPackets(protocol.EncryptionInitial, time.Now())
sph.DropPackets(protocol.EncryptionHandshake, time.Now())
var packets packetTracker
sendPacket := func(ti time.Time, isPathProbe bool) protocol.PacketNumber {
pn := sph.PopPacketNumber(protocol.Encryption1RTT)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.Encryption1RTT, protocol.ECNNon, 1200, false, isPathProbe)
return pn
}
// send 5 packets: 2 non-probe packets, 1 probe packet, 2 non-probe packets
now := time.Now()
var pns [5]protocol.PacketNumber
pns[0] = sendPacket(now, false)
now = now.Add(rtt)
pns[1] = sendPacket(now, false)
pns[2] = sendPacket(now, true)
pathProbeTimeout := now.Add(pathProbePacketLossTimeout)
now = now.Add(rtt)
pns[3] = sendPacket(now, false)
now = now.Add(rtt)
pns[4] = sendPacket(now, false)
require.Less(t, sph.GetLossDetectionTimeout(), pathProbeTimeout)
now = now.Add(100 * time.Millisecond)
// make sure that this ACK doesn't declare the path probe packet lost
require.Greater(t, pathProbeTimeout, now)
_, err := sph.ReceivedAck(
&wire.AckFrame{AckRanges: ackRanges(pns[0], pns[3], pns[4])},
protocol.Encryption1RTT,
now,
)
require.NoError(t, err)
require.Equal(t, []protocol.PacketNumber{pns[0], pns[3], pns[4]}, packets.Acked)
// despite having been sent at the same time, the probe packet was not lost
require.Equal(t, []protocol.PacketNumber{pns[1]}, packets.Lost)
// the timeout is now based on the probe packet
timeout := sph.GetLossDetectionTimeout()
require.Equal(t, pathProbeTimeout, timeout)
require.Zero(t, sph.getBytesInFlight())
pn1 := sendPacket(now, false)
pn2 := sendPacket(now, false)
require.Equal(t, protocol.ByteCount(2400), sph.getBytesInFlight())
// send one more non-probe packet
pn := sendPacket(now, false)
// the timeout is now based on this packet
require.Less(t, sph.GetLossDetectionTimeout(), pathProbeTimeout)
_, err = sph.ReceivedAck(
&wire.AckFrame{AckRanges: ackRanges(pns[2], pn)},
protocol.Encryption1RTT,
now,
)
require.NoError(t, err)
packets.Lost = packets.Lost[:0]
sph.MigratedPath(now, 1200)
require.Zero(t, sph.getBytesInFlight())
require.Zero(t, rttStats.SmoothedRTT())
require.Equal(t, []protocol.PacketNumber{pn1, pn2}, packets.Lost)
}
func TestSentPacketHandlerPathProbeAckAndLoss(t *testing.T) {
const rtt = 10 * time.Millisecond // RTT of the original path
var rttStats utils.RTTStats
rttStats.UpdateRTT(rtt, 0)
sph := newSentPacketHandler(
0,
1200,
&rttStats,
true,
false,
protocol.PerspectiveClient,
nil,
utils.DefaultLogger,
)
sph.DropPackets(protocol.EncryptionInitial, time.Now())
sph.DropPackets(protocol.EncryptionHandshake, time.Now())
var packets packetTracker
sendPacket := func(ti time.Time, isPathProbe bool) protocol.PacketNumber {
pn := sph.PopPacketNumber(protocol.Encryption1RTT)
sph.SentPacket(ti, pn, protocol.InvalidPacketNumber, nil, []Frame{packets.NewPingFrame(pn)}, protocol.Encryption1RTT, protocol.ECNNon, 1200, false, isPathProbe)
return pn
}
now := time.Now()
pn1 := sendPacket(now, true)
t1 := now
now = now.Add(100 * time.Millisecond)
_ = sendPacket(now, true)
now = now.Add(100 * time.Millisecond)
pn3 := sendPacket(now, true)
now = now.Add(100 * time.Millisecond)
require.Equal(t, t1.Add(pathProbePacketLossTimeout), sph.GetLossDetectionTimeout())
_, err := sph.ReceivedAck(
&wire.AckFrame{AckRanges: ackRanges(pn3)},
protocol.Encryption1RTT,
now,
)
require.NoError(t, err)
require.Equal(t, []protocol.PacketNumber{pn3}, packets.Acked)
require.Empty(t, packets.Lost)
require.Equal(t, t1.Add(pathProbePacketLossTimeout), sph.GetLossDetectionTimeout())
require.NoError(t, sph.OnLossDetectionTimeout(sph.GetLossDetectionTimeout()))
require.Equal(t, []protocol.PacketNumber{pn1}, packets.Lost)
}

View file

@ -155,6 +155,42 @@ func (c *MockSentPacketHandlerGetLossDetectionTimeoutCall) DoAndReturn(f func()
return c return c
} }
// MigratedPath mocks base method.
func (m *MockSentPacketHandler) MigratedPath(now time.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(time.Time, protocol.ByteCount)) *MockSentPacketHandlerMigratedPathCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerMigratedPathCall) DoAndReturn(f func(time.Time, protocol.ByteCount)) *MockSentPacketHandlerMigratedPathCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// OnLossDetectionTimeout mocks base method. // OnLossDetectionTimeout mocks base method.
func (m *MockSentPacketHandler) OnLossDetectionTimeout(now time.Time) error { func (m *MockSentPacketHandler) OnLossDetectionTimeout(now time.Time) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -458,15 +494,15 @@ func (c *MockSentPacketHandlerSendModeCall) DoAndReturn(f func(time.Time) ackhan
} }
// SentPacket mocks base method. // SentPacket mocks base method.
func (m *MockSentPacketHandler) SentPacket(t time.Time, pn, largestAcked protocol.PacketNumber, streamFrames []ackhandler.StreamFrame, frames []ackhandler.Frame, encLevel protocol.EncryptionLevel, ecn protocol.ECN, size protocol.ByteCount, isPathMTUProbePacket bool) { func (m *MockSentPacketHandler) SentPacket(t time.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.T.Helper()
m.ctrl.Call(m, "SentPacket", t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket) m.ctrl.Call(m, "SentPacket", t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket, isPathProbePacket)
} }
// SentPacket indicates an expected call of SentPacket. // SentPacket indicates an expected call of SentPacket.
func (mr *MockSentPacketHandlerMockRecorder) SentPacket(t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket any) *MockSentPacketHandlerSentPacketCall { func (mr *MockSentPacketHandlerMockRecorder) SentPacket(t, pn, largestAcked, streamFrames, frames, encLevel, ecn, size, isPathMTUProbePacket, isPathProbePacket any) *MockSentPacketHandlerSentPacketCall {
mr.mock.ctrl.T.Helper() 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) 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} return &MockSentPacketHandlerSentPacketCall{Call: call}
} }
@ -482,13 +518,13 @@ func (c *MockSentPacketHandlerSentPacketCall) Return() *MockSentPacketHandlerSen
} }
// Do rewrite *gomock.Call.Do // Do rewrite *gomock.Call.Do
func (c *MockSentPacketHandlerSentPacketCall) Do(f func(time.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool)) *MockSentPacketHandlerSentPacketCall { func (c *MockSentPacketHandlerSentPacketCall) Do(f func(time.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool, bool)) *MockSentPacketHandlerSentPacketCall {
c.Call = c.Call.Do(f) c.Call = c.Call.Do(f)
return c return c
} }
// DoAndReturn rewrite *gomock.Call.DoAndReturn // DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockSentPacketHandlerSentPacketCall) DoAndReturn(f func(time.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool)) *MockSentPacketHandlerSentPacketCall { func (c *MockSentPacketHandlerSentPacketCall) DoAndReturn(f func(time.Time, protocol.PacketNumber, protocol.PacketNumber, []ackhandler.StreamFrame, []ackhandler.Frame, protocol.EncryptionLevel, protocol.ECN, protocol.ByteCount, bool, bool)) *MockSentPacketHandlerSentPacketCall {
c.Call = c.Call.DoAndReturn(f) c.Call = c.Call.DoAndReturn(f)
return c return c
} }

View file

@ -63,9 +63,11 @@ type TimerType uint8
const ( const (
// TimerTypeACK is the timer type for the early retransmit timer // TimerTypeACK is the timer type for the early retransmit timer
TimerTypeACK TimerType = iota TimerTypeACK TimerType = iota + 1
// TimerTypePTO is the timer type for the PTO retransmit timer // TimerTypePTO is the timer type for the PTO retransmit timer
TimerTypePTO TimerTypePTO
// TimerTypePathProbe is the timer type for the path probe retransmit timer
TimerTypePathProbe
) )
// TimeoutReason is the reason why a connection is closed // TimeoutReason is the reason why a connection is closed

View file

@ -291,6 +291,8 @@ func (t timerType) String() string {
return "ack" return "ack"
case logging.TimerTypePTO: case logging.TimerTypePTO:
return "pto" return "pto"
case logging.TimerTypePathProbe:
return "path_probe"
default: default:
return "unknown timer type" return "unknown timer type"
} }

View file

@ -94,6 +94,7 @@ func TestTimerTypeStringRepresentation(t *testing.T) {
}{ }{
{logging.TimerTypeACK, "ack"}, {logging.TimerTypeACK, "ack"},
{logging.TimerTypePTO, "pto"}, {logging.TimerTypePTO, "pto"},
{logging.TimerTypePathProbe, "path_probe"},
} }
for _, tc := range testCases { for _, tc := range testCases {