mirror of
https://github.com/soypat/lneto.git
synced 2026-08-16 12:53:26 +00:00
rewrite tests to capture more finwait2 receive segment expected functionality
This commit is contained in:
+94
-98
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user