* [PATCH mptcp-next v4 1/5] mptcp: implement .splice_eof
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
@ 2026-08-24 7:09 ` Geliang Tang
2026-08-24 7:09 ` [PATCH mptcp-next v4 2/5] selftests: mptcp: connect: trigger splice_eof Geliang Tang
` (4 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Geliang Tang @ 2026-08-24 7:09 UTC (permalink / raw)
To: mptcp; +Cc: Geliang Tang, Matthieu Baerts
From: Geliang Tang <tanggeliang@kylinos.cn>
This patch implements the .splice_eof interface for MPTCP, namely
mptcp_splice_eof(), to flush any pending data when a sendfile()
operation reaches end-of-file.
The implementation first calls __mptcp_push_pending() to push all
unsent data from the MPTCP layer's write queue to the TCP subflows.
Then, for each active subflow that still has data in its send queue,
it acquires the subflow socket lock with lock_sock_nested(,
SINGLE_DEPTH_NESTING) to avoid lockdep false positives (the MPTCP
socket lock is already held). After that, it calls tcp_send_mss()
and tcp_push() to flush the subflow's send buffer.
Without this .splice_eof support, MPTCP did not flush its pending
data immediately when sendfile() reached EOF. While the data would
eventually be sent after a short delay, this patch makes the
behavior consistent with TCP.
Note: the .splice_eof field of mptcp_stream_ops is set to
inet_splice_eof, which redirects to the protocol-specific .splice_eof
(here, mptcp_splice_eof).
Suggested-by: Matthieu Baerts <matttbe@kernel.org>
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
net/mptcp/protocol.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index f22d64ab1c53..265a07e73a52 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -4249,6 +4249,34 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
return 0;
}
+static void mptcp_splice_eof(struct socket *sock)
+{
+ struct mptcp_subflow_context *subflow;
+ struct sock *sk = sock->sk, *ssk;
+ struct mptcp_sock *msk;
+ int mss_now, size_goal;
+ struct tcp_sock *tp;
+
+ msk = mptcp_sk(sk);
+
+ lock_sock(sk);
+ __mptcp_push_pending(sk, 0);
+ mptcp_rps_record_subflows(msk);
+ mptcp_for_each_subflow(msk, subflow) {
+ ssk = mptcp_subflow_tcp_sock(subflow);
+ if (ssk->sk_state == TCP_CLOSE ||
+ !tcp_write_queue_tail(ssk))
+ continue;
+
+ lock_sock_nested(ssk, SINGLE_DEPTH_NESTING);
+ tp = tcp_sk(ssk);
+ mss_now = tcp_send_mss(ssk, &size_goal, 0);
+ tcp_push(ssk, 0, mss_now, tp->nonagle, size_goal);
+ release_sock(ssk);
+ }
+ release_sock(sk);
+}
+
static struct proto mptcp_prot = {
.name = "MPTCP",
.owner = THIS_MODULE,
@@ -4280,6 +4308,7 @@ static struct proto mptcp_prot = {
.obj_size = sizeof(struct mptcp_sock),
.slab_flags = SLAB_TYPESAFE_BY_RCU,
.no_autobind = true,
+ .splice_eof = mptcp_splice_eof,
};
static int mptcp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len)
@@ -4773,6 +4802,7 @@ static const struct proto_ops mptcp_stream_ops = {
.set_rcvlowat = mptcp_set_rcvlowat,
.read_sock = mptcp_read_sock,
.splice_read = mptcp_splice_read,
+ .splice_eof = inet_splice_eof,
};
static struct inet_protosw mptcp_protosw = {
@@ -4885,6 +4915,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,
+ .splice_eof = inet_splice_eof,
};
static struct proto mptcp_v6_prot;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH mptcp-next v4 2/5] selftests: mptcp: connect: trigger splice_eof
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
2026-08-24 7:09 ` [PATCH mptcp-next v4 1/5] mptcp: implement .splice_eof Geliang Tang
@ 2026-08-24 7:09 ` Geliang Tang
2026-08-24 7:09 ` [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
` (3 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Geliang Tang @ 2026-08-24 7:09 UTC (permalink / raw)
To: mptcp; +Cc: Geliang Tang
From: Geliang Tang <tanggeliang@kylinos.cn>
Increase the sendfile count by one to ensure the transmission size
exceeds the actual data length. This triggers the splice_eof path
in the kernel, allowing the newly implemented MPTCP splice_eof
interface to be exercised during testing.
The change from 'count' to 'count + 1' forces the sendfile operation
to attempt sending one more byte than available, which activates the
end-of-file handling in the splicing logic and ensures coverage of
the related MPTCP code paths.
Additionally, handle cases where sendfile returns 0 (no more data)
or a value larger than the remaining count (e.g., due to concurrent
file growth). In such cases, break out of the loop to prevent
unsigned integer underflow and potential infinite loop.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
tools/testing/selftests/net/mptcp/mptcp_connect.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c
index 178d98d91fea..fca5d4606625 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_connect.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c
@@ -872,12 +872,15 @@ static int do_sendfile(int infd, int outfd, unsigned int count,
while (count > 0) {
ssize_t r;
- r = sendfile(outfd, infd, NULL, count);
+ r = sendfile(outfd, infd, NULL, count + 1);
if (r < 0) {
perror("sendfile");
return 3;
}
+ if (r == 0 || r > count)
+ break;
+
count -= r;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
2026-08-24 7:09 ` [PATCH mptcp-next v4 1/5] mptcp: implement .splice_eof Geliang Tang
2026-08-24 7:09 ` [PATCH mptcp-next v4 2/5] selftests: mptcp: connect: trigger splice_eof Geliang Tang
@ 2026-08-24 7:09 ` Geliang Tang
2026-08-24 7:31 ` sashiko-bot
2026-08-24 7:09 ` [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot Geliang Tang
` (2 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Geliang Tang @ 2026-08-24 7:09 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 uses
mptcp_recv_skb() to peek skbs from the MPTCP receive queue with the
correct offset, adjusts the skb via skb_pull to skip already consumed
data, unlinks it, and passes it to the recv_actor. bytes_consumed is
updated after each skb is consumed.
This follows the same pattern as tcp_read_skb() in net/ipv4/tcp.c,
adapted for MPTCP's receive queue semantics.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
net/mptcp/protocol.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 265a07e73a52..c1e26c6e5c17 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -4780,6 +4780,36 @@ 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 mptcp_sock *msk = mptcp_sk(sk);
+ struct sk_buff *skb;
+ int copied = 0;
+ u32 offset;
+
+ if (sk->sk_state == TCP_LISTEN)
+ return -ENOTCONN;
+
+ while ((skb = mptcp_recv_skb(sk, &offset)) != NULL) {
+ int used;
+
+ if (offset)
+ skb_pull(skb, offset);
+
+ __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;
+ msk->bytes_consumed += used;
+ }
+ return copied;
+}
+
static const struct proto_ops mptcp_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
@@ -4803,6 +4833,7 @@ static const struct proto_ops mptcp_stream_ops = {
.read_sock = mptcp_read_sock,
.splice_read = mptcp_splice_read,
.splice_eof = inet_splice_eof,
+ .read_skb = mptcp_read_skb,
};
static struct inet_protosw mptcp_protosw = {
@@ -4916,6 +4947,7 @@ static const struct proto_ops mptcp_v6_stream_ops = {
.read_sock = mptcp_read_sock,
.splice_read = mptcp_splice_read,
.splice_eof = inet_splice_eof,
+ .read_skb = mptcp_read_skb,
};
static struct proto mptcp_v6_prot;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict
2026-08-24 7:09 ` [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
@ 2026-08-24 7:31 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-24 7:31 UTC (permalink / raw)
To: Geliang Tang; +Cc: mptcp
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Calling skb_pull() with an unvalidated offset on potentially non-linear SKBs causes a kernel BUG() panic.
- [High] Data consumed by the BPF stream verdict program does not trigger MPTCP receive window updates or buffer auto-tuning.
- [High] Error paths drop SKBs without advancing consumption state, leading to receive window leaks and deadlocks.
--
--- Patch [3]: [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict ---
commit b365e36f46ed4c92c3a7c83b8b32cf1113c12bbf
Author: Geliang Tang <geliang@kernel.org>
mptcp: implement read_skb for sockmap stream verdict
This commit adds mptcp_read_skb() to allow BPF sockmap stream verdict
programs to process MPTCP sockets. It peeks skbs from the receive queue,
adjusts offsets with skb_pull(), unlinks them, and passes them to the BPF
program while updating the bytes_consumed counter.
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 265a07e73a529..c1e26c6e5c17b 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
[ ... ]
> @@ -4780,6 +4780,36 @@ 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 mptcp_sock *msk = mptcp_sk(sk);
> + struct sk_buff *skb;
> + int copied = 0;
> + u32 offset;
> +
> + if (sk->sk_state == TCP_LISTEN)
> + return -ENOTCONN;
> +
> + while ((skb = mptcp_recv_skb(sk, &offset)) != NULL) {
> + int used;
> +
> + if (offset)
> + skb_pull(skb, offset);
[Severity: Critical]
Does this risk a kernel panic? If the socket buffer in the receive queue is
non-linear (such as from GRO on the TCP subflow or page frags), and the
offset is greater than the linear part of the buffer (skb_headlen), calling
skb_pull() without first linearizing it can cause length underflows.
If skb->len drops below skb->data_len, it will trigger the BUG_ON() in
skb_pull_inline().
Should pskb_may_pull() be used here to ensure the bytes are in the linear
region first?
> +
> + __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;
> + }
[Severity: High]
If the recv_actor returns an error and drops the buffer, this breaks the
loop without adding the dropped bytes to bytes_consumed.
Since tcp_eat_skb() is a no-op for MPTCP sockets, does this leave
bytes_consumed permanently out of sync with bytes_received? This might lead
to a permanent loss of receive window capacity and eventual connection
stalls.
> + copied += used;
> + msk->bytes_consumed += used;
[Severity: High]
Should this update also trigger receive window updates and buffer
auto-tuning?
After advancing bytes_consumed, omitting calls to mptcp_cleanup_rbuf() and
mptcp_rcv_space_adjust() means MPTCP will not proactively send window update
ACKs when the window opens.
The sender might eventually stall and rely on slow zero window probes to
discover the new window.
> + }
> + return copied;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1787554581.git.tanggeliang@kylinos.cn?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
` (2 preceding siblings ...)
2026-08-24 7:09 ` [PATCH mptcp-next v4 3/5] mptcp: implement read_skb for sockmap stream verdict Geliang Tang
@ 2026-08-24 7:09 ` Geliang Tang
2026-08-24 7:32 ` sashiko-bot
2026-08-24 7:09 ` [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP Geliang Tang
2026-08-24 8:40 ` [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot MPTCP CI
5 siblings, 1 reply; 10+ messages in thread
From: Geliang Tang @ 2026-08-24 7:09 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.
Note that unlike TCP, this implementation does not override sendmsg and
recvmsg for the BPF variants. TCP replaces sendmsg with tcp_bpf_sendmsg
to support sk_msg redirect (outbound message-level redirect) and recvmsg
with tcp_bpf_recvmsg to read from the psock ingress queue. MPTCP does
not override these because:
- sendmsg: MPTCP sendmsg dispatches data across multiple subflows via the
scheduler. Intercepting at the MPTCP level would require coordinating
sk_msg processing with multi-path scheduling, which is non-trivial.
- recvmsg: MPTCP recvmsg reassembles ordered data from the receive queue
with sequence tracking. Overriding it to also check the psock ingress
queue would duplicate significant MPTCP-specific logic.
As a result, sk_msg redirect (msg_parser / msg_verdict) is not supported
for MPTCP sockets. Only sk_skb stream_verdict redirect via the read_skb /
sk_data_ready path is functional.
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>
---
net/mptcp/bpf.c | 106 +++++++++++++++++++++++++++++++++++++++++++
net/mptcp/protocol.c | 10 ++--
net/mptcp/protocol.h | 9 ++++
3 files changed, 121 insertions(+), 4 deletions(-)
diff --git a/net/mptcp/bpf.c b/net/mptcp/bpf.c
index 82b0ad25f700..df41f72603c0 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,108 @@ 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 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].sock_is_readable = sk_msg_is_readable;
+
+ prot[MPTCP_BPF_TX] = prot[MPTCP_BPF_BASE];
+ prot[MPTCP_BPF_RX] = prot[MPTCP_BPF_BASE];
+ prot[MPTCP_BPF_TXRX] = prot[MPTCP_BPF_BASE];
+}
+
+#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;
+}
+
+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 c1e26c6e5c17..7d36dc4fab6e 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)
+int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
struct mptcp_sock *msk = mptcp_sk(sk);
struct page_frag *pfrag;
@@ -2410,8 +2410,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;
@@ -4277,7 +4276,7 @@ static void mptcp_splice_eof(struct socket *sock)
release_sock(sk);
}
-static struct proto mptcp_prot = {
+struct proto mptcp_prot = {
.name = "MPTCP",
.owner = THIS_MODULE,
.init = mptcp_init_sock,
@@ -4309,6 +4308,9 @@ static struct proto mptcp_prot = {
.slab_flags = SLAB_TYPESAFE_BY_RCU,
.no_autobind = true,
.splice_eof = mptcp_splice_eof,
+#ifdef CONFIG_BPF_SYSCALL
+ .psock_update_sk_prot = mptcp_bpf_update_proto,
+#endif
};
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 3d250e8204d5..cc7adeb0b7f6 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -1377,4 +1377,13 @@ 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_sendmsg(struct sock *sk, struct msghdr *msg, size_t len);
+int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags);
+
+#ifdef CONFIG_BPF_SYSCALL
+int mptcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock,
+ bool restore);
+#endif
+
#endif /* __MPTCP_PROTOCOL_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot
2026-08-24 7:09 ` [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot Geliang Tang
@ 2026-08-24 7:32 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-24 7:32 UTC (permalink / raw)
To: Geliang Tang; +Cc: mptcp
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Infinite kernel loop in `sock_map_close` and `sock_map_destroy` when a listening MPTCP socket in a sockmap accepts a new connection.
- [High] Missing `recvmsg` override for MPTCP BPF sockets causes packets redirected via SK_PASS or SK_REDIRECT to be permanently leaked.
- [Medium] Attaching a `msg_parser` (`sk_msg`) program to an MPTCP socket silently succeeds but is never executed due to missing `sendmsg` override.
--
--- Patch [4]: [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot ---
commit b125c7aa25aed0ed03598733bc75a385728067e7
Author: Geliang Tang <geliang@kernel.org>
Subject: mptcp: implement psock_update_sk_prot
This patch adds basic MPTCP support for BPF sockmap by implementing the
psock_update_sk_prot callback. This enables MPTCP sockets to be added to
sockmap and supports the sk_skb stream_verdict redirect path.
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/net/mptcp/bpf.c b/net/mptcp/bpf.c
> index 82b0ad25f7001..df41f72603c0f 100644
> --- a/net/mptcp/bpf.c
> +++ b/net/mptcp/bpf.c
[ ... ]
> +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;
[Severity: Critical]
Does accepting a connection on a listening MPTCP socket in a sockmap lead to
an infinite loop here?
When an incoming connection is accepted, the socket is cloned and the child
socket inherits mptcp_bpf_prots. Because sk_user_data (the psock) is cleared
for the child socket, sock_map_close() will find a NULL psock and attempt to
call the original sk_prot->close.
Since MPTCP lacks an equivalent to tcp_bpf_clone() to restore the original
protocol on cloned sockets, sk_prot->close resolves back to sock_map_close()
itself, triggering an infinite goto retry loop.
> + prot[MPTCP_BPF_BASE].sock_is_readable = sk_msg_is_readable;
> +
> + prot[MPTCP_BPF_TX] = prot[MPTCP_BPF_BASE];
> + prot[MPTCP_BPF_RX] = prot[MPTCP_BPF_BASE];
> + prot[MPTCP_BPF_TXRX] = prot[MPTCP_BPF_BASE];
> +}
[Severity: High]
Does this cause redirected packets to be permanently leaked?
If a stream_verdict program returns SK_PASS (the default behavior to allow
packets) or another BPF program redirects to this MPTCP socket via
SK_REDIRECT, the data is placed into the BPF ingress_msg queue.
Because this patch explicitly does not override recvmsg(), mptcp_recvmsg()
only reads from sk_receive_queue and never checks the BPF ingress queues.
The data appears to be permanently blackholed, and since mptcp_poll() checks
only MPTCP-level data, EPOLLIN is broken as well.
[ ... ]
> +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;
[Severity: Medium]
Should attaching a msg_parser program return an error like -EOPNOTSUPP
instead of silently succeeding?
If a msg_parser program is attached, the configuration is set to MPTCP_BPF_TX.
However, mptcp_bpf_rebuild_protos() maps MPTCP_BPF_TX back to MPTCP_BPF_BASE,
which does not override sendmsg(). As a result, the attachment succeeds but
outgoing packets will bypass the BPF program entirely.
> +
> + if (psock->progs.stream_verdict || psock->progs.skb_verdict)
> + config = (config == MPTCP_BPF_TX) ? MPTCP_BPF_TXRX :
> + MPTCP_BPF_RX;
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1787554581.git.tanggeliang@kylinos.cn?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
` (3 preceding siblings ...)
2026-08-24 7:09 ` [PATCH mptcp-next v4 4/5] mptcp: implement psock_update_sk_prot Geliang Tang
@ 2026-08-24 7:09 ` Geliang Tang
2026-08-24 7:21 ` sashiko-bot
2026-08-24 8:40 ` [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot MPTCP CI
5 siblings, 1 reply; 10+ messages in thread
From: Geliang Tang @ 2026-08-24 7:09 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. Since sockops fires on TCP subflows which have psock_update_sk_prot
set to NULL, bpf_sock_map_update() returns -EOPNOTSUPP for MPTCP
connections. Add server_fd1 and server_fd2 to the sockmap explicitly from
userspace via bpf_map_update_elem() instead of relying on the sockops
program.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
.../testing/selftests/bpf/prog_tests/mptcp.c | 42 +++++++++++--------
1 file changed, 24 insertions(+), 18 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c
index d77c9f8c53c7..4f8727dbcbfd 100644
--- a/tools/testing/selftests/bpf/prog_tests/mptcp.c
+++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c
@@ -500,30 +500,36 @@ static void test_subflow(void)
close(cgroup_fd);
}
-/* Test sockmap on MPTCP server handling non-mp-capable clients. */
-static void test_sockmap_with_mptcp_fallback(struct mptcp_sockmap *skel)
+/* Test sockmap on MPTCP server handling MPTCP clients. */
+static void test_sockmap_with_mptcp(struct mptcp_sockmap *skel)
{
int listen_fd = -1, client_fd1 = -1, client_fd2 = -1;
int server_fd1 = -1, server_fd2 = -1, sent, recvd;
char snd[9] = "123456789";
+ int zero = 0, err;
char rcv[10];
/* start server with MPTCP enabled */
listen_fd = start_mptcp_server(AF_INET, NULL, 0, 0);
- if (!ASSERT_OK_FD(listen_fd, "sockmap-fb:start_mptcp_server"))
+ if (!ASSERT_OK_FD(listen_fd, "sockmap:start_mptcp_server"))
return;
skel->bss->trace_port = ntohs(get_socket_local_port(listen_fd));
skel->bss->sk_index = 0;
- /* create client without MPTCP enabled */
- client_fd1 = connect_to_fd_opts(listen_fd, NULL);
- if (!ASSERT_OK_FD(client_fd1, "sockmap-fb:connect_to_fd"))
+ /* create client with MPTCP enabled */
+ client_fd1 = connect_to_fd(listen_fd, 0);
+ if (!ASSERT_OK_FD(client_fd1, "sockmap:connect_to_fd"))
goto end;
server_fd1 = accept(listen_fd, NULL, 0);
+ err = bpf_map_update_elem(bpf_map__fd(skel->maps.sock_map),
+ &zero, &server_fd1, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "sockmap:add server_fd1"))
+ goto end;
+
skel->bss->sk_index = 1;
client_fd2 = connect_to_fd_opts(listen_fd, NULL);
- if (!ASSERT_OK_FD(client_fd2, "sockmap-fb:connect_to_fd"))
+ if (!ASSERT_OK_FD(client_fd2, "sockmap:connect_to_fd"))
goto end;
server_fd2 = accept(listen_fd, NULL, 0);
@@ -532,12 +538,12 @@ static void test_sockmap_with_mptcp_fallback(struct mptcp_sockmap *skel)
*/
skel->bss->redirect_idx = 1;
sent = send(client_fd1, snd, sizeof(snd), 0);
- if (!ASSERT_EQ(sent, sizeof(snd), "sockmap-fb:send(client_fd1)"))
+ if (!ASSERT_EQ(sent, sizeof(snd), "sockmap:send(client_fd1)"))
goto end;
/* try to recv more bytes to avoid truncation check */
recvd = recv(client_fd2, rcv, sizeof(rcv), 0);
- if (!ASSERT_EQ(recvd, sizeof(snd), "sockmap-fb:recv(client_fd2)"))
+ if (!ASSERT_EQ(recvd, sizeof(snd), "sockmap:recv(client_fd2)"))
goto end;
end:
@@ -552,11 +558,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 of MPTCP sockets - both server and client sides. */
+static void test_sockmap_mptcp_support(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);
@@ -577,13 +583,13 @@ static void test_sockmap_reject_mptcp(struct mptcp_sockmap *skel)
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, 0, "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)
@@ -625,8 +631,8 @@ static void test_mptcp_sockmap(void)
if (endpoint_init("subflow", 2) < 0)
goto close_netns;
- test_sockmap_with_mptcp_fallback(skel);
- test_sockmap_reject_mptcp(skel);
+ test_sockmap_with_mptcp(skel);
+ test_sockmap_mptcp_support(skel);
close_netns:
netns_free(netns);
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP
2026-08-24 7:09 ` [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP Geliang Tang
@ 2026-08-24 7:21 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-24 7:21 UTC (permalink / raw)
To: Geliang Tang; +Cc: mptcp
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Incomplete test conversion for `test_sockmap_with_mptcp` that contradicts the commit message.
Pre-existing issues:
- [High] Unchecked `skb_pull()` in `mptcp_read_skb()` causes a kernel panic if the partial read offset exceeds the linear data length.
--
--- Patch [5]: [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP ---
commit a45d03b16d520c3cfcda9a50c975aef36b416a66
Author: Geliang Tang <geliang@kernel.org>
selftests/bpf: Update sockmap tests for MPTCP
This commit updates sockmap tests to reflect new MPTCP support. It allows
MPTCP sockets in sockmap and adjusts the test expectations. It also adds
explicit map updates for server_fd1 and server_fd2 since sockops fires on TCP
subflows which return -EOPNOTSUPP for MPTCP connections.
> diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c
> index d77c9f8c53c75..4f8727dbcbfd0 100644
> --- a/tools/testing/selftests/bpf/prog_tests/mptcp.c
> +++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c
[ ... ]
> @@ -500,30 +500,36 @@ static void test_subflow(void)
[ ... ]
> server_fd1 = accept(listen_fd, NULL, 0);
> + err = bpf_map_update_elem(bpf_map__fd(skel->maps.sock_map),
> + &zero, &server_fd1, BPF_NOEXIST);
> + if (!ASSERT_OK(err, "sockmap:add server_fd1"))
> + goto end;
> +
> skel->bss->sk_index = 1;
> client_fd2 = connect_to_fd_opts(listen_fd, NULL);
> - if (!ASSERT_OK_FD(client_fd2, "sockmap-fb:connect_to_fd"))
> + if (!ASSERT_OK_FD(client_fd2, "sockmap:connect_to_fd"))
> goto end;
>
> server_fd2 = accept(listen_fd, NULL, 0);
[Severity: Medium]
Does this code miss the changes for client_fd2 and server_fd2 that were
mentioned in the commit message?
The commit message states:
"Add server_fd1 and server_fd2 to the sockmap explicitly from userspace via
bpf_map_update_elem() instead of relying on the sockops program."
However, that map update appears to be missing here for server_fd2.
Additionally, client_fd2 is still created using connect_to_fd_opts() with
NULL options, creating a TCP fallback socket rather than an MPTCP socket.
Should client_fd2 also be converted to use connect_to_fd(listen_fd, 0) to
fully test the MPTCP support for the second socket pair?
[Severity: High]
This is a pre-existing issue, but can the unchecked skb_pull() in
mptcp_read_skb() located in net/mptcp/protocol.c cause a kernel panic?
If a user creates an MPTCP socket, receives a packet where the payload is
stored in page fragments, reads a partial amount of data, and then attaches
the socket to a sockmap, mptcp_read_skb() can be invoked.
net/mptcp/protocol.c:mptcp_read_skb() {
...
if (offset)
skb_pull(skb, offset);
...
}
If the partial read offset exceeds the linear data length, the skb_pull() call
without prior bounds checking will trigger a BUG() inside __skb_pull(). Should
this offset be validated against the available linear region before pulling?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1787554581.git.tanggeliang@kylinos.cn?part=5
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot
2026-08-24 7:09 [PATCH mptcp-next v4 0/5] implement psock_update_sk_prot Geliang Tang
` (4 preceding siblings ...)
2026-08-24 7:09 ` [PATCH mptcp-next v4 5/5] selftests/bpf: Update sockmap tests for MPTCP Geliang Tang
@ 2026-08-24 8:40 ` MPTCP CI
5 siblings, 0 replies; 10+ messages in thread
From: MPTCP CI @ 2026-08-24 8:40 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): Success! ✅
- 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! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/32701957766
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/e4b106e7b0f4
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1150669
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] 10+ messages in thread