mirror of
https://github.com/soypat/lneto.git
synced 2026-08-14 03:43:45 +00:00
270b8df043
* implement basic retransmit queue reset method * tcp: rto reset on new ack; accept ack after retransmit bugfix
148 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|