Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 0/5] tcp: better visibility when receive queues are full
@ 2026-11-14 15:51 Eric Dumazet
  2026-11-14 15:51 ` [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter Eric Dumazet
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

When a TCP socket can not queue an incoming packet, it is currently
hard to tell why, and how often the stack had to work to make room.

Patch 1 adds TcpExtTCPRcvCollapseSkipped, counting the collapses
skipped by the rate limit added in commit c2213d9b61c1 ("tcp: rate
limit receive queue collapsing"). Patch 2 is the packetdrill test
for this rate limit.

Patch 3 reports SKB_DROP_REASON_SOCKET_RCVBUFF instead of
SKB_DROP_REASON_PROTO_MEM when the socket is over sk_rcvbuf.

Patch 4 adds SKB_DROP_REASON_MEMCG, to tell memory cgroup charge
failures from protocol memory limits.

Patch 5 adds a packetdrill test exercising tcp_collapse() on both
the out-of-order queue and the receive queue: no existing test
was reaching its copy loop.

Eric Dumazet (5):
  tcp: add TCPRcvCollapseSkipped counter
  selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt
  tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf
  net: add SKB_DROP_REASON_MEMCG
  selftests/net: packetdrill: add tcp_rcv_collapse.pkt

 Documentation/networking/snmp_counter.rst     |   7 +
 include/net/dropreason-core.h                 |   6 +
 include/net/sock.h                            |  33 ++-
 include/uapi/linux/snmp.h                     |   1 +
 net/core/sock.c                               |  62 +++--
 net/ipv4/proc.c                               |   1 +
 net/ipv4/tcp_input.c                          |  42 ++--
 .../net/packetdrill/tcp_rcv_collapse.pkt      | 230 ++++++++++++++++++
 .../tcp_rcv_collapse_ratelimit.pkt            |  96 ++++++++
 9 files changed, 439 insertions(+), 39 deletions(-)
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcv_collapse.pkt
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcv_collapse_ratelimit.pkt

-- 
2.53.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter
  2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
@ 2026-11-14 15:51 ` Eric Dumazet
  2026-11-14 16:28   ` Kuniyuki Iwashima
  2026-11-14 15:51 ` [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt Eric Dumazet
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

Commit c2213d9b61c1 ("tcp: rate limit receive queue collapsing") rate
limits collapsing in tcp_prune_queue(): it is only attempted when at
least 1/8 of sk_rcvbuf of new memory was queued in the receive queues
since the previous collapse.

Add a TcpExtTCPRcvCollapseSkipped counter, incremented when a collapse
is skipped for this reason. A high rate can indicate a peer repeatedly
sending segments that can not be queued.

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 Documentation/networking/snmp_counter.rst | 7 +++++++
 include/uapi/linux/snmp.h                 | 1 +
 net/ipv4/proc.c                           | 1 +
 net/ipv4/tcp_input.c                      | 2 ++
 4 files changed, 11 insertions(+)

diff --git a/Documentation/networking/snmp_counter.rst b/Documentation/networking/snmp_counter.rst
index ff1e6a8ffe21..c8f62bae632f 100644
--- a/Documentation/networking/snmp_counter.rst
+++ b/Documentation/networking/snmp_counter.rst
@@ -1024,6 +1024,13 @@ this counter will be updated. It means the 'prune' fails.
 
 This counter indicates how many skbs are freed during 'collapse'.
 
+* TcpExtTCPRcvCollapseSkipped
+
+'Collapse' was skipped, because less than 1/8 of the receive buffer of
+new memory was queued since the previous 'collapse'.
+A high rate can indicate a peer repeatedly sending segments that can
+not be queued.
+
 examples
 ========
 
diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h
index 49f5640092a0..ee2900e0d90d 100644
--- a/include/uapi/linux/snmp.h
+++ b/include/uapi/linux/snmp.h
@@ -306,6 +306,7 @@ enum
 	LINUX_MIB_TCPAOKEYNOTFOUND,		/* TCPAOKeyNotFound */
 	LINUX_MIB_TCPAOGOOD,			/* TCPAOGood */
 	LINUX_MIB_TCPAODROPPEDICMPS,		/* TCPAODroppedIcmps */
+	LINUX_MIB_TCPRCVCOLLAPSESKIPPED,	/* TCPRcvCollapseSkipped */
 	__LINUX_MIB_MAX
 };
 
