* [PATCH net v5 2/2] mptcp: fix MP_CAPABLE token migration when cloning reqsk
2026-09-01 10:33 [PATCH net v5 0/2] mptcp: fix request migration ownership Ren Wei
@ 2026-09-01 10:33 ` Ren Wei
0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-09-01 10:33 UTC (permalink / raw)
To: netdev, mptcp
Cc: matttbe, martineau, geliang, davem, edumazet, kuba, pabeni, horms,
ncardwell, kuniyu, daniel, kafai, kylebot, david.lee, vega,
caoruide123, weir, sashiko-bot
From: Ruide Cao <caoruide123@gmail.com>
TCP request migration clones pending request sockets with
inet_reqsk_clone(). For MPTCP MP_CAPABLE requests this byte-copies the
token_node hlist state into the clone even though the token table still
names the original request.
Move token request ownership from the original request to the clone
under the token bucket lock. Make mptcp_token_accept() and
mptcp_token_destroy_request() re-check token_node under the same lock and
treat an already moved or removed request as a normal race instead of
warning. This also keeps token bucket chain_len accounting balanced.
If a passive MP_CAPABLE socket cannot claim the token, destroy the
provisional MPTCP socket and let the subflow fall back rather than
installing a socket with mismatched token ownership.
If the speculative clone later loses the inet_ehash_insert() ownership
arbitration, the original request may find that its token was already
moved and fall back to plain TCP. The clone destructor then releases
the token reservation and keeps chain_len balanced. This trades an
exceptionally rare fallback for eliminating the warning and persistent
accounting drift.
Patch 1/2 introduces mptcp_subflow_reqsk_clone() and fixes the MP_JOIN
msk reference. This patch completes the same clone fixup for MP_CAPABLE
token ownership. Both patches carry the same Fixes tag and are required
for stable backports.
Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/86e2514b533bf4d55d4aa2fdbf1404022e8c9430.1776149210.git.caoruide123%40gmail.com
Assisted-by: Codex:gpt-5.4
Signed-off-by: Ruide Cao <caoruide123@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
---
net/mptcp/protocol.c | 31 +++++++++++++++----
net/mptcp/protocol.h | 4 ++-
net/mptcp/subflow.c | 2 ++
net/mptcp/token.c | 68 +++++++++++++++++++++++++++++++++++++-----
net/mptcp/token_test.c | 4 +--
5 files changed, 94 insertions(+), 15 deletions(-)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index ca644ec53eed..907a81425816 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3555,6 +3555,24 @@ static void mptcp_copy_ip_options(struct sock *newsk, const struct sock *sk)
rcu_read_unlock();
}
+static void mptcp_sk_clone_destroy(struct sock *nsk)
+{
+ struct mptcp_sock *msk = mptcp_sk(nsk);
+
+ mptcp_release_sched(msk);
+ mptcp_set_state(nsk, TCP_CLOSE);
+ /* inet_csk_prepare_forced_close() clears TCP sock_ops state via
+ * tcp_sk(), but nsk is an MPTCP master socket; keep the inet-level
+ * destroy preparation here.
+ */
+ bh_unlock_sock(nsk);
+ sock_put(nsk);
+ sock_set_flag(nsk, SOCK_DEAD);
+ tcp_orphan_count_inc();
+ inet_sk(nsk)->inet_num = 0;
+ inet_csk_destroy_sock(nsk);
+}
+
struct sock *mptcp_sk_clone_init(const struct sock *sk,
const struct mptcp_options_received *mp_opt,
struct sock *ssk,
@@ -3614,11 +3632,6 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
list_add(&subflow->node, &msk->conn_list);
sock_hold(ssk);
- /* new mpc subflow takes ownership of the newly
- * created mptcp socket
- */
- mptcp_token_accept(subflow_req, msk);
-
/* set msk addresses early to ensure mptcp_pm_get_local_id()
* uses the correct data
*/
@@ -3627,6 +3640,14 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
mptcp_rcv_space_init(msk, ssk);
msk->rcvq_space.time = mptcp_stamp();
+ if (!mptcp_token_accept(subflow_req, msk)) {
+ list_del_init(&subflow->node);
+ WRITE_ONCE(msk->first, NULL);
+ sock_put(ssk);
+ mptcp_sk_clone_destroy(nsk);
+ return NULL;
+ }
+
if (mp_opt->suboptions & OPTION_MPTCP_MPC_ACK)
__mptcp_subflow_fully_established(msk, subflow, mp_opt);
bh_unlock_sock(nsk);
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 4a2d40cd7b13..c58cfb1326b7 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -1074,9 +1074,11 @@ static inline void mptcp_token_init_request(struct request_sock *req)
}
int mptcp_token_new_request(struct request_sock *req);
+void mptcp_token_move_request(struct request_sock *req,
+ struct request_sock *new_req);
void mptcp_token_destroy_request(struct request_sock *req);
int mptcp_token_new_connect(struct sock *ssk);
-void mptcp_token_accept(struct mptcp_subflow_request_sock *r,
+bool mptcp_token_accept(struct mptcp_subflow_request_sock *r,
struct mptcp_sock *msk);
bool mptcp_token_exists(u32 token);
struct mptcp_sock *mptcp_token_get_sock(struct net *net, u32 token);
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index e08d1036ad78..033c0d6eb376 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -76,6 +76,8 @@ void mptcp_subflow_reqsk_clone(struct request_sock *req,
}
new_subflow_req->msk = msk;
+
+ mptcp_token_move_request(req, new_req);
}
static void subflow_generate_hmac(u64 key1, u64 key2, u32 nonce1, u32 nonce2,
diff --git a/net/mptcp/token.c b/net/mptcp/token.c
index f1a50f367add..e0f2823f92ef 100644
--- a/net/mptcp/token.c
+++ b/net/mptcp/token.c
@@ -180,6 +180,43 @@ int mptcp_token_new_connect(struct sock *ssk)
return 0;
}
+/**
+ * mptcp_token_move_request - move request token ownership to a clone
+ * @req: original request socket
+ * @new_req: cloned request socket
+ *
+ * Move the token hash entry from the original request to its clone.
+ */
+void mptcp_token_move_request(struct request_sock *req,
+ struct request_sock *new_req)
+{
+ struct mptcp_subflow_request_sock *subflow_req = mptcp_subflow_rsk(req);
+ struct mptcp_subflow_request_sock *new_subflow_req;
+ struct mptcp_subflow_request_sock *pos;
+ struct token_bucket *bucket;
+
+ new_subflow_req = mptcp_subflow_rsk(new_req);
+
+ if (hlist_nulls_unhashed_lockless(&subflow_req->token_node)) {
+ mptcp_token_init_request(new_req);
+ return;
+ }
+
+ bucket = token_bucket(subflow_req->token);
+ spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&subflow_req->token_node)) {
+ mptcp_token_init_request(new_req);
+ } else {
+ pos = __token_lookup_req(bucket, subflow_req->token);
+ if (pos == subflow_req)
+ hlist_nulls_replace_init_rcu(&subflow_req->token_node,
+ &new_subflow_req->token_node);
+ else
+ mptcp_token_init_request(new_req);
+ }
+ spin_unlock_bh(&bucket->lock);
+}
+
/**
* mptcp_token_accept - replace a req sk with full sock in token hash
* @req: the request socket to be removed
@@ -187,24 +224,36 @@ int mptcp_token_new_connect(struct sock *ssk)
*
* Called when a SYN packet creates a new logical connection, i.e.
* is not a join request.
+ *
+ * Return: true on success.
*/
-void mptcp_token_accept(struct mptcp_subflow_request_sock *req,
+bool mptcp_token_accept(struct mptcp_subflow_request_sock *req,
struct mptcp_sock *msk)
{
struct mptcp_subflow_request_sock *pos;
struct sock *sk = (struct sock *)msk;
struct token_bucket *bucket;
+ bool ret = false;
- sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
bucket = token_bucket(req->token);
spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&req->token_node))
+ goto unlock;
- /* pedantic lookup check for the moved token */
pos = __token_lookup_req(bucket, req->token);
- if (!WARN_ON_ONCE(pos != req))
- hlist_nulls_del_init_rcu(&req->token_node);
+ if (pos != req)
+ goto unlock;
+
+ hlist_nulls_del_init_rcu(&req->token_node);
__sk_nulls_add_node_rcu((struct sock *)msk, &bucket->msk_chain);
+ ret = true;
+
+unlock:
spin_unlock_bh(&bucket->lock);
+ if (ret)
+ sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+
+ return ret;
}
bool mptcp_token_exists(u32 token)
@@ -355,16 +404,21 @@ void mptcp_token_destroy_request(struct request_sock *req)
struct mptcp_subflow_request_sock *pos;
struct token_bucket *bucket;
- if (hlist_nulls_unhashed(&subflow_req->token_node))
+ if (hlist_nulls_unhashed_lockless(&subflow_req->token_node))
return;
bucket = token_bucket(subflow_req->token);
spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&subflow_req->token_node))
+ goto unlock;
+
pos = __token_lookup_req(bucket, subflow_req->token);
- if (!WARN_ON_ONCE(pos != subflow_req)) {
+ if (pos == subflow_req) {
hlist_nulls_del_init_rcu(&pos->token_node);
bucket->chain_len--;
}
+
+unlock:
spin_unlock_bh(&bucket->lock);
}
diff --git a/net/mptcp/token_test.c b/net/mptcp/token_test.c
index 4fc39fa2e262..be9acce8a567 100644
--- a/net/mptcp/token_test.c
+++ b/net/mptcp/token_test.c
@@ -99,7 +99,7 @@ static void mptcp_token_test_accept(struct kunit *test)
KUNIT_ASSERT_EQ(test, 0,
mptcp_token_new_request((struct request_sock *)req));
msk->token = req->token;
- mptcp_token_accept(req, msk);
+ KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk));
KUNIT_EXPECT_PTR_EQ(test, msk, mptcp_token_get_sock(&init_net, msk->token));
/* this is now a no-op */
@@ -122,7 +122,7 @@ static void mptcp_token_test_destroyed(struct kunit *test)
KUNIT_ASSERT_EQ(test, 0,
mptcp_token_new_request((struct request_sock *)req));
msk->token = req->token;
- mptcp_token_accept(req, msk);
+ KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk));
/* simulate race on removal */
refcount_set(&sk->sk_refcnt, 0);
--
2.34.1
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH net v5 2/2] mptcp: fix MP_CAPABLE token migration when cloning reqsk
@ 2026-09-06 13:02 netdev-bot+sashiko
0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-09-06 13:02 UTC (permalink / raw)
To: weir
Cc: netdev, mptcp, matttbe, martineau, geliang, davem, edumazet, kuba,
pabeni, horms, ncardwell, kuniyu, daniel, kafai, kylebot,
david.lee, vega, caoruide123, sashiko-bot
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] The new non-OOM failure path in mptcp_sk_clone_init()
(net/mptcp/protocol.c:3780-3786) downgrades an already negotiated…
--
mptcp: fix MP_CAPABLE token migration when cloning reqsk
This moves MP_CAPABLE token ownership from an original request socket to
its inet_reqsk_clone() copy under the token bucket lock, and makes
mptcp_token_accept() and mptcp_token_destroy_request() re-check
token_node under that lock instead of warning. mptcp_token_accept() now
returns a bool, and mptcp_sk_clone_init() destroys the provisional msk
and returns NULL when the token cannot be claimed.
Closes: https://sashiko.dev/#/patchset/86e2514b533bf4d55d4aa2fdbf1404022e8c9430.1776149210.git.caoruide123%40gmail.com
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index e1f08f71cdb16..30067f2a7ca8f 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
[ ... ]
> @@ -3764,6 +3777,14 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
> mptcp_rcv_space_init(msk, ssk);
> msk->rcvq_space.time = mptcp_stamp();
>
> + if (!mptcp_token_accept(subflow_req, msk)) {
> + list_del_init(&subflow->node);
> + WRITE_ONCE(msk->first, NULL);
> + sock_put(ssk);
> + mptcp_sk_clone_destroy(nsk);
> + return NULL;
> + }
> +
[Severity: Medium]
Is this new failure path completely silent from a user's point of view?
In subflow_syn_recv_sock() the NULL return is turned into a fallback:
net/mptcp/subflow.c:subflow_syn_recv_sock() {
...
if (ctx->mp_capable) {
ctx->conn = mptcp_sk_clone_init(listener->conn, &mp_opt, child, req);
if (!ctx->conn)
goto fallback;
...
}
but the fallback label only bumps a counter when the local fallback bool
is set:
net/mptcp/subflow.c:subflow_syn_recv_sock() {
...
fallback:
if (fallback)
SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK);
mptcp_subflow_drop_ctx(child);
return child;
}
Reaching the ctx->mp_capable block requires having already passed:
if (!ctx || fallback) {
...
goto fallback;
}
so on the token_accept failure path fallback is false and nothing is
counted. Every sibling failure site in the same function increments
something (MPTCP_MIB_MPJOINACKNOCTX, MPTCP_MIB_MPJOINACKNOMPJOIN,
MPTCP_MIB_JOINACKMAC, MPTCP_MIB_JOINREJECTED).
At the same time the two WARN_ON_ONCE() calls that used to fire for
exactly this state are dropped in this patch, in mptcp_token_accept():
- /* pedantic lookup check for the moved token */
pos = __token_lookup_req(bucket, req->token);
- if (!WARN_ON_ONCE(pos != req))
- hlist_nulls_del_init_rcu(&req->token_node);
and in mptcp_token_destroy_request():
pos = __token_lookup_req(bucket, subflow_req->token);
- if (!WARN_ON_ONCE(pos != subflow_req)) {
+ if (pos == subflow_req) {
The changelog describes this as trading "an exceptionally rare fallback
for eliminating the warning", but with no dmesg output and no MPTcpExt
counter, is there any way left to observe that a negotiated passive
MP_CAPABLE connection was downgraded to plain TCP, either in the field
or from the mptcp selftests?
Would it make sense to increment MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK
(exported as MPCapableFallbackACK, documented in mib.h as "Server-side
fallback during 3-way handshake"), or MPTCP_MIB_TOKENFALLBACKINIT, on
this path?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1788202924.git.caoruide123%40gmail.com
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-06 13:02 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-06 13:02 [PATCH net v5 2/2] mptcp: fix MP_CAPABLE token migration when cloning reqsk netdev-bot+sashiko
-- strict thread matches above, loose matches on Subject: below --
2026-09-01 10:33 [PATCH net v5 0/2] mptcp: fix request migration ownership Ren Wei
2026-09-01 10:33 ` [PATCH net v5 2/2] mptcp: fix MP_CAPABLE token migration when cloning reqsk Ren Wei
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox