git: 506e3e30a43c - main - tcp: Align PRR implementation with RFC 9937
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 18 Jul 2026 09:24:23 UTC
The branch main has been updated by rscheff:
URL: https://cgit.FreeBSD.org/src/commit/?id=506e3e30a43cc04a21aa65a423bbd1cc4e0543f8
commit 506e3e30a43cc04a21aa65a423bbd1cc4e0543f8
Author: Richard Scheffenegger <rscheff@FreeBSD.org>
AuthorDate: 2026-07-18 08:51:51 +0000
Commit: Richard Scheffenegger <rscheff@FreeBSD.org>
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;
/*