MPTCP Linux Development
 help / color / mirror / Atom feed
* [PATCH mptcp-next v5 00/16] MPTCP sockmap support
@ 2026-09-13 10:14 Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 01/16] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
                   ` (17 more replies)
  0 siblings, 18 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

v5:
 - Patches 1-4 are from "Reduce the differences between TCP and MPTCP for
   TLS usage" set.
 - More patches for sockmap support.
 - Rename this set from "implement psock_update_sk_prot" to "MPTCP sockmap
   support"

v4:
 - Address Mat's comments: implement .read_skb to make the selftests run.
 - Move mptcp_bpf_update_proto() from protocol.c to bpf.c.
 - Include the "implement .splice_eof" patches in this series, as they
   both modify mptcp_prot, mptcp_stream_ops and mptcp_v6_stream_ops.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787554581.git.tanggeliang@kylinos.cn/

v3:
 - Address the comments by ai review.
   - use WRITE_ONCE to set sk->sk_write_space.
   - update the selftest, set a new key for client socket.
 - Fix line length warnings.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1773826662.git.tanggeliang@kylinos.cn/

RESEND:
 - rebased.
 - to trigger ai review.

v2:
 - Include mptcp_bpf_update_proto within CONFIG_BPF_SYSCALL to fix the
compilation errors reported by the kernel test robot.
 - Add checks for IS_ENABLED(CONFIG_MPTCP_IPV6).

Implement psock_update_sk_prot, for basic MPTCP BPF SOCKMAP support.

Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/521

Geliang Tang (15):
  mptcp: add sendmsg_locked to proto_ops
  mptcp: track app-limited state in mptcp_sendmsg
  selftests: mptcp: sockopt: check app_limited
  bpf: drop duplicate check_app_limited in tcp_bpf_push
  mptcp: implement psock_update_sk_prot for sockmap
  mptcp: add sock_map_update BPF helper
  selftests/bpf: enable MPTCP support in sockmap tests
  mptcp: implement read_skb for sockmap stream verdict
  bpf: export and generalize tcp_bpf_ioctl
  mptcp: add TCP_REPAIR sockopt support
  selftests/bpf: add MPTCP coverage to sockmap_basic
  mptcp: add sk_is_msk() helper and use it in sockmap
  mptcp: add SO_ATTACH_REUSEPORT_EBPF support
  mptcp: add sk_select_reuseport BPF helper
  selftests/bpf: add MPTCP coverage to sockmap_listen

Paolo Abeni (1):
  mptcp: defer read_sock cleanup to mptcp_worker

 include/linux/bpf.h                           |  13 ++
 include/net/mptcp.h                           |  12 ++
 include/net/tcp.h                             |   7 +
 include/uapi/linux/bpf.h                      |  19 ++
 kernel/bpf/verifier.c                         |   8 +-
 net/core/filter.c                             |  60 +++++-
 net/core/sock_map.c                           |  14 +-
 net/ipv4/tcp.c                                |   9 +-
 net/ipv4/tcp_bpf.c                            |  40 ++--
 net/mptcp/bpf.c                               | 172 ++++++++++++++++++
 net/mptcp/protocol.c                          | 114 ++++++++++--
 net/mptcp/protocol.h                          |  20 ++
 net/mptcp/sockopt.c                           |  21 ++-
 .../testing/selftests/bpf/prog_tests/mptcp.c  |  27 +--
 .../selftests/bpf/prog_tests/socket_helpers.h |  25 ++-
 .../selftests/bpf/prog_tests/sockmap_basic.c  |  33 +++-
 .../selftests/bpf/prog_tests/sockmap_listen.c | 122 ++++++++-----
 .../selftests/bpf/progs/mptcp_sockmap.c       |   8 +-
 .../selftests/bpf/progs/test_sockmap_listen.c |  25 +++
 .../selftests/net/mptcp/mptcp_sockopt.c       |   1 +
 20 files changed, 622 insertions(+), 128 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 01/16] mptcp: defer read_sock cleanup to mptcp_worker
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 02/16] mptcp: add sendmsg_locked to proto_ops Geliang Tang
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Paolo Abeni, Geliang Tang

From: Paolo Abeni <pabeni@redhat.com>

When MPTCP carries TLS, the data path runs under mptcp_data_lock().
Reaching sk->sk_data_ready(sk) synchronously ends up at
tls_strp_check_rcv() -> mptcp_recv_skb() -> mptcp_move_skbs(), which
calls mptcp_data_lock() on the same sk and recurses on sk_lock.slock.

The TLS path is not the only constraint: before the mptcp_recv_skb()
calls, the TLS code would also reach __mptcp_read_sock(), which calls
mptcp_rcv_space_adjust() and mptcp_cleanup_rbuf(). Both require holding
the msk socket lock in process context, while the mptcp/TLS caller is
in BH scope.

Fix this by deferring sk->sk_data_ready(sk) to mptcp_worker() via a new
MPTCP_WORK_READ_COMPLETE bit, reusing the existing mptcp_schedule_work()/
mptcp_cancel_work() infrastructure. The wakeup bit is consumed after the
SOCK_DEAD && TCP_CLOSE destroy branch, so a socket that reaches the destroy
path drops the pending wakeup rather than running it post-free.

Co-developed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
 net/mptcp/protocol.c | 33 ++++++++++++++++++++++++---------
 net/mptcp/protocol.h |  2 ++
 2 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 0b24e0afedfb..be1b8e626653 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3118,6 +3118,20 @@ static void mptcp_backlog_purge(struct sock *sk)
 	sk_mem_reclaim(sk);
 }
 
+static void mptcp_read_complete(struct sock *sk)
+{
+	struct mptcp_sock *msk = mptcp_sk(sk);
+	int read_copied;
+
+	mptcp_data_lock(sk);
+	read_copied = msk->read_copied;
+	msk->read_copied = 0;
+	mptcp_data_unlock(sk);
+
+	mptcp_rcv_space_adjust(msk, read_copied);
+	mptcp_cleanup_rbuf(msk, read_copied);
+}
+
 static void mptcp_do_fastclose(struct sock *sk)
 {
 	struct mptcp_subflow_context *subflow, *tmp;
@@ -3193,6 +3207,9 @@ static void mptcp_worker(struct work_struct *work)
 	if (test_and_clear_bit(MPTCP_WORK_RTX, &msk->flags))
 		__mptcp_retrans(sk);
 
+	if (test_and_clear_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags))
+		mptcp_read_complete(sk);
+
 	fail_tout = msk->first ? READ_ONCE(mptcp_subflow_ctx(msk->first)->fail_tout) : 0;
 	if (fail_tout && time_after(jiffies, fail_tout))
 		mptcp_mp_fail_no_response(msk);
@@ -4565,13 +4582,9 @@ static __poll_t mptcp_poll(struct file *file, struct socket *sock,
 
 static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
 {
-	struct mptcp_sock *msk = mptcp_sk(sk);
 	struct sk_buff *skb;
 	u32 offset;
 
-	if (!list_empty(&msk->backlog_list))
-		mptcp_move_skbs(sk);
-
 	while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
 		offset = MPTCP_SKB_CB(skb)->offset;
 		if (offset < skb->len) {
@@ -4586,6 +4599,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
 /*
  * Note:
  *	- It is assumed that the socket was locked by the caller.
+ *	- Can be invoked in BH scope.
  */
 static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
 			     sk_read_actor_t recv_actor, bool noack)
@@ -4595,8 +4609,6 @@ static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
 	int copied = 0;
 	u32 offset;
 
-	msk_owned_by_me(msk);
-
 	if (sk->sk_state == TCP_LISTEN)
 		return -ENOTCONN;
 	while ((skb = mptcp_recv_skb(sk, &offset)) != NULL) {
@@ -4629,11 +4641,14 @@ static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
 	if (noack)
 		goto out;
 
-	mptcp_rcv_space_adjust(msk, copied);
-
+	/* The backlog flushing is only needed when some data is actually
+	 * moved and will take place in the workers's release callback.
+	 */
 	if (copied > 0) {
 		mptcp_recv_skb(sk, &offset);
-		mptcp_cleanup_rbuf(msk, copied);
+		msk->read_copied += copied;
+		set_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags);
+		mptcp_schedule_work(sk);
 	}
 out:
 	return copied;
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 4453db7a33cf..3f8997f15bec 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -117,6 +117,7 @@
 #define MPTCP_FALLBACK_DONE	2
 #define MPTCP_WORK_CLOSE_SUBFLOW 3
 #define MPTCP_RTX_ENABLED	4
+#define MPTCP_WORK_READ_COMPLETE 5
 
 /* MPTCP socket release cb flags */
 #define MPTCP_PUSH_PENDING	1
@@ -323,6 +324,7 @@ struct mptcp_sock {
 	u32		last_data_sent;
 	u32		last_data_recv;
 	u32		last_ack_recv;
+	int		read_copied;
 	unsigned long	timer_ival;
 	u32		token;
 	unsigned long	flags;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 02/16] mptcp: add sendmsg_locked to proto_ops
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 01/16] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 03/16] mptcp: track app-limited state in mptcp_sendmsg Geliang Tang
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

MPTCP currently provides a standard sendmsg() implementation which
acquires and releases the socket lock internally. However, certain
upper layers need to call the sendmsg method while the socket lock
is already held.

Split the existing mptcp_sendmsg() into mptcp_sendmsg_locked() which
assumes the caller holds the socket lock, and a tiny wrapper
mptcp_sendmsg() that acquires the lock and calls the locked version.

Expose .sendmsg_locked in both mptcp_stream_ops and mptcp_v6_stream_ops.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 net/mptcp/protocol.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index be1b8e626653..29e2cd2be815 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -2030,7 +2030,7 @@ static void mptcp_rps_record_subflows(const struct mptcp_sock *msk)
 	}
 }
 
-static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+static int mptcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t len)
 {
 	struct mptcp_sock *msk = mptcp_sk(sk);
 	struct page_frag *pfrag;
@@ -2042,8 +2042,6 @@ static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 	msg->msg_flags &= MSG_MORE | MSG_DONTWAIT | MSG_NOSIGNAL |
 			  MSG_FASTOPEN | MSG_EOR;
 
-	lock_sock(sk);
-
 	mptcp_rps_record_subflows(msk);
 
 	if (unlikely(inet_test_bit(DEFER_CONNECT, sk) ||
@@ -2159,7 +2157,6 @@ static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 	}
 
 out:
-	release_sock(sk);
 	return copied;
 
 do_error:
@@ -2170,6 +2167,17 @@ static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 	goto out;
 }
 
+static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+{
+	int ret;
+
+	lock_sock(sk);
+	ret = mptcp_sendmsg_locked(sk, msg, len);
+	release_sock(sk);
+
+	return ret;
+}
+
 static void mptcp_rcv_space_adjust(struct mptcp_sock *msk, int copied);
 
 static void mptcp_eat_recv_skb(struct sock *sk, struct sk_buff *skb)
@@ -4795,6 +4803,7 @@ static const struct proto_ops mptcp_stream_ops = {
 	.set_rcvlowat	   = mptcp_set_rcvlowat,
 	.read_sock	   = mptcp_read_sock,
 	.splice_read	   = mptcp_splice_read,
+	.sendmsg_locked	   = mptcp_sendmsg_locked,
 };
 
 static struct inet_protosw mptcp_protosw = {
@@ -4907,6 +4916,7 @@ static const struct proto_ops mptcp_v6_stream_ops = {
 	.set_rcvlowat	   = mptcp_set_rcvlowat,
 	.read_sock	   = mptcp_read_sock,
 	.splice_read	   = mptcp_splice_read,
+	.sendmsg_locked	   = mptcp_sendmsg_locked,
 };
 
 static struct proto mptcp_v6_prot;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 03/16] mptcp: track app-limited state in mptcp_sendmsg
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 01/16] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 02/16] mptcp: add sendmsg_locked to proto_ops Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 04/16] selftests: mptcp: sockopt: check app_limited Geliang Tang
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

The application-limited accounting in TCP is updated by
tcp_rate_check_app_limited(), which currently takes a struct sock * and
internally calls tcp_sk(). MPTCP needs to apply the same accounting to
each subflow individually - every subflow is an independent TCP socket
with its own tp->app_limited / delivered state - so wrapping the call as
a struct sock * -> tcp_sk() helper is awkward at the call site.

Split the existing function: keep the logic as
tcp_sock_rate_check_app_limited(struct tcp_sock *tp), and turn
tcp_rate_check_app_limited(struct sock *) into a thin wrapper so the
exported API is unchanged for other TCP users.

Then add mptcp_sock_rate_check_app_limited() that walks every subflow of
the mptcp_sock and runs tcp_sock_rate_check_app_limited() under each
subflow's socket lock. Invoke it from mptcp_sendmsg() right after the
send-side setup, so the delivery-rate app_limited state stays in sync
with what the application actually has to send across each subflow.

With this in place, TCP_INFO.tcpi_delivery_rate_app_limited is reported
correctly for MPTCP connections instead of being left at 0.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/net/tcp.h    |  1 +
 net/ipv4/tcp.c       |  9 +++++++--
 net/mptcp/protocol.c | 18 ++++++++++++++++++
 3 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 5e5f5f9b89a3..14baee01c74b 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -851,6 +851,7 @@ static inline int tcp_bound_to_half_wnd(struct tcp_sock *tp, int pktsize)
 
 /* tcp.c */
 void tcp_get_info(struct sock *, struct tcp_info *);
+void tcp_sock_rate_check_app_limited(struct tcp_sock *tp);
 void tcp_rate_check_app_limited(struct sock *sk);
 
 /* Read 'sendfile()'-style from a TCP socket */
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 1dd80a75f223..cd4347bfa029 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1096,9 +1096,9 @@ int tcp_sendmsg_fastopen(struct sock *sk, struct msghdr *msg, int *copied,
 }
 
 /* If a gap is detected between sends, mark the socket application-limited. */
-void tcp_rate_check_app_limited(struct sock *sk)
+void tcp_sock_rate_check_app_limited(struct tcp_sock *tp)
 {
-	struct tcp_sock *tp = tcp_sk(sk);
+	struct sock *sk = (struct sock *)tp;
 
 	if (/* We have less than one packet to send. */
 	    tp->write_seq - tp->snd_nxt < tp->mss_cache &&
@@ -1111,6 +1111,11 @@ void tcp_rate_check_app_limited(struct sock *sk)
 		tp->app_limited =
 			(tp->delivered + tcp_packets_in_flight(tp)) ? : 1;
 }
+
+void tcp_rate_check_app_limited(struct sock *sk)
+{
+	tcp_sock_rate_check_app_limited(tcp_sk(sk));
+}
 EXPORT_SYMBOL_GPL(tcp_rate_check_app_limited);
 
 int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 29e2cd2be815..1a19e494c609 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -2030,6 +2030,21 @@ static void mptcp_rps_record_subflows(const struct mptcp_sock *msk)
 	}
 }
 
+static void mptcp_rate_check_app_limited(struct sock *sk)
+{
+	struct mptcp_sock *msk = mptcp_sk(sk);
+	struct mptcp_subflow_context *subflow;
+
+	mptcp_for_each_subflow(msk, subflow) {
+		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
+		bool slow;
+
+		slow = lock_sock_fast_nested(ssk);
+		tcp_sock_rate_check_app_limited(tcp_sk(ssk));
+		unlock_sock_fast(ssk, slow);
+	}
+}
+
 static int mptcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t len)
 {
 	struct mptcp_sock *msk = mptcp_sk(sk);
@@ -2058,6 +2073,9 @@ static int mptcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t len)
 
 	timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
 
+	/* is sending application-limited? */
+	mptcp_rate_check_app_limited(sk);
+
 	if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) {
 		ret = sk_stream_wait_connect(sk, &timeo);
 		if (ret)
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 04/16] selftests: mptcp: sockopt: check app_limited
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (2 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 03/16] mptcp: track app-limited state in mptcp_sendmsg Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push Geliang Tang
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

connect_one_server() in mptcp_sockopt exchanges only a few packets between
the client and server, then closes the socket. After such a small transfer
the application has nothing further to send, so the connection is, by
definition, application-limited.

Extend the TCP_INFO readback at the end of the function to assert
s.tcp_info.tcpi_delivery_rate_app_limited == 1.

Without the preceding commit, mptcp_sendmsg() never updates the per-subflow
app-limited state, and this field stays at 0 - the assertion would fail.
With it in place, the value is forced to 1, turning this into a regression
guard for the subflow-side application-limited accounting.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 tools/testing/selftests/net/mptcp/mptcp_sockopt.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
index d68515b7903b..8d712bdb4325 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
@@ -640,6 +640,7 @@ static void connect_one_server(int fd, int pipefd)
 		total += 1; /* sequence advances due to FIN */
 
 	assert(s.mptcpi_rcv_delta == (uint64_t)total);
+	assert(s.tcp_info.tcpi_delivery_rate_app_limited == 1);
 	close(fd);
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (3 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 04/16] selftests: mptcp: sockopt: check app_limited Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 18:18   ` Matthieu Baerts
  2026-09-13 10:14 ` [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap Geliang Tang
                   ` (12 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

When the sendpage->MSG_SPLICE_PAGES migration series replaced
do_tcp_sendpages() with direct tcp_sendmsg_locked() calls, callers
that had used do_tcp_sendpages() kept an explicit
tcp_rate_check_app_limited(sk) that was originally needed to cover
do_tcp_sendpages() (which did not call tcp_rate_check_app_limited()
itself). After the inlining, tcp_sendmsg_locked() always provides
the check, and the outer call became redundant.

The site changed here, tcp_bpf_push(), is a MSG_SPLICE_PAGES loop
that holds the socket lock and only iterates when size > 0;
tcp_sendmsg_locked() is invoked on every iteration with state
identical to what the outer call sees, so dropping the outer call
is safe and behavior-preserving.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 net/ipv4/tcp_bpf.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index 2e234d155b5e..d5fcf3ce4861 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -108,7 +108,6 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 		off  = sge->offset;
 		page = sg_page(sge);
 
-		tcp_rate_check_app_limited(sk);
 retry:
 		msghdr.msg_flags = flags | MSG_SPLICE_PAGES;
 		has_tx_ulp = tls_sw_has_ctx_tx(sk);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (4 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:46   ` sashiko-bot
  2026-09-13 18:22   ` Matthieu Baerts
  2026-09-13 10:14 ` [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper Geliang Tang
                   ` (11 subsequent siblings)
  17 siblings, 2 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang, kernel test robot, Cong Wang

From: Geliang Tang <tanggeliang@kylinos.cn>

This patch adds basic MPTCP support for BPF sockmap by implementing the
psock_update_sk_prot callback (mptcp_bpf_update_proto). This allows MPTCP
sockets to be added to sockmap and enables the sk_skb stream_verdict
redirect path via the read_skb callback. Separate protocol structures are
maintained for IPv4/IPv6 and BASE/TX/RX/TXRX configurations, mirroring
tcp_bpf_update_proto(). The IPv6 variant is lazily rebuilt via
mptcp_bpf_check_v6_needs_rebuild() when the underlying protocol ops change.

MPTCP delegates to the original protocol operations so that MPTCP-specific
logic (multi-path scheduling in sendmsg, ordered reassembly in recvmsg) is
preserved. recvmsg is overridden in all BPF configurations with
mptcp_bpf_recvmsg, which checks the psock ingress queue first and falls
back to mptcp_recvmsg via the shared __tcp_bpf_recvmsg() helper. sendmsg
is overridden in the TX/TXRX configurations with mptcp_bpf_sendmsg, which
processes sk_msg redirect before delegating to mptcp_sendmsg via
__tcp_bpf_sendmsg().

Export mptcp_sendmsg, mptcp_recvmsg and mptcp_prot from protocol.c so they
can be referenced by bpf.c.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202512261144.DxrvwMS3-lkp@intel.com/
Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/521
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/net/tcp.h    |   5 ++
 net/ipv4/tcp_bpf.c   |  29 +++++++---
 net/mptcp/bpf.c      | 129 +++++++++++++++++++++++++++++++++++++++++++
 net/mptcp/protocol.c |  10 ++--
 net/mptcp/protocol.h |  18 ++++++
 5 files changed, 179 insertions(+), 12 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 14baee01c74b..d9c0214328e3 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -2873,6 +2873,11 @@ static inline void tcp_eat_skb(struct sock *sk, struct sk_buff *skb)
 
 int tcp_bpf_sendmsg_redir(struct sock *sk, bool ingress,
 			  struct sk_msg *msg, u32 bytes, int flags);
+int
+__tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags,
+		  int (*recvmsg)(struct sock *, struct msghdr *, size_t, int));
+int __tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size,
+		      int (*sendmsg)(struct sock *, struct msghdr *, size_t));
 #endif /* CONFIG_NET_SOCK_MSG */
 
 #if !defined(CONFIG_BPF_SYSCALL) || !defined(CONFIG_NET_SOCK_MSG)
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index d5fcf3ce4861..e006131696e9 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -119,7 +119,7 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 
 		bvec_set_page(&bvec, page, size, off);
 		iov_iter_bvec(&msghdr.msg_iter, ITER_SOURCE, &bvec, 1, size);
-		ret = tcp_sendmsg_locked(sk, &msghdr, size);
+		ret = sk->sk_socket->ops->sendmsg_locked(sk, &msghdr, size);
 		if (ret <= 0)
 			return ret;
 
@@ -364,8 +364,9 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
 	return 0;
 }
 
-static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
-			   int flags)
+int
+__tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags,
+		  int (*recvmsg)(struct sock *, struct msghdr *, size_t, int))
 {
 	struct sk_psock *psock;
 	int copied, ret;
@@ -378,11 +379,11 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 
 	psock = sk_psock_get(sk);
 	if (unlikely(!psock))
-		return tcp_recvmsg(sk, msg, len, flags);
+		return recvmsg(sk, msg, len, flags);
 	if (!skb_queue_empty(&sk->sk_receive_queue) &&
 	    sk_psock_queue_empty(psock)) {
 		sk_psock_put(sk, psock);
-		return tcp_recvmsg(sk, msg, len, flags);
+		return recvmsg(sk, msg, len, flags);
 	}
 	lock_sock(sk);
 msg_bytes_ready:
@@ -402,7 +403,7 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 				goto msg_bytes_ready;
 			release_sock(sk);
 			sk_psock_put(sk, psock);
-			return tcp_recvmsg(sk, msg, len, flags);
+			return recvmsg(sk, msg, len, flags);
 		}
 		copied = -EAGAIN;
 	}
@@ -414,6 +415,12 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	return ret;
 }
 
+static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
+			   int flags)
+{
+	return __tcp_bpf_recvmsg(sk, msg, len, flags, tcp_recvmsg);
+}
+
 static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 				struct sk_msg *msg, int *copied, int flags)
 {
@@ -530,7 +537,8 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 	return ret;
 }
 
-static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
+int __tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size,
+		      int (*sendmsg)(struct sock *, struct msghdr *, size_t))
 {
 	struct sk_msg tmp, *msg_tx = NULL;
 	int copied = 0, err = 0, ret = 0;
@@ -544,7 +552,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 
 	psock = sk_psock_get(sk);
 	if (unlikely(!psock))
-		return tcp_sendmsg(sk, msg, size);
+		return sendmsg(sk, msg, size);
 
 	lock_sock(sk);
 	timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
@@ -618,6 +626,11 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	return copied > 0 ? copied : err;
 }
 
+static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
+{
+	return __tcp_bpf_sendmsg(sk, msg, size, tcp_sendmsg);
+}
+
 enum {
 	TCP_BPF_IPV4,
 	TCP_BPF_IPV6,
diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
index 82b0ad25f700..662b80ce2cf6 100644
--- a/net/mptcp/bpf.c
+++ b/net/mptcp/bpf.c
@@ -13,6 +13,7 @@
 #include <linux/bpf_verifier.h>
 #include <linux/btf.h>
 #include <linux/btf_ids.h>
+#include <linux/skmsg.h>
 #include <net/bpf_sk_storage.h>
 #include "protocol.h"
 
@@ -361,3 +362,131 @@ static int __init bpf_mptcp_kfunc_init(void)
 	return ret;
 }
 late_initcall(bpf_mptcp_kfunc_init);
+
+enum {
+	MPTCP_BPF_IPV4,
+	MPTCP_BPF_IPV6,
+	MPTCP_BPF_NUM_PROTS,
+};
+
+enum {
+	MPTCP_BPF_BASE,
+	MPTCP_BPF_TX,
+	MPTCP_BPF_RX,
+	MPTCP_BPF_TXRX,
+	MPTCP_BPF_NUM_CFGS,
+};
+
+static struct proto mptcp_bpf_prots[MPTCP_BPF_NUM_PROTS][MPTCP_BPF_NUM_CFGS];
+
+static int mptcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
+			     int flags)
+{
+	return __tcp_bpf_recvmsg(sk, msg, len, flags, mptcp_recvmsg);
+}
+
+static int mptcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
+{
+	return __tcp_bpf_sendmsg(sk, msg, size, mptcp_sendmsg);
+}
+
+static void mptcp_bpf_rebuild_protos(struct proto prot[MPTCP_BPF_NUM_CFGS],
+				     struct proto *base)
+{
+	prot[MPTCP_BPF_BASE]			= *base;
+	prot[MPTCP_BPF_BASE].destroy		= sock_map_destroy;
+	prot[MPTCP_BPF_BASE].close		= sock_map_close;
+	prot[MPTCP_BPF_BASE].recvmsg		= mptcp_bpf_recvmsg;
+	prot[MPTCP_BPF_BASE].sock_is_readable	= sk_msg_is_readable;
+
+	prot[MPTCP_BPF_TX]			= prot[MPTCP_BPF_BASE];
+	prot[MPTCP_BPF_TX].sendmsg		= mptcp_bpf_sendmsg;
+
+	prot[MPTCP_BPF_RX]			= prot[MPTCP_BPF_BASE];
+
+	prot[MPTCP_BPF_TXRX]			= prot[MPTCP_BPF_TX];
+}
+
+#if IS_ENABLED(CONFIG_MPTCP_IPV6)
+static struct proto *mptcpv6_prot_saved __read_mostly;
+static DEFINE_SPINLOCK(mptcpv6_prot_lock);
+
+static void mptcp_bpf_check_v6_needs_rebuild(struct proto *ops)
+{
+	/* Load with acquire semantics to ensure we see the latest protocol
+	 * structure before checking for rebuild.
+	 */
+	if (unlikely(ops != smp_load_acquire(&mptcpv6_prot_saved))) {
+		spin_lock_bh(&mptcpv6_prot_lock);
+		if (likely(ops != mptcpv6_prot_saved)) {
+			struct proto *v6_prots;
+
+			v6_prots = mptcp_bpf_prots[MPTCP_BPF_IPV6];
+			mptcp_bpf_rebuild_protos(v6_prots, ops);
+			/* Ensure mptcpv6_prot_saved update is visible before
+			 * releasing lock
+			 */
+			smp_store_release(&mptcpv6_prot_saved, ops);
+		}
+		spin_unlock_bh(&mptcpv6_prot_lock);
+	}
+}
+
+static int mptcp_bpf_assert_proto_ops(struct proto *ops)
+{
+	/* In order to avoid retpoline, we make assumptions when we call
+	 * into ops if e.g. a psock is not present. Make sure they are
+	 * indeed valid assumptions.
+	 */
+	return ops->recvmsg == mptcp_recvmsg &&
+	       ops->sendmsg == mptcp_sendmsg ? 0 : -EOPNOTSUPP;
+}
+#endif
+
+int mptcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock,
+			   bool restore)
+{
+	int family = sk->sk_family == AF_INET6 ? MPTCP_BPF_IPV6 :
+						 MPTCP_BPF_IPV4;
+	int config = psock->progs.msg_parser   ? MPTCP_BPF_TX   :
+						 MPTCP_BPF_BASE;
+
+	if (psock->progs.stream_verdict || psock->progs.skb_verdict)
+		config = (config == MPTCP_BPF_TX) ? MPTCP_BPF_TXRX :
+						    MPTCP_BPF_RX;
+
+	if (restore) {
+		WRITE_ONCE(sk->sk_write_space, psock->saved_write_space);
+		/* Pairs with lockless read in sk_clone() */
+		sock_replace_proto(sk, psock->sk_proto);
+		return 0;
+	}
+
+#if IS_ENABLED(CONFIG_MPTCP_IPV6)
+	if (sk->sk_family == AF_INET6) {
+		if (mptcp_bpf_assert_proto_ops(psock->sk_proto))
+			return -EINVAL;
+
+		mptcp_bpf_check_v6_needs_rebuild(psock->sk_proto);
+	}
+#endif
+
+	/* Pairs with lockless read in sk_clone() */
+	sock_replace_proto(sk, &mptcp_bpf_prots[family][config]);
+	return 0;
+}
+
+void mptcp_bpf_clone(const struct sock *sk, struct sock *newsk)
+{
+	struct proto *prot = newsk->sk_prot;
+
+	if (is_insidevar(prot, mptcp_bpf_prots))
+		newsk->sk_prot = sk->sk_prot_creator;
+}
+
+static int __init mptcp_bpf_v4_build_proto(void)
+{
+	mptcp_bpf_rebuild_protos(mptcp_bpf_prots[MPTCP_BPF_IPV4], &mptcp_prot);
+	return 0;
+}
+late_initcall(mptcp_bpf_v4_build_proto);
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 1a19e494c609..3d3c5af7c266 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -2185,7 +2185,7 @@ static int mptcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t len)
 	goto out;
 }
 
-static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 {
 	int ret;
 
@@ -2436,8 +2436,7 @@ static unsigned int mptcp_inq_hint(const struct sock *sk)
 	return 0;
 }
 
-static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
-			 int flags)
+int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
 {
 	struct mptcp_sock *msk = mptcp_sk(sk);
 	struct scm_timestamping_internal tss;
@@ -3795,6 +3794,8 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
 	if (!nsk)
 		return NULL;
 
+	mptcp_bpf_clone(sk, nsk);
+
 #if IS_ENABLED(CONFIG_MPTCP_IPV6)
 	if (nsk->sk_family == AF_INET6)
 		inet_sk(nsk)->pinet6 = mptcp_inet6_sk(nsk);
@@ -4298,7 +4299,7 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 	return 0;
 }
 
