From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794335030; b=Uf+axHSlZcTY+aWTP9PYyFVJJ13kMd93Zo5Q64KPHpEXfh2KxWwMOsBQsG1GRaA6CAjF k0wPF3n4eTs+4+NCq5e9BrMj/uTNBgWrUmlkZEg/IGOdzBozongPF611hTfs5G5/CASTv BQo0l3Hf67GelK9NoTC1WwaDSsrUKIrVpcU9tWwUnuYroD/aNAaf84xXNIo7in1khskMj f9kWlj8D2l/3fgOaNPj1QPJvq4ofT8YRZqkZVm0Y/h8qIBxfuU9ByeOzNurNevALfwm5Z rXQPRJGoFM3VKiCOK0OYngaSbYx4fWuM1b+jybHSY/uc8ydWFdInWRPDNtMmNi/vTpQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794335030; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=CNA7Ug4Kav76WIwSgKwuC3GeY1D0zkHFHcZQVFFciXE=; b=0uMwYq02ZsEWNeu3nLU+AtLyOXGTuULotBl65Tbhee7VHBQHFXRtrXxONBXrcx2Yusk0 p5hvaqKvvbMm1IDEtcfVWMDBhfa/5dkVB0emtTRjjEFiR930W/8xXKp55FGBrDmZ1kqO+ 4Q1ybgbKh9VPzMCLA6X8tkAULSgKZ8NEko+l/v9Lt1GZNHHnUxcVDUjZ9H0de8Kqshuil 6MCOqgHIkAGdKGjCuXjAYpvCfOiAmcYNS2qpw1Z/hbS7lJ+m+N3lWjx12X+Idsp9YoUJN QZMlv9salHvTyC6FGN4947U9TiuhjLae8dREA2QWfPGxL9ddy1mKZwN+s6PevCUqGNw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=FKXsRFFg; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 088CC606A321 for ; Tue, 10 Nov 2026 18:23:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=FKXsRFFg Received: by smtp.kernel.org (Postfix) with ESMTPSA id 8A4C61F0089F; Tue, 10 Nov 2026 18:23:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1794335028; bh=CNA7Ug4Kav76WIwSgKwuC3GeY1D0zkHFHcZQVFFciXE=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=FKXsRFFgQmBznuvKQoYILU1S+1vDbqenoEkN/salRRsFBoSRGhbPhu7Mt/g5qR9U5 UKHMHf4U+zLeNlAwbssKC4dwNvURLs+tkFbiGp1zUcT8V2BdUyjSeCfoi2BlyN93SP uowhnYzk0XmJdNKDBlzOh45CveJPkXF5jBZXBUesy7HCl+UakinxmkFu7MnkJNt0ae IsYzMXwhDH69sYzNup5oUrj2MoLUeJe9y0zWBvo0jOUzPxObb1m+Jc3J+nam44/lwI ry93/iGquVo8q3PEWRQJmG/AuWkyiwP7IJu/viKVRR6oERjPxD4iK15Orxs9B4i80V Rixx5MqS63vtQ== From: Sasha Levin To: stable@vger.kernel.org Cc: Ruide Cao , Vega , Sashiko , Ren Wei , "Matthieu Baerts (NGI0)" , Jakub Kicinski , Sasha Levin Subject: [PATCH 6.6.y] mptcp: fix MP_CAPABLE token migration when cloning reqsk Date: Tue, 10 Nov 2026 13:23:45 -0500 Message-ID: <20261110182126.3127421-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261107053613-failed-6.6-2093bc7fa500@kernel.org> References: <20261107053613-failed-6.6-2093bc7fa500@kernel.org> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Ruide Cao [ Upstream commit 2093bc7fa50056fef8620d2b82bcb2e9a0a5575d ] 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, and the following warning can be seen: WARNING: net/mptcp/token.c:364 at mptcp_token_destroy_request+0x2b0/0x330, CPU#2: swapper/2/0 CPU: 2 UID: 0 PID: 0 Comm: swapper/2 Not tainted 7.2.0-rc5-00353-gc27e36054537 #10 PREEMPT(full) RIP: 0010:mptcp_token_destroy_request (net/mptcp/token.c:364 (discriminator 1)) Call Trace: subflow_v4_req_destructor (net/mptcp/subflow.c:694) __reqsk_free (net/ipv4/inet_connection_sock.c:906) reqsk_timer_handler (include/net/request_sock.h:137 net/ipv4/inet_connection_sock.c:1147) call_timer_fn (kernel/time/timer.c:1748) 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. Mark that path as a passive handshake fallback so MPCapableFallbackACK records the downgrade. 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. Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Cc: stable@vger.kernel.org Reported-by: Vega Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/86e2514b533bf4d55d4aa2fdbf1404022e8c9430.1776149210.git.caoruide123%40gmail.com Signed-off-by: Ruide Cao Signed-off-by: Ren Wei Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-4-932c9b182fc7@kernel.org Signed-off-by: Jakub Kicinski [ sashal: replaced tcp_orphan_count_inc() with this_cpu_inc() using MPTCP's orphan_count pointer initialized to &tcp_orphan_count ] Signed-off-by: Sasha Levin --- net/mptcp/protocol.c | 32 ++++++++++++++++---- net/mptcp/protocol.h | 4 ++- net/mptcp/subflow.c | 6 +++- net/mptcp/token.c | 68 +++++++++++++++++++++++++++++++++++++----- net/mptcp/token_test.c | 4 +-- 5 files changed, 98 insertions(+), 16 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 331a10fc46eb5..d2ffd3b46de2e 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3419,6 +3419,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); + this_cpu_inc(*nsk->sk_prot->orphan_count); + 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, @@ -3478,11 +3496,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 */ @@ -3491,6 +3504,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); @@ -3989,6 +4010,7 @@ static struct proto mptcp_prot = { .get_port = mptcp_get_port, .forward_alloc_get = mptcp_forward_alloc_get, .sockets_allocated = &mptcp_sockets_allocated, + .orphan_count = &tcp_orphan_count, .memory_allocated = &tcp_memory_allocated, .per_cpu_fw_alloc = &tcp_memory_per_cpu_fw_alloc, diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 1cf67a557ff0d..4850cad4f799a 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -890,9 +890,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 9c4b83cd2c1aa..4cd3a8f090070 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -82,6 +82,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, @@ -911,8 +913,10 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk, if (ctx->mp_capable) { ctx->conn = mptcp_sk_clone_init(listener->conn, &mp_opt, child, req); - if (!ctx->conn) + if (!ctx->conn) { + fallback = true; goto fallback; + } ctx->subflow_id = 1; owner = mptcp_sk(ctx->conn); diff --git a/net/mptcp/token.c b/net/mptcp/token.c index 5bb9245343874..d5735d3ac793b 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) @@ -353,16 +402,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 0758865ab658e..2253f5f802ff6 100644 --- a/net/mptcp/token_test.c +++ b/net/mptcp/token_test.c @@ -94,7 +94,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 */ @@ -117,7 +117,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.53.0