diff --git a/tcp/control_rcvhandlers.go b/tcp/control_rcvhandlers.go index 8bc412a..1358f38 100644 --- a/tcp/control_rcvhandlers.go +++ b/tcp/control_rcvhandlers.go @@ -99,9 +99,14 @@ func (tcb *ControlBlock) rcvFinWait1(seg Segment) (pending Flags, err error) { } func (tcb *ControlBlock) rcvFinWait2(seg Segment) (pending Flags, err error) { - if !seg.Flags.HasAll(finack) { - return pending, errFinwaitExpectedFinack + if seg.Flags.HasAny(FlagFIN) { + tcb._state = StateTimeWait + return FlagACK, nil } - tcb._state = StateTimeWait - return FlagACK, nil + // Bare ACKs and data are valid in FIN-WAIT-2 per RFC 9293 §3.10.7.4. + // The remote side hasn't closed yet; it may still send data. + if seg.DATALEN > 0 { + return FlagACK, nil + } + return 0, nil } diff --git a/tcp/tcp_test.go b/tcp/tcp_test.go index ba933f0..69b1692 100644 --- a/tcp/tcp_test.go +++ b/tcp/tcp_test.go @@ -590,6 +590,114 @@ 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 + 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) { + t.Helper() + if tcb.State() != want { + t.Fatalf("want state %s; got %s", want, tcb.State()) + } + } + + // 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) + + // 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) + + // 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()) + } +} + func FuzzTCBActions(f *testing.F) { const mtu = 2048 const (