-static struct proto mptcp_prot = {
+struct proto mptcp_prot = {
 	.name		= "MPTCP",
 	.owner		= THIS_MODULE,
 	.init		= mptcp_init_sock,
@@ -4329,6 +4330,7 @@ static struct proto mptcp_prot = {
 	.obj_size	= sizeof(struct mptcp_sock),
 	.slab_flags	= SLAB_TYPESAFE_BY_RCU,
 	.no_autobind	= true,
+	.psock_update_sk_prot	= mptcp_bpf_update_proto,
 };
 
 static int mptcp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len)
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 3f8997f15bec..ae1e9ab78724 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -1393,4 +1393,22 @@ mptcp_token_join_cookie_init_state(struct mptcp_subflow_request_sock *subflow_re
 static inline void mptcp_join_cookie_init(void) {}
 #endif
 
+extern struct proto mptcp_prot;
+int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags);
+int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len);
+
+#ifdef CONFIG_BPF_SYSCALL
+int mptcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock,
+			   bool restore);
+void mptcp_bpf_clone(const struct sock *sk, struct sock *newsk);
+#else
+static inline int
+mptcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void mptcp_bpf_clone(const struct sock *sk, struct sock *newsk) {}
+#endif
+
 #endif /* __MPTCP_PROTOCOL_H */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (5 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:30   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests Geliang Tang
                   ` (10 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Add a BPF helper bpf_mptcp_sock_map_update() that allows sockops programs
running on MPTCP subflows to insert the parent MPTCP socket into a sockmap
or sockhash. When called from a sockops callback on a subflow, it resolves
the parent MPTCP socket via bpf_mptcp_sock_from_subflow() and validates
that the subflow is the first subflow (msk->first) before inserting the
MPTCP socket into the map via sock_map_update_common().

Export sock_map_update_common() from sock_map.c and register the helper in
the BPF function mapper (id 212) with corresponding verifier compatibility
checks.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/linux/bpf.h      |  7 +++++++
 include/uapi/linux/bpf.h | 10 ++++++++++
 kernel/bpf/verifier.c    |  2 ++
 net/core/filter.c        | 18 ++++++++++++++++++
 net/core/sock_map.c      |  4 ++--
 net/mptcp/bpf.c          | 20 ++++++++++++++++++++
 6 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index e57af902560c..ecb53a7ad0e8 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4022,6 +4022,13 @@ u32 bpf_xdp_sock_convert_ctx_access(enum bpf_access_type type,
 				    struct bpf_insn *insn_buf,
 				    struct bpf_prog *prog,
 				    u32 *target_size);
+
+int sock_map_update_common(struct bpf_map *map, u32 idx,
+			   struct sock *sk, u64 flags);
+
+#ifdef CONFIG_MPTCP
+u64 mptcp_sock_map_update(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
+#endif
 #else
 static inline bool bpf_tcp_sock_is_valid_access(int off, int size,
 						enum bpf_access_type type,
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 732b35cc08d1..41eecb8622ac 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -5998,6 +5998,15 @@ union bpf_attr {
  *		0 on success.
  *
  *		**-ENOENT** if the bpf_local_storage cannot be found.
+ *
+ * long bpf_mptcp_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		MPTCP-aware variant of **bpf_sock_map_update**\ ().
+ *		When called from a sockops callback on an MPTCP subflow,
+ *		it resolves to the parent MPTCP socket before inserting
+ *		it into the *map*.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
  */
 #define ___BPF_FUNC_MAPPER(FN, ctx...)			\
 	FN(unspec, 0, ##ctx)				\
@@ -6212,6 +6221,7 @@ union bpf_attr {
 	FN(user_ringbuf_drain, 209, ##ctx)		\
 	FN(cgrp_storage_get, 210, ##ctx)		\
 	FN(cgrp_storage_delete, 211, ##ctx)		\
+	FN(mptcp_sock_map_update, 212, ##ctx)		\
 	/* This helper list is effectively frozen. If you are trying to	\
 	 * add a new helper, you should add a kfunc instead which has	\
 	 * less stability guarantees. See Documentation/bpf/kfuncs.rst	\
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 72a3f5998dd2..c58c2ba87d26 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9066,6 +9066,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 		    func_id != BPF_FUNC_msg_redirect_map &&
 		    func_id != BPF_FUNC_sk_select_reuseport &&
 		    func_id != BPF_FUNC_map_lookup_elem &&
+		    func_id != BPF_FUNC_mptcp_sock_map_update &&
 		    !may_update_sockmap(env, func_id))
 			goto error;
 		break;
@@ -9075,6 +9076,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 		    func_id != BPF_FUNC_msg_redirect_hash &&
 		    func_id != BPF_FUNC_sk_select_reuseport &&
 		    func_id != BPF_FUNC_map_lookup_elem &&
+		    func_id != BPF_FUNC_mptcp_sock_map_update &&
 		    !may_update_sockmap(env, func_id))
 			goto error;
 		break;
diff --git a/net/core/filter.c b/net/core/filter.c
index 61940e753552..0b4fec5a8848 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -8830,6 +8830,10 @@ xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 const struct bpf_func_proto bpf_sock_map_update_proto __weak;
 const struct bpf_func_proto bpf_sock_hash_update_proto __weak;
 
+#if IS_ENABLED(CONFIG_MPTCP)
+static const struct bpf_func_proto mptcp_sock_map_update_proto;
+#endif
+
 static const struct bpf_func_proto *
 sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 {
@@ -8850,6 +8854,10 @@ sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 		return &bpf_sock_map_update_proto;
 	case BPF_FUNC_sock_hash_update:
 		return &bpf_sock_hash_update_proto;
+#if IS_ENABLED(CONFIG_MPTCP)
+	case BPF_FUNC_mptcp_sock_map_update:
+		return &mptcp_sock_map_update_proto;
+#endif
 	case BPF_FUNC_get_socket_cookie:
 		return &bpf_get_socket_cookie_sock_ops_proto;
 	case BPF_FUNC_perf_event_output:
@@ -11790,6 +11798,16 @@ static const struct bpf_func_proto sk_select_reuseport_proto = {
 	.arg4_type	= ARG_ANYTHING,
 };
 
+static const struct bpf_func_proto mptcp_sock_map_update_proto = {
+	.func		= mptcp_sock_map_update,
+	.gpl_only	= false,
+	.ret_type	= RET_INTEGER,
+	.arg1_type	= ARG_PTR_TO_CTX,
+	.arg2_type	= ARG_CONST_MAP_PTR,
+	.arg3_type	= ARG_PTR_TO_MAP_KEY,
+	.arg4_type	= ARG_ANYTHING,
+};
+
 BPF_CALL_4(sk_reuseport_load_bytes,
 	   const struct sk_reuseport_kern *, reuse_kern, u32, offset,
 	   void *, to, u32, len)
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index ca49bc7f8687..3e3e85e3bd54 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -467,8 +467,8 @@ static int sock_map_get_next_key(struct bpf_map *map, void *key, void *next)
 	return 0;
 }
 
-static int sock_map_update_common(struct bpf_map *map, u32 idx,
-				  struct sock *sk, u64 flags)
+int sock_map_update_common(struct bpf_map *map, u32 idx,
+			   struct sock *sk, u64 flags)
 {
 	struct bpf_stab *stab = container_of(map, struct bpf_stab, map);
 	struct sk_psock_link *link;
diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
index 662b80ce2cf6..7ecafbf51fa0 100644
--- a/net/mptcp/bpf.c
+++ b/net/mptcp/bpf.c
@@ -490,3 +490,23 @@ static int __init mptcp_bpf_v4_build_proto(void)
 	return 0;
 }
 late_initcall(mptcp_bpf_v4_build_proto);
+
+BPF_CALL_4(mptcp_sock_map_update, struct bpf_sock_ops_kern *, sops,
+	   struct bpf_map *, map, void *, key, u64, flags)
+{
+	struct sock *sk = sops->sk;
+	struct mptcp_sock *msk;
+
+	WARN_ON_ONCE(!rcu_read_lock_held());
+
+	msk = bpf_mptcp_sock_from_subflow(sk);
+	if (msk) {
+		if (sk != READ_ONCE(msk->first))
+			return -EINVAL;
+
+		sk = (struct sock *)msk;
+	}
+
+	return sock_map_update_common(map, *(u32 *)key, sk, flags);
+}
+EXPORT_SYMBOL_GPL(mptcp_sock_map_update);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (6 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:33   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
                   ` (9 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Update sockmap tests to reflect new MPTCP support. MPTCP sockets are now
allowed in sockmap, so test expectations are adjusted accordingly.

Use a different key (1) for MPTCP client sockets to validate sockmap.

In test_sockmap_with_mptcp, client_fd1 is changed from TCP fallback to
MPTCP. The sockops program now uses bpf_mptcp_sock_map_update() to insert
the parent MPTCP socket into the sockmap, which succeeds (returns 0).
Since the server socket is already inserted by the sockops program, a
duplicate userspace insertion at the same key returns -EBUSY. The client
socket is additionally inserted at a different key (1) from userspace to
verify that MPTCP client sockets are allowed in sockmap.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 .../testing/selftests/bpf/prog_tests/mptcp.c  | 27 ++++++++++---------
 .../selftests/bpf/progs/mptcp_sockmap.c       |  8 ++++--
 2 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c
index d77c9f8c53c7..59ea562882a0 100644
--- a/tools/testing/selftests/bpf/prog_tests/mptcp.c
+++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c
@@ -552,11 +552,11 @@ static void test_sockmap_with_mptcp_fallback(struct mptcp_sockmap *skel)
 	close(listen_fd);
 }
 
-/* Test sockmap rejection of MPTCP sockets - both server and client sides. */
-static void test_sockmap_reject_mptcp(struct mptcp_sockmap *skel)
+/* Test sockmap on MPTCP sockets - both server and client sides. */
+static void test_sockmap_with_mptcp(struct mptcp_sockmap *skel)
 {
 	int listen_fd = -1, server_fd = -1, client_fd1 = -1;
-	int err, zero = 0;
+	int err, zero = 0, one = 1;
 
 	/* start server with MPTCP enabled */
 	listen_fd = start_mptcp_server(AF_INET, NULL, 0, 0);
@@ -570,20 +570,20 @@ static void test_sockmap_reject_mptcp(struct mptcp_sockmap *skel)
 	if (!ASSERT_OK_FD(client_fd1, "connect_to_fd client_fd1"))
 		goto end;
 
-	/* bpf_sock_map_update() called from sockops should reject MPTCP sk */
-	if (!ASSERT_EQ(skel->bss->helper_ret, -EOPNOTSUPP, "should reject"))
+	/* bpf_mptcp_sock_map_update() called from sockops should be allowed */
+	if (!ASSERT_EQ(skel->bss->helper_ret, 0, "should be allowed"))
 		goto end;
 
 	server_fd = accept(listen_fd, NULL, 0);
 	err = bpf_map_update_elem(bpf_map__fd(skel->maps.sock_map),
 				  &zero, &server_fd, BPF_NOEXIST);
-	if (!ASSERT_EQ(err, -EOPNOTSUPP, "server should be disallowed"))
+	if (!ASSERT_EQ(err, -EBUSY, "server should be allowed"))
 		goto end;
 
-	/* MPTCP client should also be disallowed */
+	/* MPTCP client should also be allowed */
 	err = bpf_map_update_elem(bpf_map__fd(skel->maps.sock_map),
-				  &zero, &client_fd1, BPF_NOEXIST);
-	if (!ASSERT_EQ(err, -EOPNOTSUPP, "client should be disallowed"))
+				  &one, &client_fd1, BPF_NOEXIST);
+	if (!ASSERT_EQ(err, 0, "client should be allowed"))
 		goto end;
 end:
 	if (client_fd1 >= 0)
@@ -607,9 +607,10 @@ static void test_mptcp_sockmap(void)
 	if (!ASSERT_OK_PTR(skel, "skel_open_load: mptcp_sockmap"))
 		goto close_cgroup;
 
-	skel->links.mptcp_sockmap_inject =
-		bpf_program__attach_cgroup(skel->progs.mptcp_sockmap_inject, cgroup_fd);
-	if (!ASSERT_OK_PTR(skel->links.mptcp_sockmap_inject, "attach sockmap"))
+	skel->links.mptcp_sockmap_update =
+		bpf_program__attach_cgroup(skel->progs.mptcp_sockmap_update,
+					   cgroup_fd);
+	if (!ASSERT_OK_PTR(skel->links.mptcp_sockmap_update, "attach sockmap"))
 		goto skel_destroy;
 
 	err = bpf_prog_attach(bpf_program__fd(skel->progs.mptcp_sockmap_redirect),
@@ -626,7 +627,7 @@ static void test_mptcp_sockmap(void)
 		goto close_netns;
 
 	test_sockmap_with_mptcp_fallback(skel);
-	test_sockmap_reject_mptcp(skel);
+	test_sockmap_with_mptcp(skel);
 
 close_netns:
 	netns_free(netns);
diff --git a/tools/testing/selftests/bpf/progs/mptcp_sockmap.c b/tools/testing/selftests/bpf/progs/mptcp_sockmap.c
index d4eef0cbadb9..085e14bea015 100644
--- a/tools/testing/selftests/bpf/progs/mptcp_sockmap.c
+++ b/tools/testing/selftests/bpf/progs/mptcp_sockmap.c
@@ -2,6 +2,9 @@
 
 #include "bpf_tracing_net.h"
 
+static long (*bpf_mptcp_sock_map_update)(struct bpf_sock_ops *, void *,
+					 void *, __u64) = (void *)212;
+
 char _license[] SEC("license") = "GPL";
 
 int sk_index;
@@ -16,7 +19,7 @@ struct {
 } sock_map SEC(".maps");
 
 SEC("sockops")
-int mptcp_sockmap_inject(struct bpf_sock_ops *skops)
+int mptcp_sockmap_update(struct bpf_sock_ops *skops)
 {
 	struct bpf_sock *sk;
 
@@ -30,7 +33,8 @@ int mptcp_sockmap_inject(struct bpf_sock_ops *skops)
 		return 1;
 
 	/* update sk handler */
-	helper_ret = bpf_sock_map_update(skops, &sock_map, &sk_index, BPF_NOEXIST);
+	helper_ret = bpf_mptcp_sock_map_update(skops, &sock_map, &sk_index,
+					       BPF_NOEXIST);
 
 	return 1;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (7 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:40   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl Geliang Tang
                   ` (8 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

BPF sockmap's stream verdict path (sk_psock_verdict_data_ready) calls
ops->read_skb() to dequeue skbs from the socket and pass them to the
verdict BPF program. MPTCP's proto_ops (mptcp_stream_ops and
mptcp_v6_stream_ops) did not define .read_skb, causing
sk_psock_verdict_data_ready() to return early without processing any
data. This made the stream verdict redirect completely non-functional
for MPTCP sockets.

Add mptcp_read_skb() as the skb_read_actor_t callback. It peeks skbs
from the socket receive queue, unlinks each one, and passes it to the
recv_actor. Processing continues until the queue is drained or the actor
returns a negative value.

This follows the same pattern as tcp_read_skb() in net/ipv4/tcp.c.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 net/mptcp/protocol.c | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 3d3c5af7c266..c5be29767cb1 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -4801,6 +4801,31 @@ static ssize_t mptcp_splice_read(struct socket *sock, loff_t *ppos,
 	return ret;
 }
 
+static int mptcp_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
+{
+	struct sk_buff *skb;
+	int copied = 0;
+
+	if (sk->sk_state == TCP_LISTEN)
+		return -ENOTCONN;
+
+	while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
+		int used;
+
+		__skb_unlink(skb, &sk->sk_receive_queue);
+		WARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));
+		used = recv_actor(sk, skb);
+		if (used < 0) {
+			if (!copied)
+				copied = used;
+			break;
+		}
+		copied += used;
+	}
+
+	return copied;
+}
+
 static const struct proto_ops mptcp_stream_ops = {
 	.family		   = PF_INET,
 	.owner		   = THIS_MODULE,
@@ -4824,6 +4849,7 @@ static const struct proto_ops mptcp_stream_ops = {
 	.read_sock	   = mptcp_read_sock,
 	.splice_read	   = mptcp_splice_read,
 	.sendmsg_locked	   = mptcp_sendmsg_locked,
+	.read_skb	   = mptcp_read_skb,
 };
 
 static struct inet_protosw mptcp_protosw = {
@@ -4937,6 +4963,7 @@ static const struct proto_ops mptcp_v6_stream_ops = {
 	.read_sock	   = mptcp_read_sock,
 	.splice_read	   = mptcp_splice_read,
 	.sendmsg_locked	   = mptcp_sendmsg_locked,
+	.read_skb	   = mptcp_read_skb,
 };
 
 static struct proto mptcp_v6_prot;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (8 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:28   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support Geliang Tang
                   ` (7 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Export tcp_bpf_ioctl() and generalize it to use protocol-specific
operations (sk->sk_prot->ioctl and sk->sk_socket->ops->peek_len) instead
of hard-coded tcp_ioctl() and tcp_inq(). Wire it into the MPTCP BPF
protocol structures so that SIOCINQ returns correct results accounting
for data in the psock ingress queue.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/net/tcp.h  |  1 +
 net/ipv4/tcp_bpf.c | 10 +++++-----
 net/mptcp/bpf.c    |  1 +
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index d9c0214328e3..fb4dec998adc 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -2878,6 +2878,7 @@ __tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags,
 		  int (*recvmsg)(struct sock *, struct msghdr *, size_t, int));
 int __tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size,
 		      int (*sendmsg)(struct sock *, struct msghdr *, size_t));
+int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg);
 #endif /* CONFIG_NET_SOCK_MSG */
 
 #if !defined(CONFIG_BPF_SYSCALL) || !defined(CONFIG_NET_SOCK_MSG)
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index e006131696e9..8aed3c309b0d 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -331,15 +331,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
 	return copied;
 }
 