diff --git a/net/ipv4/proc.c b/net/ipv4/proc.c
index bfc06d1713ec..a4c53a78efc8 100644
--- a/net/ipv4/proc.c
+++ b/net/ipv4/proc.c
@@ -302,6 +302,7 @@ static const struct snmp_mib snmp4_net_list[] = {
 	SNMP_MIB_ITEM("TCPAOKeyNotFound", LINUX_MIB_TCPAOKEYNOTFOUND),
 	SNMP_MIB_ITEM("TCPAOGood", LINUX_MIB_TCPAOGOOD),
 	SNMP_MIB_ITEM("TCPAODroppedIcmps", LINUX_MIB_TCPAODROPPEDICMPS),
+	SNMP_MIB_ITEM("TCPRcvCollapseSkipped", LINUX_MIB_TCPRCVCOLLAPSESKIPPED),
 };
 
 static void icmpmsg_put_line(struct seq_file *seq, unsigned long *vals,
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index a2074a1af422..80f8f262bc44 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -6100,6 +6100,8 @@ static int tcp_prune_queue(struct sock *sk, const struct sk_buff *in_skb)
 
 		if (tcp_can_ingest(sk, in_skb))
 			return 0;
+	} else {
+		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPRCVCOLLAPSESKIPPED);
 	}
 
 	/* Collapsing did not help (or was not allowed),
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt
  2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
  2026-11-14 15:51 ` [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter Eric Dumazet
@ 2026-11-14 15:51 ` Eric Dumazet
  2026-11-14 16:34   ` Kuniyuki Iwashima
  2026-11-14 15:51 ` [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf Eric Dumazet
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

Fill the out-of-order queue of a socket with a small locked receive
buffer, then send 10 times a new segment landing last in the
out-of-order queue. Check that no collapse is attempted for these
segments (TcpExtTCPRcvCollapseSkipped), and that filling the hole
still lets the application read data.

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 .../tcp_rcv_collapse_ratelimit.pkt            | 96 +++++++++++++++++++
 1 file changed, 96 insertions(+)
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcv_collapse_ratelimit.pkt

diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse_ratelimit.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse_ratelimit.pkt
new file mode 100644
index 000000000000..498ac753e715
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse_ratelimit.pkt
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// A peer can repeatedly send an out-of-order segment that can not be
+// queued because the socket is over its receive buffer limit. Check that
+// tcp_prune_queue() does not collapse the queues for every such segment
+// (TcpExtTCPRcvCollapseSkipped), and that filling the hole still works.
+
+--mss=1000
+
+`./defaults.sh`
+
+// Small locked receive buffer: sk_rcvbuf = 20000, can not grow.
+   +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+   +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+   +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [10000], 4) = 0
+   +0 bind(3, ..., ...) = 0
+   +0 listen(3, 1) = 0
+
+   +0 < S 0:0(0) win 32792 <mss 1000,nop,wscale 7>
+   +0 > S. 0:0(0) ack 1 <mss 1460,nop,wscale 0>
+  +.1 < . 1:1(0) ack 1 win 257
+
+   +0 accept(3, ..., ...) = 4
+
+// Leave a hole at 1:201, then send small non-contiguous out-of-order
+// segments until the socket goes over its limit. Exactly when this
+// happens depends on skb->truesize, so ACKs are not checked here.
+   +0 < . 201:301(100) ack 1 win 257
+   +0 < . 401:501(100) ack 1 win 257
+   +0 < . 601:701(100) ack 1 win 257
+   +0 < . 801:901(100) ack 1 win 257
+   +0 < . 1001:1101(100) ack 1 win 257
+   +0 < . 1201:1301(100) ack 1 win 257
+   +0 < . 1401:1501(100) ack 1 win 257
+   +0 < . 1601:1701(100) ack 1 win 257
+   +0 < . 1801:1901(100) ack 1 win 257
+   +0 < . 2001:2101(100) ack 1 win 257
+   +0 < . 2201:2301(100) ack 1 win 257
+   +0 < . 2401:2501(100) ack 1 win 257
+   +0 < . 2601:2701(100) ack 1 win 257
+   +0 < . 2801:2901(100) ack 1 win 257
+   +0 < . 3001:3101(100) ack 1 win 257
+   +0 < . 3201:3301(100) ack 1 win 257
+   +0 < . 3401:3501(100) ack 1 win 257
+   +0 < . 3601:3701(100) ack 1 win 257
+   +0 < . 3801:3901(100) ack 1 win 257
+   +0 < . 4001:4101(100) ack 1 win 257
+   +0 < . 4201:4301(100) ack 1 win 257
+   +0 < . 4401:4501(100) ack 1 win 257
+   +0 < . 4601:4701(100) ack 1 win 257
+   +0 < . 4801:4901(100) ack 1 win 257
+   +0 < . 5001:5101(100) ack 1 win 257
+   +0 < . 5201:5301(100) ack 1 win 257
+   +0 < . 5401:5501(100) ack 1 win 257
+   +0 < . 5601:5701(100) ack 1 win 257
+   +0 < . 5801:5901(100) ack 1 win 257
+   +0 < . 6001:6101(100) ack 1 win 257
+   +0 < . 6201:6301(100) ack 1 win 257
+   +0 < . 6401:6501(100) ack 1 win 257
+   +0 < . 6601:6701(100) ack 1 win 257
+   +0 < . 6801:6901(100) ack 1 win 257
+   +0 < . 7001:7101(100) ack 1 win 257
+   +0 < . 7201:7301(100) ack 1 win 257
+   +0 < . 7401:7501(100) ack 1 win 257
+   +0 < . 7601:7701(100) ack 1 win 257
+   +0 < . 7801:7901(100) ack 1 win 257
+   +0 < . 8001:8101(100) ack 1 win 257
+
+   +0 `nstat -n`
+
+// A segment beyond all the previous ones, sent 10 times. It lands last
+// in the out-of-order queue, can not be queued, and must not trigger
+// a collapse: no new memory was added to the out-of-order queue.
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+
+   +0 `nstat -sz | grep -q "TcpExtPruneCalled  *10 "`
+   +0 `nstat -sz | grep -q "TcpExtTCPRcvCollapseSkipped  *10 "`
+
+// Fill the hole: in-order segments are rate limited too, so the collapse
+// is skipped again. tcp_prune_ofo_queue() frees room from the tail of the
+// out-of-order queue, and 201:301 is moved to the receive queue.
+   +0 < . 1:201(200) ack 1 win 257
+   +0 read(4, ..., 1000) = 300
+
+   +0 `nstat -sz | grep -q "TcpExtPruneCalled  *11 "`
+   +0 `nstat -sz | grep -q "TcpExtTCPRcvCollapseSkipped  *11 "`
+   +0 `nstat -sz | grep -q "TcpExtOfoPruned  *1 "`
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf
  2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
  2026-11-14 15:51 ` [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter Eric Dumazet
  2026-11-14 15:51 ` [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt Eric Dumazet
@ 2026-11-14 15:51 ` Eric Dumazet
  2026-11-14 16:36   ` Kuniyuki Iwashima
  2026-11-14 15:51 ` [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG Eric Dumazet
  2026-11-14 15:51 ` [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt Eric Dumazet
  4 siblings, 1 reply; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

tcp_data_queue() and tcp_data_queue_ofo() use SKB_DROP_REASON_PROTO_MEM
for all packets that can not be queued, whether the socket is over its
receive buffer, or the protocol memory limits (tcp_mem) are reached.

Make tcp_try_rmem_schedule() return a drop reason:

- SKB_DROP_REASON_SOCKET_RCVBUFF when the socket is over sk_rcvbuf,
  as already used by UDP and sock_queue_rcv_skb().

- SKB_DROP_REASON_PROTO_MEM when sk_rmem_schedule() fails.

This helps to identify peers repeatedly sending segments that can not
be queued, with tools like drop monitor or perf.

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 net/ipv4/tcp_input.c | 39 ++++++++++++++++++++++++---------------
 1 file changed, 24 insertions(+), 15 deletions(-)

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 80f8f262bc44..2d8f224f9bf0 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -5400,21 +5400,28 @@ static bool tcp_can_ingest(const struct sock *sk, const struct sk_buff *skb)
 	return rmem <= sk->sk_rcvbuf;
 }
 
-static int tcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb,
-				 unsigned int size)
+/* Return SKB_NOT_DROPPED_YET if @skb can be queued, or the drop reason. */
+static enum skb_drop_reason
+tcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb,
+		      unsigned int size)
 {
-	if (!tcp_can_ingest(sk, skb) ||
-	    !sk_rmem_schedule(sk, skb, size)) {
+	enum skb_drop_reason reason;
 
-		if (tcp_prune_queue(sk, skb) < 0)
-			return -1;
+	if (!tcp_can_ingest(sk, skb))
+		reason = SKB_DROP_REASON_SOCKET_RCVBUFF;
+	else if (!sk_rmem_schedule(sk, skb, size))
+		reason = SKB_DROP_REASON_PROTO_MEM;
+	else
+		return SKB_NOT_DROPPED_YET;
 
-		while (!sk_rmem_schedule(sk, skb, size)) {
-			if (!tcp_prune_ofo_queue(sk, skb))
-				return -1;
-		}
+	if (tcp_prune_queue(sk, skb) < 0)
+		return reason;
+
+	while (!sk_rmem_schedule(sk, skb, size)) {
+		if (!tcp_prune_ofo_queue(sk, skb))
+			return SKB_DROP_REASON_PROTO_MEM;
 	}
-	return 0;
+	return SKB_NOT_DROPPED_YET;
 }
 
 /* Credit the memory added to the receive queues since @rmem was sampled
@@ -5434,6 +5441,7 @@ static void tcp_collapse_credit_add(struct sock *sk, int rmem)
 static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
 {
 	struct tcp_sock *tp = tcp_sk(sk);
+	enum skb_drop_reason reason;
 	struct rb_node **p, *parent;
 	struct sk_buff *skb1;
 	u32 seq, end_seq;
@@ -5443,10 +5451,11 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
 	tcp_save_lrcv_flowlabel(sk, skb);
 	tcp_data_ecn_check(sk, skb);
 
-	if (unlikely(tcp_try_rmem_schedule(sk, skb, skb->truesize))) {
+	reason = tcp_try_rmem_schedule(sk, skb, skb->truesize);
+	if (unlikely(reason)) {
 		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFODROP);
 		READ_ONCE(sk->sk_data_ready)(sk);
-		tcp_drop_reason(sk, skb, SKB_DROP_REASON_PROTO_MEM);
+		tcp_drop_reason(sk, skb, reason);
 		return;
 	}
 	/* Sampled after tcp_try_rmem_schedule(), which can prune. */
@@ -5716,7 +5725,8 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
 
 		/* Ok. In sequence. In window. */
 queue_and_out:
-		if (tcp_try_rmem_schedule(sk, skb, skb->truesize)) {
+		reason = tcp_try_rmem_schedule(sk, skb, skb->truesize);
+		if (reason) {
 			/* TODO: maybe ratelimit these WIN 0 ACK ? */
 			inet_csk(sk)->icsk_ack.pending |=
 					(ICSK_ACK_NOMEM | ICSK_ACK_NOW);
@@ -5724,7 +5734,6 @@ static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
 			READ_ONCE(sk->sk_data_ready)(sk);
 
 			if (skb_queue_len(&sk->sk_receive_queue) && skb->len) {
-				reason = SKB_DROP_REASON_PROTO_MEM;
 				NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPRCVQDROP);
 				goto drop;
 			}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG
  2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
                   ` (2 preceding siblings ...)
  2026-11-14 15:51 ` [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf Eric Dumazet
@ 2026-11-14 15:51 ` Eric Dumazet
  2026-11-14 16:43   ` Kuniyuki Iwashima
  2026-11-14 15:51 ` [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt Eric Dumazet
  4 siblings, 1 reply; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

__sk_mem_raise_allocated() fails either because the memory cgroup charge
failed, or because protocol memory limits are reached, but callers can
not tell the difference, and TCP reports SKB_DROP_REASON_PROTO_MEM
in both cases.

Add __sk_mem_raise_allocated_reason(), __sk_mem_schedule_reason() and
sk_rmem_schedule_reason() returning SKB_NOT_DROPPED_YET on success,
SKB_DROP_REASON_MEMCG when the memcg charge failed, or
SKB_DROP_REASON_PROTO_MEM otherwise. Existing helpers become wrappers,
other callers are unchanged.

Use sk_rmem_schedule_reason() in tcp_try_rmem_schedule().

Note that memcg charges done from softirq use GFP_ATOMIC and are
forced over the limit, so on the receive path this reason is only seen
for packets processed in process context, like the socket backlog.

Tested with a receiver in a memory cgroup doing small reads while a
peer sends at full speed: kfree_skb tracepoint reports MEMCG drops.

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 include/net/dropreason-core.h |  6 ++++
 include/net/sock.h            | 33 ++++++++++++++++---
 net/core/sock.c               | 62 ++++++++++++++++++++++++-----------
 net/ipv4/tcp_input.c          | 15 +++++----
 4 files changed, 85 insertions(+), 31 deletions(-)

diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index fa2fe8990557..27bc63cbc020 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -82,6 +82,7 @@
 	FN(DEV_READY)			\
 	FN(FULL_RING)			\
 	FN(NOMEM)			\
+	FN(MEMCG)			\
 	FN(TAP_FILTER)			\
 	FN(TAP_TXFILTER)		\
 	FN(ICMP_CSUM)			\
@@ -426,6 +427,11 @@ enum skb_drop_reason {
 	SKB_DROP_REASON_FULL_RING,
 	/** @SKB_DROP_REASON_NOMEM: error due to OOM */
 	SKB_DROP_REASON_NOMEM,
+	/**
+	 * @SKB_DROP_REASON_MEMCG: socket memory could not be charged
+	 * to its memory cgroup.
+	 */
+	SKB_DROP_REASON_MEMCG,
 	/**
 	 * @SKB_DROP_REASON_TAP_FILTER: dropped by (ebpf) filter directly attached
 	 * to tun/tap, e.g., via TUNSETFILTEREBPF
diff --git a/include/net/sock.h b/include/net/sock.h
index d58d38db732d..005addcc4dc4 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -1557,7 +1557,11 @@ static inline struct inode *SOCK_INODE(struct socket *socket)
 /*
  * Functions for memory accounting
  */
+enum skb_drop_reason __sk_mem_raise_allocated_reason(struct sock *sk, int size,
+						     int amt, int kind);
 int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind);
+enum skb_drop_reason __sk_mem_schedule_reason(struct sock *sk, int size,
+					      int kind);
 int __sk_mem_schedule(struct sock *sk, int size, int kind);
 void __sk_mem_reduce_allocated(struct sock *sk, int amount);
 void __sk_mem_reclaim(struct sock *sk, int amount);
@@ -1592,16 +1596,35 @@ static inline bool sk_wmem_schedule(struct sock *sk, int size)
 	return delta <= 0 || __sk_mem_schedule(sk, delta, SK_MEM_SEND);
 }
 
-static inline bool
-__sk_rmem_schedule(struct sock *sk, int size, bool pfmemalloc)
+/* Return SKB_NOT_DROPPED_YET on success, or the drop reason. */
+static inline enum skb_drop_reason
+__sk_rmem_schedule_reason(struct sock *sk, int size, bool pfmemalloc)
 {
+	enum skb_drop_reason reason;
 	int delta;
 
 	if (!sk_has_account(sk))
-		return true;
+		return SKB_NOT_DROPPED_YET;
 	delta = size - sk->sk_forward_alloc;
-	return delta <= 0 || __sk_mem_schedule(sk, delta, SK_MEM_RECV) ||
-	       pfmemalloc;
+	if (delta <= 0)
+		return SKB_NOT_DROPPED_YET;
+	reason = __sk_mem_schedule_reason(sk, delta, SK_MEM_RECV);
+	if (reason && pfmemalloc)
+		return SKB_NOT_DROPPED_YET;
+	return reason;
+}
+
+static inline bool
+__sk_rmem_schedule(struct sock *sk, int size, bool pfmemalloc)
+{
+	return __sk_rmem_schedule_reason(sk, size, pfmemalloc) ==
+	       SKB_NOT_DROPPED_YET;
+}
+
+static inline enum skb_drop_reason
+sk_rmem_schedule_reason(struct sock *sk, const struct sk_buff *skb, int size)
+{
+	return __sk_rmem_schedule_reason(sk, size, skb_pfmemalloc(skb));
 }
 
 static inline bool
diff --git a/net/core/sock.c b/net/core/sock.c
index e529ffeaedb9..226a587c5fa5 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -3409,13 +3409,17 @@ int sk_wait_data(struct sock *sk, long *timeo, const struct sk_buff *skb)
 EXPORT_SYMBOL(sk_wait_data);
 
 /**
- *	__sk_mem_raise_allocated - increase memory_allocated
+ *	__sk_mem_raise_allocated_reason - increase memory_allocated
  *	@sk: socket
  *	@size: memory size to allocate
  *	@amt: pages to allocate
  *	@kind: allocation type
  *
- *	Similar to __sk_mem_schedule(), but does not update sk_forward_alloc.
+ *	Similar to __sk_mem_schedule_reason(), but does not update
+ *	sk_forward_alloc.
+ *
+ *	Return: SKB_NOT_DROPPED_YET on success, SKB_DROP_REASON_MEMCG if the
+ *	memcg charge failed, SKB_DROP_REASON_PROTO_MEM otherwise.
  *
  *	Unlike the globally shared limits among the sockets under same protocol,
  *	consuming the budget of a memcg won't have direct effect on other ones.
@@ -3423,8 +3427,10 @@ EXPORT_SYMBOL(sk_wait_data);
  *	whether or not to raise allocated through sk_under_memory_pressure() or
  *	its variants.
  */
-int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
+enum skb_drop_reason __sk_mem_raise_allocated_reason(struct sock *sk, int size,
+						     int amt, int kind)
 {
+	enum skb_drop_reason reason = SKB_DROP_REASON_PROTO_MEM;
 	bool memcg_enabled = false, charged = false;
 	struct proto *prot = sk->sk_prot;
 	long allocated = 0;
@@ -3437,17 +3443,19 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 	if (mem_cgroup_sk_enabled(sk)) {
 		memcg_enabled = true;
 		charged = mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge());
-		if (!charged)
+		if (!charged) {
+			reason = SKB_DROP_REASON_MEMCG;
 			goto suppress_allocation;
+		}
 	}
 
 	if (sk->sk_bypass_prot_mem)
-		return 1;
+		return SKB_NOT_DROPPED_YET;
 
 	/* Under limit. */
 	if (allocated <= sk_prot_mem_limits(sk, 0)) {
 		sk_leave_memory_pressure(sk);
-		return 1;
+		return SKB_NOT_DROPPED_YET;
 	}
 
 	/* Under pressure. */
@@ -3468,16 +3476,16 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 	 */
 	if (kind == SK_MEM_RECV) {
 		if (atomic_read(&sk->sk_rmem_alloc) < sk_get_rmem0(sk, prot))
-			return 1;
+			return SKB_NOT_DROPPED_YET;
 
 	} else { /* SK_MEM_SEND */
 		int wmem0 = sk_get_wmem0(sk, prot);
 
 		if (sk->sk_type == SOCK_STREAM) {
 			if (sk->sk_wmem_queued < wmem0)
-				return 1;
+				return SKB_NOT_DROPPED_YET;
 		} else if (refcount_read(&sk->sk_wmem_alloc) < wmem0) {
-				return 1;
+			return SKB_NOT_DROPPED_YET;
 		}
 	}
 
@@ -3489,7 +3497,7 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 		 * sense for global memory pressure.
 		 */
 		if (!sk_under_global_memory_pressure(sk))
-			return 1;
+			return SKB_NOT_DROPPED_YET;
 
 		/* Try to be fair among all the sockets under global
 		 * pressure by allowing the ones that below average
@@ -3500,7 +3508,7 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 		    sk_mem_pages(sk->sk_wmem_queued +
 				 atomic_read(&sk->sk_rmem_alloc) +
 				 sk->sk_forward_alloc))
-			return 1;
+			return SKB_NOT_DROPPED_YET;
 	}
 
 suppress_allocation:
@@ -3516,7 +3524,7 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 			if (memcg_enabled && !charged)
 				mem_cgroup_sk_charge(sk, amt,
 						     gfp_memcg_charge() | __GFP_NOFAIL);
-			return 1;
+			return SKB_NOT_DROPPED_YET;
 		}
 	}
 
@@ -3528,11 +3536,17 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
 	if (charged)
 		mem_cgroup_sk_uncharge(sk, amt);
 
-	return 0;
+	return reason;
+}
+
+int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
+{
+	return __sk_mem_raise_allocated_reason(sk, size, amt, kind) ==
+	       SKB_NOT_DROPPED_YET;
 }
 
 /**
- *	__sk_mem_schedule - increase sk_forward_alloc and memory_allocated
+ *	__sk_mem_schedule_reason - increase sk_forward_alloc and memory_allocated
  *	@sk: socket
  *	@size: memory size to allocate
  *	@kind: allocation type
@@ -3540,16 +3554,26 @@ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind)
  *	If kind is SK_MEM_SEND, it means wmem allocation. Otherwise it means
  *	rmem allocation. This function assumes that protocols which have
  *	memory_pressure use sk_wmem_queued as write buffer accounting.
+ *
+ *	Return: SKB_NOT_DROPPED_YET on success, or the drop reason.
  */
-int __sk_mem_schedule(struct sock *sk, int size, int kind)
+enum skb_drop_reason __sk_mem_schedule_reason(struct sock *sk, int size,
+					      int kind)
 {
-	int ret, amt = sk_mem_pages(size);
+	int amt = sk_mem_pages(size);
+	enum skb_drop_reason reason;
 
 	sk_forward_alloc_add(sk, amt << PAGE_SHIFT);
-	ret = __sk_mem_raise_allocated(sk, size, amt, kind);
-	if (!ret)
+	reason = __sk_mem_raise_allocated_reason(sk, size, amt, kind);
+	if (reason)
 		sk_forward_alloc_add(sk, -(amt << PAGE_SHIFT));
-	return ret;
+	return reason;
+}
+EXPORT_SYMBOL(__sk_mem_schedule_reason);
+
+int __sk_mem_schedule(struct sock *sk, int size, int kind)
+{
+	return __sk_mem_schedule_reason(sk, size, kind) == SKB_NOT_DROPPED_YET;
 }
 EXPORT_SYMBOL(__sk_mem_schedule);
 
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 2d8f224f9bf0..ede6fa9105d5 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -5407,19 +5407,20 @@ tcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb,
 {
 	enum skb_drop_reason reason;
 
-	if (!tcp_can_ingest(sk, skb))
+	if (!tcp_can_ingest(sk, skb)) {
 		reason = SKB_DROP_REASON_SOCKET_RCVBUFF;
-	else if (!sk_rmem_schedule(sk, skb, size))
-		reason = SKB_DROP_REASON_PROTO_MEM;
-	else
-		return SKB_NOT_DROPPED_YET;
+	} else {
+		reason = sk_rmem_schedule_reason(sk, skb, size);
+		if (!reason)
+			return SKB_NOT_DROPPED_YET;
+	}
 
 	if (tcp_prune_queue(sk, skb) < 0)
 		return reason;
 
-	while (!sk_rmem_schedule(sk, skb, size)) {
+	while ((reason = sk_rmem_schedule_reason(sk, skb, size))) {
 		if (!tcp_prune_ofo_queue(sk, skb))
-			return SKB_DROP_REASON_PROTO_MEM;
+			return reason;
 	}
 	return SKB_NOT_DROPPED_YET;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt
  2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
                   ` (3 preceding siblings ...)
  2026-11-14 15:51 ` [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG Eric Dumazet
@ 2026-11-14 15:51 ` Eric Dumazet
  2026-11-14 16:49   ` Kuniyuki Iwashima
  4 siblings, 1 reply; 11+ messages in thread
From: Eric Dumazet @ 2026-11-14 15:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
	Neal Cardwell
  Cc: Simon Horman, netdev, Eric Dumazet

No packetdrill test was reaching the copy loop of tcp_collapse():
TcpExtTCPRcvCollapsed stays at zero for the whole TCP suite.

Add a test using a small locked SO_RCVBUF and out-of-order segments
sent in decreasing sequence order, so that they can not be coalesced
when they are queued. Their truesize exceeds sk_rcvbuf several times,
thus all of them can only be queued if tcp_prune_queue() collapses
them.

The test covers:

- A small out-of-order range made of two skbs.

- A big out-of-order range, bigger than one page.

- The receive queue, starting with a partially read skb.

It checks that TcpExtTCPRcvCollapsed is not zero, that nothing had
to be pruned, and that all the payload can be read.

Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
 .../net/packetdrill/tcp_rcv_collapse.pkt      | 230 ++++++++++++++++++
 1 file changed, 230 insertions(+)
 create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rcv_collapse.pkt

diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse.pkt
new file mode 100644
index 000000000000..5650c5138231
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_collapse.pkt
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Exercise tcp_collapse() on the out-of-order queue and on the receive
+// queue, for small ranges and for ranges bigger than one page, and check
+// that no payload is lost.
+//
+// Out-of-order segments are sent in decreasing sequence order, so that
+// they can not be coalesced when they are queued: only tcp_collapse()
+// can merge them.
+
+--mss=1000
+
+`./defaults.sh`
+
+// Small locked receive buffer: sk_rcvbuf = 40000, can not grow.
+   +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+   +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+   +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [20000], 4) = 0
+   +0 bind(3, ..., ...) = 0
+   +0 listen(3, 1) = 0
+
+   +0 < S 0:0(0) win 32792 <mss 1000,nop,wscale 7>
+   +0 > S. 0:0(0) ack 1 <mss 1460,nop,wscale 0>
+  +.1 < . 1:1(0) ack 1 win 257
+
+   +0 accept(3, ..., ...) = 4
+
+   +0 `nstat -n`
+
+// A small isolated range (12401:12601) made of two skbs.
+// Holes are left at 1:201 and 12301:12401.
+   +0 < . 12501:12601(100) ack 1 win 257
+   +0 < . 12401:12501(100) ack 1 win 257
+
+// A big range (201:12301) made of 121 skbs: their truesize is far bigger
+// than sk_rcvbuf, the out-of-order queue must be collapsed to queue them
+// all. Exactly when this happens depends on skb->truesize, so ACKs are
+// not checked here.
+   +0 < . 12201:12301(100) ack 1 win 257
+   +0 < . 12101:12201(100) ack 1 win 257
+   +0 < . 12001:12101(100) ack 1 win 257
+   +0 < . 11901:12001(100) ack 1 win 257
+   +0 < . 11801:11901(100) ack 1 win 257
+   +0 < . 11701:11801(100) ack 1 win 257
+   +0 < . 11601:11701(100) ack 1 win 257
+   +0 < . 11501:11601(100) ack 1 win 257
+   +0 < . 11401:11501(100) ack 1 win 257
+   +0 < . 11301:11401(100) ack 1 win 257
+   +0 < . 11201:11301(100) ack 1 win 257
+   +0 < . 11101:11201(100) ack 1 win 257
+   +0 < . 11001:11101(100) ack 1 win 257
+   +0 < . 10901:11001(100) ack 1 win 257
+   +0 < . 10801:10901(100) ack 1 win 257
+   +0 < . 10701:10801(100) ack 1 win 257
+   +0 < . 10601:10701(100) ack 1 win 257
+   +0 < . 10501:10601(100) ack 1 win 257
+   +0 < . 10401:10501(100) ack 1 win 257
+   +0 < . 10301:10401(100) ack 1 win 257
+   +0 < . 10201:10301(100) ack 1 win 257
+   +0 < . 10101:10201(100) ack 1 win 257
+   +0 < . 10001:10101(100) ack 1 win 257
+   +0 < . 9901:10001(100) ack 1 win 257
+   +0 < . 9801:9901(100) ack 1 win 257
+   +0 < . 9701:9801(100) ack 1 win 257
+   +0 < . 9601:9701(100) ack 1 win 257
+   +0 < . 9501:9601(100) ack 1 win 257
+   +0 < . 9401:9501(100) ack 1 win 257
+   +0 < . 9301:9401(100) ack 1 win 257
+   +0 < . 9201:9301(100) ack 1 win 257
+   +0 < . 9101:9201(100) ack 1 win 257
+   +0 < . 9001:9101(100) ack 1 win 257
+   +0 < . 8901:9001(100) ack 1 win 257
+   +0 < . 8801:8901(100) ack 1 win 257
+   +0 < . 8701:8801(100) ack 1 win 257
+   +0 < . 8601:8701(100) ack 1 win 257
+   +0 < . 8501:8601(100) ack 1 win 257
+   +0 < . 8401:8501(100) ack 1 win 257
+   +0 < . 8301:8401(100) ack 1 win 257
+   +0 < . 8201:8301(100) ack 1 win 257
+   +0 < . 8101:8201(100) ack 1 win 257
+   +0 < . 8001:8101(100) ack 1 win 257
+   +0 < . 7901:8001(100) ack 1 win 257
+   +0 < . 7801:7901(100) ack 1 win 257
+   +0 < . 7701:7801(100) ack 1 win 257
+   +0 < . 7601:7701(100) ack 1 win 257
+   +0 < . 7501:7601(100) ack 1 win 257
+   +0 < . 7401:7501(100) ack 1 win 257
+   +0 < . 7301:7401(100) ack 1 win 257
+   +0 < . 7201:7301(100) ack 1 win 257
+   +0 < . 7101:7201(100) ack 1 win 257
+   +0 < . 7001:7101(100) ack 1 win 257
+   +0 < . 6901:7001(100) ack 1 win 257
+   +0 < . 6801:6901(100) ack 1 win 257
+   +0 < . 6701:6801(100) ack 1 win 257
+   +0 < . 6601:6701(100) ack 1 win 257
+   +0 < . 6501:6601(100) ack 1 win 257
+   +0 < . 6401:6501(100) ack 1 win 257
+   +0 < . 6301:6401(100) ack 1 win 257
+   +0 < . 6201:6301(100) ack 1 win 257
+   +0 < . 6101:6201(100) ack 1 win 257
+   +0 < . 6001:6101(100) ack 1 win 257
+   +0 < . 5901:6001(100) ack 1 win 257
+   +0 < . 5801:5901(100) ack 1 win 257
+   +0 < . 5701:5801(100) ack 1 win 257
+   +0 < . 5601:5701(100) ack 1 win 257
+   +0 < . 5501:5601(100) ack 1 win 257
+   +0 < . 5401:5501(100) ack 1 win 257
+   +0 < . 5301:5401(100) ack 1 win 257
+   +0 < . 5201:5301(100) ack 1 win 257
+   +0 < . 5101:5201(100) ack 1 win 257
+   +0 < . 5001:5101(100) ack 1 win 257
+   +0 < . 4901:5001(100) ack 1 win 257
+   +0 < . 4801:4901(100) ack 1 win 257
+   +0 < . 4701:4801(100) ack 1 win 257
+   +0 < . 4601:4701(100) ack 1 win 257
+   +0 < . 4501:4601(100) ack 1 win 257
+   +0 < . 4401:4501(100) ack 1 win 257
+   +0 < . 4301:4401(100) ack 1 win 257
+   +0 < . 4201:4301(100) ack 1 win 257
+   +0 < . 4101:4201(100) ack 1 win 257
+   +0 < . 4001:4101(100) ack 1 win 257
+   +0 < . 3901:4001(100) ack 1 win 257
+   +0 < . 3801:3901(100) ack 1 win 257
+   +0 < . 3701:3801(100) ack 1 win 257
+   +0 < . 3601:3701(100) ack 1 win 257
+   +0 < . 3501:3601(100) ack 1 win 257
+   +0 < . 3401:3501(100) ack 1 win 257
+   +0 < . 3301:3401(100) ack 1 win 257
+   +0 < . 3201:3301(100) ack 1 win 257
+   +0 < . 3101:3201(100) ack 1 win 257
+   +0 < . 3001:3101(100) ack 1 win 257
+   +0 < . 2901:3001(100) ack 1 win 257
+   +0 < . 2801:2901(100) ack 1 win 257
+   +0 < . 2701:2801(100) ack 1 win 257
+   +0 < . 2601:2701(100) ack 1 win 257
+   +0 < . 2501:2601(100) ack 1 win 257
+   +0 < . 2401:2501(100) ack 1 win 257
+   +0 < . 2301:2401(100) ack 1 win 257
+   +0 < . 2201:2301(100) ack 1 win 257
+   +0 < . 2101:2201(100) ack 1 win 257
+   +0 < . 2001:2101(100) ack 1 win 257
+   +0 < . 1901:2001(100) ack 1 win 257
+   +0 < . 1801:1901(100) ack 1 win 257
+   +0 < . 1701:1801(100) ack 1 win 257
+   +0 < . 1601:1701(100) ack 1 win 257
+   +0 < . 1501:1601(100) ack 1 win 257
+   +0 < . 1401:1501(100) ack 1 win 257
+   +0 < . 1301:1401(100) ack 1 win 257
+   +0 < . 1201:1301(100) ack 1 win 257
+   +0 < . 1101:1201(100) ack 1 win 257
+   +0 < . 1001:1101(100) ack 1 win 257
+   +0 < . 901:1001(100) ack 1 win 257
+   +0 < . 801:901(100) ack 1 win 257
+   +0 < . 701:801(100) ack 1 win 257
+   +0 < . 601:701(100) ack 1 win 257
+   +0 < . 501:601(100) ack 1 win 257
+   +0 < . 401:501(100) ack 1 win 257
+   +0 < . 301:401(100) ack 1 win 257
+   +0 < . 201:301(100) ack 1 win 257
+
+   +0 `nstat -sz | grep -q "^TcpExtTCPRcvCollapsed  *[1-9]"`
+   +0 `nstat -sz | grep -q "^TcpExtOfoPruned  *0 "`
+
+// Fill the first hole: 1:12301 is moved to the receive queue.
+// A partial read leaves a partially consumed skb at the head of the
+// receive queue.
+   +0 < . 1:201(200) ack 1 win 257
+   +0 read(4, ..., 100) = 100
+
+// More out-of-order segments (12601:17601), following the small range:
+// the receive queue has to be collapsed as well.
+   +0 < . 17501:17601(100) ack 1 win 257
+   +0 < . 17401:17501(100) ack 1 win 257
+   +0 < . 17301:17401(100) ack 1 win 257
+   +0 < . 17201:17301(100) ack 1 win 257
+   +0 < . 17101:17201(100) ack 1 win 257
+   +0 < . 17001:17101(100) ack 1 win 257
+   +0 < . 16901:17001(100) ack 1 win 257
+   +0 < . 16801:16901(100) ack 1 win 257
+   +0 < . 16701:16801(100) ack 1 win 257
+   +0 < . 16601:16701(100) ack 1 win 257
+   +0 < . 16501:16601(100) ack 1 win 257
+   +0 < . 16401:16501(100) ack 1 win 257
+   +0 < . 16301:16401(100) ack 1 win 257
+   +0 < . 16201:16301(100) ack 1 win 257
+   +0 < . 16101:16201(100) ack 1 win 257
+   +0 < . 16001:16101(100) ack 1 win 257
+   +0 < . 15901:16001(100) ack 1 win 257
+   +0 < . 15801:15901(100) ack 1 win 257
+   +0 < . 15701:15801(100) ack 1 win 257
+   +0 < . 15601:15701(100) ack 1 win 257
+   +0 < . 15501:15601(100) ack 1 win 257
+   +0 < . 15401:15501(100) ack 1 win 257
+   +0 < . 15301:15401(100) ack 1 win 257
+   +0 < . 15201:15301(100) ack 1 win 257
+   +0 < . 15101:15201(100) ack 1 win 257
+   +0 < . 15001:15101(100) ack 1 win 257
+   +0 < . 14901:15001(100) ack 1 win 257
+   +0 < . 14801:14901(100) ack 1 win 257
+   +0 < . 14701:14801(100) ack 1 win 257
+   +0 < . 14601:14701(100) ack 1 win 257
+   +0 < . 14501:14601(100) ack 1 win 257
+   +0 < . 14401:14501(100) ack 1 win 257
+   +0 < . 14301:14401(100) ack 1 win 257
+   +0 < . 14201:14301(100) ack 1 win 257
+   +0 < . 14101:14201(100) ack 1 win 257
+   +0 < . 14001:14101(100) ack 1 win 257
+   +0 < . 13901:14001(100) ack 1 win 257
+   +0 < . 13801:13901(100) ack 1 win 257
+   +0 < . 13701:13801(100) ack 1 win 257
+   +0 < . 13601:13701(100) ack 1 win 257
+   +0 < . 13501:13601(100) ack 1 win 257
+   +0 < . 13401:13501(100) ack 1 win 257
+   +0 < . 13301:13401(100) ack 1 win 257
+   +0 < . 13201:13301(100) ack 1 win 257
+   +0 < . 13101:13201(100) ack 1 win 257
+   +0 < . 13001:13101(100) ack 1 win 257
+   +0 < . 12901:13001(100) ack 1 win 257
+   +0 < . 12801:12901(100) ack 1 win 257
+   +0 < . 12701:12801(100) ack 1 win 257
+   +0 < . 12601:12701(100) ack 1 win 257
+
+// Fill the second hole and check that all the payload can be read:
+// nothing was dropped to make room.
+   +0 < . 12301:12401(100) ack 1 win 257
+   +0 read(4, ..., 30000) = 17500
+
+   +0 `nstat -sz | grep -q "^TcpExtOfoPruned  *0 "`
+   +0 `nstat -sz | grep -q "^TcpExtRcvPruned  *0 "`
+   +0 `nstat -sz | grep -q "^TcpExtTCPRcvCollapseSkipped  *0 "`
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter
  2026-11-14 15:51 ` [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter Eric Dumazet
@ 2026-11-14 16:28   ` Kuniyuki Iwashima
  0 siblings, 0 replies; 11+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 16:28 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Neal Cardwell,
	Simon Horman, netdev

On Sat, Nov 14, 2026 at 7:52 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> Commit c2213d9b61c1 ("tcp: rate limit receive queue collapsing") rate
> limits collapsing in tcp_prune_queue(): it is only attempted when at
> least 1/8 of sk_rcvbuf of new memory was queued in the receive queues
> since the previous collapse.
>
> Add a TcpExtTCPRcvCollapseSkipped counter, incremented when a collapse
> is skipped for this reason. A high rate can indicate a peer repeatedly
> sending segments that can not be queued.
>
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>

Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt
  2026-11-14 15:51 ` [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt Eric Dumazet
@ 2026-11-14 16:34   ` Kuniyuki Iwashima
  0 siblings, 0 replies; 11+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 16:34 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Neal Cardwell,
	Simon Horman, netdev

On Sat, Nov 14, 2026 at 7:52 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> Fill the out-of-order queue of a socket with a small locked receive
> buffer, then send 10 times a new segment landing last in the
> out-of-order queue. Check that no collapse is attempted for these
> segments (TcpExtTCPRcvCollapseSkipped), and that filling the hole
> still lets the application read data.
>
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>

Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>

Saving outgoing ACKs is an interesting trick.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf
  2026-11-14 15:51 ` [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf Eric Dumazet
@ 2026-11-14 16:36   ` Kuniyuki Iwashima
  0 siblings, 0 replies; 11+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 16:36 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Neal Cardwell,
	Simon Horman, netdev

On Sat, Nov 14, 2026 at 7:52 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> tcp_data_queue() and tcp_data_queue_ofo() use SKB_DROP_REASON_PROTO_MEM
> for all packets that can not be queued, whether the socket is over its
> receive buffer, or the protocol memory limits (tcp_mem) are reached.
>
> Make tcp_try_rmem_schedule() return a drop reason:
>
> - SKB_DROP_REASON_SOCKET_RCVBUFF when the socket is over sk_rcvbuf,
>   as already used by UDP and sock_queue_rcv_skb().
>
> - SKB_DROP_REASON_PROTO_MEM when sk_rmem_schedule() fails.
>
> This helps to identify peers repeatedly sending segments that can not
> be queued, with tools like drop monitor or perf.
>
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>

Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG
  2026-11-14 15:51 ` [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG Eric Dumazet
@ 2026-11-14 16:43   ` Kuniyuki Iwashima
  0 siblings, 0 replies; 11+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 16:43 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Neal Cardwell,
	Simon Horman, netdev

On Sat, Nov 14, 2026 at 7:52 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> __sk_mem_raise_allocated() fails either because the memory cgroup charge
> failed, or because protocol memory limits are reached, but callers can
> not tell the difference, and TCP reports SKB_DROP_REASON_PROTO_MEM
> in both cases.
>
> Add __sk_mem_raise_allocated_reason(), __sk_mem_schedule_reason() and
> sk_rmem_schedule_reason() returning SKB_NOT_DROPPED_YET on success,
> SKB_DROP_REASON_MEMCG when the memcg charge failed, or
> SKB_DROP_REASON_PROTO_MEM otherwise. Existing helpers become wrappers,
> other callers are unchanged.
>
> Use sk_rmem_schedule_reason() in tcp_try_rmem_schedule().
>
> Note that memcg charges done from softirq use GFP_ATOMIC and are
> forced over the limit, so on the receive path this reason is only seen
> for packets processed in process context, like the socket backlog.
>
> Tested with a receiver in a memory cgroup doing small reads while a
> peer sends at full speed: kfree_skb tracepoint reports MEMCG drops.
>
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>

Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt
  2026-11-14 15:51 ` [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt Eric Dumazet
@ 2026-11-14 16:49   ` Kuniyuki Iwashima
  0 siblings, 0 replies; 11+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 16:49 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Neal Cardwell,
	Simon Horman, netdev

On Sat, Nov 14, 2026 at 7:52 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> No packetdrill test was reaching the copy loop of tcp_collapse():
> TcpExtTCPRcvCollapsed stays at zero for the whole TCP suite.
>
> Add a test using a small locked SO_RCVBUF and out-of-order segments
> sent in decreasing sequence order, so that they can not be coalesced
> when they are queued. Their truesize exceeds sk_rcvbuf several times,
> thus all of them can only be queued if tcp_prune_queue() collapses
> them.
>
> The test covers:
>
> - A small out-of-order range made of two skbs.
>
> - A big out-of-order range, bigger than one page.
>
> - The receive queue, starting with a partially read skb.
>
> It checks that TcpExtTCPRcvCollapsed is not zero, that nothing had
> to be pruned, and that all the payload can be read.
>
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>

Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-11-14 16:49 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 15:51 [PATCH net-next 0/5] tcp: better visibility when receive queues are full Eric Dumazet
2026-11-14 15:51 ` [PATCH net-next 1/5] tcp: add TCPRcvCollapseSkipped counter Eric Dumazet
2026-11-14 16:28   ` Kuniyuki Iwashima
2026-11-14 15:51 ` [PATCH net-next 2/5] selftests/net: packetdrill: add tcp_rcv_collapse_ratelimit.pkt Eric Dumazet
2026-11-14 16:34   ` Kuniyuki Iwashima
2026-11-14 15:51 ` [PATCH net-next 3/5] tcp: use SKB_DROP_REASON_SOCKET_RCVBUFF when over sk_rcvbuf Eric Dumazet
2026-11-14 16:36   ` Kuniyuki Iwashima
2026-11-14 15:51 ` [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG Eric Dumazet
2026-11-14 16:43   ` Kuniyuki Iwashima
2026-11-14 15:51 ` [PATCH net-next 5/5] selftests/net: packetdrill: add tcp_rcv_collapse.pkt Eric Dumazet
2026-11-14 16:49   ` Kuniyuki Iwashima

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox