rewrite tests to capture more finwait2 receive segment expected functionality

This commit is contained in:
Patricio Whittingslow
2026-02-23 13:39:12 -03:00
parent f061cbd744
commit 87a22441e7
+94 -98
View File
@@ -590,112 +590,108 @@ func TestIssue19(t *testing.T) {
}
}
// TestRcvFinWait2_BareACK reproduces the bug where rcvFinWait2 rejects any segment
// without both FIN and ACK flags. Per RFC 9293 §3.10.7.4, FIN-WAIT-2 should:
// - Silently accept bare ACKs (duplicate ACKs, window updates)
// - Accept incoming data (remote hasn't sent FIN yet)
// - Transition to TIME-WAIT only when FIN is received
//
// The overly strict check causes connections to get stuck in FIN-WAIT-2 until timeout,
// exhausting the connection pool when multiple clients are affected.
func TestRcvFinWait2_BareACK(t *testing.T) {
var tcb tcp.ControlBlock
// TestRcvFinWait2 tests rcvFinWait2 behavior per RFC 9293 §3.10.7.4.
// In FIN-WAIT-2, the local side has sent FIN and received its ACK.
// The remote side has NOT closed yet and may still send data or ACKs.
func TestRcvFinWait2(t *testing.T) {
const windowA, windowB = 1000, 1000
const issA, issB = 100, 300
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
tcb.HelperInitRcv(issB, issB, windowB)
assertState := func(want tcp.State) {
// enterFinWait2 returns a ControlBlock in FIN-WAIT-2 with no pending flags.
// Simulates: A sent FIN|ACK, B acknowledged it, A drained pending ACK.
enterFinWait2 := func(t *testing.T) tcp.ControlBlock {
t.Helper()
if tcb.State() != want {
t.Fatalf("want state %s; got %s", want, tcb.State())
var tcb tcp.ControlBlock
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
tcb.HelperInitRcv(issB, issB, windowB)
if err := tcb.Close(); err != nil {
t.Fatal(err)
}
seg, _ := tcb.PendingSegment(0)
if err := tcb.Send(seg); err != nil {
t.Fatal(err)
}
if err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB}); err != nil {
t.Fatal(err)
}
if tcb.State() != tcp.StateFinWait2 {
t.Fatalf("setup: want FIN-WAIT-2; got %s", tcb.State())
}
// Drain pending ACK left from rcvFinWait1 so subtests start clean.
if seg, ok := tcb.PendingSegment(0); ok {
if err := tcb.Send(seg); err != nil {
t.Fatal(err)
}
}
return tcb
}
// A initiates close → sends FIN|ACK.
err := tcb.Close()
if err != nil {
t.Fatal(err)
}
seg, ok := tcb.PendingSegment(0)
if !ok || !seg.Flags.HasAll(FINACK) {
t.Fatalf("expected FIN|ACK pending; got %+v (ok=%v)", seg, ok)
}
err = tcb.Send(seg)
if err != nil {
t.Fatal(err)
}
assertState(tcp.StateFinWait1)
// Bare ACK: no state change, no pending flags generated.
t.Run("BareACK", func(t *testing.T) {
tcb := enterFinWait2(t)
err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB})
if err != nil {
t.Fatalf("bare ACK rejected: %v", err)
}
if tcb.State() != tcp.StateFinWait2 {
t.Fatalf("want FIN-WAIT-2; got %s", tcb.State())
}
checkNoPending(t, &tcb)
})
// A receives ACK for its FIN → FIN-WAIT-2.
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB})
if err != nil {
t.Fatal(err)
}
assertState(tcp.StateFinWait2)
// In-window data: ACK generated, RCV.NXT advanced by payload length.
t.Run("InWindowData", func(t *testing.T) {
tcb := enterFinWait2(t)
rcvNxtBefore := tcb.RecvNext()
const dataLen = 50
err := tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: PSHACK, WND: windowB, DATALEN: dataLen})
if err != nil {
t.Fatalf("in-window data rejected: %v", err)
}
if tcb.State() != tcp.StateFinWait2 {
t.Fatalf("want FIN-WAIT-2; got %s", tcb.State())
}
seg, ok := tcb.PendingSegment(0)
if !ok {
t.Fatal("expected ACK pending for received data")
}
if !seg.Flags.HasAll(tcp.FlagACK) {
t.Fatalf("pending flags: want ACK; got %s", seg.Flags)
}
if seg.ACK != rcvNxtBefore+dataLen {
t.Fatalf("pending ACK: want %d; got %d", rcvNxtBefore+dataLen, seg.ACK)
}
if tcb.RecvNext() != rcvNxtBefore+dataLen {
t.Fatalf("RCV.NXT: want %d; got %d", rcvNxtBefore+dataLen, tcb.RecvNext())
}
})
// A receives bare ACK (duplicate/window update) in FIN-WAIT-2.
// RFC 9293: this should be silently accepted, NOT rejected.
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB})
if err != nil {
t.Fatalf("bare ACK in FIN-WAIT-2 rejected: %v", err)
}
assertState(tcp.StateFinWait2)
// A receives FIN|ACK → TIME-WAIT.
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: FINACK, WND: windowB})
if err != nil {
t.Fatal(err)
}
assertState(tcp.StateTimeWait)
}
// TestRcvFinWait2_DataFromRemote tests that data segments are accepted in FIN-WAIT-2.
// The remote side has not sent FIN yet and may still send data per RFC 9293 §3.10.7.4.
func TestRcvFinWait2_DataFromRemote(t *testing.T) {
var tcb tcp.ControlBlock
const windowA, windowB = 1000, 1000
const issA, issB = 100, 300
tcb.HelperInitState(tcp.StateEstablished, issA, issA, windowA)
tcb.HelperInitRcv(issB, issB, windowB)
// A sends FIN|ACK → FIN-WAIT-1.
err := tcb.Close()
if err != nil {
t.Fatal(err)
}
seg, _ := tcb.PendingSegment(0)
err = tcb.Send(seg)
if err != nil {
t.Fatal(err)
}
// A receives ACK → FIN-WAIT-2.
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: tcp.FlagACK, WND: windowB})
if err != nil {
t.Fatal(err)
}
if tcb.State() != tcp.StateFinWait2 {
t.Fatalf("want FIN-WAIT-2; got %s", tcb.State())
}
// A receives data from remote (remote hasn't closed yet).
const dataLen = 50
err = tcb.Recv(tcp.Segment{SEQ: issB, ACK: issA + 1, Flags: PSHACK, WND: windowB, DATALEN: dataLen})
if err != nil {
t.Fatalf("data in FIN-WAIT-2 rejected: %v", err)
}
if tcb.State() != tcp.StateFinWait2 {
t.Fatalf("want FIN-WAIT-2 after data; got %s", tcb.State())
}
// A receives FIN|ACK → TIME-WAIT.
err = tcb.Recv(tcp.Segment{SEQ: issB + dataLen, ACK: issA + 1, Flags: FINACK, WND: windowB})
if err != nil {
t.Fatal(err)
}
if tcb.State() != tcp.StateTimeWait {
t.Fatalf("want TIME-WAIT; got %s", tcb.State())
}
// FIN (without ACK): transition to TIME-WAIT, ACK generated, RCV.NXT advanced by 1.
t.Run("FIN", func(t *testing.T) {
tcb := enterFinWait2(t)
rcvNxtBefore := tcb.RecvNext()
// Bare FIN without ACK — tests that HasAny(FlagFIN) is used, not HasAll(finack).
err := tcb.Recv(tcp.Segment{SEQ: issB, Flags: tcp.FlagFIN, WND: windowB})
if err != nil {
t.Fatalf("FIN rejected: %v", err)
}
if tcb.State() != tcp.StateTimeWait {
t.Fatalf("want TIME-WAIT; got %s", tcb.State())
}
seg, ok := tcb.PendingSegment(0)
if !ok {
t.Fatal("expected ACK pending for FIN")
}
if !seg.Flags.HasAll(tcp.FlagACK) {
t.Fatalf("pending flags: want ACK; got %s", seg.Flags)
}
if seg.ACK != rcvNxtBefore+1 {
t.Fatalf("pending ACK: want %d; got %d", rcvNxtBefore+1, seg.ACK)
}
if tcb.RecvNext() != rcvNxtBefore+1 {
t.Fatalf("RCV.NXT: want %d; got %d", rcvNxtBefore+1, tcb.RecvNext())
}
})
}
func FuzzTCBActions(f *testing.F) {