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-next] mptcp: fallback to TCP on MP_FAIL with a single subflow
Date: Mon, 13 Jul 2026 14:41:34 +0800 [thread overview]
Message-ID: <20260713064134.914507-1-chenguang.zhao@linux.dev> (raw)
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, teach
mptcp_is_fully_established() to reject joins after fallback or when
subflows are disallowed, and annotate allow_subflows with READ/WRITE_ONCE.
Fixes: 1e39e5a32ad7 ("mptcp: infinite mapping sending")
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
- On a valid MP_FAIL while infinite fallback is still allowed (single
subflow, contiguous data), comply with RFC 8684 §3.7 by calling
mptcp_try_fallback() immediately after the MP_FAIL response, instead
of deferring fallback until the infinite-map transmit path runs.
- Send the MP_FAIL ACK before setting FALLBACK_DONE, so MPTCP options
are not dropped from that ACK by mptcp_established_options().
- If the out-of-order queue is non-empty, fallback cannot proceed safely:
increment the FallbackFailed MIB and reset the subflow, rather than leaving
allow_subflows=false without completing fallback.
- Add the MPTcpExtMPFailFallback MIB to count fallbacks triggered by MP_FAIL.
- In mptcp_update_infinite_map(), if fallback was already completed on MP_FAIL
reception, only account for InfiniteMapTx and do not attempt fallback again.
- Extend mptcp_is_fully_established() to require that the socket is not in
fallback and that allow_subflows is true, so new subflow joins are rejected
after fallback.
- Use READ_ONCE/WRITE_ONCE for all allow_subflows accesses to avoid concurrent read/write issues.
- Fix the fallback_lock comment: allow_join → allow_subflows.
net/mptcp/mib.c | 1 +
net/mptcp/mib.h | 1 +
net/mptcp/pm.c | 33 +++++++++++++++++++++++++++++++--
net/mptcp/protocol.c | 19 ++++++++++++-------
net/mptcp/protocol.h | 18 +++++++++++-------
net/mptcp/subflow.c | 2 +-
6 files changed, 57 insertions(+), 17 deletions(-)
diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c
index f23fda0c55a7..4dc107ab1aec 100644
--- a/net/mptcp/mib.c
+++ b/net/mptcp/mib.c
@@ -83,6 +83,7 @@ static const struct snmp_mib mptcp_snmp_list[] = {
SNMP_MIB_ITEM("MD5SigFallback", MPTCP_MIB_MD5SIGFALLBACK),
SNMP_MIB_ITEM("DssFallback", MPTCP_MIB_DSSFALLBACK),
SNMP_MIB_ITEM("SimultConnectFallback", MPTCP_MIB_SIMULTCONNFALLBACK),
+ SNMP_MIB_ITEM("MPFailFallback", MPTCP_MIB_MPFAILFALLBACK),
SNMP_MIB_ITEM("FallbackFailed", MPTCP_MIB_FALLBACKFAILED),
SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE),
};
diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h
index 812218b5ed2b..9db8812d8e04 100644
--- a/net/mptcp/mib.h
+++ b/net/mptcp/mib.h
@@ -86,6 +86,7 @@ enum linux_mptcp_mib_field {
MPTCP_MIB_MD5SIGFALLBACK, /* Conflicting TCP option enabled */
MPTCP_MIB_DSSFALLBACK, /* Bad or missing DSS */
MPTCP_MIB_SIMULTCONNFALLBACK, /* Simultaneous connect */
+ MPTCP_MIB_MPFAILFALLBACK, /* Received MP_FAIL, fallback to TCP */
MPTCP_MIB_FALLBACKFAILED, /* Can't fallback due to msk status */
MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */
__MPTCP_MIB_MAX
diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c
index 6afd39aea110..82d3dbedd603 100644
--- a/net/mptcp/pm.c
+++ b/net/mptcp/pm.c
@@ -870,21 +870,50 @@ 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);
return;
}
- msk->allow_subflows = false;
+ WRITE_ONCE(msk->allow_subflows, false);
spin_unlock_bh(&msk->fallback_lock);
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. Use
+ * mptcp_try_fallback() so pending DATA_FIN is handled.
+ * InfiniteMapTx is counted when the map is transmitted.
+ */
+ if (!mptcp_try_fallback(sk, MPTCP_MIB_MPFAILFALLBACK)) {
+ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FALLBACKFAILED);
+ mptcp_subflow_reset(sk);
+ }
} 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 b32f0cd262a7..181403f43e54 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -93,7 +93,7 @@ bool __mptcp_try_fallback(struct mptcp_sock *msk, int fb_mib)
return false;
}
- msk->allow_subflows = false;
+ WRITE_ONCE(msk->allow_subflows, false);
set_bit(MPTCP_FALLBACK_DONE, &msk->flags);
__MPTCP_INC_STATS(net, fb_mib);
spin_unlock_bh(&msk->fallback_lock);
@@ -958,7 +958,7 @@ static bool __mptcp_finish_join(struct mptcp_sock *msk, struct sock *ssk)
return false;
spin_lock_bh(&msk->fallback_lock);
- if (!msk->allow_subflows) {
+ if (!READ_ONCE(msk->allow_subflows)) {
spin_unlock_bh(&msk->fallback_lock);
return false;
}
@@ -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;
@@ -2839,7 +2844,7 @@ static void __mptcp_retrans(struct sock *sk)
*/
spin_lock_bh(&msk->fallback_lock);
if (__mptcp_check_fallback(msk) ||
- !msk->allow_subflows) {
+ !READ_ONCE(msk->allow_subflows)) {
spin_unlock_bh(&msk->fallback_lock);
release_sock(ssk);
goto clear_scheduled;
@@ -3050,7 +3055,7 @@ static void __mptcp_init_sock(struct sock *sk)
inet_csk(sk)->icsk_sync_mss = mptcp_sync_mss;
WRITE_ONCE(msk->csum_enabled, mptcp_is_checksum_enabled(sock_net(sk)));
msk->allow_infinite_fallback = true;
- msk->allow_subflows = true;
+ WRITE_ONCE(msk->allow_subflows, true);
msk->recovery = false;
msk->subflow_id = 1;
msk->last_data_sent = tcp_jiffies32;
@@ -3468,7 +3473,7 @@ static int mptcp_disconnect(struct sock *sk, int flags)
* can't overlap with a fallback anymore
*/
spin_lock_bh(&msk->fallback_lock);
- msk->allow_subflows = true;
+ WRITE_ONCE(msk->allow_subflows, true);
msk->allow_infinite_fallback = true;
WRITE_ONCE(msk->flags, 0);
spin_unlock_bh(&msk->fallback_lock);
@@ -3868,7 +3873,7 @@ bool mptcp_finish_join(struct sock *ssk)
*/
if (!list_empty(&subflow->node)) {
spin_lock_bh(&msk->fallback_lock);
- if (!msk->allow_subflows) {
+ if (!READ_ONCE(msk->allow_subflows)) {
spin_unlock_bh(&msk->fallback_lock);
return false;
}
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 4a2d40cd7b13..833c76c41768 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) &&
+ READ_ONCE(msk->allow_subflows);
+}
+
static inline bool __mptcp_has_initial_subflow(const struct mptcp_sock *msk)
{
struct sock *ssk = READ_ONCE(msk->first);
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 8e386899ceb9..b04d6edb9be3 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -1323,7 +1323,7 @@ static bool mptcp_subflow_fail(struct mptcp_sock *msk, struct sock *ssk)
spin_unlock_bh(&msk->fallback_lock);
return false;
}
- msk->allow_subflows = false;
+ WRITE_ONCE(msk->allow_subflows, false);
spin_unlock_bh(&msk->fallback_lock);
/* graceful failure can happen only on the MPC subflow */
--
2.25.1
next reply other threads:[~2026-07-13 6:41 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 6:41 Chenguang Zhao [this message]
2026-07-13 7:32 ` [PATCH mptcp-next] mptcp: fallback to TCP on MP_FAIL with a single subflow gang.yan
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=20260713064134.914507-1-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.