| From 97a9063518f198ec0adb2ecb89789de342bb8283 Mon Sep 17 00:00:00 2001 |
| From: Eric Dumazet <edumazet@google.com> |
| Date: Wed, 10 Jul 2024 00:14:01 +0000 |
| Subject: tcp: avoid too many retransmit packets |
| |
| From: Eric Dumazet <edumazet@google.com> |
| |
| commit 97a9063518f198ec0adb2ecb89789de342bb8283 upstream. |
| |
| If a TCP socket is using TCP_USER_TIMEOUT, and the other peer |
| retracted its window to zero, tcp_retransmit_timer() can |
| retransmit a packet every two jiffies (2 ms for HZ=1000), |
| for about 4 minutes after TCP_USER_TIMEOUT has 'expired'. |
| |
| The fix is to make sure tcp_rtx_probe0_timed_out() takes |
| icsk->icsk_user_timeout into account. |
| |
| Before blamed commit, the socket would not timeout after |
| icsk->icsk_user_timeout, but would use standard exponential |
| backoff for the retransmits. |
| |
| Also worth noting that before commit e89688e3e978 ("net: tcp: |
| fix unexcepted socket die when snd_wnd is 0"), the issue |
| would last 2 minutes instead of 4. |
| |
| Fixes: b701a99e431d ("tcp: Add tcp_clamp_rto_to_user_timeout() helper to improve accuracy") |
| Signed-off-by: Eric Dumazet <edumazet@google.com> |
| Cc: Neal Cardwell <ncardwell@google.com> |
| Reviewed-by: Jason Xing <kerneljasonxing@gmail.com> |
| Reviewed-by: Jon Maxwell <jmaxwell37@gmail.com> |
| Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com> |
| Link: https://patch.msgid.link/20240710001402.2758273-1-edumazet@google.com |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/ipv4/tcp_timer.c | 22 +++++++++++++++++----- |
| 1 file changed, 17 insertions(+), 5 deletions(-) |
| |
| --- a/net/ipv4/tcp_timer.c |
| +++ b/net/ipv4/tcp_timer.c |
| @@ -457,22 +457,34 @@ static void tcp_fastopen_synack_timer(st |
| static bool tcp_rtx_probe0_timed_out(const struct sock *sk, |
| const struct sk_buff *skb) |
| { |
| + const struct inet_connection_sock *icsk = inet_csk(sk); |
| + u32 user_timeout = READ_ONCE(icsk->icsk_user_timeout); |
| const struct tcp_sock *tp = tcp_sk(sk); |
| - const int timeout = TCP_RTO_MAX * 2; |
| + int timeout = TCP_RTO_MAX * 2; |
| u32 rtx_delta; |
| s32 rcv_delta; |
| |
| + rtx_delta = (u32)msecs_to_jiffies(tcp_time_stamp(tp) - |
| + (tp->retrans_stamp ?: tcp_skb_timestamp(skb))); |
| + |
| + if (user_timeout) { |
| + /* If user application specified a TCP_USER_TIMEOUT, |
| + * it does not want win 0 packets to 'reset the timer' |
| + * while retransmits are not making progress. |
| + */ |
| + if (rtx_delta > user_timeout) |
| + return true; |
| + timeout = min_t(u32, timeout, msecs_to_jiffies(user_timeout)); |
| + } |
| + |
| /* Note: timer interrupt might have been delayed by at least one jiffy, |
| * and tp->rcv_tstamp might very well have been written recently. |
| * rcv_delta can thus be negative. |
| */ |
| - rcv_delta = inet_csk(sk)->icsk_timeout - tp->rcv_tstamp; |
| + rcv_delta = icsk->icsk_timeout - tp->rcv_tstamp; |
| if (rcv_delta <= timeout) |
| return false; |
| |
| - rtx_delta = (u32)msecs_to_jiffies(tcp_time_stamp(tp) - |
| - (tp->retrans_stamp ?: tcp_skb_timestamp(skb))); |
| - |
| return rtx_delta > timeout; |
| } |
| |