-static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
+int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
 {
 	struct sk_psock *psock;
 	bool slow;
 
 	if (cmd != SIOCINQ)
-		return tcp_ioctl(sk, cmd, karg);
+		return sk->sk_prot->ioctl(sk, cmd, karg);
 
-	/* works similar as tcp_ioctl */
+	/* works similar as sk_prot->ioctl */
 	if (sk->sk_state == TCP_LISTEN)
 		return -EINVAL;
 
@@ -347,7 +347,7 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
 	psock = sk_psock_get(sk);
 	if (unlikely(!psock)) {
 		unlock_sock_fast(sk, slow);
-		return tcp_ioctl(sk, cmd, karg);
+		return sk->sk_prot->ioctl(sk, cmd, karg);
 	}
 	*karg = sk_psock_get_msg_len_nolock(psock);
 	/* Without a verdict program, ingress data is never diverted to
@@ -357,7 +357,7 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
 	 */
 	if (!READ_ONCE(psock->progs.stream_verdict) &&
 	    !READ_ONCE(psock->progs.skb_verdict))
-		*karg += tcp_inq(sk);
+		*karg += sk->sk_socket->ops->peek_len(sk->sk_socket);
 	sk_psock_put(sk, psock);
 	unlock_sock_fast(sk, slow);
 
diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
index 7ecafbf51fa0..f6fd610f7077 100644
--- a/net/mptcp/bpf.c
+++ b/net/mptcp/bpf.c
@@ -398,6 +398,7 @@ static void mptcp_bpf_rebuild_protos(struct proto prot[MPTCP_BPF_NUM_CFGS],
 	prot[MPTCP_BPF_BASE].close		= sock_map_close;
 	prot[MPTCP_BPF_BASE].recvmsg		= mptcp_bpf_recvmsg;
 	prot[MPTCP_BPF_BASE].sock_is_readable	= sk_msg_is_readable;
+	prot[MPTCP_BPF_BASE].ioctl		= tcp_bpf_ioctl;
 
 	prot[MPTCP_BPF_TX]			= prot[MPTCP_BPF_BASE];
 	prot[MPTCP_BPF_TX].sendmsg		= mptcp_bpf_sendmsg;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (9 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:38   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic Geliang Tang
                   ` (6 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Add TCP_REPAIR socket option support for MPTCP, required for sockmap CRIU
checkpoint/restore. When TCP_REPAIR is set on the first subflow, skip the
normal connection handshake in mptcp_connect() and transition directly to
TCP_ESTABLISHED. On close, skip the shutdown sequence and jump to cleanup
when repair mode is active.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 net/mptcp/protocol.c | 10 ++++++++++
 net/mptcp/sockopt.c  |  4 +++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index c5be29767cb1..07132976016e 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3560,6 +3560,11 @@ bool __mptcp_close(struct sock *sk, long timeout)
 		__mptcp_wr_shutdown(sk);
 	}
 
+	if (msk->first && tcp_sk(msk->first)->repair) {
+		mptcp_set_state(sk, TCP_CLOSE);
+		goto cleanup;
+	}
+
 	sk_stream_wait_close(sk, timeout);
 
 cleanup:
@@ -4281,6 +4286,11 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 
 	inet_assign_bit(DEFER_CONNECT, sk, inet_test_bit(DEFER_CONNECT, ssk));
 
+	if (unlikely(tcp_sk(ssk)->repair)) {
+		mptcp_set_state(sk, TCP_ESTABLISHED);
+		sk->sk_state_change(sk);
+	}
+
 out:
 	if (!msk->fastopening)
 		release_sock(ssk);
diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index 922f6ae5c80c..d2d1c9d1ed1d 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -671,12 +671,13 @@ static bool mptcp_supported_sockopt(int level, int optname)
 		case TCP_FASTOPEN_CONNECT:
 		case TCP_FASTOPEN_KEY:
 		case TCP_FASTOPEN_NO_COOKIE:
+		case TCP_REPAIR:
 			return true;
 		}
 
 		/* TCP_MD5SIG, TCP_MD5SIG_EXT are not supported, MD5 is not compatible with MPTCP */
 
-		/* TCP_REPAIR, TCP_REPAIR_QUEUE, TCP_QUEUE_SEQ, TCP_REPAIR_OPTIONS,
+		/* TCP_REPAIR_QUEUE, TCP_QUEUE_SEQ, TCP_REPAIR_OPTIONS,
 		 * TCP_REPAIR_WINDOW are not supported, better avoid this mess
 		 */
 	}
@@ -918,6 +919,7 @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname,
 	case TCP_CONGESTION:
 		return mptcp_setsockopt_sol_tcp_congestion(msk, optval, optlen);
 	case TCP_DEFER_ACCEPT:
+	case TCP_REPAIR:
 		/* See tcp.c: TCP_DEFER_ACCEPT does not fail */
 		mptcp_setsockopt_first_sf_only(msk, SOL_TCP, optname, optval, optlen);
 		return 0;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (10 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:30   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap Geliang Tang
                   ` (5 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

This patch extends socket_helpers.h with protocol-aware variants
(socket_loopback_proto, create_pair_proto) that accept an explicit protocol
parameter. Run the sockmap_basic test suite in both TCP and MPTCP modes by
looping over run_basic_tests() with a mptcp flag. Skip UDP-specific tests
in MPTCP mode since UDP is unaffected by MPTCP.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 .../selftests/bpf/prog_tests/socket_helpers.h | 25 ++++++++++----
 .../selftests/bpf/prog_tests/sockmap_basic.c  | 33 ++++++++++++++-----
 2 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
index 0d59503a0c73..d9bd6b11e2cc 100644
--- a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
+++ b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
@@ -231,7 +231,8 @@ static inline int enable_reuseport(int s, int progfd)
 	return 0;
 }
 
-static inline int socket_loopback_reuseport(int family, int sotype, int progfd)
+static inline int socket_loopback_reuseport_proto(int family, int sotype,
+						  int proto, int progfd)
 {
 	struct sockaddr_storage addr;
 	socklen_t len = 0;
@@ -239,7 +240,7 @@ static inline int socket_loopback_reuseport(int family, int sotype, int progfd)
 
 	init_addr_loopback(family, &addr, &len);
 
-	s = xsocket(family, sotype, 0);
+	s = xsocket(family, sotype, proto);
 	if (s == -1)
 		return -1;
 
@@ -263,11 +264,17 @@ static inline int socket_loopback_reuseport(int family, int sotype, int progfd)
 	return -1;
 }
 
-static inline int socket_loopback(int family, int sotype)
+#define socket_loopback_reuseport(family, sotype, progfd) \
+	socket_loopback_reuseport_proto(family, sotype, 0, progfd)
+
+static inline int socket_loopback_proto(int family, int sotype, int proto)
 {
-	return socket_loopback_reuseport(family, sotype, -1);
+	return socket_loopback_reuseport_proto(family, sotype, proto, -1);
 }
 
+#define socket_loopback(family, sotype) \
+	socket_loopback_proto(family, sotype, 0)
+
 static inline int poll_connect(int fd, unsigned int timeout_sec)
 {
 	struct timeval timeout = { .tv_sec = timeout_sec };
@@ -329,18 +336,19 @@ static inline int recv_timeout(int fd, void *buf, size_t len, int flags,
 }
 
 
-static inline int create_pair(int family, int sotype, int *p0, int *p1)
+static inline int create_pair_proto(int family, int sotype, int proto,
+				    int *p0, int *p1)
 {
 	__close_fd int s, c = -1, p = -1;
 	struct sockaddr_storage addr;
 	socklen_t len;
 	int err;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, proto);
 	if (s < 0)
 		return s;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, proto);
 	if (c < 0)
 		return c;
 
@@ -397,6 +405,9 @@ static inline int create_pair(int family, int sotype, int *p0, int *p1)
 	return 0;
 }
 
+#define create_pair(family, sotype, p0, p1) \
+	create_pair_proto(family, sotype, 0, p0, p1)
+
 static inline int create_socket_pairs(int family, int sotype, int *c0, int *c1,
 				      int *p0, int *p1)
 {
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index 1fef6ec2ba7a..80919a5fef49 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -32,6 +32,8 @@
 #define SOL_TCP 6
 #endif
 
+static bool mptcp;
+
 static int connected_socket_v4(void)
 {
 	struct sockaddr_in addr = {
@@ -42,7 +44,7 @@ static int connected_socket_v4(void)
 	socklen_t len = sizeof(addr);
 	int s, repair, err;
 
-	s = socket(AF_INET, SOCK_STREAM, 0);
+	s = socket(AF_INET, SOCK_STREAM, mptcp ? IPPROTO_MPTCP : 0);
 	if (!ASSERT_GE(s, 0, "socket"))
 		goto error;
 
@@ -466,7 +468,8 @@ static void test_sockmap_skb_verdict_shutdown(void)
 	if (!ASSERT_OK(err, "bpf_prog_attach"))
 		goto out;
 
-	err = create_pair(AF_INET, SOCK_STREAM, &c1, &p1);
+	err = create_pair_proto(AF_INET, SOCK_STREAM,
+				mptcp ? IPPROTO_MPTCP : 0, &c1, &p1);
 	if (err < 0)
 		goto out;
 
@@ -568,7 +571,9 @@ static void do_test_sockmap_skb_verdict_fionread(int sotype, bool pass_prog)
 static void test_sockmap_skb_verdict_fionread(bool pass_prog)
 {
 	do_test_sockmap_skb_verdict_fionread(SOCK_STREAM, pass_prog);
-	do_test_sockmap_skb_verdict_fionread(SOCK_DGRAM, pass_prog);
+	/* UDP is unaffected by MPTCP, only run it once (in tcp mode) */
+	if (!mptcp)
+		do_test_sockmap_skb_verdict_fionread(SOCK_DGRAM, pass_prog);
 }
 
 static void test_sockmap_skb_verdict_change_tail(void)
@@ -588,7 +593,8 @@ static void test_sockmap_skb_verdict_change_tail(void)
 	err = bpf_prog_attach(verdict, map, BPF_SK_SKB_STREAM_VERDICT, 0);
 	if (!ASSERT_OK(err, "bpf_prog_attach"))
 		goto out;
-	err = create_pair(AF_INET, SOCK_STREAM, &c1, &p1);
+	err = create_pair_proto(AF_INET, SOCK_STREAM,
+				mptcp ? IPPROTO_MPTCP : 0, &c1, &p1);
 	if (!ASSERT_OK(err, "create_pair()"))
 		goto out;
 	err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
@@ -639,7 +645,8 @@ static void test_sockmap_msg_verdict_pop_data(void)
 	if (!ASSERT_OK(err, "bpf_prog_attach"))
 		goto out;
 
-	err = create_pair(AF_INET, SOCK_STREAM, &c1, &p1);
+	err = create_pair_proto(AF_INET, SOCK_STREAM,
+				mptcp ? IPPROTO_MPTCP : 0, &c1, &p1);
 	if (!ASSERT_OK(err, "create_pair"))
 		goto out;
 
@@ -670,7 +677,8 @@ static void test_sockmap_skb_verdict_peek_helper(int map)
 	char snd[256] = "0123456789";
 	char rcv[256] = "0";
 
-	err = create_pair(AF_INET, SOCK_STREAM, &c1, &p1);
+	err = create_pair_proto(AF_INET, SOCK_STREAM,
+				mptcp ? IPPROTO_MPTCP : 0, &c1, &p1);
 	if (!ASSERT_OK(err, "create_pair()"))
 		return;
 
@@ -1362,7 +1370,7 @@ static void test_sockmap_no_verdict_fionread(void)
 	test_sockmap_pass_prog__destroy(skel);
 }
 
-void test_sockmap_basic(void)
+static void run_basic_tests(void)
 {
 	if (test__start_subtest("sockmap create_update_free"))
 		test_sockmap_create_update_free(BPF_MAP_TYPE_SOCKMAP);
@@ -1436,6 +1444,15 @@ void test_sockmap_basic(void)
 		test_sockmap_copied_seq(true);
 	if (test__start_subtest("sockmap tcp multi channels"))
 		test_sockmap_multi_channels(SOCK_STREAM);
-	if (test__start_subtest("sockmap udp multi channels"))
+	/* UDP is unaffected by MPTCP, only run it once (in tcp mode) */
+	if (!mptcp && test__start_subtest("sockmap udp multi channels"))
 		test_sockmap_multi_channels(SOCK_DGRAM);
 }
+
+void test_sockmap_basic(void)
+{
+	for (int i = 0; i < 2; i++) {
+		mptcp = i;
+		run_basic_tests();
+	}
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (11 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:48   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 14/16] mptcp: add SO_ATTACH_REUSEPORT_EBPF support Geliang Tang
                   ` (4 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Introduce sk_is_msk() to check whether a socket is an MPTCP socket
(IPPROTO_MPTCP on SOCK_STREAM), modeled after sk_is_tcp(). Unlike
sk_is_mptcp() which tests a subflow socket, this helper takes an
MPTCP parent socket directly.

Use it alongside sk_is_tcp() in sock_map.c so that MPTCP sockets are
treated the same as TCP sockets for strp linking, redirect checks,
state validation and egress redirect in bpf_msg_redirect_{map,hash}.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/net/mptcp.h | 12 ++++++++++++
 net/core/sock_map.c | 10 +++++-----
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/include/net/mptcp.h b/include/net/mptcp.h
index 333bde2a0b76..6ab8d3e5e0f8 100644
--- a/include/net/mptcp.h
+++ b/include/net/mptcp.h
@@ -150,6 +150,13 @@ static inline bool rsk_drop_req(const struct request_sock *req)
 	return tcp_rsk(req)->is_mptcp && tcp_rsk(req)->drop_req;
 }
 
+static inline bool sk_is_msk(const struct sock *sk)
+{
+	return sk_is_inet(sk) &&
+	       sk->sk_type == SOCK_STREAM &&
+	       sk->sk_protocol == IPPROTO_MPTCP;
+}
+
 void mptcp_space(const struct sock *ssk, int *space, int *full_space);
 bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb,
 		       unsigned int *size, struct mptcp_out_options *opts);
@@ -258,6 +265,11 @@ static inline bool rsk_drop_req(const struct request_sock *req)
 	return false;
 }
 
+static inline bool sk_is_msk(const struct sock *sk)
+{
+	return false;
+}
+
 static inline bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb,
 				     unsigned int *size,
 				     struct mptcp_out_options *opts)
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index 3e3e85e3bd54..e9e0b676aa1c 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -303,7 +303,7 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
 
 	write_lock_bh(&sk->sk_callback_lock);
 	if (stream_parser && stream_verdict && !psock->saved_data_ready) {
-		if (sk_is_tcp(sk))
+		if (sk_is_tcp(sk) || sk_is_msk(sk))
 			ret = sk_psock_init_strp(sk, psock);
 		else
 			ret = -EOPNOTSUPP;
@@ -527,7 +527,7 @@ static bool sock_map_op_okay(const struct bpf_sock_ops_kern *ops)
 
 static bool sock_map_redirect_allowed(const struct sock *sk)
 {
-	if (sk_is_tcp(sk))
+	if (sk_is_tcp(sk) || sk_is_msk(sk))
 		return sk->sk_state != TCP_LISTEN;
 	else
 		return READ_ONCE(sk->sk_state) == TCP_ESTABLISHED;
@@ -540,7 +540,7 @@ static bool sock_map_sk_is_suitable(const struct sock *sk)
 
 static bool sock_map_sk_state_allowed(const struct sock *sk)
 {
-	if (sk_is_tcp(sk))
+	if (sk_is_tcp(sk) || sk_is_msk(sk))
 		return (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_LISTEN);
 	if (sk_is_udp(sk))
 		return sk_hashed(sk);
@@ -683,7 +683,7 @@ BPF_CALL_4(bpf_msg_redirect_map, struct sk_msg *, msg,
 	sk = __sock_map_lookup_elem(map, key);
 	if (unlikely(!sk || !sock_map_redirect_allowed(sk)))
 		return SK_DROP;
-	if (!(flags & BPF_F_INGRESS) && !sk_is_tcp(sk))
+	if (!(flags & BPF_F_INGRESS) && !(sk_is_tcp(sk) || sk_is_msk(sk)))
 		return SK_DROP;
 	if (sk_is_vsock(sk))
 		return SK_DROP;
@@ -1289,7 +1289,7 @@ BPF_CALL_4(bpf_msg_redirect_hash, struct sk_msg *, msg,
 	sk = __sock_hash_lookup_elem(map, key);
 	if (unlikely(!sk || !sock_map_redirect_allowed(sk)))
 		return SK_DROP;
-	if (!(flags & BPF_F_INGRESS) && !sk_is_tcp(sk))
+	if (!(flags & BPF_F_INGRESS) && !(sk_is_tcp(sk) || sk_is_msk(sk)))
 		return SK_DROP;
 	if (sk_is_vsock(sk))
 		return SK_DROP;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 14/16] mptcp: add SO_ATTACH_REUSEPORT_EBPF support
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (12 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:14 ` [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper Geliang Tang
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Add MPTCP support for SO_ATTACH_REUSEPORT_EBPF and SO_DETACH_REUSEPORT_BPF
socket options by forwarding them to the first subflow via
sk_setsockopt(). This enables MPTCP listener sockets to use reuseport
BPF programs, which is required for sockmap reuseport integration.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 net/mptcp/sockopt.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index d2d1c9d1ed1d..e740207f05ce 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -338,6 +338,18 @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname,
 		}
 		release_sock(sk);
 		return ret;
+	case SO_ATTACH_REUSEPORT_EBPF:
+	case SO_DETACH_REUSEPORT_BPF:
+		lock_sock(sk);
+		ssk = __mptcp_nmpc_sk(msk);
+		if (IS_ERR(ssk)) {
+			release_sock(sk);
+			return PTR_ERR(ssk);
+		}
+
+		ret = sk_setsockopt(ssk, SOL_SOCKET, optname, optval, optlen);
+		release_sock(sk);
+		return ret;
 	case SO_KEEPALIVE:
 	case SO_PRIORITY:
 	case SO_SNDBUF:
@@ -385,12 +397,9 @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname,
 
 	/* SO_OOBINLINE is not supported, let's avoid the related mess
 	 * SO_ATTACH_FILTER, SO_ATTACH_BPF, SO_ATTACH_REUSEPORT_CBPF,
-	 * SO_DETACH_REUSEPORT_BPF, SO_DETACH_FILTER, SO_LOCK_FILTER,
+	 * SO_DETACH_FILTER, SO_LOCK_FILTER,
 	 * we must be careful with subflows
 	 *
-	 * SO_ATTACH_REUSEPORT_EBPF is not supported, at it checks
-	 * explicitly the sk_protocol field
-	 *
 	 * SO_PEEK_OFF is unsupported, as it is for plain TCP
 	 * SO_MAX_PACING_RATE is unsupported, we must be careful with subflows
 	 * SO_CNX_ADVICE is currently unsupported, could possibly be relevant,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (13 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 14/16] mptcp: add SO_ATTACH_REUSEPORT_EBPF support Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:48   ` sashiko-bot
  2026-09-13 10:14 ` [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen Geliang Tang
                   ` (2 subsequent siblings)
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Add an MPTCP-aware variant of bpf_sk_select_reuseport() (helper id 213).
When the map contains an MPTCP parent socket, it resolves to the first
subflow (msk->first) before selecting it for reuseport dispatch.

Refactor sk_select_reuseport() to extract the core selection logic into
sk_select_reuseport_lookup(), which is reused by the MPTCP variant.
Register the helper for both sock_ops and sk_reuseport BPF program types
with corresponding verifier compatibility checks.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 include/linux/bpf.h      |  6 ++++++
 include/uapi/linux/bpf.h |  9 +++++++++
 kernel/bpf/verifier.c    |  6 +++++-
 net/core/filter.c        | 42 +++++++++++++++++++++++++++++++++-------
 net/mptcp/bpf.c          | 22 +++++++++++++++++++++
 5 files changed, 77 insertions(+), 8 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index ecb53a7ad0e8..00061ef54d57 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4026,8 +4026,14 @@ u32 bpf_xdp_sock_convert_ctx_access(enum bpf_access_type type,
 int sock_map_update_common(struct bpf_map *map, u32 idx,
 			   struct sock *sk, u64 flags);
 
+int sk_select_reuseport_lookup(struct sk_reuseport_kern *reuse_kern,
+			       struct sock *selected_sk,
+			       struct bpf_map *map);
+
 #ifdef CONFIG_MPTCP
 u64 mptcp_sock_map_update(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
+
+u64 mptcp_sk_select_reuseport(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
 #endif
 #else
 static inline bool bpf_tcp_sock_is_valid_access(int off, int size,
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 41eecb8622ac..b44450a3e7ae 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -6007,6 +6007,14 @@ union bpf_attr {
  *		it into the *map*.
  *	Return
  *		0 on success, or a negative error in case of failure.
+ *
+ * long bpf_mptcp_sk_select_reuseport(struct sk_reuseport_md *reuse, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		MPTCP-aware variant of **bpf_sk_select_reuseport**\ ().
+ *		When the *map* contains an MPTCP parent socket, it
+ *		resolves to the first subflow before selecting it.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
  */
 #define ___BPF_FUNC_MAPPER(FN, ctx...)			\
 	FN(unspec, 0, ##ctx)				\
@@ -6222,6 +6230,7 @@ union bpf_attr {
 	FN(cgrp_storage_get, 210, ##ctx)		\
 	FN(cgrp_storage_delete, 211, ##ctx)		\
 	FN(mptcp_sock_map_update, 212, ##ctx)		\
+	FN(mptcp_sk_select_reuseport, 213, ##ctx)	\
 	/* This helper list is effectively frozen. If you are trying to	\
 	 * add a new helper, you should add a kfunc instead which has	\
 	 * less stability guarantees. See Documentation/bpf/kfuncs.rst	\
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c58c2ba87d26..33a36afd0eaf 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9067,6 +9067,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 		    func_id != BPF_FUNC_sk_select_reuseport &&
 		    func_id != BPF_FUNC_map_lookup_elem &&
 		    func_id != BPF_FUNC_mptcp_sock_map_update &&
+		    func_id != BPF_FUNC_mptcp_sk_select_reuseport &&
 		    !may_update_sockmap(env, func_id))
 			goto error;
 		break;
@@ -9077,11 +9078,13 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 		    func_id != BPF_FUNC_sk_select_reuseport &&
 		    func_id != BPF_FUNC_map_lookup_elem &&
 		    func_id != BPF_FUNC_mptcp_sock_map_update &&
+		    func_id != BPF_FUNC_mptcp_sk_select_reuseport &&
 		    !may_update_sockmap(env, func_id))
 			goto error;
 		break;
 	case BPF_MAP_TYPE_REUSEPORT_SOCKARRAY:
-		if (func_id != BPF_FUNC_sk_select_reuseport)
+		if (func_id != BPF_FUNC_sk_select_reuseport &&
+		    func_id != BPF_FUNC_mptcp_sk_select_reuseport)
 			goto error;
 		break;
 	case BPF_MAP_TYPE_QUEUE:
@@ -9191,6 +9194,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
 			goto error;
 		break;
 	case BPF_FUNC_sk_select_reuseport:
+	case BPF_FUNC_mptcp_sk_select_reuseport:
 		if (map->map_type != BPF_MAP_TYPE_REUSEPORT_SOCKARRAY &&
 		    map->map_type != BPF_MAP_TYPE_SOCKMAP &&
 		    map->map_type != BPF_MAP_TYPE_SOCKHASH)
diff --git a/net/core/filter.c b/net/core/filter.c
index 0b4fec5a8848..f1415ff42a51 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -8832,6 +8832,7 @@ const struct bpf_func_proto bpf_sock_hash_update_proto __weak;
 
 #if IS_ENABLED(CONFIG_MPTCP)
 static const struct bpf_func_proto mptcp_sock_map_update_proto;
+static const struct bpf_func_proto mptcp_sk_select_reuseport_proto;
 #endif
 
 static const struct bpf_func_proto *
@@ -8857,6 +8858,8 @@ sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 #if IS_ENABLED(CONFIG_MPTCP)
 	case BPF_FUNC_mptcp_sock_map_update:
 		return &mptcp_sock_map_update_proto;
+	case BPF_FUNC_mptcp_sk_select_reuseport:
+		return &mptcp_sk_select_reuseport_proto;
 #endif
 	case BPF_FUNC_get_socket_cookie:
 		return &bpf_get_socket_cookie_sock_ops_proto;
@@ -11738,15 +11741,14 @@ struct sock *bpf_run_sk_reuseport(struct sock_reuseport *reuse, struct sock *sk,
 		return ERR_PTR(-ECONNREFUSED);
 }
 
-BPF_CALL_4(sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
-	   struct bpf_map *, map, void *, key, u32, flags)
+int sk_select_reuseport_lookup(struct sk_reuseport_kern *reuse_kern,
+			       struct sock *selected_sk,
+			       struct bpf_map *map)
 {
 	bool is_sockarray = map->map_type == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY;
 	struct sock_reuseport *reuse;
-	struct sock *selected_sk;
 	int err;
 
-	selected_sk = map->ops->map_lookup_elem(map, key);
 	if (!selected_sk)
 		return -ENOENT;
 
@@ -11781,10 +11783,20 @@ BPF_CALL_4(sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
 
 	return 0;
 error:
-	/* Lookup in sock_map can return TCP ESTABLISHED sockets. */
-	if (sk_is_refcounted(selected_sk))
-		sock_put(selected_sk);
+	return err;
+}
+EXPORT_SYMBOL(sk_select_reuseport_lookup);
+
+BPF_CALL_4(sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
+	   struct bpf_map *, map, void *, key, u32, flags)
+{
+	struct sock *selected_sk;
+	int err;
 
+	selected_sk = map->ops->map_lookup_elem(map, key);
+	err = sk_select_reuseport_lookup(reuse_kern, selected_sk, map);
+	if (unlikely(err) && selected_sk && sk_is_refcounted(selected_sk))
+		sock_put(selected_sk);
 	return err;
 }
 
@@ -11798,6 +11810,7 @@ static const struct bpf_func_proto sk_select_reuseport_proto = {
 	.arg4_type	= ARG_ANYTHING,
 };
 
+#if IS_ENABLED(CONFIG_MPTCP)
 static const struct bpf_func_proto mptcp_sock_map_update_proto = {
 	.func		= mptcp_sock_map_update,
 	.gpl_only	= false,
@@ -11808,6 +11821,17 @@ static const struct bpf_func_proto mptcp_sock_map_update_proto = {
 	.arg4_type	= ARG_ANYTHING,
 };
 
+static const struct bpf_func_proto mptcp_sk_select_reuseport_proto = {
+	.func           = mptcp_sk_select_reuseport,
+	.gpl_only       = true,
+	.ret_type       = RET_INTEGER,
+	.arg1_type	= ARG_PTR_TO_CTX,
+	.arg2_type      = ARG_CONST_MAP_PTR,
+	.arg3_type      = ARG_PTR_TO_MAP_KEY,
+	.arg4_type	= ARG_ANYTHING,
+};
+#endif
+
 BPF_CALL_4(sk_reuseport_load_bytes,
 	   const struct sk_reuseport_kern *, reuse_kern, u32, offset,
 	   void *, to, u32, len)
@@ -11851,6 +11875,10 @@ sk_reuseport_func_proto(enum bpf_func_id func_id,
 	switch (func_id) {
 	case BPF_FUNC_sk_select_reuseport:
 		return &sk_select_reuseport_proto;
+#if IS_ENABLED(CONFIG_MPTCP)
+	case BPF_FUNC_mptcp_sk_select_reuseport:
+		return &mptcp_sk_select_reuseport_proto;
+#endif
 	case BPF_FUNC_skb_load_bytes:
 		return &sk_reuseport_load_bytes_proto;
 	case BPF_FUNC_skb_load_bytes_relative:
diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
index f6fd610f7077..fe10c5ed28f0 100644
--- a/net/mptcp/bpf.c
+++ b/net/mptcp/bpf.c
@@ -511,3 +511,25 @@ BPF_CALL_4(mptcp_sock_map_update, struct bpf_sock_ops_kern *, sops,
 	return sock_map_update_common(map, *(u32 *)key, sk, flags);
 }
 EXPORT_SYMBOL_GPL(mptcp_sock_map_update);
+
+BPF_CALL_4(mptcp_sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
+	   struct bpf_map *, map, void *, key, u32, flags)
+{
+	struct sock *sk, *selected_sk;
+
+	sk = map->ops->map_lookup_elem(map, key);
+	if (!sk)
+		return -ENOENT;
+
+	selected_sk = READ_ONCE(mptcp_sk(sk)->first);
+	if (!selected_sk) {
+		if (sk_is_refcounted(sk))
+			sock_put(sk);
+		return -ENOENT;
+	}
+
+	if (sk_is_refcounted(sk))
+		sock_put(sk);
+	return sk_select_reuseport_lookup(reuse_kern, selected_sk, map);
+}
+EXPORT_SYMBOL_GPL(mptcp_sk_select_reuseport);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (14 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper Geliang Tang
@ 2026-09-13 10:14 ` Geliang Tang
  2026-09-13 10:45   ` sashiko-bot
  2026-09-13 11:24 ` [PATCH mptcp-next v5 00/16] MPTCP sockmap support MPTCP CI
  2026-09-13 11:43 ` MPTCP CI
  17 siblings, 1 reply; 31+ messages in thread
From: Geliang Tang @ 2026-09-13 10:14 UTC (permalink / raw)
  To: mptcp; +Cc: Geliang Tang

From: Geliang Tang <tanggeliang@kylinos.cn>

Extend sockmap_listen tests to cover MPTCP sockets. Run the full test
suite (ops, redir, reuseport) in both TCP and MPTCP modes, using
IPPROTO_MPTCP for stream sockets when in MPTCP mode. Skip UDP-specific
tests in MPTCP mode. Add a dedicated BPF reuseport program
(prog_reuseport_mptcp) that uses bpf_mptcp_sk_select_reuseport() for
MPTCP-aware reuseport selection. Prefix subtest names with the protocol
(tcp/mptcp) for clarity.

Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
 .../selftests/bpf/prog_tests/sockmap_listen.c | 122 +++++++++++-------
 .../selftests/bpf/progs/test_sockmap_listen.c |  25 ++++
 2 files changed, 101 insertions(+), 46 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
index 1c96a3cf4b97..d31f3eb661d6 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
@@ -31,6 +31,10 @@
 
 #define NO_FLAGS 0
 
+static bool mptcp;
+#define SOTYPE_PROTO(st) \
+	(mptcp && ((st) & SOCK_STREAM) ? IPPROTO_MPTCP : 0)
+
 static void test_insert_invalid(struct test_sockmap_listen *skel __always_unused,
 				int family, int sotype, int mapfd)
 {
@@ -56,7 +60,7 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused,
 	int err, s;
 	u64 value;
 
-	s = xsocket(family, sotype, 0);
+	s = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (s == -1)
 		return;
 
@@ -79,7 +83,7 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused,
 
 	init_addr_loopback(family, &addr, &len);
 
-	s = xsocket(family, sotype, 0);
+	s = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (s == -1)
 		return;
 
@@ -107,7 +111,7 @@ static void test_insert(struct test_sockmap_listen *skel __always_unused,
 	u32 key;
 	int s;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -124,7 +128,7 @@ static void test_delete_after_insert(struct test_sockmap_listen *skel __always_u
 	u32 key;
 	int s;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -142,7 +146,7 @@ static void test_delete_after_close(struct test_sockmap_listen *skel __always_un
 	u64 value;
 	u32 key;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -167,7 +171,7 @@ static void test_lookup_after_insert(struct test_sockmap_listen *skel __always_u
 	u32 key;
 	int s;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -195,7 +199,7 @@ static void test_lookup_after_delete(struct test_sockmap_listen *skel __always_u
 	u64 value;
 	u32 key;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -218,7 +222,7 @@ static void test_lookup_32_bit_value(struct test_sockmap_listen *skel __always_u
 	u32 key, value32;
 	int err, s;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -250,11 +254,11 @@ static void test_update_existing(struct test_sockmap_listen *skel __always_unuse
 	u64 value;
 	u32 key;
 
-	s1 = socket_loopback(family, sotype);
+	s1 = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s1 < 0)
 		return;
 
-	s2 = socket_loopback(family, sotype);
+	s2 = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s2 < 0)
 		goto close_s1;
 
@@ -280,7 +284,7 @@ static void do_destroy_orphan_child(int family, int sotype, int mapfd)
 	u64 value;
 	u32 key;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -293,7 +297,7 @@ static void do_destroy_orphan_child(int family, int sotype, int mapfd)
 	value = s;
 	xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c == -1)
 		goto close_srv;
 
@@ -342,7 +346,7 @@ static void test_clone_after_delete(struct test_sockmap_listen *skel __always_un
 	u64 value;
 	u32 key;
 
-	s = socket_loopback(family, sotype);
+	s = socket_loopback_proto(family, sotype, SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -356,7 +360,7 @@ static void test_clone_after_delete(struct test_sockmap_listen *skel __always_un
 	xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
 	xbpf_map_delete_elem(mapfd, &key);
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c < 0)
 		goto close_srv;
 
@@ -379,7 +383,8 @@ static void test_accept_after_delete(struct test_sockmap_listen *skel __always_u
 	socklen_t len;
 	u64 value;
 
-	s = socket_loopback(family, sotype | SOCK_NONBLOCK);
+	s = socket_loopback_proto(family, sotype | SOCK_NONBLOCK,
+				  SOTYPE_PROTO(sotype));
 	if (s == -1)
 		return;
 
@@ -393,7 +398,7 @@ static void test_accept_after_delete(struct test_sockmap_listen *skel __always_u
 	if (err)
 		goto close_srv;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c == -1)
 		goto close_srv;
 
@@ -434,7 +439,8 @@ static void test_accept_before_delete(struct test_sockmap_listen *skel __always_
 	socklen_t len;
 	u64 value;
 
-	s = socket_loopback(family, sotype | SOCK_NONBLOCK);
+	s = socket_loopback_proto(family, sotype | SOCK_NONBLOCK,
+				  SOTYPE_PROTO(sotype));
 	if (s == -1)
 		return;
 
@@ -448,7 +454,7 @@ static void test_accept_before_delete(struct test_sockmap_listen *skel __always_
 	if (err)
 		goto close_srv;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c == -1)
 		goto close_srv;
 
@@ -511,7 +517,7 @@ static void *connect_accept_thread(void *arg)
 	for (i = 0; i < ctx->nr_iter; i++) {
 		int c, p;
 
-		c = xsocket(family, socktype, 0);
+		c = xsocket(family, socktype, SOTYPE_PROTO(socktype));
 		if (c < 0)
 			break;
 
@@ -546,7 +552,8 @@ static void test_syn_recv_insert_delete(struct test_sockmap_listen *skel __alway
 	int err, s;
 	u64 value;
 
-	s = socket_loopback(family, sotype | SOCK_NONBLOCK);
+	s = socket_loopback_proto(family, sotype | SOCK_NONBLOCK,
+				  SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -609,7 +616,7 @@ static void test_race_insert_listen(struct test_sockmap_listen *skel __always_un
 	int err, s;
 	u64 value;
 
-	s = xsocket(family, socktype, 0);
+	s = xsocket(family, socktype, SOTYPE_PROTO(socktype));
 	if (s < 0)
 		return;
 
@@ -795,7 +802,8 @@ static void redir_to_listening(int family, int sotype, int sock_mapfd,
 
 	zero_verdict_count(verd_mapfd);
 
-	s = socket_loopback(family, sotype | SOCK_NONBLOCK);
+	s = socket_loopback_proto(family, sotype | SOCK_NONBLOCK,
+				  SOTYPE_PROTO(sotype));
 	if (s < 0)
 		return;
 
@@ -804,7 +812,7 @@ static void redir_to_listening(int family, int sotype, int sock_mapfd,
 	if (err)
 		goto close_srv;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c < 0)
 		goto close_srv;
 	err = xconnect(c, sockaddr(&addr), len);
@@ -984,8 +992,9 @@ static void test_reuseport_select_listening(int family, int sotype,
 
 	zero_verdict_count(verd_map);
 
-	s = socket_loopback_reuseport(family, sotype | SOCK_NONBLOCK,
-				      reuseport_prog);
+	s = socket_loopback_reuseport_proto(family, sotype | SOCK_NONBLOCK,
+					    SOTYPE_PROTO(sotype),
+					    reuseport_prog);
 	if (s < 0)
 		return;
 
@@ -1000,7 +1009,7 @@ static void test_reuseport_select_listening(int family, int sotype,
 	if (err)
 		goto close_srv;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c < 0)
 		goto close_srv;
 	err = xconnect(c, sockaddr(&addr), len);
@@ -1053,7 +1062,9 @@ static void test_reuseport_select_connected(int family, int sotype,
 
 	zero_verdict_count(verd_map);
 
-	s = socket_loopback_reuseport(family, sotype, reuseport_prog);
+	s = socket_loopback_reuseport_proto(family, sotype,
+					    SOTYPE_PROTO(sotype),
+					    reuseport_prog);
 	if (s < 0)
 		return;
 
@@ -1069,7 +1080,7 @@ static void test_reuseport_select_connected(int family, int sotype,
 	if (err)
 		goto close_srv;
 
-	c0 = xsocket(family, sotype, 0);
+	c0 = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c0 < 0)
 		goto close_srv;
 
@@ -1082,7 +1093,7 @@ static void test_reuseport_select_connected(int family, int sotype,
 		if (p0 < 0)
 			goto close_cli0;
 	} else {
-		p0 = xsocket(family, sotype, 0);
+		p0 = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 		if (p0 < 0)
 			goto close_cli0;
 
@@ -1103,7 +1114,7 @@ static void test_reuseport_select_connected(int family, int sotype,
 	if (err)
 		goto close_peer0;
 
-	c1 = xsocket(family, sotype, 0);
+	c1 = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c1 < 0)
 		goto close_peer0;
 
@@ -1158,11 +1169,15 @@ static void test_reuseport_mixed_groups(int family, int sotype, int sock_map,
 	zero_verdict_count(verd_map);
 
 	/* Create two listeners, each in its own reuseport group */
-	s1 = socket_loopback_reuseport(family, sotype, reuseport_prog);
+	s1 = socket_loopback_reuseport_proto(family, sotype,
+					     SOTYPE_PROTO(sotype),
+					     reuseport_prog);
 	if (s1 < 0)
 		return;
 
-	s2 = socket_loopback_reuseport(family, sotype, reuseport_prog);
+	s2 = socket_loopback_reuseport_proto(family, sotype,
+					     SOTYPE_PROTO(sotype),
+					     reuseport_prog);
 	if (s2 < 0)
 		goto close_srv1;
 
@@ -1176,7 +1191,7 @@ static void test_reuseport_mixed_groups(int family, int sotype, int sock_map,
 	if (err)
 		goto close_srv2;
 
-	c = xsocket(family, sotype, 0);
+	c = xsocket(family, sotype, SOTYPE_PROTO(sotype));
 	if (c < 0)
 		goto close_srv2;
 
@@ -1272,7 +1287,7 @@ static const char *sotype_str(int sotype)
 	case SOCK_DGRAM:
 		return "UDP";
 	case SOCK_STREAM:
-		return "TCP";
+		return mptcp ? "MPTCP" : "TCP";
 	default:
 		return "unknown";
 	}
@@ -1321,7 +1336,8 @@ static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map,
 	map_fd = bpf_map__fd(map);
 
 	for (t = tests; t < tests + ARRAY_SIZE(tests); t++) {
-		snprintf(s, sizeof(s), "%s %s %s %s", map_name, family_name,
+		snprintf(s, sizeof(s), "%s %s %s %s %s",
+			 mptcp ? "mptcp" : "tcp", map_name, family_name,
 			 sotype_name, t->name);
 
 		if (t->sotype != 0 && t->sotype != sotype)
@@ -1359,7 +1375,8 @@ static void test_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
 	map_name = map_type_str(map);
 
 	for (t = tests; t < tests + ARRAY_SIZE(tests); t++) {
-		snprintf(s, sizeof(s), "%s %s %s", map_name, family_name,
+		snprintf(s, sizeof(s), "%s %s %s %s",
+			 mptcp ? "mptcp" : "tcp", map_name, family_name,
 			 t->name);
 
 		if (!test__start_subtest(s))
@@ -1393,10 +1410,15 @@ static void test_reuseport(struct test_sockmap_listen *skel,
 
 	socket_map = bpf_map__fd(map);
 	verdict_map = bpf_map__fd(skel->maps.verdict_map);
-	reuseport_prog = bpf_program__fd(skel->progs.prog_reuseport);
+	if (mptcp)
+		reuseport_prog =
+			bpf_program__fd(skel->progs.prog_reuseport_mptcp);
+	else
+		reuseport_prog = bpf_program__fd(skel->progs.prog_reuseport);
 
 	for (t = tests; t < tests + ARRAY_SIZE(tests); t++) {
-		snprintf(s, sizeof(s), "%s %s %s %s", map_name, family_name,
+		snprintf(s, sizeof(s), "%s %s %s %s %s",
+			 mptcp ? "mptcp" : "tcp", map_name, family_name,
 			 sotype_name, t->name);
 
 		if (t->sotype != 0 && t->sotype != sotype)
@@ -1413,10 +1435,14 @@ static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
 		      int family)
 {
 	test_ops(skel, map, family, SOCK_STREAM);
-	test_ops(skel, map, family, SOCK_DGRAM);
 	test_redir(skel, map, family, SOCK_STREAM);
 	test_reuseport(skel, map, family, SOCK_STREAM);
-	test_reuseport(skel, map, family, SOCK_DGRAM);
+
+	/* UDP is unaffected by MPTCP, only run it once (in tcp mode) */
+	if (!mptcp) {
+		test_ops(skel, map, family, SOCK_DGRAM);
+		test_reuseport(skel, map, family, SOCK_DGRAM);
+	}
 }
 
 void serial_test_sockmap_listen(void)
@@ -1429,13 +1455,17 @@ void serial_test_sockmap_listen(void)
 		return;
 	}
 
-	skel->bss->test_sockmap = true;
-	run_tests(skel, skel->maps.sock_map, AF_INET);
-	run_tests(skel, skel->maps.sock_map, AF_INET6);
+	for (int i = 0; i < 2; i++) {
+		mptcp = i;
 
-	skel->bss->test_sockmap = false;
-	run_tests(skel, skel->maps.sock_hash, AF_INET);
-	run_tests(skel, skel->maps.sock_hash, AF_INET6);
+		skel->bss->test_sockmap = true;
+		run_tests(skel, skel->maps.sock_map, AF_INET);
+		run_tests(skel, skel->maps.sock_map, AF_INET6);
+
+		skel->bss->test_sockmap = false;
+		run_tests(skel, skel->maps.sock_hash, AF_INET);
+		run_tests(skel, skel->maps.sock_hash, AF_INET6);
+	}
 
 	test_sockmap_listen__destroy(skel);
 }
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_listen.c b/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
index b7250eb9c30c..46e9929ba7c4 100644
--- a/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
@@ -7,6 +7,9 @@
 
 #include <bpf/bpf_helpers.h>
 
+static long (*bpf_mptcp_sk_select_reuseport)(struct sk_reuseport_md *, void *,
+					     void *, __u64) = (void *)213;
+
 struct {
 	__uint(type, BPF_MAP_TYPE_SOCKMAP);
 	__uint(max_entries, 2);
@@ -137,4 +140,26 @@ int prog_reuseport(struct sk_reuseport_md *reuse)
 	return verdict;
 }
 
+SEC("sk_reuseport")
+int prog_reuseport_mptcp(struct sk_reuseport_md *reuse)
+{
+	unsigned int *count;
+	int err, verdict;
+	__u32 zero = 0;
+
+	if (test_sockmap)
+		err = bpf_mptcp_sk_select_reuseport(reuse, &sock_map,
+						    &zero, 0);
+	else
+		err = bpf_mptcp_sk_select_reuseport(reuse, &sock_hash,
+						    &zero, 0);
+	verdict = err ? SK_DROP : SK_PASS;
+
+	count = bpf_map_lookup_elem(&verdict_map, &verdict);
+	if (count)
+		(*count)++;
+
+	return verdict;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl
  2026-09-13 10:14 ` [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl Geliang Tang
@ 2026-09-13 10:28   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:28 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Calling `sk->sk_prot->ioctl(sk, cmd, karg)` from within `tcp_bpf_ioctl` unconditionally causes an infinite recursive loop, leading to kernel stack exhaustion and a panic (DoS).
- [Critical] Dereferencing `sk->sk_socket->ops->peek_len` unconditionally crashes on MPTCP sockets because their protocol operations do not implement the `.peek_len` callback.
--

commit 2a70238c7c665c1d078c38019a80c54ba0029da5
Author: Geliang Tang <geliang@kernel.org>

bpf: export and generalize tcp_bpf_ioctl

This commit exports tcp_bpf_ioctl() and generalizes it to use
protocol-specific operations instead of hard-coded TCP functions. It also
wires this generalized ioctl handler into the MPTCP BPF protocol structures
so that SIOCINQ returns correct results accounting for data in the psock
ingress queue.

> diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
> index e006131696e96..8aed3c309b0dc 100644
> --- a/net/ipv4/tcp_bpf.c
> +++ b/net/ipv4/tcp_bpf.c
[ ... ]
> @@ -331,15 +331,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
>  	return copied;
>  }
>  
> -static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
> +int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
>  {
>  	struct sk_psock *psock;
>  	bool slow;
>  
>  	if (cmd != SIOCINQ)
> -		return tcp_ioctl(sk, cmd, karg);
> +		return sk->sk_prot->ioctl(sk, cmd, karg);

[Severity: Critical]
Does this cause an infinite recursive loop regression?

When a socket is attached to a BPF sockmap, its protocol operations
structure is replaced by a BPF-specific protocol structure. Since this patch
assigns .ioctl = tcp_bpf_ioctl in those structures, sk->sk_prot->ioctl will
resolve right back to tcp_bpf_ioctl itself, exhausting the kernel stack and
leading to a system panic.

>  
> -	/* works similar as tcp_ioctl */
> +	/* works similar as sk_prot->ioctl */
>  	if (sk->sk_state == TCP_LISTEN)
>  		return -EINVAL;
>  
> @@ -347,7 +347,7 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
>  	psock = sk_psock_get(sk);
>  	if (unlikely(!psock)) {
>  		unlock_sock_fast(sk, slow);
> -		return tcp_ioctl(sk, cmd, karg);
> +		return sk->sk_prot->ioctl(sk, cmd, karg);

[Severity: Critical]
Will this fallback path also trigger the same infinite recursion regression
as above?

>  	}
>  	*karg = sk_psock_get_msg_len_nolock(psock);
>  	/* Without a verdict program, ingress data is never diverted to
> @@ -357,7 +357,7 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
>  	 */
>  	if (!READ_ONCE(psock->progs.stream_verdict) &&
>  	    !READ_ONCE(psock->progs.skb_verdict))
> -		*karg += tcp_inq(sk);
> +		*karg += sk->sk_socket->ops->peek_len(sk->sk_socket);

[Severity: Critical]
Will this cause a NULL pointer dereference regression for MPTCP sockets?

While TCP's socket operations define a .peek_len callback, MPTCP's socket
operations do not implement it, leaving the function pointer initialized to
NULL. Dereferencing it unconditionally here will crash when SIOCINQ is
invoked on an MPTCP socket attached to a BPF sockmap.

>  	sk_psock_put(sk, psock);
>  	unlock_sock_fast(sk, slow);
>  
> diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
> index 7ecafbf51fa05..f6fd610f70778 100644
> --- a/net/mptcp/bpf.c
> +++ b/net/mptcp/bpf.c
> @@ -398,6 +398,7 @@ static void mptcp_bpf_rebuild_protos(struct proto prot[MPTCP_BPF_NUM_CFGS],
>  	prot[MPTCP_BPF_BASE].close		= sock_map_close;
>  	prot[MPTCP_BPF_BASE].recvmsg		= mptcp_bpf_recvmsg;
>  	prot[MPTCP_BPF_BASE].sock_is_readable	= sk_msg_is_readable;
> +	prot[MPTCP_BPF_BASE].ioctl		= tcp_bpf_ioctl;

[Severity: Critical]
Since tcp_bpf_ioctl is wired into the MPTCP BPF protocol structure here,
does this expose MPTCP sockets to the NULL pointer dereference mentioned
above?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=10

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic
  2026-09-13 10:14 ` [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic Geliang Tang
@ 2026-09-13 10:30   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:30 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Tests using `create_socket_pairs()` silently fall back to testing TCP instead of MPTCP, resulting in incomplete coverage.
- [Medium] Duplicate subtest names for TCP and MPTCP runs make test results indistinguishable in CI logs.
--

commit afd6e61e2a0ed4da071e04fed27c852f1128b3f9
Author: Geliang Tang <geliang@kernel.org>

    selftests/bpf: add MPTCP coverage to sockmap_basic
    
    This patch extends socket_helpers.h with protocol-aware variants
    (socket_loopback_proto, create_pair_proto) that accept an explicit protocol
    parameter. Run the sockmap_basic test suite in both TCP and MPTCP modes by
    looping over run_basic_tests() with a mptcp flag. Skip UDP-specific tests
    in MPTCP mode since UDP is unaffected by MPTCP.

> diff --git a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
> index 0d59503a0c73e..d9bd6b11e2cca 100644
> --- a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
> +++ b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
[ ... ]
> @@ -397,6 +405,9 @@ static inline int create_pair(int family, int sotype, int *p0, int *p1)
>  	return 0;
>  }
>  
> +#define create_pair(family, sotype, p0, p1) \
> +	create_pair_proto(family, sotype, 0, p0, p1)
> +
>  static inline int create_socket_pairs(int family, int sotype, int *c0, int *c1,
>  				      int *p0, int *p1)
>  {

[Severity: Medium]
Since create_socket_pairs() internally uses the create_pair() macro which now
hardcodes protocol 0 (TCP), do tests calling create_socket_pairs() silently
fall back to TCP when they are supposed to be testing MPTCP?

For example, do_test_sockmap_skb_verdict_fionread() in sockmap_basic.c:

	err = create_socket_pairs(AF_INET, sotype, &c0, &c1, &p0, &p1);
	if (!ASSERT_OK(err, "create_socket_pairs()"))
		goto out;

Should create_socket_pairs() be updated to accept a proto argument or should a
create_socket_pairs_proto() variant be used instead?

> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> index 1fef6ec2ba7ae..80919a5fef493 100644
> --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
[ ... ]
> @@ -1436,6 +1444,15 @@ void test_sockmap_basic(void)
>  		test_sockmap_copied_seq(true);
>  	if (test__start_subtest("sockmap tcp multi channels"))
>  		test_sockmap_multi_channels(SOCK_STREAM);
> -	if (test__start_subtest("sockmap udp multi channels"))
> +	/* UDP is unaffected by MPTCP, only run it once (in tcp mode) */
> +	if (!mptcp && test__start_subtest("sockmap udp multi channels"))
>  		test_sockmap_multi_channels(SOCK_DGRAM);
>  }
> +
> +void test_sockmap_basic(void)
> +{
> +	for (int i = 0; i < 2; i++) {
> +		mptcp = i;
> +		run_basic_tests();
> +	}
> +}

[Severity: Medium]
Does this loop register identical subtest names for both TCP and MPTCP test runs?

Because run_basic_tests() uses static string literals in test__start_subtest(),
the subtests for TCP and MPTCP might be indistinguishable in the CI output.

Could a protocol suffix be added to the subtest names when mptcp is true?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=12

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper
  2026-09-13 10:14 ` [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper Geliang Tang
@ 2026-09-13 10:30   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:30 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] The verifier incorrectly allows bpf_mptcp_sock_map_update to be used with BPF_MAP_TYPE_SOCKHASH and other arbitrary map types, but its implementation treats the map strictly as an array-based BPF_MAP_TYPE_SOCKMAP. This causes type confusion and severe out-of-bounds memory corruption.
- [High] bpf_mptcp_sock_map_update modifies the parent MPTCP socket (msk) state without holding its socket lock, leading to data races with userspace and MPTCP stack operations.
- [High] bpf_mptcp_sock_map_update bypasses necessary socket state and operation context validation (sock_map_op_okay), allowing transient state MPTCP sockets or ANY standard TCP socket to be unsafely inserted into a sockmap.
--

commit d25c16225b28c5b937139f3df217e21f3cd9bb7b
Author: Geliang Tang <geliang@kernel.org>

    mptcp: add sock_map_update BPF helper
    
    Add a BPF helper bpf_mptcp_sock_map_update() that allows sockops programs
    running on MPTCP subflows to insert the parent MPTCP socket into a sockmap
    or sockhash. When called from a sockops callback on a subflow, it resolves
    the parent MPTCP socket via bpf_mptcp_sock_from_subflow() and validates
    that the subflow is the first subflow (msk->first) before inserting the
    MPTCP socket into the map via sock_map_update_common().

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 72a3f5998dd27..c58c2ba87d264 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -9075,6 +9076,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
>  		    func_id != BPF_FUNC_msg_redirect_hash &&
>  		    func_id != BPF_FUNC_sk_select_reuseport &&
>  		    func_id != BPF_FUNC_map_lookup_elem &&
> +		    func_id != BPF_FUNC_mptcp_sock_map_update &&
>  		    !may_update_sockmap(env, func_id))
>  			goto error;
>  		break;

[Severity: Critical]
Does allowing BPF_FUNC_mptcp_sock_map_update for BPF_MAP_TYPE_SOCKHASH maps
cause type confusion?

Because mptcp_sock_map_update() calls sock_map_update_common() which casts
the map to struct bpf_stab, passing a SOCKHASH (backed by struct bpf_shtab)
will write the socket pointer into what it thinks is the stab->sks array.
Couldn't this overlap with hash bucket lists or spinlocks and corrupt memory?

Additionally, because BPF_FUNC_mptcp_sock_map_update is omitted from the
subsequent switch (func_id) statement in check_map_func_compatibility(),
could this helper be incorrectly allowed for other unrestricted map types
like ARRAY or HASH?

> diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
> index 662b80ce2cf63..7ecafbf51fa05 100644
> --- a/net/mptcp/bpf.c
> +++ b/net/mptcp/bpf.c
[ ... ]
> +BPF_CALL_4(mptcp_sock_map_update, struct bpf_sock_ops_kern *, sops,
> +	   struct bpf_map *, map, void *, key, u64, flags)
> +{
> +	struct sock *sk = sops->sk;
> +	struct mptcp_sock *msk;
> +
> +	WARN_ON_ONCE(!rcu_read_lock_held());
> +
> +	msk = bpf_mptcp_sock_from_subflow(sk);
> +	if (msk) {

[Severity: High]
Does this bypass the socket state and operation validation checks normally
performed before inserting a socket into a sockmap?

If a BPF program calls this helper from an early hook (e.g.,
BPF_SOCK_OPS_TCP_CONNECT_CB) to insert a TCP_SYN_SENT socket, it appears
unprepared or closing sockets could be inserted, violating subsystem
invariants.

Furthermore, if the socket is a standard TCP socket rather than an MPTCP
subflow, bpf_mptcp_sock_from_subflow() returns NULL. Would this cause the
helper to silently fall through and insert the standard TCP socket into the
map, completely bypassing checks like sock_map_op_okay() and
sock_map_sk_is_suitable()?

> +		if (sk != READ_ONCE(msk->first))
> +			return -EINVAL;
> +
> +		sk = (struct sock *)msk;

[Severity: High]
Does modifying the parent MPTCP socket (msk) state without holding its socket
lock lead to data races with userspace and MPTCP stack operations?

When called from a sockops hook running on a subflow socket, the network stack
only holds the subflow socket lock, not the parent MPTCP socket lock.
Because sock_map_update_common() modifies the socket's protocol operations
(sk->sk_prot) and sets sk_user_data, could modifying sk_prot concurrently
while another CPU executes userspace system calls (e.g., recvmsg or sendmsg)
on the parent socket cause use-after-free crashes or data corruption?

> +	}
> +
> +	return sock_map_update_common(map, *(u32 *)key, sk, flags);

[Severity: Critical]
Does unconditionally calling the array-based sockmap update logic here cause
type confusion when a hash map is supplied?

Also, doesn't this truncate variable-length hash keys to u32?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=7

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests
  2026-09-13 10:14 ` [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests Geliang Tang
@ 2026-09-13 10:33   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:33 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing state validation in `bpf_mptcp_sock_map_update` allows BPF programs to insert sockets in unsafe states into sockmaps.
- [Medium] Test logic error causes spurious pass due to unreset state variable combined with a race condition.
--

commit d0c081b9b82e0db905c0b2d9dbdca1c86bbc4292
Author: Geliang Tang <geliang@kernel.org>

selftests/bpf: enable MPTCP support in sockmap tests
    
Update sockmap tests to reflect new MPTCP support. MPTCP sockets are now
allowed in sockmap, so test expectations are adjusted accordingly.

> diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c
> index d77c9f8c53c75..59ea562882a08 100644
> --- a/tools/testing/selftests/bpf/prog_tests/mptcp.c
> +++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c
[ ... ]
> @@ -570,20 +570,20 @@ static void test_sockmap_reject_mptcp(struct mptcp_sockmap *skel)
>  	if (!ASSERT_OK_FD(client_fd1, "connect_to_fd client_fd1"))
>  		goto end;
>  
> -	/* bpf_sock_map_update() called from sockops should reject MPTCP sk */
> -	if (!ASSERT_EQ(skel->bss->helper_ret, -EOPNOTSUPP, "should reject"))
> +	/* bpf_mptcp_sock_map_update() called from sockops should be allowed */
> +	if (!ASSERT_EQ(skel->bss->helper_ret, 0, "should be allowed"))

[Severity: Medium]
Will this test spuriously pass due to the state variable not being reset?

test_sockmap_with_mptcp_fallback() executes first, which successfully
triggers bpf_mptcp_sock_map_update() and sets skel->bss->helper_ret = 0.

When test_sockmap_with_mptcp() executes, it doesn't reset helper_ret.
After connect_to_fd() initiates a connection, the server's BPF program
might not execute until the server processes the final ACK, creating a race.
Because the expected value is now 0, the immediate check of helper_ret will
match the stale value from the previous test, guaranteeing a pass even if
the BPF program hasn't run yet.

Could we reset skel->bss->helper_ret before the connect_to_fd() call?


While reviewing this test, I noticed an issue in the underlying helper being
tested, introduced in commit d25c16225b28 ("mptcp: add sock_map_update BPF helper"):

> diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
> index 662b80ce2cf63..7ecafbf51fa05 100644
> --- a/net/mptcp/bpf.c
> +++ b/net/mptcp/bpf.c
[ ... ]
> +BPF_CALL_4(mptcp_sock_map_update, struct bpf_sock_ops_kern *, sops,
> +	   struct bpf_map *, map, void *, key, u64, flags)
> +{
[ ... ]
> +	return sock_map_update_common(map, *(u32 *)key, sk, flags);

[Severity: High]
Does this code allow inserting sockets in unsafe states into sockmaps?

The standard bpf_sock_map_update helper explicitly validates the socket's
state via sock_map_op_okay() and sock_map_sk_is_suitable() before inserting
it.

By calling sock_map_update_common() directly without these checks, a BPF
program could insert closing or uninitialized sockets into the map, which
bypasses the invariants established by the sockmap subsystem. This could
lead to use-after-free or resource leaks.

Should this function validate the socket state before insertion?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=8

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support
  2026-09-13 10:14 ` [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support Geliang Tang
@ 2026-09-13 10:38   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:38 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The check for repair mode in __mptcp_close() is placed after the fastclose and shutdown sequences, breaking silent teardown.
- [Medium] mptcp_setsockopt_sol_tcp() silently ignores error codes when setting TCP_REPAIR.
- [Medium] TCP_REPAIR is missing from mptcp_getsockopt_sol_tcp(), causing getsockopt() to fail with -EOPNOTSUPP.
--

commit 31eec098dfb51659558f320d2e2ec1abfdffb89c
Author: Geliang Tang <geliang@kernel.org>

mptcp: add TCP_REPAIR sockopt support

Add TCP_REPAIR socket option support for MPTCP, required for sockmap CRIU
checkpoint/restore. When TCP_REPAIR is set on the first subflow, skip the
normal connection handshake in mptcp_connect() and transition directly to
TCP_ESTABLISHED. On close, skip the shutdown sequence and jump to cleanup
when repair mode is active.

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index c5be29767cb15..07132976016e3 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
> @@ -3560,6 +3560,11 @@ bool __mptcp_close(struct sock *sk, long timeout)
>  		__mptcp_wr_shutdown(sk);
>  	}
>  
> +	if (msk->first && tcp_sk(msk->first)->repair) {
> +		mptcp_set_state(sk, TCP_CLOSE);
> +		goto cleanup;
> +	}
> +

[Severity: High]
Does this check evaluate too late in the shutdown sequence?

Since mptcp_do_fastclose() or __mptcp_wr_shutdown() can already be invoked
just before this block, could closing an MPTCP socket in repair mode incorrectly
leak RST or FIN packets to the network instead of being silently dismantled?

>  	sk_stream_wait_close(sk, timeout);
>  
>  cleanup:

[ ... ]

> diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
> index 922f6ae5c80cb..d2d1c9d1ed1d7 100644
> --- a/net/mptcp/sockopt.c
> +++ b/net/mptcp/sockopt.c

[ ... ]

> @@ -918,6 +919,7 @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname,
>  	case TCP_CONGESTION:
>  		return mptcp_setsockopt_sol_tcp_congestion(msk, optval, optlen);
>  	case TCP_DEFER_ACCEPT:
> +	case TCP_REPAIR:
>  		/* See tcp.c: TCP_DEFER_ACCEPT does not fail */
>  		mptcp_setsockopt_first_sf_only(msk, SOL_TCP, optname, optval, optlen);
>  		return 0;

[Severity: Medium]
Since TCP_DEFER_ACCEPT does not fail, returning 0 unconditionally is correct
for it. However, because setting TCP_REPAIR requires privileges, can the
underlying tcp_setsockopt() fail and return an error like -EPERM?

If so, does discarding the return value of mptcp_setsockopt_first_sf_only()
cause these errors to be silently swallowed while returning 0 to the
application?

[Severity: Medium]
Also, should TCP_REPAIR be added to mptcp_getsockopt_sol_tcp()?

If it is omitted from the switch statement there, will applications querying the
repair state via getsockopt(SOL_TCP, TCP_REPAIR) incorrectly receive
-EOPNOTSUPP? For example:

net/mptcp/sockopt.c:mptcp_getsockopt_sol_tcp() {
	...
	switch (optname) {
	case TCP_ULP:
	case TCP_CONGESTION:
	...
}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=11

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict
  2026-09-13 10:14 ` [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
@ 2026-09-13 10:40   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:40 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mptcp_read_skb ignores MPTCP_SKB_CB(skb)->offset, passing already-consumed or overlapping data to the BPF verdict program.
- [High] mptcp_read_skb delegates SKB consumption to the BPF framework, but the framework's cleanup routine (tcp_eat_skb) ignores MPTCP sockets, causing a permanent receive window stall.
--

commit 6e8107704d63a143d654e959c9d367d27969a1b4
Author: Geliang Tang <geliang@kernel.org>

mptcp: implement read_skb for sockmap stream verdict

This commit implements the read_skb callback for MPTCP stream operations,
allowing MPTCP sockets to function with BPF sockmap stream verdict programs.
It adds mptcp_read_skb() which peeks, unlinks, and hands skbs to the
receive actor.

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c

[ ... ]

> @@ -4801,6 +4801,31 @@ static ssize_t mptcp_splice_read(struct socket *sock, loff_t *ppos,
> +static int mptcp_read_skb(struct sock *sk, skb_read_actor_t recv_actor)
> +{
> +	struct sk_buff *skb;
> +	int copied = 0;
> +
> +	if (sk->sk_state == TCP_LISTEN)
> +		return -ENOTCONN;
> +
> +	while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
> +		int used;
> +
> +		__skb_unlink(skb, &sk->sk_receive_queue);
> +		WARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));
> +		used = recv_actor(sk, skb);

[Severity: High]
Does this expose stale or overlapping data to the BPF stream verdict program?

It looks like the SKB is handed off to the BPF framework without first
pulling or accounting for MPTCP_SKB_CB(skb)->offset. If the SKB has a
non-zero offset (e.g., due to overlapping sequences handled by MPTCP),
passing it directly to recv_actor() without calling skb_pull() or adjusting
skb->data could allow the verdict program to read and redirect bytes that
precede the valid payload.

[Severity: High]
Will this cause a permanent receive window stall for MPTCP sockets?

When the BPF verdict is SK_REDIRECT or SK_DROP, the framework calls
tcp_eat_skb() to advance sequences and update the receive window. However,
tcp_eat_skb() checks sk_is_tcp():

net/ipv4/tcp_bpf.c:tcp_eat_skb() {
    if (!skb || !skb->len || !sk_is_tcp(sk))
        return;
    ...
}

Since sk_is_tcp() returns false for MPTCP sockets (IPPROTO_MPTCP),
tcp_eat_skb() will return early. This means the consumed bytes are never
advanced and mptcp_cleanup_rbuf() is never invoked, which appears to cause
the MPTCP receive window to permanently close.

> +		if (used < 0) {
> +			if (!copied)
> +				copied = used;
> +			break;
> +		}
> +		copied += used;
> +	}
> +
> +	return copied;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=9

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen
  2026-09-13 10:14 ` [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen Geliang Tang
@ 2026-09-13 10:45   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:45 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] Unconditional loading of MPTCP BPF program breaks the entire test suite on kernels without CONFIG_MPTCP.
- [Medium] MPTCP redirect tests silently test standard TCP instead due to hardcoded protocol in create_socket_pairs().
- [Medium] The test suite unconditionally executes MPTCP socket tests and will report them as failures if the kernel does not support MPTCP.
- [Low] The SOTYPE_PROTO macro uses a bitwise AND on the SOCK_STREAM enum value.
--

commit 94803c56b754dd47ba0a9cd2f2a41576b9144b10
Author: Geliang Tang <geliang@kernel.org>

selftests/bpf: add MPTCP coverage to sockmap_listen

Extend sockmap_listen tests to cover MPTCP sockets. Run the full test
suite (ops, redir, reuseport) in both TCP and MPTCP modes, using
IPPROTO_MPTCP for stream sockets when in MPTCP mode. Skip UDP-specific
tests in MPTCP mode. Add a dedicated BPF reuseport program
(prog_reuseport_mptcp) that uses bpf_mptcp_sk_select_reuseport() for
MPTCP-aware reuseport selection. Prefix subtest names with the protocol
(tcp/mptcp) for clarity.

> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
> index 1c96a3cf4b979..d31f3eb661d65 100644
> --- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
> @@ -31,6 +31,10 @@
>  
>  #define NO_FLAGS 0
>  
> +static bool mptcp;
> +#define SOTYPE_PROTO(st) \
> +	(mptcp && ((st) & SOCK_STREAM) ? IPPROTO_MPTCP : 0)

[Severity: Low]
Does this bitwise AND on the socket type work correctly for all socket types?

Since SOCK_STREAM is defined as 1, ((st) & SOCK_STREAM) coincidentally
evaluates to true for SOCK_STREAM (1) and false for SOCK_DGRAM (2). However,
it evaluates to true for other odd-numbered socket types like SOCK_RAW (3) or
SOCK_SEQPACKET (5).

Could this be rewritten as ((st) == SOCK_STREAM)?

[ ... ]

> @@ -1359,7 +1375,8 @@ static void test_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
>  	map_name = map_type_str(map);
>  
>  	for (t = tests; t < tests + ARRAY_SIZE(tests); t++) {
> -		snprintf(s, sizeof(s), "%s %s %s", map_name, family_name,
> +		snprintf(s, sizeof(s), "%s %s %s %s",
> +			 mptcp ? "mptcp" : "tcp", map_name, family_name,
>  			 t->name);

[Severity: Medium]
Are the MPTCP redirect tests silently testing standard TCP instead?

The test_redir() function loops through tests like redir_to_connected() which
use create_socket_pairs() to establish connections.

However, create_socket_pairs() (defined in socket_helpers.h) hardcodes
proto = 0, which resolves to IPPROTO_TCP for SOCK_STREAM. It completely ignores
the SOTYPE_PROTO macro logic intended to create IPPROTO_MPTCP sockets.

[ ... ]

> @@ -1429,13 +1455,17 @@ void serial_test_sockmap_listen(void)
>  		return;
>  	}
>  
> -	skel->bss->test_sockmap = true;
> -	run_tests(skel, skel->maps.sock_map, AF_INET);
> -	run_tests(skel, skel->maps.sock_map, AF_INET6);
> +	for (int i = 0; i < 2; i++) {
> +		mptcp = i;
>  
> -	skel->bss->test_sockmap = false;
> -	run_tests(skel, skel->maps.sock_hash, AF_INET);
> -	run_tests(skel, skel->maps.sock_hash, AF_INET6);
> +		skel->bss->test_sockmap = true;
> +		run_tests(skel, skel->maps.sock_map, AF_INET);
> +		run_tests(skel, skel->maps.sock_map, AF_INET6);

[Severity: Medium]
Will this unconditionally execute MPTCP socket tests and report them as failures
if the kernel lacks MPTCP support?

When mptcp is set to 1, functions like socket_loopback_proto() call xsocket(),
which invokes FAIL_ERRNO("socket") if the socket creation fails (e.g., returning
-1 with EPROTONOSUPPORT).

Should the test check for MPTCP support first and gracefully skip the MPTCP
iterations?

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_listen.c b/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
> index b7250eb9c30cc..46e9929ba7c4d 100644
> --- a/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
> +++ b/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
> @@ -137,4 +140,26 @@ int prog_reuseport(struct sk_reuseport_md *reuse)
>  	return verdict;
>  }
>  
> +SEC("sk_reuseport")
> +int prog_reuseport_mptcp(struct sk_reuseport_md *reuse)
> +{
> +	unsigned int *count;
> +	int err, verdict;
> +	__u32 zero = 0;
> +
> +	if (test_sockmap)
> +		err = bpf_mptcp_sk_select_reuseport(reuse, &sock_map,
> +						    &zero, 0);

[Severity: Medium]
Will this break the entire test suite on kernels without CONFIG_MPTCP?

The bpf_mptcp_sk_select_reuseport helper is unknown to the verifier when
CONFIG_MPTCP is disabled.

Since test_sockmap_listen__open_and_load() unconditionally loads all programs
in the skeleton, this will cause the load to fail and skip all standard TCP
and UDP tests.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=16

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap
  2026-09-13 10:14 ` [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap Geliang Tang
@ 2026-09-13 10:46   ` sashiko-bot
  2026-09-13 18:22   ` Matthieu Baerts
  1 sibling, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:46 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Dereferencing `sk->sk_socket` unconditionally in `tcp_bpf_push` causes a kernel panic if the redirect target socket is concurrently orphaned.
- [High] Consuming packets via BPF on MPTCP sockets stalls the connection because the receive window is not updated in `tcp_eat_skb`.
--

commit d29dc722a5294658d21912dd69a9ad5d23908e6f
Author: Geliang Tang <geliang@kernel.org>

mptcp: implement psock_update_sk_prot for sockmap

This patch adds basic MPTCP support for BPF sockmap by implementing the
psock_update_sk_prot callback (mptcp_bpf_update_proto).

Link: https://lore.kernel.org/oe-kbuild-all/202512261144.DxrvwMS3-lkp@intel.com/
Link: https://github.com/multipath-tcp/mptcp_net-next/issues/521

> diff --git a/include/net/tcp.h b/include/net/tcp.h
> index 14baee01c74bd..d9c0214328e3f 100644
> --- a/include/net/tcp.h
> +++ b/include/net/tcp.h
> @@ -2873,6 +2873,11 @@ static inline void tcp_eat_skb(struct sock *sk, struct sk_buff *skb)

[Severity: High]
Does consuming packets via BPF on MPTCP sockets stall the connection here?

When an MPTCP socket has an sk_skb stream verdict program attached, the
sockmap layer processes ingress data. When the program returns SK_DROP or
SK_REDIRECT, the SKB is consumed and sk_psock_verdict_apply() calls
tcp_eat_skb() to advance the receive window.

However, tcp_eat_skb() checks sk_is_tcp(sk) and returns immediately for
MPTCP sockets:

	if (!skb || !skb->len || !sk_is_tcp(sk))
		return;

Unlike TCP's tcp_read_skb(), the MPTCP equivalent mptcp_read_skb() does
not independently update the receive window or msk->bytes_consumed.

Since no MPTCP-specific cleanup equivalent to tcp_eat_skb() is invoked, the
consumed packets are never accounted for, stalling the MPTCP connection
permanently.

This appears to be reachable when userspace utilizes BPF sockmap stream
verdict programs with MPTCP sockets, and doesn't seem to be resolved in any
subsequent patch.

>  int tcp_bpf_sendmsg_redir(struct sock *sk, bool ingress,
>  			  struct sk_msg *msg, u32 bytes, int flags);

[ ... ]

> diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
> index d5fcf3ce4861a..e006131696e96 100644
> --- a/net/ipv4/tcp_bpf.c
> +++ b/net/ipv4/tcp_bpf.c
> @@ -119,7 +119,7 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
>  
>  		bvec_set_page(&bvec, page, size, off);
>  		iov_iter_bvec(&msghdr.msg_iter, ITER_SOURCE, &bvec, 1, size);
> -		ret = tcp_sendmsg_locked(sk, &msghdr, size);
> +		ret = sk->sk_socket->ops->sendmsg_locked(sk, &msghdr, size);

[Severity: Critical]
Can this unconditionally dereference sk->sk_socket and cause a panic if the
redirect target socket is concurrently orphaned?

A BPF program using bpf_msg_redirect_map() selects a target socket. If the
target socket is concurrently closed, tcp_close() or mptcp_close() will
acquire the socket lock and call sock_orphan(), which sets
sk->sk_socket = NULL.

The redirect thread could execute:

tcp_bpf_sendmsg_redir()
   sk_psock_get(sk)
   lock_sock(sk)

Once the close path drops the lock, the redirect thread acquires it and
calls tcp_bpf_push(). The code attempts to call
sk->sk_socket->ops->sendmsg_locked(), but sk->sk_socket is now NULL.

This is reachable by an unprivileged user loading a BPF program and
triggering concurrent socket closure. It appears this bug was introduced by
replacing the safe direct call to tcp_sendmsg_locked() with
sk->sk_socket->ops->sendmsg_locked(), and does not seem to be resolved in
any subsequent patch in the series.

>  		if (ret <= 0)
>  			return ret;
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=6

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper
  2026-09-13 10:14 ` [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper Geliang Tang
@ 2026-09-13 10:48   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:48 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Registering `mptcp_sk_select_reuseport` for `SOCK_OPS` programs causes context type confusion, leading to arbitrary memory dereferences.
- [High] The helper `bpf_mptcp_sk_select_reuseport` blindly casts a socket retrieved from a BPF map to an `mptcp_sock` without validating its protocol type, leading to an out-of-bounds read and kernel panic.
--

commit bd581565353c0c8e12fc690ee324023efbe0b30c
Author: Geliang Tang <geliang@kernel.org>
Date:   Sun Sep 13 10:14:54 2026 +0000

    mptcp: add sk_select_reuseport BPF helper

This commit adds an MPTCP-aware variant of the bpf_sk_select_reuseport
helper. It extracts the core selection logic into sk_select_reuseport_lookup
and registers the new helper for both sock_ops and sk_reuseport BPF programs.

> diff --git a/net/core/filter.c b/net/core/filter.c
> index 0b4fec5a8848a..f1415ff42a514 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -8857,6 +8858,8 @@ sock_ops_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
>  #if IS_ENABLED(CONFIG_MPTCP)
>  	case BPF_FUNC_mptcp_sock_map_update:
>  		return &mptcp_sock_map_update_proto;
> +	case BPF_FUNC_mptcp_sk_select_reuseport:
> +		return &mptcp_sk_select_reuseport_proto;

[Severity: Critical]
Is it safe to expose this helper to sock_ops programs?

The verifier will validate the first argument against the sock_ops context
type (struct bpf_sock_ops_kern *), but the helper implementation expects a
struct sk_reuseport_kern *.

>  #endif
>  	case BPF_FUNC_get_socket_cookie:
>  		return &bpf_get_socket_cookie_sock_ops_proto;

[ ... ]

> @@ -11738,15 +11741,14 @@ struct sock *bpf_run_sk_reuseport(struct sock_reuseport *reuse, struct sock *sk,
>  		return ERR_PTR(-ECONNREFUSED);
>  }
>  
> -BPF_CALL_4(sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
> -	   struct bpf_map *, map, void *, key, u32, flags)
> +int sk_select_reuseport_lookup(struct sk_reuseport_kern *reuse_kern,
> +			       struct sock *selected_sk,
> +			       struct bpf_map *map)
>  {
>  	bool is_sockarray = map->map_type == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY;

[Severity: Critical]
Could the type confusion mentioned above lead to an arbitrary memory read
inside this function?

When called from a sock_ops program via mptcp_sk_select_reuseport,
reuse_kern actually points to a struct bpf_sock_ops_kern. Later in this
function, the code dereferences reuse_kern->sk to check the protocol.
In bpf_sock_ops_kern, the offset corresponding to the sk field points
to the args array, which means an arbitrary or user-influenced value is
treated as a struct sock pointer and dereferenced.

>  	struct sock_reuseport *reuse;
> -	struct sock *selected_sk;
>  	int err;

[ ... ]

> diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
> index f6fd610f70778..fe10c5ed28f03 100644
> --- a/net/mptcp/bpf.c
> +++ b/net/mptcp/bpf.c
> @@ -511,3 +511,25 @@ BPF_CALL_4(mptcp_sock_map_update, struct bpf_sock_ops_kern *, sops,
>  	return sock_map_update_common(map, *(u32 *)key, sk, flags);
>  }
>  EXPORT_SYMBOL_GPL(mptcp_sock_map_update);
> +
> +BPF_CALL_4(mptcp_sk_select_reuseport, struct sk_reuseport_kern *, reuse_kern,
> +	   struct bpf_map *, map, void *, key, u32, flags)
> +{
> +	struct sock *sk, *selected_sk;
> +
> +	sk = map->ops->map_lookup_elem(map, key);
> +	if (!sk)
> +		return -ENOENT;
> +
> +	selected_sk = READ_ONCE(mptcp_sk(sk)->first);

[Severity: High]
Does this helper safely handle non-MPTCP sockets?

Since a normal TCP socket can be inserted into maps like sockmap, sockhash,
or reuseport_sockarray, a BPF program could look it up and pass it here.
Because there is no protocol check before casting, this blindly casts the
tcp_sock to an mptcp_sock.

The first subflow pointer is located at a large offset in mptcp_sock that
exceeds the bounds of tcp_sock, which could result in an out-of-bounds
memory read and a kernel panic. Should there be a validation check before
this cast?

> +	if (!selected_sk) {
> +		if (sk_is_refcounted(sk))
> +			sock_put(sk);
> +		return -ENOENT;
> +	}
> +
> +	if (sk_is_refcounted(sk))
> +		sock_put(sk);
> +	return sk_select_reuseport_lookup(reuse_kern, selected_sk, map);
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=15

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap
  2026-09-13 10:14 ` [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap Geliang Tang
@ 2026-09-13 10:48   ` sashiko-bot
  0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-13 10:48 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Dereferencing a NULL function pointer (`sock->ops->peek_len`) when an MPTCP socket is attached to a sockmap or stream parser, causing a kernel panic.
- [High] MPTCP sockets are whitelisted for sockmap and stream parser operations without implementing the necessary BPF protocol hooks, leading to connection stalls, memory exhaustion, or dead code.
--

commit 2158e08c7c84a4a26fae56ddc0a38db5585b51c3
Author: Geliang Tang <geliang@kernel.org>

mptcp: add sk_is_msk() helper and use it in sockmap

This commit introduces the sk_is_msk() helper to identify MPTCP sockets.
It modifies sock_map.c to treat MPTCP sockets identically to TCP sockets
for stream parser linking, redirect checks, and state validation.

> diff --git a/net/core/sock_map.c b/net/core/sock_map.c
> index 3e3e85e3bd54e..e9e0b676aa1ca 100644
> --- a/net/core/sock_map.c
> +++ b/net/core/sock_map.c
> @@ -303,7 +303,7 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
>  
>  	write_lock_bh(&sk->sk_callback_lock);
>  	if (stream_parser && stream_verdict && !psock->saved_data_ready) {
> -		if (sk_is_tcp(sk))
> +		if (sk_is_tcp(sk) || sk_is_msk(sk))
>  			ret = sk_psock_init_strp(sk, psock);

[Severity: High]
Does this code expose MPTCP sockets to a NULL pointer dereference?

By enabling stream parser initialization for MPTCP sockets here, we
override the socket operations and expose the stream parser paths.
However, MPTCP sockets do not implement the .peek_len operation
in mptcp_stream_ops.

When an MPTCP socket is attached to a sockmap, its ioctl is overridden
to tcp_bpf_ioctl(), which unconditionally calls it:

net/ipv4/tcp_bpf.c:tcp_bpf_ioctl() {
    ...
    *karg += sk->sk_socket->ops->peek_len(sk->sk_socket);
    ...
}

Similarly, the stream parser unconditionally calls it when parsing:

net/strparser/strparser.c:strp_peek_len() {
    ...
    return sock->ops->peek_len(sock);
}

Can an unprivileged user trigger a kernel panic by adding an MPTCP socket
to a sockmap and invoking ioctl(SIOCINQ) or receiving partial data?

[Severity: High]
Does this code also cause connection stalls and potential memory exhaustion
by whitelisting MPTCP for sockmap without implementing the required BPF
protocol hooks?

When an MPTCP socket is attached and processing SKBs, mptcp_read_skb()
unlinks the SKB for BPF verdict processing but entirely skips MPTCP's
internal consumption accounting (msk->bytes_consumed). This causes
mptcp_data_avail() to always return > 0, leading to mptcp_poll() busy-looping
with EPOLLIN at 100% CPU.

Additionally, when a stream parser is attached, sk_psock_init_strp()
skips overriding read_sock for MPTCP:

net/core/skmsg.c:sk_psock_init_strp() {
    ...
    if (sk_is_tcp(sk)) {
        psock->strp.cb.read_sock = tcp_bpf_strp_read_sock;
    ...
}

This forces a fallback to mptcp_read_sock(), which improperly uncharges the
SKB memory from the socket and ACKs the sender while the parser clones the
uncharged data into psock->ingress_msg. This bypasses socket memory limits,
allowing an attacker to stream infinite data and exhaust kernel memory.

Finally, MPTCP fails to provide a custom recvmsg parser for the BPF
configurations in mptcp_bpf_rebuild_protos(), falling back to mapping
mptcp_bpf_recvmsg directly to __tcp_bpf_recvmsg. This completely breaks
sequence tracking when SKB verdicts route data to the psock.

>  		else
>  			ret = -EOPNOTSUPP;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1789294365.git.tanggeliang@kylinos.cn?part=13

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 00/16] MPTCP sockmap support
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (15 preceding siblings ...)
  2026-09-13 10:14 ` [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen Geliang Tang
@ 2026-09-13 11:24 ` MPTCP CI
  2026-09-13 11:43 ` MPTCP CI
  17 siblings, 0 replies; 31+ messages in thread
From: MPTCP CI @ 2026-09-13 11:24 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Hi Geliang,

Thank you for your modifications, that's great!

But sadly, our CI spotted some issues with it when trying to build it.

You can find more details there:

  https://github.com/multipath-tcp/mptcp_net-next/actions/runs/34752281733

Status: failure
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/30f4301b3392
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1163753

Feel free to reply to this email if you cannot access logs, if you need
some support to fix the error, if this doesn't seem to be caused by your
modifications or if the error is a false positive one.

Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 00/16] MPTCP sockmap support
  2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
                   ` (16 preceding siblings ...)
  2026-09-13 11:24 ` [PATCH mptcp-next v5 00/16] MPTCP sockmap support MPTCP CI
@ 2026-09-13 11:43 ` MPTCP CI
  17 siblings, 0 replies; 31+ messages in thread
From: MPTCP CI @ 2026-09-13 11:43 UTC (permalink / raw)
  To: Geliang Tang; +Cc: mptcp

Hi Geliang,

Thank you for your modifications, that's great!

Our CI did some validations and here is its report:

- KVM Validation: normal (except selftest_mptcp_join): Unstable: 3 failed test(s): packetdrill_mp_join packetdrill_sockopts selftest_simult_flows ⚠️ 
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Perf: Success! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/34752281734

Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/30f4301b3392
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1163753


If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:

    $ cd [kernel source code]
    $ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
        --pull always mptcp/mptcp-upstream-virtme-docker:latest \
        auto-normal

For more details:

    https://github.com/multipath-tcp/mptcp-upstream-virtme-docker


Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)

Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push
  2026-09-13 10:14 ` [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push Geliang Tang
@ 2026-09-13 18:18   ` Matthieu Baerts
  0 siblings, 0 replies; 31+ messages in thread
From: Matthieu Baerts @ 2026-09-13 18:18 UTC (permalink / raw)
  To: Geliang Tang, mptcp; +Cc: Geliang Tang

Hi Geliang,

Thank you for sharing this!

On 13/09/2026 12:14, Geliang Tang wrote:
> From: Geliang Tang <tanggeliang@kylinos.cn>
> 
> When the sendpage->MSG_SPLICE_PAGES migration series replaced
> do_tcp_sendpages() with direct tcp_sendmsg_locked() calls, callers
> that had used do_tcp_sendpages() kept an explicit
> tcp_rate_check_app_limited(sk) that was originally needed to cover
> do_tcp_sendpages() (which did not call tcp_rate_check_app_limited()
> itself). After the inlining, tcp_sendmsg_locked() always provides
> the check, and the outer call became redundant.
> 
> The site changed here, tcp_bpf_push(), is a MSG_SPLICE_PAGES loop
> that holds the socket lock and only iterates when size > 0;
> tcp_sendmsg_locked() is invoked on every iteration with state
> identical to what the outer call sees, so dropping the outer call
> is safe and behavior-preserving.
If it is not related to MPTCP, could you please send this patch to
netdev/bpf directly?

Also, should this be seen as a fix? From what I understand, some
behaviours have changed, and it is only recently that this call is no
longer needed.

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap
  2026-09-13 10:14 ` [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap Geliang Tang
  2026-09-13 10:46   ` sashiko-bot
@ 2026-09-13 18:22   ` Matthieu Baerts
  1 sibling, 0 replies; 31+ messages in thread
From: Matthieu Baerts @ 2026-09-13 18:22 UTC (permalink / raw)
  To: Geliang Tang, mptcp; +Cc: Geliang Tang, kernel test robot, Cong Wang

On 13/09/2026 12:14, Geliang Tang wrote:
> From: Geliang Tang <tanggeliang@kylinos.cn>
> 
> This patch adds basic MPTCP support for BPF sockmap by implementing the
> psock_update_sk_prot callback (mptcp_bpf_update_proto). This allows MPTCP
> sockets to be added to sockmap and enables the sk_skb stream_verdict
> redirect path via the read_skb callback. Separate protocol structures are
> maintained for IPv4/IPv6 and BASE/TX/RX/TXRX configurations, mirroring
> tcp_bpf_update_proto(). The IPv6 variant is lazily rebuilt via
> mptcp_bpf_check_v6_needs_rebuild() when the underlying protocol ops change.
> 
> MPTCP delegates to the original protocol operations so that MPTCP-specific
> logic (multi-path scheduling in sendmsg, ordered reassembly in recvmsg) is
> preserved. recvmsg is overridden in all BPF configurations with
> mptcp_bpf_recvmsg, which checks the psock ingress queue first and falls
> back to mptcp_recvmsg via the shared __tcp_bpf_recvmsg() helper. sendmsg
> is overridden in the TX/TXRX configurations with mptcp_bpf_sendmsg, which
> processes sk_msg redirect before delegating to mptcp_sendmsg via
> __tcp_bpf_sendmsg().
> 
> Export mptcp_sendmsg, mptcp_recvmsg and mptcp_prot from protocol.c so they
> can be referenced by bpf.c.
> 
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202512261144.DxrvwMS3-lkp@intel.com/
> Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/521
> Cc: Cong Wang <xiyou.wangcong@gmail.com>
> Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
> ---
>  include/net/tcp.h    |   5 ++
>  net/ipv4/tcp_bpf.c   |  29 +++++++---

Probably best to separate this change from the rest: TCP/BPF on one
side, then MPTCP/BPF (and eventually MPTCP only before, just to do the
exports.)

>  net/mptcp/bpf.c      | 129 +++++++++++++++++++++++++++++++++++++++++++
>  net/mptcp/protocol.c |  10 ++--
>  net/mptcp/protocol.h |  18 ++++++

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.


^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2026-09-13 18:22 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-13 10:14 [PATCH mptcp-next v5 00/16] MPTCP sockmap support Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 01/16] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 02/16] mptcp: add sendmsg_locked to proto_ops Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 03/16] mptcp: track app-limited state in mptcp_sendmsg Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 04/16] selftests: mptcp: sockopt: check app_limited Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 05/16] bpf: drop duplicate check_app_limited in tcp_bpf_push Geliang Tang
2026-09-13 18:18   ` Matthieu Baerts
2026-09-13 10:14 ` [PATCH mptcp-next v5 06/16] mptcp: implement psock_update_sk_prot for sockmap Geliang Tang
2026-09-13 10:46   ` sashiko-bot
2026-09-13 18:22   ` Matthieu Baerts
2026-09-13 10:14 ` [PATCH mptcp-next v5 07/16] mptcp: add sock_map_update BPF helper Geliang Tang
2026-09-13 10:30   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 08/16] selftests/bpf: enable MPTCP support in sockmap tests Geliang Tang
2026-09-13 10:33   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 09/16] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
2026-09-13 10:40   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 10/16] bpf: export and generalize tcp_bpf_ioctl Geliang Tang
2026-09-13 10:28   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 11/16] mptcp: add TCP_REPAIR sockopt support Geliang Tang
2026-09-13 10:38   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 12/16] selftests/bpf: add MPTCP coverage to sockmap_basic Geliang Tang
2026-09-13 10:30   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 13/16] mptcp: add sk_is_msk() helper and use it in sockmap Geliang Tang
2026-09-13 10:48   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 14/16] mptcp: add SO_ATTACH_REUSEPORT_EBPF support Geliang Tang
2026-09-13 10:14 ` [PATCH mptcp-next v5 15/16] mptcp: add sk_select_reuseport BPF helper Geliang Tang
2026-09-13 10:48   ` sashiko-bot
2026-09-13 10:14 ` [PATCH mptcp-next v5 16/16] selftests/bpf: add MPTCP coverage to sockmap_listen Geliang Tang
2026-09-13 10:45   ` sashiko-bot
2026-09-13 11:24 ` [PATCH mptcp-next v5 00/16] MPTCP sockmap support MPTCP CI
2026-09-13 11:43 ` MPTCP CI

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox