Files
lneto/tcp/handler_retransmit_test.go
T
Pat Whittingslow 270b8df043 Implement basic retransmit queue reset method (#52)
* implement basic retransmit queue reset method

* tcp: rto reset on new ack; accept ack after retransmit bugfix
2026-03-05 17:35:16 -03:00

148 lines
4.4 KiB
Go

package tcp
import (
"math/rand"
"testing"
)
// TestRTOResetsOnNewACK is a regression test for a bug where prevUNA was
// captured AFTER ControlBlock.Recv updated snd.UNA, making the "new ACK"
// condition (seg.ACK != prevUNA) always false. This caused the RTO to never
// reset per RFC 6298 §5.3, leading to exponential backoff escalation even
// when the network was healthy.
//
// The fix: capture prevUNA before calling scb.Recv in Handler.Recv.
func TestRTOResetsOnNewACK(t *testing.T) {
const mtu = 1500
const maxpackets = 3
rng := rand.New(rand.NewSource(100))
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
setupClientServer(t, rng, client, server)
var rawbuf [mtu]byte
establish(t, client, server, rawbuf[:])
// Write and send data from client.
data := []byte("hello retransmit")
n, err := client.Write(data)
if err != nil {
t.Fatal("client write:", err)
} else if n != len(data) {
t.Fatal("short write")
}
clear(rawbuf[:])
n, err = client.Send(rawbuf[:])
if err != nil {
t.Fatal("client send:", err)
}
// Simulate prior retransmissions: RTO has been backed off and nRetx > 0.
client.rto = rtoInitial * 4
client.nRetx = 2
// Server receives data and sends ACK.
err = server.Recv(rawbuf[:n])
if err != nil {
t.Fatal("server recv:", err)
}
clear(rawbuf[:])
n, err = server.Send(rawbuf[:])
if err != nil {
t.Fatal("server send ACK:", err)
}
if n == 0 {
t.Fatal("expected server to send ACK")
}
// Client receives ACK — RTO and nRetx should reset.
err = client.Recv(rawbuf[:n])
if err != nil {
t.Fatal("client recv ACK:", err)
}
if client.rto != rtoInitial {
t.Fatalf("BUG: RTO not reset on new ACK: got %d, want %d (RFC 6298 §5.3)", client.rto, rtoInitial)
}
if client.nRetx != 0 {
t.Fatalf("BUG: nRetx not reset on new ACK: got %d, want 0", client.nRetx)
}
if client.dupACKs != 0 {
t.Fatalf("dupACKs not reset on new ACK: got %d, want 0", client.dupACKs)
}
}
// TestPostRetransmitACKAccepted is a regression test for a bug where after
// Retransmit() rewound snd.NXT to snd.UNA, a valid cumulative ACK from the
// remote (acknowledging data sent pre-rewind) was rejected as "acks unsent
// data" because seg.ACK > snd.NXT.
//
// The fix: in validateIncomingSegment, when snd.NXT == snd.UNA (retransmit
// active) and seg.ACK is within the send window, accept the ACK and advance
// snd.NXT to seg.ACK.
func TestPostRetransmitACKAccepted(t *testing.T) {
const mtu = 1500
const maxpackets = 3
rng := rand.New(rand.NewSource(200))
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
setupClientServer(t, rng, client, server)
var rawbuf [mtu]byte
establish(t, client, server, rawbuf[:])
// Client writes and sends data.
data := []byte("data before rewind")
n, err := client.Write(data)
if err != nil {
t.Fatal("client write:", err)
} else if n != len(data) {
t.Fatal("short write")
}
clear(rawbuf[:])
n, err = client.Send(rawbuf[:])
if err != nil {
t.Fatal("client send:", err)
}
// Server receives data — its next ACK will acknowledge up to the
// original snd.NXT.
err = server.Recv(rawbuf[:n])
if err != nil {
t.Fatal("server recv:", err)
}
// Client triggers retransmit: snd.NXT rewound to snd.UNA.
preRewindNXT := client.scb.snd.NXT
client.triggerRetransmit()
if client.scb.snd.NXT != client.scb.snd.UNA {
t.Fatal("retransmit did not rewind snd.NXT to snd.UNA")
}
// Server sends ACK for the data it already received. seg.ACK = preRewindNXT,
// which is > client.snd.NXT (now rewound to snd.UNA).
clear(rawbuf[:])
n, err = server.Send(rawbuf[:])
if err != nil {
t.Fatal("server send ACK:", err)
}
if n == 0 {
t.Fatal("expected server to send ACK")
}
// Client receives ACK — should NOT be rejected.
err = client.Recv(rawbuf[:n])
if err != nil {
t.Fatalf("BUG: post-retransmit ACK rejected: %v\n"+
"After Retransmit() rewound snd.NXT to snd.UNA, the remote's cumulative\n"+
"ACK (for data sent pre-rewind) exceeds the rewound snd.NXT and was\n"+
"incorrectly rejected as 'acks unsent data'.", err)
}
// snd.NXT should have advanced back to where it was before the rewind.
if client.scb.snd.NXT != preRewindNXT {
t.Fatalf("snd.NXT not restored: got %d, want %d", client.scb.snd.NXT, preRewindNXT)
}
// snd.UNA should have advanced to acknowledge the data.
if client.scb.snd.UNA != preRewindNXT {
t.Fatalf("snd.UNA not advanced: got %d, want %d", client.scb.snd.UNA, preRewindNXT)
}
}