[PATCH net-next] selftests/net: packetdrill: check rcv_ssthresh vs scaling_ratio changes
From: Nathan Gao
Date: Fri Sep 18 2026 - 00:50:44 EST
tcp_measure_rcv_mss() recomputes tp->scaling_ratio from
skb->len/skb->truesize and updates tp->window_clamp accordingly. Commit
f5da7c45188e ("tcp: adjust rcvq_space after updating scaling ratio")
made that update go through tcp_set_window_clamp(), which also shrinks
tp->rcv_ssthresh, so every scaling_ratio dip cut the advertised window
as well. Commit 0e125ecfe20c ("tcp: do not change rcv_ssthresh in
tcp_measure_rcv_mss()") restored the plain window_clamp update.
Add a packetdrill test for it, as suggested during the review of that
fix. A fixed SO_RCVBUF pins sk_rcvbuf, so window_clamp can only move
when scaling_ratio does, and the peer announces TCP_MIN_MSS so that
icsk_ack.rcv_mss sits at its 88 byte floor. A single 89 byte segment is
then measured, and 89 bytes of payload in a ~900 byte skb is far below
the 50% that TCP_DEFAULT_SCALING_RATIO assumes, so scaling_ratio and
window_clamp drop sharply. Nothing puts the socket under memory
pressure, so rcv_ssthresh must not move at all.
The test checks the state it depends on instead of assuming it.
tcp_measure_rcv_mss() only measures segments of at least rcv_mss bytes,
so rcv_mss is checked to be 88 before the segment is sent. It is checked
to be 89 afterwards, because rcv_mss is only updated by the same code
that recomputes scaling_ratio, so this proves the segment was measured.
rcv_ssthresh is compared against the value it had right after the
handshake, not against a fixed number, so the test does not depend on
the receive buffer size or on how the architecture accounts skb memory.
Verified on net-next, where it passes for ipv4, ipv6 and
ipv4-mapped-ipv6, and on the same tree with the fix reverted, where it
fails in all three modes with rcv_ssthresh cut from 260684 to 51200.
Signed-off-by: Nathan Gao <zcgao@xxxxxxxxxx>
---
.../tcp_rcv_ssthresh_scaling_ratio.pkt | 46 +++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcv_ssthresh_scaling_ratio.pkt
diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_ssthresh_scaling_ratio.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_ssthresh_scaling_ratio.pkt
new file mode 100644
index 0000000000000..f99ecb8a224f7
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_ssthresh_scaling_ratio.pkt
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+// tcp_measure_rcv_mss() lowers tp->window_clamp when a segment carries less
+// payload per byte of memory than the ratio it had assumed. Test that it does
+// not lower tp->rcv_ssthresh as well: rcv_ssthresh is cut back under
+// memory pressure, and it only grows back slowly, through tcp_grow_window().
+
+--mss=1000
+
+`./defaults.sh`
+
+// A fixed SO_RCVBUF keeps sk_rcvbuf from moving, so window_clamp can only
+// change when the ratio does. The MSS of 88 (TCP_MIN_MSS) is the smallest
+// segment size TCP will measure, and window scaling lets rcv_ssthresh start
+// well above 64 KB. The window the peer announces does not matter here, this
+// side never sends data.
+ +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [262144], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+
+ +0 < S 0:0(0) win 65535 <mss 88,nop,nop,sackOK,nop,wscale 7>
+ +0 > S. 0:0(0) ack 1 <...>
+ +.1 < . 1:1(0) ack 1 win 65535
+
+ +0 accept(3, ..., ...) = 4
+
+// Only segments of at least rcv_mss bytes are measured. Note it down, along
+// with the rcv_ssthresh the connection starts with.
+ +0 %{
+assert tcpi_rcv_mss == 88, tcpi_rcv_mss
+ssthresh_0 = tcpi_rcv_ssthresh
+}%
+
+// One segment, one byte above that threshold. 89 bytes of payload sit in an
+// skb of about 900 bytes, far below the 50% ratio TCP assumes by default, so
+// window_clamp drops sharply.
+ +0 < P. 1:90(89) ack 1 win 65535
+ +0 > . 1:1(0) ack 90
+
+// rcv_mss is only updated by the code that recomputes the ratio, so 89 here
+// means the segment was measured. rcv_ssthresh must have been left alone.
+ +0 %{
+assert tcpi_rcv_mss == 89, tcpi_rcv_mss
+assert tcpi_rcv_ssthresh >= ssthresh_0, (tcpi_rcv_ssthresh, ssthresh_0)
+}%
base-commit: 4982d3552a3bf94de503acf93433277d08421de6
--
2.50.1