The branch main has been updated by rscheff: URL: https://cgit.FreeBSD.org/src/commit/?id=506e3e30a43cc04a21aa65a423bbd1cc4e0543f8
commit 506e3e30a43cc04a21aa65a423bbd1cc4e0543f8 Author: Richard Scheffenegger <[email protected]> AuthorDate: 2026-07-18 08:51:51 +0000 Commit: Richard Scheffenegger <[email protected]> CommitDate: 2026-07-18 09:24:05 +0000 tcp: Align PRR implementation with RFC 9937 - Early return when no new data is delivered - Switching from PRR-CRB to PRR-SSRB only when both SND.UNA advances and no further loss is indicated. - Accounting for sequence ranges SACKed before entering recovery in RecoverFS calculation. - Force a fast retransmit upon entering recovery when prr_out is 0 AND SndCnt is 0. - Set cwnd to ssthresh post recovery. Obtained from: mohnishhemanthkumar_gmail.com Reviewed by: rscheff, tuexen Differential Revision: https://reviews.freebsd.org/D56535 MFC after: 3 months --- sys/netinet/tcp_input.c | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c index 35a9d7633fa7..2a778687faf2 100644 --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -496,6 +496,7 @@ cc_post_recovery(struct tcpcb *tp, struct tcphdr *th) tp->sackhint.delivered_data = 0; tp->sackhint.prr_delivered = 0; tp->sackhint.prr_out = 0; + tp->snd_cwnd = tp->snd_ssthresh; } /* @@ -1496,6 +1497,7 @@ tcp_do_segment(struct tcpcb *tp, struct mbuf *m, struct tcphdr *th, struct tcpopt to; int tfo_syn; u_int maxseg = 0; + uint32_t prev_sacked_bytes = 0; bool no_data; no_data = (tlen == 0); @@ -2515,6 +2517,7 @@ tcp_do_segment(struct tcpcb *tp, struct mbuf *m, struct tcphdr *th, goto dropafterack; } if (tcp_is_sack_recovery(tp, &to)) { + prev_sacked_bytes = tp->sackhint.sacked_bytes; sack_changed = tcp_sack_doack(tp, &to, th->th_ack); if ((sack_changed != SACK_NOCHANGE) && (tp->t_flags & TF_LRD)) { @@ -2666,12 +2669,15 @@ enter_recovery: tp->sackhint.prr_delivered = imin(tp->snd_max - th->th_ack, (tp->snd_limited + 1) * maxseg); + tp->sackhint.recover_fs = imax(1, + (tp->snd_nxt - tp->snd_una) - prev_sacked_bytes + + tp->sackhint.delivered_data); } else { tp->sackhint.prr_delivered = maxseg; + tp->sackhint.recover_fs = max(1, + tp->snd_nxt - tp->snd_una); } - tp->sackhint.recover_fs = max(1, - tp->snd_nxt - tp->snd_una); } tp->snd_limited = 0; if (tcp_is_sack_recovery(tp, &to)) { @@ -3994,8 +4000,9 @@ void tcp_do_prr_ack(struct tcpcb *tp, struct tcphdr *th, struct tcpopt *to, sackstatus_t sack_changed, u_int *maxsegp) { - int snd_cnt = 0, limit = 0, del_data = 0, pipe = 0; + int snd_cnt = 0, del_data = 0, pipe = 0; u_int maxseg; + bool safe_ack; INP_WLOCK_ASSERT(tptoinpcb(tp)); @@ -4021,6 +4028,10 @@ tcp_do_prr_ack(struct tcpcb *tp, struct tcphdr *th, struct tcpopt *to, pipe = imax(0, tp->snd_max - tp->snd_una - imin(INT_MAX / 65536, tp->t_dupacks) * maxseg); } + + if (del_data == 0) + return; + tp->sackhint.prr_delivered += del_data; /* * Proportional Rate Reduction @@ -4033,25 +4044,16 @@ tcp_do_prr_ack(struct tcpcb *tp, struct tcphdr *th, struct tcpopt *to, tp->snd_ssthresh, tp->sackhint.recover_fs) - tp->sackhint.prr_out + maxseg - 1; } else { - /* - * PRR 6937bis heuristic: - * - A partial ack without SACK block beneath snd_recover - * indicates further loss. - * - An SACK scoreboard update adding a new hole indicates - * further loss, so be conservative and send at most one - * segment. - * - Prevent ACK splitting attacks, by being conservative - * when no new data is acked. - */ - if ((sack_changed == SACK_NEWLOSS) || (del_data == 0)) { - limit = tp->sackhint.prr_delivered - - tp->sackhint.prr_out; - } else { - limit = imax(tp->sackhint.prr_delivered - - tp->sackhint.prr_out, del_data) + - maxseg; + safe_ack = SEQ_GT(th->th_ack, tp->snd_una) && (sack_changed != SACK_NEWLOSS); + snd_cnt = imax(tp->sackhint.prr_delivered - tp->sackhint.prr_out, del_data); + if (safe_ack) { + snd_cnt += maxseg; } - snd_cnt = imin((tp->snd_ssthresh - pipe), limit); + snd_cnt = imin(tp->snd_ssthresh - pipe, snd_cnt); + } + + if (tp->sackhint.prr_out == 0 && snd_cnt == 0) { + snd_cnt = maxseg; } snd_cnt = imax(snd_cnt, 0) / maxseg; /*
