tcp: retransmit logic rollback (#61)

* tcp: remove retransmit logic entirely; add duplicate ack counting to ControlBlock

* retransmit implemented in nice simple straightforward way

* narrow down retransmission cases

* remove old timing tests

* add ControlBlock retransmit test

* add failing handler test

* reworking payload length semantic meaning in code

* fix establish conn logic

* clean up tests and add TCB dupack generation and test it

* catch pending retransmit satisfy in test

* add fuzz test for control block

* bugfix: be more strict in what is considered dupack

* add IncomingIsDupACK docs

* limit queue of retransmits

* protect retransmit overflow from incorrectly updating nxt
This commit is contained in:
Pat Whittingslow
2026-03-26 11:48:02 -03:00
committed by GitHub
parent abb114fc31
commit 64b2647a5e
12 changed files with 508 additions and 889 deletions
+20 -41
View File
@@ -45,9 +45,6 @@ type ringidx struct {
seq Value
// size is the size of the packet in bytes.
size Size
// sentAt is the time in milliseconds when this packet was first sent.
// Used for RTO detection per RFC 6298 §5.
sentAt uint32
}
// Reset resets the RingTx's internal state to use buf as the main ring buffer and creates or reuses
@@ -118,20 +115,31 @@ func (rtx *ringTx) Write(b []byte) (n int, err error) {
}
// MakePacket reads from the unsent data ring buffer and generates a new packet segment.
// It fails if the sent packet queue is full. sentAt is the current time in milliseconds,
// stamped on the packet for RTO detection per RFC 6298 §5.1.
func (rtx *ringTx) MakePacket(b []byte, currentSeq Value, sentAt uint32) (int, error) {
// It fails if the sent packet queue is full.
func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
free := rtx.slist.Free()
if free == 0 {
return 0, lneto.ErrBufferFull
}
endSeq, ok := rtx.sentEndSeq()
if ok && currentSeq.LessThan(endSeq) {
// maybe retransmit. Look for exact match.
for i := range rtx.slist.pkts {
pkt := &rtx.slist.pkts[i]
if pkt.seq == currentSeq {
// This packet to be retransmit.
data := rtx.ring(pkt.off, pkt.end)
return data.Read(b)
}
}
internal.LogAttrs(nil, slog.LevelError, "txqueue:seq<endseq", slog.Uint64("seq", uint64(currentSeq)), slog.Uint64("endseq", uint64(endSeq)))
return 0, lneto.ErrBug
}
// Reading unsent ring consumes unsent and converts it to "sent".
unsent, _ := rtx.unsentRing()
if unsent.IsEmpty() {
return 0, nil // No data to send.
}
oldUnsentOff := unsent.Off
n, err := unsent.Read(b)
if err != nil {
@@ -141,7 +149,7 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value, sentAt uint32) (int, e
// Start of buffer will be SENT, end of buffer will be UNSENT(or empty).
// Packet generated has offset at old unsentOff.
size := rtx.Size()
pkt := rtx.slist.AddPacket(n, oldUnsentOff, size, currentSeq, sentAt)
pkt := rtx.slist.AddPacket(n, oldUnsentOff, size, currentSeq)
if pkt.off != oldUnsentOff || pkt.end != addEnd(pkt.off, n, size) {
panic("invalid generated packet")
}
@@ -233,34 +241,6 @@ func (rtx *ringTx) RetransmitFromUNA() {
rtx.slist.Reset(cap(rtx.slist.pkts), unaSeq)
}
// RecoveryACK processes a cumulative ACK that covers data sent before a
// retransmit rewind. After RetransmitFromUNA merged sent→unsent and cleared
// the sentlist, a recovery ACK may exceed what's currently in the sentlist.
// This method acks any sentlist entries, then skips unsent bytes that were
// implicitly acknowledged (they were received by the remote before the rewind).
func (rtx *ringTx) RecoveryACK(ack Value) {
size := rtx.Size()
// First, ack everything in the sentlist (if any packets were re-sent).
if newest := rtx.slist.Newest(); newest != nil {
rtx.slist.RecvAck(newest.endSeq(), size)
}
rtx.sentoff = 0
rtx.sentend = 0
// Skip unsent data that was implicitly acked. The sequence of the first
// unsent byte is slist.ssn (the end-seq of the last acked packet).
excess := int32(ack - rtx.slist.ssn)
if excess > 0 && rtx.unsentend != 0 {
rtx.unsentoff = addOff(rtx.unsentoff, int(excess), size)
if rtx.unsentoff == rtx.unsentend {
rtx.unsentoff = 0
rtx.unsentend = 0
}
}
rtx.slist.Reset(cap(rtx.slist.pkts), ack)
rtx.consolidateBufs()
}
func (rtx *ringTx) consolidateBufs() {
unsentEmpty := rtx.unsentend == 0
sentEmpty := rtx.sentend == 0
@@ -351,7 +331,7 @@ func (sl *sentlist) Free() int {
return cap(sl.pkts) - len(sl.pkts)
}
func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value, sentAt uint32) *ringidx {
func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value) *ringidx {
free := sl.Free()
if free == 0 {
panic("pkt buffer full")
@@ -361,11 +341,10 @@ func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value, sentAt uint3
panic("new sent packet offset must match last sent packet end")
}
sl.pkts = append(sl.pkts, ringidx{
off: off,
end: addEnd(off, datalen, bufsize),
seq: seq,
size: Size(datalen),
sentAt: sentAt,
off: off,
end: addEnd(off, datalen, bufsize),
seq: seq,
size: Size(datalen),
})
return &sl.pkts[len(sl.pkts)-1]
}