* [PATCH mptcp-net v8 1/2] mptcp: pm: in-kernel: fullmesh: skip closed subflows
2026-10-24 11:18 [PATCH mptcp-net v8 0/2] mptcp: pm: in-kernel: fullmesh: re-add of the initial subflow endp Abdul Wasey
@ 2026-10-24 11:18 ` Abdul Wasey
2026-10-24 11:18 ` [PATCH mptcp-net v8 2/2] selftests: mptcp: join: re-add the initial subflow endp with fullmesh Abdul Wasey
2026-10-24 12:39 ` [PATCH mptcp-net v8 0/2] mptcp: pm: in-kernel: fullmesh: re-add of the initial subflow endp MPTCP CI
2 siblings, 0 replies; 4+ messages in thread
From: Abdul Wasey @ 2026-10-24 11:18 UTC (permalink / raw)
To: mptcp; +Cc: Matthieu Baerts, Mat Martineau, Geliang Tang, Paolo Abeni, stable
With fullmesh endpoints, if the endpoint used by the initial subflow is
removed and then added back, no new subflow is created from it: the
connection never uses this local address again.
The initial subflow is kept in the conn_list once closed. If it was
still established when closed, it keeps the local ID 0, which is also
the ID used by the re-added endpoint, and
fill_remote_addresses_fullmesh() then marks its remote ID as
unavailable. If it was already in TCP_CLOSE, e.g. after a reset, it is
disconnected and its context is reset: the 'closing' flag and the remote
ID are cleared, and its destination port is zeroed. It is then picked as
a remote address, with the port 0 and the ID 0, which also marks the
remote ID 0 as unavailable. In both cases, when the peer has a single
address, all subflows use the remote ID 0, and no valid remote address
is left.
Skip the subflows being closed or in TCP_CLOSE state, both when looking
for the remote IDs already in use and when picking the remote addresses.
__mptcp_subflow_active() cannot be used here: subflows still being
established have to keep their remote ID marked as in use, to avoid
creating duplicated subflows.
When no subflow is left to take the remote ID 0 address from, e.g. when
the initial subflow was the only one, use the remote address of the MPTCP
socket, as fill_remote_addr() does for non-fullmesh endpoints. Before,
re-adding the initial endpoint created no subflow in this case either,
and another endpoint only worked by picking the closed initial subflow.
Without the fullmesh flag, the remote address is taken from the MPTCP
socket, so this issue is specific to fullmesh endpoints.
Fixes: 045e9d812868 ("mptcp: fix duplicate subflow creation")
Cc: stable@vger.kernel.org
Signed-off-by: Abdul Wasey <w453y.me@gmail.com>
---
net/mptcp/pm_kernel.c | 33 ++++++++++++++++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c
index 1a7750813235..c240c1056a59 100644
--- a/net/mptcp/pm_kernel.c
+++ b/net/mptcp/pm_kernel.c
@@ -199,6 +199,16 @@ fill_remote_addr(struct mptcp_sock *msk, struct mptcp_addr_info *local,
return 1;
}
+/* Closed subflows can stay in the conn_list, e.g. the initial one: their
+ * addresses and IDs are not reliable, possibly even reset.
+ */
+static bool mptcp_subflow_is_closed(struct mptcp_subflow_context *subflow)
+{
+ struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
+
+ return subflow->closing || inet_sk_state_load(ssk) == TCP_CLOSE;
+}
+
static unsigned int
fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
struct mptcp_addr_info *local,
@@ -216,11 +226,18 @@ fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
* already created by incoming ADD_ADDR
*/
bitmap_zero(unavail_id, MPTCP_PM_MAX_ADDR_ID + 1);
- mptcp_for_each_subflow(msk, subflow)
+ mptcp_for_each_subflow(msk, subflow) {
+ if (mptcp_subflow_is_closed(subflow))
+ continue;
+
if (READ_ONCE(subflow->local_id) == local->id)
__set_bit(subflow->remote_id, unavail_id);
+ }
mptcp_for_each_subflow(msk, subflow) {
+ if (mptcp_subflow_is_closed(subflow))
+ continue;
+
ssk = mptcp_subflow_tcp_sock(subflow);
mptcp_remote_address((struct sock_common *)ssk, &addrs[i]);
addrs[i].id = READ_ONCE(subflow->remote_id);
@@ -243,6 +260,20 @@ fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
break;
}
+ /* The initial subflow can be closed: like fill_remote_addr(), use the
+ * remote address of the MPTCP socket for the remote ID 0.
+ */
+ if (!deny_id0 && !test_bit(0, unavail_id) && i < addrs_size &&
+ msk->pm.extra_subflows < limit_extra_subflows) {
+ struct mptcp_addr_info remote = { 0 };
+
+ mptcp_remote_address((struct sock_common *)sk, &remote);
+ if (mptcp_pm_addr_families_match(sk, local, &remote)) {
+ addrs[i++] = remote;
+ msk->pm.extra_subflows++;
+ }
+ }
+
return i;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH mptcp-net v8 2/2] selftests: mptcp: join: re-add the initial subflow endp with fullmesh
2026-10-24 11:18 [PATCH mptcp-net v8 0/2] mptcp: pm: in-kernel: fullmesh: re-add of the initial subflow endp Abdul Wasey
2026-10-24 11:18 ` [PATCH mptcp-net v8 1/2] mptcp: pm: in-kernel: fullmesh: skip closed subflows Abdul Wasey
@ 2026-10-24 11:18 ` Abdul Wasey
2026-10-24 12:39 ` [PATCH mptcp-net v8 0/2] mptcp: pm: in-kernel: fullmesh: re-add of the initial subflow endp MPTCP CI
2 siblings, 0 replies; 4+ messages in thread
From: Abdul Wasey @ 2026-10-24 11:18 UTC (permalink / raw)
To: mptcp; +Cc: Matthieu Baerts, Mat Martineau, Geliang Tang, Paolo Abeni, stable
The "delete and re-add" test validates the removal and the re-addition
of the endpoint used by the initial subflow, but without the fullmesh
flag.
Do the same with the fullmesh flag: with a second subflow, after a reset
of the initial subflow, and with the initial subflow only, then re-adding
its endpoint or adding another one. Without the previous patch, no new
subflow is created, except when adding another endpoint.
The 'Fixes' tag here below is the same as the one from the previous
commit: this patch here is not fixing anything wrong in the selftests,
but it validates the previous fix for an issue introduced by this commit
ID.
Fixes: 045e9d812868 ("mptcp: fix duplicate subflow creation")
Cc: stable@vger.kernel.org
Signed-off-by: Abdul Wasey <w453y.me@gmail.com>
---
.../testing/selftests/net/mptcp/mptcp_join.sh | 133 ++++++++++++++++++
1 file changed, 133 insertions(+)
diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
index f4d8afaeda4f..65ba01356112 100755
--- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
+++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
@@ -4424,6 +4424,139 @@ endpoint_tests()
chk_rm_nr 4 4
fi
+ # remove and re-add the endpoint of the initial subflow, with fullmesh
+ if reset_with_events "delete and re-add, fullmesh" &&
+ continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then
+ pm_nl_set_limits $ns1 0 2
+ pm_nl_set_limits $ns2 0 2
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ pm_nl_add_endpoint $ns2 10.0.2.2 id 2 dev ns2eth2 flags subflow,fullmesh
+ { timeout_test=120 test_linkfail=128 speed=5 \
+ run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null
+ local tests_pid=$!
+
+ wait_event ns2 MPTCP_LIB_EVENT_ESTABLISHED 1
+ wait_mpj 1
+ chk_subflow_nr "before delete id 0" 2
+
+ pm_nl_del_endpoint $ns2 1 10.0.1.2
+ sleep 0.5
+ chk_subflow_nr "after delete id 0" 1
+
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ wait_mpj 2
+ chk_subflow_nr "after re-add id 0" 2
+
+ mptcp_lib_kill_group_wait $tests_pid
+ kill_events_pids
+
+ chk_join_nr 2 2 2
+ chk_rm_nr 1 1
+ fi
+
+ # same, but the initial subflow is reset before removing its endpoint
+ if reset_with_events "delete and re-add, fullmesh, after reset" &&
+ continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then
+ pm_nl_set_limits $ns1 0 2
+ pm_nl_set_limits $ns2 0 2
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ pm_nl_add_endpoint $ns2 10.0.2.2 id 2 dev ns2eth2 flags subflow,fullmesh
+ { timeout_test=120 test_linkfail=128 speed=5 \
+ run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null
+ local tests_pid=$!
+
+ wait_event ns2 MPTCP_LIB_EVENT_ESTABLISHED 1
+ wait_mpj 1
+ chk_subflow_nr "before reset" 2
+
+ local filtered=0
+ init_nftables
+ if ip netns exec "${ns1}" nft add rule ip filter INPUT \
+ ip saddr "10.0.1.2" meta l4proto tcp \
+ tcp option mptcp exists reject with tcp reset; then
+ filtered=1
+ wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 1
+ ip netns exec "${ns1}" nft flush chain ip filter INPUT
+ chk_evt_nr ns2 MPTCP_LIB_EVENT_SUB_CLOSED 1 error 1
+
+ pm_nl_del_endpoint $ns2 1 10.0.1.2
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ wait_mpj 2
+ chk_mptcp_info subflows 2 subflows 2
+ chk_join_nr 2 2 2
+ fi
+
+ mptcp_lib_kill_group_wait $tests_pid
+ kill_events_pids
+ # after the cleanup: this exits when all features are expected
+ [ "${filtered}" = 1 ] || mark_as_skipped "unable to set the filter rules"
+ fi
+
+ # only the initial subflow: once its endpoint is removed, no live subflow
+ # is left to take the remote address from
+ if reset_with_events "delete and re-add, fullmesh, single subflow" &&
+ continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then
+ pm_nl_set_limits $ns1 0 2
+ pm_nl_set_limits $ns2 0 2
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ { timeout_test=120 test_linkfail=128 speed=5 \
+ run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null
+ local tests_pid=$!
+
+ wait_event ns2 MPTCP_LIB_EVENT_ESTABLISHED 1
+ # the PM worker accounts the initial subflow endpoint after the
+ # ESTABLISHED event, in the same run where it creates the subflows:
+ # a join on another endpoint shows it is done, then remove it
+ pm_nl_add_endpoint $ns2 10.0.3.2 id 3 dev ns2eth3 flags subflow
+ wait_mpj 1
+ pm_nl_del_endpoint $ns2 3 10.0.3.2
+ wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 1
+ pm_nl_del_endpoint $ns2 1 10.0.1.2
+ wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2
+ sleep 0.5
+ chk_subflow_nr "after delete" 0
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ wait_mpj 2
+ chk_mptcp_info subflows 1 subflows 1
+
+ mptcp_lib_kill_group_wait $tests_pid
+ kill_events_pids
+
+ chk_join_nr 2 2 2
+ fi
+
+ # same, but another endpoint is added after the removal
+ if reset_with_events "delete and add another, fullmesh, single subflow" &&
+ continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then
+ pm_nl_set_limits $ns1 0 2
+ pm_nl_set_limits $ns2 0 2
+ pm_nl_add_endpoint $ns2 10.0.1.2 id 1 dev ns2eth1 flags subflow,fullmesh
+ { timeout_test=120 test_linkfail=128 speed=5 \
+ run_tests $ns1 $ns2 10.0.1.1 & } 2>/dev/null
+ local tests_pid=$!
+
+ wait_event ns2 MPTCP_LIB_EVENT_ESTABLISHED 1
+ # the PM worker accounts the initial subflow endpoint after the
+ # ESTABLISHED event, in the same run where it creates the subflows:
+ # a join on another endpoint shows it is done, then remove it
+ pm_nl_add_endpoint $ns2 10.0.3.2 id 3 dev ns2eth3 flags subflow
+ wait_mpj 1
+ pm_nl_del_endpoint $ns2 3 10.0.3.2
+ wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 1
+ pm_nl_del_endpoint $ns2 1 10.0.1.2
+ wait_event ns2 MPTCP_LIB_EVENT_SUB_CLOSED 2
+ sleep 0.5
+ chk_subflow_nr "after delete" 0
+ pm_nl_add_endpoint $ns2 10.0.2.2 id 2 dev ns2eth2 flags subflow,fullmesh
+ wait_mpj 2
+ chk_mptcp_info subflows 1 subflows 1
+
+ mptcp_lib_kill_group_wait $tests_pid
+ kill_events_pids
+
+ chk_join_nr 2 2 2
+ fi
+
# remove and re-add
if reset_with_events "delete re-add signal" &&
continue_if mptcp_lib_kallsyms_has "subflow_rebuild_header$"; then
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread