Netdev List
 help / color / mirror / Atom feed
From: Eric Dumazet <edumazet@kernel.org>
To: "David S . Miller" <davem@davemloft.net>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Kuniyuki Iwashima <kuniyu@google.com>,
	Neal Cardwell <ncardwell@google.com>
Cc: Simon Horman <horms@kernel.org>,
	netdev@vger.kernel.org, Eric Dumazet <edumazet@kernel.org>
Subject: [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG
Date: Sat, 14 Nov 2026 16:51:49 +0100	[thread overview]
Message-ID: <20261114155150.235743-5-edumazet@kernel.org> (raw)
In-Reply-To: <20261114155150.235743-1-edumazet@kernel.org>

__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


  parent reply	other threads:[~2026-11-14 15:52 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Eric Dumazet [this message]
2026-11-14 16:43   ` [PATCH net-next 4/5] net: add SKB_DROP_REASON_MEMCG 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

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=20261114155150.235743-5-edumazet@kernel.org \
    --to=edumazet@kernel.org \
    --cc=davem@davemloft.net \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=kuniyu@google.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