When a TCP connection is in FIN-WAIT-1 state with the FIN packet blocked in the send buffer, and the peer continuously sends zero-window advertisements, the current implementation reset the zero-window probe timer while maintaining the current `icsk->icsk_backoff`, causing the connection to remain permanently in FIN-WAIT-1 state. Reproduce conditions: 1. Peer's receive window is full and actively sending continuous zero window advertisements. 2. Local FIN packet is blocked in send buffer due to peer's zero-window. 3. Local socket has been closed (entered orphan state). The root cause lies in the tcp_ack_probe() function: when receiving a zero-window ACK, - It reset the probe timer while keeping the current `icsk->icsk_backoff`. - This would result in the condition `icsk->icsk_backoff >= max_probes` false. - Orphaned socket cannot be set to close. This patch modifies the tcp_ack_probe() logic: when the socket is dead, upon receiving a zero-window packet, instead of resetting the probe timer, we maintain the current timer, ensuring the probe interval grows according to 'icsk->icsk_backoff', thus causing the zero-window probe timer to eventually timeout and close the socket. Signed-off-by: HaiYang Zhong --- net/ipv4/tcp_input.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 71b76e98371a..22fc82cb6b73 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -3440,6 +3440,8 @@ static void tcp_ack_probe(struct sock *sk) } else { unsigned long when = tcp_probe0_when(sk, tcp_rto_max(sk)); + if (sock_flag(sk, SOCK_DEAD) && icsk->icsk_backoff != 0) + return; when = tcp_clamp_probe0_to_user_timeout(sk, when); tcp_reset_xmit_timer(sk, ICSK_TIME_PROBE0, when, true); } -- 2.43.7 Move the packetdrill test to the packetdrill directory and shorten the test duration. In the previous packetdrill test script, the long duration was due to presenting the entire zero-window probe backoff process. The test has been modified to only observe the first few packets to shorten the test time while still effectively verifying the fix. - Moved test to tools/testing/selftests/net/packetdrill/ - Reduced test duration from 360+ seconds to under 4 seconds Signed-off-by: HaiYang Zhong --- .../packetdrill/tcp_fin_wait1_zero_window.pkt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_fin_wait1_zero_window.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_fin_wait1_zero_window.pkt b/tools/testing/selftests/net/packetdrill/tcp_fin_wait1_zero_window.pkt new file mode 100644 index 000000000000..854ede56e7dd --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_fin_wait1_zero_window.pkt @@ -0,0 +1,34 @@ +// Test for permanent FIN-WAIT-1 state with continuous zero-window advertisements +// Author: HaiYang Zhong + + +0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 +0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 +0.000 bind(3, ..., ...) = 0 +0.000 listen(3, 1) = 0 + +0.100 < S 0:0(0) win 65535 +0.100 > S. 0:0(0) ack 1 +0.100 < . 1:1(0) ack 1 win 65535 +0.100 accept(3, ..., ...) = 4 + +// Send data to fill receive window +0.200 write(4, ..., 5) = 5 +0.200 > P. 1:6(5) ack 1 + +// Advertise zero-window +0.200 < . 1:1(0) ack 6 win 0 + +// Application closes connection, sends FIN (but blocked by zero window) +0.200 close(4) = 0 + +//Send zero-window probe packet ++0.200 > . 5:5(0) ack 1 ++0.400 > . 5:5(0) ack 1 ++0.800 > . 5:5(0) ack 1 + ++1.000 < . 1:1(0) ack 6 win 0 + +// Without fix: This probe won't match - timer was reset, probe will be sent 2.600s after the previous probe +// With fix: This probe matches - exponential backoff continues (1.600s after previous probe) ++0.600~+0.700 > . 5:5(0) ack 1 -- 2.43.7