From: Kuniyuki Iwashima <kuniyu@google.com>
To: Eric Dumazet <edumazet@kernel.org>,
Neal Cardwell <ncardwell@google.com>,
"David S . Miller" <davem@davemloft.net>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>,
Kuniyuki Iwashima <kuniyu@google.com>,
Kuniyuki Iwashima <kuni1840@gmail.com>,
netdev@vger.kernel.org
Subject: [PATCH v1 net-next 8/8] selftest: packetdrill: Add test for tcp_collapse().
Date: Sat, 14 Nov 2026 04:00:51 +0000 [thread overview]
Message-ID: <20261114040101.1043546-9-kuniyu@google.com> (raw)
In-Reply-To: <20261114040101.1043546-1-kuniyu@google.com>
tcp_collapse() now does not collapse high-density skbs and a
low-density skb sandwitched by hihg-density skbs.
tcp_collapse.pkt verifies the behaviour by
1. set a large SO_RCVBUF
2. send one high-density skb with 100 bytes read()
3. send one high-density skb
4. send one low-density skb
5. send one high-density skb
6. send 30 low-density skbs
7. set a small SO_RCVBUF
8. send a small data to trigger tcp_collapse()
9. check TcpExtTCPRcvCollapsed via nstat
Without the series, the first high-density skb is collapsed.
In addition, the first low-density skb drags the following
high-density skb into collapsing, consuming more memory.
If we remove sk_forced_mem_schedule() without this series,
sk->sk_forwad_alloc becomes negative at the end of tcp_collapse().
Note that 30 segments are to make the memory savings large
enough so that the margin can absorb minor size delta of
struct sk_buff or L1_CACHE_BYTES across configs.
skb truesize, TCP_COLLAPSE_MAX_SIZE, TCP_COLLAPSE_SCALING_RATIO
depend on PAGE_SIZE being 4K, so the test is skipped if getconf
returns non-4K PAGE_SIZE.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
.../selftests/net/packetdrill/ksft_runner.sh | 9 ++
.../net/packetdrill/tcp_collapse.pkt | 135 ++++++++++++++++++
2 files changed, 144 insertions(+)
create mode 100644 tools/testing/selftests/net/packetdrill/tcp_collapse.pkt
diff --git a/tools/testing/selftests/net/packetdrill/ksft_runner.sh b/tools/testing/selftests/net/packetdrill/ksft_runner.sh
index 0a97d5ae3469..250d143bb9f7 100755
--- a/tools/testing/selftests/net/packetdrill/ksft_runner.sh
+++ b/tools/testing/selftests/net/packetdrill/ksft_runner.sh
@@ -44,6 +44,15 @@ if [ -z "$(which packetdrill)" ]; then
exit "$KSFT_SKIP"
fi
+page_size=$(grep -E '^--define="?PAGE_SIZE=' $script | sed -E 's/.*PAGE_SIZE="?([0-9]+)"?.*/\1/')
+if [[ -n "$page_size" ]]; then
+ cur_page_size=$(getconf PAGESIZE 2>/dev/null)
+ if [[ "$cur_page_size" != "$page_size" ]]; then
+ ktap_skip_all "requires PAGE_SIZE=$page_size (current: ${cur_page_size:-unknown})"
+ exit "$KSFT_SKIP"
+ fi
+fi
+
declare -a optargs
failfunc=ktap_test_fail
diff --git a/tools/testing/selftests/net/packetdrill/tcp_collapse.pkt b/tools/testing/selftests/net/packetdrill/tcp_collapse.pkt
new file mode 100644
index 000000000000..90e8be86968f
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_collapse.pkt
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Test that tcp_collapse() only collapses bloated skb runs, skips leading
+// and trailing high-density skbs, skips isolated bloated skbs when collapsing
+// them would not shrink truesize, and properly recovers receive buffer space.
+
+--mss=1000
+--define="PAGE_SIZE=4096"
+
+`./defaults.sh`
+
+// Establish a connection with a large receive buffer.
+// __sock_set_rcvbuf() doubles, so 2000000
+ +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1000000], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+
+ +0 < S 0:0(0) win 32792 <mss 1000,nop,nop,sackOK,nop,wscale 7>
+ +0 > S. 0:0(0) ack 1 win 65535 <mss 1460,nop,nop,sackOK,nop,wscale 4>
+ +0 < . 1:1(0) ack 1 win 65535
+
+ +0 accept(3, ..., ...) = 4
+
+// 1. Queue two high-density 62,000 B segments (each adds 15 pages of frag
+// truesize with enough margin across NET_SKB_PAD, L1_CACHE_BYTES, and
+// MAX_SKB_FRAGS configs).
+// Read 100 bytes from the first one so before(skb->seq, tp->copied_seq) is true.
+ +0 < P. 1:62001(62000) ack 1 win 65535
+ * > . 1:1(0) ack 62001
+ +0 read(4, ..., 100) = 100
+
+ +0 < P. 62001:124001(62000) ack 1 win 65535
+ * > . 1:1(0) ack 124001
+
+// 2. Queue an isolated bloated 1,000 B segment.
+ +0 < P. 124001:125001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 125001
+
+// 3. Queue another high-density 62,000 B segment.
+ +0 < P. 125001:187001(62000) ack 1 win 65535
+ * > . 1:1(0) ack 187001
+
+// 4. Queue a run of 30 bloated 1,000 B segments.
+ +0 < P. 187001:188001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 188001
+ +0 < P. 188001:189001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 189001
+ +0 < P. 189001:190001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 190001
+ +0 < P. 190001:191001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 191001
+ +0 < P. 191001:192001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 192001
+ +0 < P. 192001:193001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 193001
+ +0 < P. 193001:194001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 194001
+ +0 < P. 194001:195001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 195001
+ +0 < P. 195001:196001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 196001
+ +0 < P. 196001:197001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 197001
+ +0 < P. 197001:198001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 198001
+ +0 < P. 198001:199001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 199001
+ +0 < P. 199001:200001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 200001
+ +0 < P. 200001:201001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 201001
+ +0 < P. 201001:202001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 202001
+ +0 < P. 202001:203001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 203001
+ +0 < P. 203001:204001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 204001
+ +0 < P. 204001:205001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 205001
+ +0 < P. 205001:206001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 206001
+ +0 < P. 206001:207001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 207001
+ +0 < P. 207001:208001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 208001
+ +0 < P. 208001:209001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 209001
+ +0 < P. 209001:210001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 210001
+ +0 < P. 210001:211001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 211001
+ +0 < P. 211001:212001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 212001
+ +0 < P. 212001:213001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 213001
+ +0 < P. 213001:214001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 214001
+ +0 < P. 214001:215001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 215001
+ +0 < P. 215001:216001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 216001
+ +0 < P. 216001:217001(1000) ack 1 win 65535
+ * > . 1:1(0) ack 217001
+
+// 5. Queue a trailing high-density 62,000 B segment.
+ +0 < P. 217001:279001(62000) ack 1 win 65535
+ * > . 1:1(0) ack 279001
+
+// Reset SNMP counters.
+ +0 `nstat -n`
+
+// Shrink SO_RCVBUF so sk_rcvbuf (320,000) is smaller than current
+// sk_rmem_alloc (334,592 on x86_64 default), but larger than sk_rmem_alloc
+// after collapsing the 30 bloated skbs (300,288 on x86_64 default).
+ +0 setsockopt(4, SOL_SOCKET, SO_RCVBUF, [160000], 4) = 0
+
+// Inject the next 1,000 B segment with a changed window to bypass
+// header prediction. This triggers tcp_prune_queue() -> tcp_collapse().
+// Only the 30 bloated segments should be collapsed; the high-density
+// segments and the isolated bloated segment are skipped. sk_rmem_alloc
+// shrinks below sk_rcvbuf, so tcp_can_ingest() succeeds and the packet
+// is accepted.
+ +0 < P. 279001:280001(1000) ack 1 win 65534
+ * > . 1:1(0) ack 280001
+
+// Verify exactly 30 skbs were collapsed.
+ +0 `nstat -s | grep TcpExtTCPRcvCollapsed | grep -q " 30 "`
+
+// Verify no packets were dropped.
+ +0 `test -z "$(nstat -s | grep TcpExtTCPRcvQDrop)"`
+
+// Read all remaining queued data to verify data integrity.
+ +0 read(4, ..., 300000) = 279900
--
2.56.0.611.g1030f0b940-goog
prev parent reply other threads:[~2026-11-14 4:01 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 4:00 [PATCH v1 net-next 0/8] tcp: Prevent tcp_collapse() from bloating queue Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 1/8] tcp: Clean up the first loop in tcp_collapse() Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 2/8] tcp: Don't collapse high density skb Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 3/8] tcp: Stop collapsing if not bloated Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 4/8] net: Move SKB_SMALL_HEAD_CACHE_SIZE to skbuff.h Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 5/8] tcp: Skip collapsing if collapsed skb is less-dense Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 6/8] tcp: Allocate all new skb before collapsing Kuniyuki Iwashima
2026-11-14 4:00 ` [PATCH v1 net-next 7/8] tcp: Batch memory accounting in tcp_collapse() Kuniyuki Iwashima
2026-11-14 4:00 ` Kuniyuki Iwashima [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261114040101.1043546-9-kuniyu@google.com \
--to=kuniyu@google.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuni1840@gmail.com \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox