Re: [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow

Matthieu Baerts <[email protected]>
Newsgroups dev.linux.lists.mptcp
Organization NGI0 Core
Message-ID <[email protected]>
Hi Chenguang,

On 15/07/2026 08:18, Chenguang Zhao wrote:
> From: Chenguang Zhao <[email protected]>
> 
> 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")

Thank you for this fix. However, it is a bit big, and it might be
difficult to backport.

> Signed-off-by: Chenguang Zhao <[email protected]>
> ---
>  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).
> +		 */

Maybe enough to just say:

 /* RFC8684 §3.7: Infinite mapping requires contiguous data */
> +		if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) {
> +			MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_FALLBACKFAILED);
> +			subflow->send_mp_fail = 1;

I didn't check the reason, by why do you need to set this before the reset?

> +			mptcp_subflow_reset(sk);
> +			return;
> +		}

This could maybe go in a dedicated commit? Easier to explain and
backport, no? Also, it is different from "fallback to TCP on MP_FAIL
with a single subflow".

Also, out_of_order_queue() is checked in mptcp_try_fallback(), maybe
this part is not needed? I guess it is still needed because we don't
want to send an MP_FAIL here. But do we want to send an MP_FAIL also in
case of fallback with a single subflow?
Note: maybe we do, I didn't check the RFC about this specific case, but
if it is not clear about that, maybe easier to check for fallback before
sending the MP_FAIL → in theory, we shouldn't get an MP_FAIL with a
single subflow, except with checksum IIRC, so let's use the simplest
path for this unlikely case. WDYT?

>  		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().
> +		 */

Maybe just:

  /* RFC8684 §3.7: fallback with a single subflow */


> +		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;
> +		}

Quite a bit of duplicated code. I think it would be better to introduce
patch 3 first, with the following tag, then use mptcp_try_fallback() here:

  Fixes: c65c2e3bae69 ("mptcp: track fallbacks accurately via mibs")

(or use another MIB counter, and change it in -next? I don't think
that's better)

WDYT?

>  	} 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

Probably best to fix that only on -next: this will cause issues during
the backport, just to fix a comment introduced by another commit.

>  					 */
>  
>  	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;
> +}

I wonder if this modification shouldn't be split to a dedicated commit:
that part is important to avoid the kernel to "ignore" the MP_FAIL
received before being fully established.

Also, when thinking about that (but not checking the code), is the
modification you did above to fallback directly when an MP_FAIL is
received not enough?

Or maybe only __mptcp_check_fallback() should be added, and no need to
look at allow_subflows? (then patch 2/3 is not needed).

WDYT?

>  static inline bool __mptcp_has_initial_subflow(const struct mptcp_sock *msk)
>  {
>  	struct sock *ssk = READ_ONCE(msk->first);

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.