Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions congestion/live/receive.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,24 @@ func (r *receiver) Push(pkt packet.Packet) {
}

if pkt.Header().PacketSequenceNumber.Lt(r.lastACKSequenceNumber) {
// Already acknowledged, ignoring
r.statistics.PktDrop++
r.statistics.ByteDrop += pktLen
if !pkt.Header().RetransmittedPacketFlag {
// Already acknowledged, ignoring
r.statistics.PktDrop++
r.statistics.ByteDrop += pktLen

return
return
}
// Retransmission for a sequence number that was ACKed past but
// not yet delivered. periodicACK and the delivery loop are
// separate critical sections within Tick: lastACKSequenceNumber
// can advance past a gap (e.g. when packets after the gap have
// ripe PktTsbpdTime) before lastDeliveredSequenceNumber catches
// up. A retransmission may legitimately arrive in that window.
// Allow it to flow into the out-of-order branch below, where
// it will either fill the gap or be detected as a duplicate.
// TLPKTDROP semantics are still enforced by the earlier
// Lte(lastDeliveredSequenceNumber) check, which drops anything
// already delivered or skipped past.
}

if pkt.Header().PacketSequenceNumber.Equals(r.maxSeenSequenceNumber.Inc()) {
Expand Down
91 changes: 91 additions & 0 deletions congestion/live/receive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,97 @@ func TestSkipTooLate(t *testing.T) {
require.Equal(t, []uint32{0, 1, 2, 3, 4, 8, 9}, numbers)
}

// TestRecvRetransmitPastACK verifies that a retransmission whose sequence
// number satisfies lastDeliveredSequenceNumber < seq < lastACKSequenceNumber
// is accepted and reinserted into packetList rather than dropped.
//
// This case occurs because periodicACK and the delivery loop are separate
// critical sections within Tick: periodicACK can advance lastACKSequenceNumber
// past a gap when packets after the gap have ripe PktTsbpdTime, while the
// delivery loop breaks early on the first packet whose PktTsbpdTime is still
// in the future. A retransmission arriving in this window must not be dropped
// as "already acknowledged".
//
// Without the fix, Push() returned early via the Lt(lastACKSequenceNumber)
// branch, the packet was counted as PktDrop, and the gap was never filled.
// With the fix, the retransmission flows into the out-of-order branch and is
// inserted between its predecessor and successor in packetList.
func TestRecvRetransmitPastACK(t *testing.T) {
deliveredSeq := []uint32{}
recv := mockLiveRecv(
nil,
nil,
func(p packet.Packet) {
deliveredSeq = append(deliveredSeq, p.Header().PacketSequenceNumber.Val())
},
)

addr, _ := net.ResolveIPAddr("ip", "127.0.0.1")

// Push 0..4 with PktTsbpdTime in the future so the delivery loop will
// break on the front of the list and not advance lastDeliveredSequenceNumber.
for i := range 5 {
p := packet.NewPacket(addr)
p.Header().PacketSequenceNumber = circular.New(uint32(i), packet.MAX_SEQUENCENUMBER)
p.Header().PktTsbpdTime = uint64(200 + i)
recv.Push(p)
}

// Push 6, 7, 8 (gap at 5) with PktTsbpdTime in the past so periodicACK
// will advance lastACKSequenceNumber past the gap.
for _, i := range []uint32{6, 7, 8} {
p := packet.NewPacket(addr)
p.Header().PacketSequenceNumber = circular.New(i, packet.MAX_SEQUENCENUMBER)
p.Header().PktTsbpdTime = uint64(10 + i)
recv.Push(p)
}

// Drive Tick. periodicACK walks 0..4 in order (advances ackSeq to 4),
// then accepts 6,7,8 because their PktTsbpdTime <= now (advances to 8).
// The delivery loop then breaks on packet 0 because its PktTsbpdTime
// (200) is still in the future relative to now (50).
recv.Tick(50)

require.Equal(t, uint32(8), recv.lastACKSequenceNumber.Val(),
"periodicACK should advance lastACK past the gap to 8")
require.Equal(t, uint32(0), recv.lastDeliveredSequenceNumber.Inc().Val(),
"delivery loop must not have advanced lastDelivered")

pktDropBefore := recv.Stats().PktDrop

// Retransmission for the gap arrives. seq=5 sits strictly between
// lastDeliveredSequenceNumber (uninitialized, treated as -1) and
// lastACKSequenceNumber (8). With the bug, this would be dropped as
// "already acknowledged".
p := packet.NewPacket(addr)
p.Header().PacketSequenceNumber = circular.New(5, packet.MAX_SEQUENCENUMBER)
p.Header().PktTsbpdTime = uint64(15)
p.Header().RetransmittedPacketFlag = true
recv.Push(p)

stats := recv.Stats()
require.Equal(t, pktDropBefore, stats.PktDrop,
"retransmission for an ACKed-but-not-yet-delivered sequence must not be dropped")
require.Equal(t, uint64(1), stats.PktRetrans,
"retransmission counter must still increment")

// The retransmission must be in packetList in the correct position.
found := false
for e := recv.packetList.Front(); e != nil; e = e.Next() {
if e.Value.(packet.Packet).Header().PacketSequenceNumber.Val() == 5 {
found = true
break
}
}
require.True(t, found, "retransmitted packet seq=5 must be inserted into packetList")

// Drive Tick once more once all packets are ripe. The whole sequence
// 0..8 must be delivered in order, with the late-arriving 5 in place.
recv.Tick(300)

require.Equal(t, []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8}, deliveredSeq)
}

func TestIssue67(t *testing.T) {
ackNumbers := []uint32{}
nakNumbers := [][2]uint32{}
Expand Down