* [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL
@ 2026-07-15 6:18 Chenguang Zhao
2026-07-15 6:18 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Chenguang Zhao
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Chenguang Zhao @ 2026-07-15 6:18 UTC (permalink / raw)
To: mptcp; +Cc: chenguang.zhao, Chenguang Zhao
From: Chenguang Zhao <zhaochenguang@kylinos.cn>
When a valid MP_FAIL is received and infinite fallback is still allowed
(single contiguous subflow), RFC8684 section 3.7 requires leaving MPTCP
mode. Today the stack only clears allow_subflows and defers the real
fallback to the later infinite-map transmit path. If no data is sent in
between, the peer can still complete the 4th ACK as MPTCP and keep using
MPTCP options.
This series falls back immediately after the MP_FAIL response, rejects
later joins via mptcp_is_fully_established(), annotates allow_subflows
with READ/WRITE_ONCE, and adds an MPFailFallback MIB while switching to
mptcp_try_fallback().
Patch 1 fixes the fallback behaviour.
Patch 2 annotates allow_subflows accesses.
Patch 3 adds the dedicated MIB and uses the common fallback helper.
v1 -> v2:
- Split the previous single patch as suggested by Gang Yan
- Keep the MIB as a separate patch so the functional fix can be reviewed
on its own
Chenguang Zhao (3):
mptcp: fallback to TCP on MP_FAIL with a single subflow
mptcp: use READ/WRITE_ONCE for allow_subflows
mptcp: add MPFailFallback MIB for MP_FAIL TCP fallback
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(-)
---
v1:
https://lore.kernel.org/all/20260713064134.914507-1-chenguang.zhao@linux.dev/
--
2.25.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow 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 2026-08-10 18:26 ` Matthieu Baerts 2026-07-15 6:18 ` [PATCH mptcp-net v2 2/3] mptcp: use READ/WRITE_ONCE for allow_subflows Chenguang Zhao ` (2 subsequent siblings) 3 siblings, 1 reply; 7+ messages in thread From: Chenguang Zhao @ 2026-07-15 6:18 UTC (permalink / raw) To: mptcp; +Cc: chenguang.zhao, Chenguang Zhao 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 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow 2026-07-15 6:18 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Chenguang Zhao @ 2026-08-10 18:26 ` Matthieu Baerts 0 siblings, 0 replies; 7+ messages in thread From: Matthieu Baerts @ 2026-08-10 18:26 UTC (permalink / raw) To: Chenguang Zhao, mptcp; +Cc: Chenguang Zhao Hi Chenguang, On 15/07/2026 08:18, Chenguang Zhao wrote: > 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") Thank you for this fix. However, it is a bit big, and it might be difficult to backport. > 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). > + */ 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. ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH mptcp-net v2 2/3] mptcp: use READ/WRITE_ONCE for allow_subflows 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 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Chenguang Zhao @ 2026-07-15 6:18 ` 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 3 siblings, 1 reply; 7+ messages in thread From: Chenguang Zhao @ 2026-07-15 6:18 UTC (permalink / raw) To: mptcp; +Cc: chenguang.zhao, Chenguang Zhao From: Chenguang Zhao <zhaochenguang@kylinos.cn> mptcp_is_fully_established() may read allow_subflows without holding fallback_lock. Annotate all allow_subflows accesses with READ_ONCE and WRITE_ONCE to avoid data-race warnings and make the concurrency model explicit. Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn> --- net/mptcp/pm.c | 2 +- net/mptcp/protocol.c | 12 ++++++------ net/mptcp/protocol.h | 2 +- net/mptcp/subflow.c | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index c1f5c3ced4ee..869876a06493 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -884,7 +884,7 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq) 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) { diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 5b9522caaf43..13a8d16c657a 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; } @@ -2844,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; @@ -3055,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; @@ -3473,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); @@ -3873,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 03f0b33694d7..833c76c41768 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1291,7 +1291,7 @@ static inline bool mptcp_is_fully_established(struct sock *sk) return inet_sk_state_load(sk) == TCP_ESTABLISHED && READ_ONCE(msk->fully_established) && !__mptcp_check_fallback(msk) && - msk->allow_subflows; + READ_ONCE(msk->allow_subflows); } static inline bool __mptcp_has_initial_subflow(const struct mptcp_sock *msk) 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 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-net v2 2/3] mptcp: use READ/WRITE_ONCE for allow_subflows 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 0 siblings, 0 replies; 7+ messages in thread From: Matthieu Baerts @ 2026-08-10 18:26 UTC (permalink / raw) To: Chenguang Zhao, mptcp; +Cc: Chenguang Zhao Hi Chenguang, On 15/07/2026 08:18, Chenguang Zhao wrote: > From: Chenguang Zhao <zhaochenguang@kylinos.cn> > > mptcp_is_fully_established() may read allow_subflows without holding > fallback_lock. Annotate all allow_subflows accesses with READ_ONCE and > WRITE_ONCE to avoid data-race warnings and make the concurrency model > explicit. Is it not enough to use READ_ONCE only with mptcp_is_fully_established? The others read this variable with fallback_lock. WRITE_ONCE is still needed. Or maybe mptcp_is_fully_established should check allow_subflows while holding fallback_lock? (I didn't check what's best, but I don't think this helper is used in fast paths) Cheers, Matt -- Sponsored by the NGI0 Core fund. ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH mptcp-net v2 3/3] mptcp: add MPFailFallback MIB for MP_FAIL TCP fallback 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 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Chenguang Zhao 2026-07-15 6:18 ` [PATCH mptcp-net v2 2/3] mptcp: use READ/WRITE_ONCE for allow_subflows Chenguang Zhao @ 2026-07-15 6:18 ` Chenguang Zhao 2026-08-10 18:26 ` [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL Matthieu Baerts 3 siblings, 0 replies; 7+ messages in thread From: Chenguang Zhao @ 2026-07-15 6:18 UTC (permalink / raw) To: mptcp; +Cc: chenguang.zhao, Chenguang Zhao From: Chenguang Zhao <zhaochenguang@kylinos.cn> Track fallbacks triggered by receiving MP_FAIL with a dedicated MIB, and switch mptcp_pm_mp_fail_received() to mptcp_try_fallback() so the counter is updated and pending DATA_FIN is handled through the common path. InfiniteMapTx remains counted when the infinite mapping is transmitted. Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn> --- net/mptcp/mib.c | 1 + net/mptcp/mib.h | 1 + net/mptcp/pm.c | 28 ++++------------------------ 3 files changed, 6 insertions(+), 24 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 869876a06493..82d3dbedd603 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -906,33 +906,13 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq) 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(). + * 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. */ - 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); + if (!mptcp_try_fallback(sk, MPTCP_MIB_MPFAILFALLBACK)) { 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"); -- 2.25.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL 2026-07-15 6:18 [PATCH mptcp-net v2 0/3] mptcp: fix TCP fallback on single-subflow MP_FAIL Chenguang Zhao ` (2 preceding siblings ...) 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 ` Matthieu Baerts 3 siblings, 0 replies; 7+ messages in thread From: Matthieu Baerts @ 2026-08-10 18:26 UTC (permalink / raw) To: Chenguang Zhao, mptcp; +Cc: Chenguang Zhao Hi Chenguang, On 15/07/2026 08:18, Chenguang Zhao wrote: > From: Chenguang Zhao <zhaochenguang@kylinos.cn> > > When a valid MP_FAIL is received and infinite fallback is still allowed > (single contiguous subflow), RFC8684 section 3.7 requires leaving MPTCP > mode. Today the stack only clears allow_subflows and defers the real > fallback to the later infinite-map transmit path. If no data is sent in > between, the peer can still complete the 4th ACK as MPTCP and keep using > MPTCP options. > > This series falls back immediately after the MP_FAIL response, rejects > later joins via mptcp_is_fully_established(), annotates allow_subflows > with READ/WRITE_ONCE, and adds an MPFailFallback MIB while switching to > mptcp_try_fallback(). > > Patch 1 fixes the fallback behaviour. > Patch 2 annotates allow_subflows accesses. > Patch 3 adds the dedicated MIB and uses the common fallback helper. Thank you for this series and your patience. It looks like the CI didn't manage to validate this. Do you mind rebasing it on top of the 'export' (or 'for-review') branch please? I also have a couple of comments/questions on the different patches if you don't mind. Cheers, Matt -- Sponsored by the NGI0 Core fund. ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-10 18:26 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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 ` [PATCH mptcp-net v2 1/3] mptcp: fallback to TCP on MP_FAIL with a single subflow Chenguang Zhao 2026-08-10 18:26 ` 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
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.