From: Chenguang Zhao <chenguang.zhao@linux.dev>
To: mptcp@lists.linux.dev
Cc: chenguang.zhao@linux.dev, Chenguang Zhao <zhaochenguang@kylinos.cn>
Subject: [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow
Date: Wed, 15 Jul 2026 14:18:28 +0800 [thread overview]
Message-ID: <20260715061830.1057851-2-chenguang.zhao@linux.dev> (raw)
In-Reply-To: <20260715061830.1057851-1-chenguang.zhao@linux.dev>
From: Chenguang Zhao <zhaochenguang@kylinos.cn>
When a valid MP_FAIL is received and infinite fallback is still allowed
(single contiguous subflow), RFC8684 §3.7 requires leaving MPTCP mode.
The stack only cleared allow_subflows and deferred the real fallback to
the later infinite-map transmit path. Before any data is sent, a peer
could still complete the 4th ACK as MPTCP and keep using MPTCP options.
Fall back immediately after sending the MP_FAIL response, and teach
mptcp_is_fully_established() to reject joins after fallback or when
subflows are disallowed. If the out-of-order queue is non-empty, reset
the subflow instead of leaving a half-fallback state.
Fixes: 1e39e5a32ad7 ("mptcp: infinite mapping sending")
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
net/mptcp/pm.c | 51 +++++++++++++++++++++++++++++++++++++++++++-
net/mptcp/protocol.c | 7 +++++-
net/mptcp/protocol.h | 18 ++++++++++------
3 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c
index 6afd39aea110..c1f5c3ced4ee 100644
--- a/net/mptcp/pm.c
+++ b/net/mptcp/pm.c
@@ -870,7 +870,15 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq)
pr_debug("fail_seq=%llu\n", fail_seq);
- /* After accepting the fail, we can't create any other subflows */
+ /* MP_FAIL on a single contiguous subflow: fall back to TCP.
+ * allow_infinite_fallback is cleared once other subflows join or
+ * non-contiguous data is retransmitted; in that case ignore MP_FAIL
+ * here (the peer should reset the failing subflow instead).
+ *
+ * Send the MP_FAIL (+ DSS) response before setting FALLBACK_DONE,
+ * otherwise mptcp_established_options() would drop all MPTCP options
+ * on this ACK. InfiniteMapTx is accounted later when the map is sent.
+ */
spin_lock_bh(&msk->fallback_lock);
if (!msk->allow_infinite_fallback) {
spin_unlock_bh(&msk->fallback_lock);
@@ -882,9 +890,50 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq)
if (!subflow->fail_tout) {
pr_debug("send MP_FAIL response and infinite map\n");
+ /* Infinite mapping requires contiguous data. With OoO still
+ * queued, do not leave allow_subflows=false without
+ * FALLBACK_DONE; tear the subflow down instead (RFC8684 §3.7).
+ */
+ if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) {
+ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FALLBACKFAILED);
+ subflow->send_mp_fail = 1;
+ mptcp_subflow_reset(sk);
+ return;
+ }
+
subflow->send_mp_fail = 1;
subflow->send_infinite_map = 1;
tcp_send_ack(sk);
+
+ /* RFC8684 §3.7: after accepting MP_FAIL with a single
+ * subflow, leave MPTCP mode and never revert. No dedicated
+ * fallback MIB yet; InfiniteMapTx is counted when the map
+ * is transmitted. Handle pending DATA_FIN like
+ * mptcp_try_fallback().
+ */
+ spin_lock_bh(&msk->fallback_lock);
+ if (__mptcp_check_fallback(msk)) {
+ spin_unlock_bh(&msk->fallback_lock);
+ return;
+ }
+ if (!msk->allow_infinite_fallback) {
+ spin_unlock_bh(&msk->fallback_lock);
+ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FALLBACKFAILED);
+ mptcp_subflow_reset(sk);
+ return;
+ }
+ set_bit(MPTCP_FALLBACK_DONE, &msk->flags);
+ spin_unlock_bh(&msk->fallback_lock);
+
+ if (READ_ONCE(msk->snd_data_fin_enable) &&
+ !(sk->sk_shutdown & SEND_SHUTDOWN)) {
+ gfp_t saved_allocation = sk->sk_allocation;
+
+ sk->sk_allocation = GFP_ATOMIC;
+ sk->sk_shutdown |= SEND_SHUTDOWN;
+ tcp_shutdown(sk, SEND_SHUTDOWN);
+ sk->sk_allocation = saved_allocation;
+ }
} else {
pr_debug("MP_FAIL response received\n");
WRITE_ONCE(subflow->fail_tout, 0);
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index cb9515f505aa..5b9522caaf43 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -1299,7 +1299,12 @@ static void mptcp_update_infinite_map(struct mptcp_sock *msk,
mpext->infinite_map = 1;
mpext->data_len = 0;
- if (!mptcp_try_fallback(ssk, MPTCP_MIB_INFINITEMAPTX)) {
+ /* Fallback may already have been completed on MP_FAIL reception;
+ * still account for the infinite mapping being transmitted.
+ */
+ if (__mptcp_check_fallback(msk)) {
+ MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_INFINITEMAPTX);
+ } else if (!mptcp_try_fallback(ssk, MPTCP_MIB_INFINITEMAPTX)) {
MPTCP_INC_STATS(sock_net(ssk), MPTCP_MIB_FALLBACKFAILED);
mptcp_subflow_reset(ssk);
return;
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 4a2d40cd7b13..03f0b33694d7 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -369,7 +369,7 @@ struct mptcp_sock {
spinlock_t fallback_lock; /* protects fallback,
* allow_infinite_fallback and
- * allow_join
+ * allow_subflows
*/
struct list_head backlog_list; /* protected by the data lock */
@@ -947,12 +947,6 @@ static inline void mptcp_start_tout_timer(struct sock *sk)
mptcp_reset_tout_timer(mptcp_sk(sk), 0);
}
-static inline bool mptcp_is_fully_established(struct sock *sk)
-{
- return inet_sk_state_load(sk) == TCP_ESTABLISHED &&
- READ_ONCE(mptcp_sk(sk)->fully_established);
-}
-
static inline u64 mptcp_stamp(void)
{
return div_u64(tcp_clock_ns(), NSEC_PER_USEC);
@@ -1290,6 +1284,16 @@ static inline bool mptcp_check_fallback(const struct sock *sk)
return __mptcp_check_fallback(msk);
}
+static inline bool mptcp_is_fully_established(struct sock *sk)
+{
+ struct mptcp_sock *msk = mptcp_sk(sk);
+
+ return inet_sk_state_load(sk) == TCP_ESTABLISHED &&
+ READ_ONCE(msk->fully_established) &&
+ !__mptcp_check_fallback(msk) &&
+ msk->allow_subflows;
+}
+
static inline bool __mptcp_has_initial_subflow(const struct mptcp_sock *msk)
{
struct sock *ssk = READ_ONCE(msk->first);
--
2.25.1
next prev parent reply other threads:[~2026-07-15 6:19 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-15 6:18 [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL Chenguang Zhao
2026-07-15 6:18 ` Chenguang Zhao [this message]
2026-08-10 18:26 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Matthieu Baerts
2026-07-15 6:18 ` [PATCH mptcp-net v2 2/3] mptcp: use READ/WRITE_ONCE for allow_subflows Chenguang Zhao
2026-08-10 18:26 ` Matthieu Baerts
2026-07-15 6:18 ` [PATCH mptcp-net v2 3/3] mptcp: add MPFailFallback MIB for MP_FAIL TCP fallback Chenguang Zhao
2026-08-10 18:26 ` [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL Matthieu Baerts
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=20260715061830.1057851-2-chenguang.zhao@linux.dev \
--to=chenguang.zhao@linux.dev \
--cc=mptcp@lists.linux.dev \
--cc=zhaochenguang@kylinos.cn \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.