* [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