[PATCH net v2 2/2] mptcp: diag: bound listener bucket lock hold
From: Zihan Xi
Date: Tue Sep 01 2026 - 09:19:48 EST
MPTCP listener diag dumping reuses sk_diag_dump(), which executes
inet_diag_bc_sk() before filling the netlink reply. The listener walk in
mptcp_diag_dump_listeners() currently performs that work while holding the
listener bucket lock.
The time spent under the listener bucket lock can therefore grow with the
number of sockets visited and with per-socket dump work. The resume state
also requires later batches to revisit the bucket prefix.
Fix this by collecting only referenced listener sockets while holding the
bucket lock. After dropping it, re-check the listener properties, obtain
the parent MPTCP socket reference, and call sk_diag_dump(). Keep a
referenced cursor so later batches resume after the previous listener
instead of rescanning the bucket head. Validate a cursor against the
current listener bucket and TCP_LISTEN state before resuming from it.
After dropping the lock, read icsk_ulp_data with rcu_dereference().
Fixes: 4fa39b701ce9 ("mptcp: listen diag dump support")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Vega <vega@xxxxxxxxxx>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@xxxxxxxxxx>
---
changes in v2:
- Rebased onto net commit e2a6641e3bfd (2026-08-27).
- Added current-bucket cursor validation and safe restart.
- Reject MPTCP listener cursors unless the socket is still TCP_LISTEN.
- Read icsk_ulp_data with rcu_dereference() after dropping the listener
lock.
- Moved INET_DIAG_DUMP_CURSOR_MPTCP_LISTEN into this patch.
- Moved MPTCP_DIAG_BULK_SZ below the includes and sorted new local
declarations reverse xmas tree.
- Refreshed the reviewed PoC and decoded crash-log artifacts.
- v1 Link: https://lore.kernel.org/all/cover.1785307984.git.zihanx@xxxxxxxxxx/
include/linux/inet_diag.h | 1 +
net/mptcp/mptcp_diag.c | 124 +++++++++++++++++++++++++++-----------
2 files changed, 89 insertions(+), 36 deletions(-)
diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 6ccd32bc48f9..4859e77a28c7 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -39,6 +39,7 @@ enum inet_diag_dump_cursor_type {
INET_DIAG_DUMP_CURSOR_TCP_LISTEN,
INET_DIAG_DUMP_CURSOR_TCP_BIND,
INET_DIAG_DUMP_CURSOR_TCP_EHASH,
+ INET_DIAG_DUMP_CURSOR_MPTCP_LISTEN,
};
struct inet_diag_dump_data {
diff --git a/net/mptcp/mptcp_diag.c b/net/mptcp/mptcp_diag.c
index 136c2d05c0ee..37b33ea5d79d 100644
--- a/net/mptcp/mptcp_diag.c
+++ b/net/mptcp/mptcp_diag.c
@@ -12,6 +12,19 @@
#include <net/netlink.h>
#include "protocol.h"
+/* Process a bounded number of listeners per bucket lock hold. */
+#define MPTCP_DIAG_BULK_SZ 16
+
+static void mptcp_diag_save_cursor(struct inet_diag_dump_data *cb_data,
+ unsigned int slot, struct sock *sk)
+{
+ sock_hold(sk);
+ inet_diag_dump_clear_cursor(cb_data);
+ cb_data->dump_cursor = sk;
+ cb_data->dump_cursor_slot = slot;
+ cb_data->dump_cursor_type = INET_DIAG_DUMP_CURSOR_MPTCP_LISTEN;
+}
+
static int sk_diag_dump(struct sock *sk, struct sk_buff *skb,
struct netlink_callback *cb,
const struct inet_diag_req_v2 *req,
@@ -77,6 +90,7 @@ static void mptcp_diag_dump_listeners(struct sk_buff *skb, struct netlink_callba
bool net_admin)
{
struct mptcp_diag_ctx *diag_ctx = (void *)cb->ctx;
+ struct inet_diag_dump_data *cb_data = cb->data;
struct net *net = sock_net(skb->sk);
struct inet_hashinfo *hinfo;
int i;
@@ -84,64 +98,102 @@ static void mptcp_diag_dump_listeners(struct sk_buff *skb, struct netlink_callba
hinfo = net->ipv4.tcp_death_row.hashinfo;
for (i = diag_ctx->l_slot; i <= hinfo->lhash2_mask; i++) {
+ struct sock *tmp, *sk, *sk_arr[MPTCP_DIAG_BULK_SZ];
struct inet_listen_hashbucket *ilb;
+ int num_arr[MPTCP_DIAG_BULK_SZ];
struct hlist_nulls_node *node;
- struct sock *sk;
- int num = 0;
+ int accum, idx, num, ret;
+ struct sock *cursor;
+ bool use_cursor;
+resume_listen_walk:
+ num = 0;
+ accum = 0;
ilb = &hinfo->lhash2[i];
+ ret = 0;
rcu_read_lock();
spin_lock(&ilb->lock);
- sk_nulls_for_each(sk, node, &ilb->nulls_head) {
- const struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(sk);
- struct inet_sock *inet = inet_sk(sk);
- int ret;
-
- if (num < diag_ctx->l_num)
- goto next_listen;
-
- if (!ctx || strcmp(inet_csk(sk)->icsk_ulp_ops->name, "mptcp"))
- goto next_listen;
-
- sk = ctx->conn;
- if (!sk || !net_eq(sock_net(sk), net))
- goto next_listen;
-
- if (r->sdiag_family != AF_UNSPEC &&
- sk->sk_family != r->sdiag_family)
- goto next_listen;
-
- if (r->id.idiag_sport != inet->inet_sport &&
- r->id.idiag_sport)
+ cursor = cb_data->dump_cursor;
+ use_cursor = cursor &&
+ cb_data->dump_cursor_type ==
+ INET_DIAG_DUMP_CURSOR_MPTCP_LISTEN &&
+ cb_data->dump_cursor_slot == i &&
+ inet_sk_state_load(cursor) == TCP_LISTEN &&
+ !hlist_nulls_unhashed(&cursor->sk_nulls_node) &&
+ cursor->sk_nulls_node.pprev != LIST_POISON2 &&
+ inet_lhash2_bucket_sk(hinfo, cursor) == ilb;
+ node = use_cursor ? cursor->sk_nulls_node.next :
+ ilb->nulls_head.first;
+ hlist_nulls_for_each_entry_from(sk, node, sk_nulls_node) {
+ if (!use_cursor && num < diag_ctx->l_num)
goto next_listen;
if (!refcount_inc_not_zero(&sk->sk_refcnt))
goto next_listen;
- ret = sk_diag_dump(sk, skb, cb, r, net_admin);
-
- sock_put(sk);
-
- if (ret < 0) {
- spin_unlock(&ilb->lock);
- rcu_read_unlock();
- diag_ctx->l_slot = i;
- diag_ctx->l_num = num;
- return;
- }
- diag_ctx->l_num = num + 1;
- num = 0;
+ num_arr[accum] = num;
+ sk_arr[accum] = sk;
+ if (++accum == MPTCP_DIAG_BULK_SZ)
+ break;
next_listen:
++num;
}
spin_unlock(&ilb->lock);
rcu_read_unlock();
+ for (idx = 0; idx < accum; idx++) {
+ const struct mptcp_subflow_context *ctx;
+ const struct tcp_ulp_ops *ulp_ops;
+ struct inet_sock *inet;
+
+ sk = sk_arr[idx];
+ rcu_read_lock();
+ ctx = rcu_dereference(inet_csk(sk)->icsk_ulp_data);
+ ulp_ops = READ_ONCE(inet_csk(sk)->icsk_ulp_ops);
+ inet = inet_sk(sk);
+ tmp = ctx ? ctx->conn : NULL;
+ if (!ctx || !ulp_ops || strcmp(ulp_ops->name, "mptcp") ||
+ !tmp || !net_eq(sock_net(tmp), net) ||
+ (r->sdiag_family != AF_UNSPEC &&
+ tmp->sk_family != r->sdiag_family) ||
+ (r->id.idiag_sport != inet->inet_sport &&
+ r->id.idiag_sport) ||
+ !refcount_inc_not_zero(&tmp->sk_refcnt)) {
+ rcu_read_unlock();
+ goto processed_listener_sk;
+ }
+ rcu_read_unlock();
+ if (ret >= 0) {
+ ret = sk_diag_dump(tmp, skb, cb, r, net_admin);
+ if (ret < 0)
+ num = num_arr[idx];
+ }
+ sock_put(tmp);
+processed_listener_sk:
+ if (ret >= 0)
+ mptcp_diag_save_cursor(cb_data, i, sk);
+ sock_put(sk);
+ }
+
+ if (ret < 0) {
+ diag_ctx->l_slot = i;
+ diag_ctx->l_num = num;
+ return;
+ }
+
cond_resched();
+
+ if (accum == MPTCP_DIAG_BULK_SZ) {
+ diag_ctx->l_num = 0;
+ goto resume_listen_walk;
+ }
+
+ inet_diag_dump_clear_cursor(cb_data);
diag_ctx->l_num = 0;
}
+ inet_diag_dump_clear_cursor(cb_data);
diag_ctx->l_num = 0;
diag_ctx->l_slot = i;
}
--
2.43.0