Skip to content

Commit 72d39b8

Browse files
nealcardwellgregkh
authored andcommitted
tcp: fix incorrect undo caused by DSACK of TLP retransmit
[ Upstream commit 0ec986e ] Loss recovery undo_retrans bookkeeping had a long-standing bug where a DSACK from a spurious TLP retransmit packet could cause an erroneous undo of a fast recovery or RTO recovery that repaired a single really-lost packet (in a sequence range outside that of the TLP retransmit). Basically, because the loss recovery state machine didn't account for the fact that it sent a TLP retransmit, the DSACK for the TLP retransmit could erroneously be implicitly be interpreted as corresponding to the normal fast recovery or RTO recovery retransmit that plugged a real hole, thus resulting in an improper undo. For example, consider the following buggy scenario where there is a real packet loss but the congestion control response is improperly undone because of this bug: + send packets P1, P2, P3, P4 + P1 is really lost + send TLP retransmit of P4 + receive SACK for original P2, P3, P4 + enter fast recovery, fast-retransmit P1, increment undo_retrans to 1 + receive DSACK for TLP P4, decrement undo_retrans to 0, undo (bug!) + receive cumulative ACK for P1-P4 (fast retransmit plugged real hole) The fix: when we initialize undo machinery in tcp_init_undo(), if there is a TLP retransmit in flight, then increment tp->undo_retrans so that we make sure that we receive a DSACK corresponding to the TLP retransmit, as well as DSACKs for all later normal retransmits, before triggering a loss recovery undo. Note that we also have to move the line that clears tp->tlp_high_seq for RTO recovery, so that upon RTO we remember the tp->tlp_high_seq value until tcp_init_undo() and clear it only afterward. Also note that the bug dates back to the original 2013 TLP implementation, commit 6ba8a3b ("tcp: Tail loss probe (TLP)"). However, this patch will only compile and work correctly with kernels that have tp->tlp_retrans, which was added only in v5.8 in 2020 in commit 76be93f ("tcp: allow at most one TLP probe per flight"). So we associate this fix with that later commit. Fixes: 76be93f ("tcp: allow at most one TLP probe per flight") Signed-off-by: Neal Cardwell <ncardwell@google.com> Reviewed-by: Eric Dumazet <edumazet@google.com> Cc: Yuchung Cheng <ycheng@google.com> Cc: Kevin Yang <yyd@google.com> Link: https://patch.msgid.link/20240703171246.1739561-1-ncardwell.sw@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent f6f6fdc commit 72d39b8

File tree

2 files changed

+10
-3
lines changed

2 files changed

+10
-3
lines changed

net/ipv4/tcp_input.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2101,8 +2101,16 @@ void tcp_clear_retrans(struct tcp_sock *tp)
21012101
static inline void tcp_init_undo(struct tcp_sock *tp)
21022102
{
21032103
tp->undo_marker = tp->snd_una;
2104+
21042105
/* Retransmission still in flight may cause DSACKs later. */
2105-
tp->undo_retrans = tp->retrans_out ? : -1;
2106+
/* First, account for regular retransmits in flight: */
2107+
tp->undo_retrans = tp->retrans_out;
2108+
/* Next, account for TLP retransmits in flight: */
2109+
if (tp->tlp_high_seq && tp->tlp_retrans)
2110+
tp->undo_retrans++;
2111+
/* Finally, avoid 0, because undo_retrans==0 means "can undo now": */
2112+
if (!tp->undo_retrans)
2113+
tp->undo_retrans = -1;
21062114
}
21072115

21082116
static bool tcp_is_rack(const struct sock *sk)
@@ -2181,6 +2189,7 @@ void tcp_enter_loss(struct sock *sk)
21812189

21822190
tcp_set_ca_state(sk, TCP_CA_Loss);
21832191
tp->high_seq = tp->snd_nxt;
2192+
tp->tlp_high_seq = 0;
21842193
tcp_ecn_queue_cwr(tp);
21852194

21862195
/* F-RTO RFC5682 sec 3.1 step 1: retransmit SND.UNA if no previous

net/ipv4/tcp_timer.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,6 @@ void tcp_retransmit_timer(struct sock *sk)
496496
if (WARN_ON_ONCE(!skb))
497497
return;
498498

499-
tp->tlp_high_seq = 0;
500-
501499
if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
502500
!((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
503501
/* Receiver dastardly shrinks window. Our retransmits

0 commit comments

Comments
 (0)