MPTCP Linux Development
 help / color / mirror / Atom feed
* [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket
@ 2026-05-09 21:16 David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 1/4] mptcp: sockopt: factor inet_flags propagation into a mask David Carlier
                   ` (5 more replies)
  0 siblings, 6 replies; 10+ messages in thread
From: David Carlier @ 2026-05-09 21:16 UTC (permalink / raw)
  To: mptcp; +Cc: matttbe, martineau, geliang, pabeni, David Carlier

This series adds MSG_ERRQUEUE support on the MPTCP parent socket, so
poll() and recvmsg(MSG_ERRQUEUE) observe TX timestamps and MSG_ZEROCOPY
completion notifications through the standard inet ABI. IP_RECVERR /
IPV6_RECVERR (and their RFC4884 variants) are propagated to existing
and future subflows.

Patch 1 factors per-flag inet_assign_bit() calls in
sync_socket_options() into a mask-driven loop so future propagated
flags only need to extend MPTCP_INET_FLAGS_MASK.

Patch 2 wires up RECVERR setsockopt/getsockopt: snapshot the value,
apply it on the parent, and forward to every subflow under lock_sock()
so concurrent setsockopt callers cannot leave parent and subflows
desynchronized. Newly-joining subflows pick up the four RECVERR bits
through sync_socket_options().

Patch 3 splices forwardable err skbs (TIMESTAMPING / ZEROCOPY / LOCAL)
from each subflow's error queue onto the parent's, so pollers see
EPOLLERR and recvmsg(MSG_ERRQUEUE) on the parent drains them. Subflow
ICMP errors are dropped — they will be carried by a future
MPTCP_RECERR channel.

Patch 4 covers IP_RECVERR / IPV6_RECVERR propagation and the empty-
errqueue EAGAIN contract on MSG_ERRQUEUE | MSG_DONTWAIT in selftest.

v6 -> v7:
 - patch 2: gate SOL_IPV6 setsockopt/getsockopt dispatch on
   sk_family == AF_INET6, returning -ENOPROTOOPT otherwise, mirroring
   plain TCP. Addresses the sashiko Medium finding on v6 where
   IPV6_RECVERR silently succeeded on AF_INET MPTCP sockets.
 - patch 3: track moved skbs in mptcp_recv_error() and retry
   inet_recv_error() when ret == -EAGAIN && moved, so a successful
   subflow splice is not masked by the initial drain returning EAGAIN
   (sashiko High #2 on v6).
 - patch 3: add mptcp_subflow_errqueue_pending() and OR it into the
   EPOLLERR check in mptcp_poll(), so events stranded on a subflow
   when the parent is under rmem pressure still wake userspace
   (sashiko High #1 on v6).
 - rebased on current export.

Tested with KVM-validation auto-normal: 25/25 pass.

David Carlier (4):
  mptcp: sockopt: factor inet_flags propagation into a mask
  mptcp: propagate RECVERR sockopts to subflows
  mptcp: support MSG_ERRQUEUE on the parent socket
  selftests: mptcp: cover IP_RECVERR sockopt propagation

 net/mptcp/protocol.c                          |  92 ++++++++++-
 net/mptcp/sockopt.c                           | 146 ++++++++++++++----
 .../selftests/net/mptcp/mptcp_sockopt.c       |  55 +++++++
 3 files changed, 261 insertions(+), 32 deletions(-)


base-commit: 63b133728231ebba5167bd1e53dda9bcf0bee7c7
-- 
2.53.0


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

* [PATCH mptcp-next v7 1/4] mptcp: sockopt: factor inet_flags propagation into a mask
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
@ 2026-05-09 21:16 ` David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows David Carlier
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: David Carlier @ 2026-05-09 21:16 UTC (permalink / raw)
  To: mptcp; +Cc: matttbe, martineau, geliang, pabeni, David Carlier

Introduce MPTCP_INET_FLAGS_MASK and replace the per-flag
inet_assign_bit() calls in sync_socket_options() with a loop driven
by the mask that calls assign_bit() per set bit, preserving the
per-bit atomicity of the original. Further flags propagated by MPTCP
can be added by extending the mask rather than touching the call
site.

No functional change.

Suggested-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: David Carlier <devnexen@gmail.com>
---
 net/mptcp/sockopt.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index 1544e3563852..114436d2e401 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -16,6 +16,10 @@
 
 #define MIN_INFO_OPTLEN_SIZE		16
 #define MIN_FULL_INFO_OPTLEN_SIZE	40
+#define MPTCP_INET_FLAGS_MASK \
+	(BIT(INET_FLAGS_TRANSPARENT) | \
+	 BIT(INET_FLAGS_FREEBIND) | \
+	 BIT(INET_FLAGS_BIND_ADDRESS_NO_PORT))
 
 static struct sock *__mptcp_tcp_fallback(struct mptcp_sock *msk)
 {
@@ -1546,6 +1550,9 @@ static void sync_socket_options(struct mptcp_sock *msk, struct sock *ssk)
 {
 	static const unsigned int tx_rx_locks = SOCK_RCVBUF_LOCK | SOCK_SNDBUF_LOCK;
 	struct sock *sk = (struct sock *)msk;
+	unsigned long mask = MPTCP_INET_FLAGS_MASK;
+	unsigned long src;
+	int b;
 	bool keep_open;
 
 	keep_open = sock_flag(sk, SOCK_KEEPOPEN);
@@ -1592,9 +1599,11 @@ static void sync_socket_options(struct mptcp_sock *msk, struct sock *ssk)
 	tcp_sock_set_keepcnt(ssk, msk->keepalive_cnt);
 	tcp_sock_set_maxseg(ssk, msk->maxseg);
 
-	inet_assign_bit(TRANSPARENT, ssk, inet_test_bit(TRANSPARENT, sk));
-	inet_assign_bit(FREEBIND, ssk, inet_test_bit(FREEBIND, sk));
-	inet_assign_bit(BIND_ADDRESS_NO_PORT, ssk, inet_test_bit(BIND_ADDRESS_NO_PORT, sk));
+	src = READ_ONCE(inet_sk(sk)->inet_flags);
+
+	for_each_set_bit(b, &mask, BITS_PER_LONG)
+		assign_bit(b, &inet_sk(ssk)->inet_flags, src & BIT(b));
+
 	WRITE_ONCE(inet_sk(ssk)->local_port_range, READ_ONCE(inet_sk(sk)->local_port_range));
 }
 
-- 
2.53.0


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

* [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 1/4] mptcp: sockopt: factor inet_flags propagation into a mask David Carlier
@ 2026-05-09 21:16 ` David Carlier
  2026-05-27  5:37   ` Matthieu Baerts
  2026-05-09 21:16 ` [PATCH mptcp-next v7 3/4] mptcp: support MSG_ERRQUEUE on the parent socket David Carlier
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: David Carlier @ 2026-05-09 21:16 UTC (permalink / raw)
  To: mptcp; +Cc: matttbe, martineau, geliang, pabeni, David Carlier

Propagate IP_RECVERR/IP_RECVERR_RFC4884 and
IPV6_RECVERR/IPV6_RECVERR_RFC4884 from the MPTCP socket to existing
and future subflows.

mptcp_setsockopt_recverr() snapshots optval into a local int, applies
it to the parent socket via ip_setsockopt() / ipv6_setsockopt(), bumps
msk->setsockopt_seq, and forwards to every subflow via
mptcp_setsockopt_all_sf(). Newly-joining subflows pick up the four
RECVERR bits through sync_socket_options() now that
MPTCP_INET_FLAGS_MASK covers them.

mptcp_setsockopt_all_sf() skips IPv4 subflows when called with
SOL_IPV6 to avoid the -ENOPROTOOPT that ip_setsockopt() returns on
level mismatch in AF_INET6 msks carrying IPv4 subflows.

Suggested-by: Paolo Abeni <pabeni@redhat.com>
Assisted-by: Codex:gpt-5
Signed-off-by: David Carlier <devnexen@gmail.com>
---
 net/mptcp/sockopt.c | 133 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 109 insertions(+), 24 deletions(-)

diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index 114436d2e401..fbbd1692af7e 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -8,6 +8,8 @@
 
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
 #include <net/sock.h>
 #include <net/protocol.h>
 #include <net/tcp.h>
@@ -19,7 +21,11 @@
 #define MPTCP_INET_FLAGS_MASK \
 	(BIT(INET_FLAGS_TRANSPARENT) | \
 	 BIT(INET_FLAGS_FREEBIND) | \
-	 BIT(INET_FLAGS_BIND_ADDRESS_NO_PORT))
+	 BIT(INET_FLAGS_BIND_ADDRESS_NO_PORT) | \
+	 BIT(INET_FLAGS_RECVERR) | \
+	 BIT(INET_FLAGS_RECVERR_RFC4884) | \
+	 BIT(INET_FLAGS_RECVERR6) | \
+	 BIT(INET_FLAGS_RECVERR6_RFC4884))
 
 static struct sock *__mptcp_tcp_fallback(struct mptcp_sock *msk)
 {
@@ -394,6 +400,81 @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname,
 	return -EOPNOTSUPP;
 }
 
+static int mptcp_setsockopt_all_sf(struct mptcp_sock *msk, int level,
+				   int optname, sockptr_t optval,
+				   unsigned int optlen)
+{
+	struct mptcp_subflow_context *subflow;
+	int ret = 0;
+
+	mptcp_for_each_subflow(msk, subflow) {
+		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
+
+		if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
+			continue;
+
+		ret = tcp_setsockopt(ssk, level, optname, optval, optlen);
+		if (ret)
+			break;
+	}
+
+	if (!ret)
+		sockopt_seq_inc(msk);
+
+	return ret;
+}
+
+static int mptcp_setsockopt_recverr(struct mptcp_sock *msk, int level,
+				    int optname, sockptr_t optval,
+				    unsigned int optlen)
+{
+	struct sock *sk = (struct sock *)msk;
+	int val, ret;
+
+	/* Let ip_setsockopt() / ipv6_setsockopt() validate optval and optlen
+	 * (so 1-byte boolean writes keep the same ABI as plain TCP) and update
+	 * the parent's RECVERR bit. Re-read that bit under lock_sock() and
+	 * push it to the subflows: concurrent setsockopt callers cannot leave
+	 * parent and subflows desynchronized this way.
+	 */
+	if (level == SOL_IP)
+		ret = ip_setsockopt(sk, level, optname, optval, optlen);
+#if IS_ENABLED(CONFIG_IPV6)
+	else if (level == SOL_IPV6)
+		ret = ipv6_setsockopt(sk, level, optname, optval, optlen);
+#endif
+	else
+		return -EOPNOTSUPP;
+	if (ret)
+		return ret;
+
+	lock_sock(sk);
+	switch (optname) {
+	case IP_RECVERR:
+		val = inet_test_bit(RECVERR, sk);
+		break;
+	case IP_RECVERR_RFC4884:
+		val = inet_test_bit(RECVERR_RFC4884, sk);
+		break;
+#if IS_ENABLED(CONFIG_IPV6)
+	case IPV6_RECVERR:
+		val = inet6_test_bit(RECVERR6, sk);
+		break;
+	case IPV6_RECVERR_RFC4884:
+		val = inet6_test_bit(RECVERR6_RFC4884, sk);
+		break;
+#endif
+	default:
+		release_sock(sk);
+		return -EOPNOTSUPP;
+	}
+
+	ret = mptcp_setsockopt_all_sf(msk, level, optname,
+				      KERNEL_SOCKPTR(&val), sizeof(val));
+	release_sock(sk);
+	return ret;
+}
+
 static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname,
 			       sockptr_t optval, unsigned int optlen)
 {
@@ -436,6 +517,10 @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname,
 
 		release_sock(sk);
 		break;
+	case IPV6_RECVERR:
+	case IPV6_RECVERR_RFC4884:
+		ret = mptcp_setsockopt_recverr(msk, SOL_IPV6, optname, optval, optlen);
+		break;
 	}
 
 	return ret;
@@ -781,6 +866,9 @@ static int mptcp_setsockopt_v4(struct mptcp_sock *msk, int optname,
 		return mptcp_setsockopt_sol_ip_set(msk, optname, optval, optlen);
 	case IP_TOS:
 		return mptcp_setsockopt_v4_set_tos(msk, optname, optval, optlen);
+	case IP_RECVERR:
+	case IP_RECVERR_RFC4884:
+		return mptcp_setsockopt_recverr(msk, SOL_IP, optname, optval, optlen);
 	}
 
 	return -EOPNOTSUPP;
@@ -808,27 +896,6 @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int
 	return ret;
 }
 
-static int mptcp_setsockopt_all_sf(struct mptcp_sock *msk, int level,
-				   int optname, sockptr_t optval,
-				   unsigned int optlen)
-{
-	struct mptcp_subflow_context *subflow;
-	int ret = 0;
-
-	mptcp_for_each_subflow(msk, subflow) {
-		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
-
-		ret = tcp_setsockopt(ssk, level, optname, optval, optlen);
-		if (ret)
-			break;
-	}
-
-	if (!ret)
-		sockopt_seq_inc(msk);
-
-	return ret;
-}
-
 static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname,
 				    sockptr_t optval, unsigned int optlen)
 {
@@ -932,8 +999,11 @@ int mptcp_setsockopt(struct sock *sk, int level, int optname,
 	if (level == SOL_IP)
 		return mptcp_setsockopt_v4(msk, optname, optval, optlen);
 
-	if (level == SOL_IPV6)
+	if (level == SOL_IPV6) {
+		if (sk->sk_family != AF_INET6)
+			return -ENOPROTOOPT;
 		return mptcp_setsockopt_v6(msk, optname, optval, optlen);
+	}
 
 	if (level == SOL_TCP)
 		return mptcp_setsockopt_sol_tcp(msk, optname, optval, optlen);
@@ -1473,6 +1543,12 @@ static int mptcp_getsockopt_v4(struct mptcp_sock *msk, int optname,
 	case IP_LOCAL_PORT_RANGE:
 		return mptcp_put_int_option(msk, optval, optlen,
 				READ_ONCE(inet_sk(sk)->local_port_range));
+	case IP_RECVERR:
+		return mptcp_put_int_option(msk, optval, optlen,
+				inet_test_bit(RECVERR, sk));
+	case IP_RECVERR_RFC4884:
+		return mptcp_put_int_option(msk, optval, optlen,
+				inet_test_bit(RECVERR_RFC4884, sk));
 	}
 
 	return -EOPNOTSUPP;
@@ -1493,6 +1569,12 @@ static int mptcp_getsockopt_v6(struct mptcp_sock *msk, int optname,
 	case IPV6_FREEBIND:
 		return mptcp_put_int_option(msk, optval, optlen,
 					    inet_test_bit(FREEBIND, sk));
+	case IPV6_RECVERR:
+		return mptcp_put_int_option(msk, optval, optlen,
+					    inet6_test_bit(RECVERR6, sk));
+	case IPV6_RECVERR_RFC4884:
+		return mptcp_put_int_option(msk, optval, optlen,
+					    inet6_test_bit(RECVERR6_RFC4884, sk));
 	}
 
 	return -EOPNOTSUPP;
@@ -1537,8 +1619,11 @@ int mptcp_getsockopt(struct sock *sk, int level, int optname,
 
 	if (level == SOL_IP)
 		return mptcp_getsockopt_v4(msk, optname, optval, option);
-	if (level == SOL_IPV6)
+	if (level == SOL_IPV6) {
+		if (sk->sk_family != AF_INET6)
+			return -ENOPROTOOPT;
 		return mptcp_getsockopt_v6(msk, optname, optval, option);
+	}
 	if (level == SOL_TCP)
 		return mptcp_getsockopt_sol_tcp(msk, optname, optval, option);
 	if (level == SOL_MPTCP)
-- 
2.53.0


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

* [PATCH mptcp-next v7 3/4] mptcp: support MSG_ERRQUEUE on the parent socket
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 1/4] mptcp: sockopt: factor inet_flags propagation into a mask David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows David Carlier
@ 2026-05-09 21:16 ` David Carlier
  2026-05-09 21:16 ` [PATCH mptcp-next v7 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation David Carlier
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: David Carlier @ 2026-05-09 21:16 UTC (permalink / raw)
  To: mptcp; +Cc: matttbe, martineau, geliang, pabeni, David Carlier

Splice pending err skbs from each subflow's error queue onto the parent
msk's error queue at error-report time, so poll() and recvmsg(MSG_ERRQUEUE)
on the parent socket observe TX timestamps and MSG_ZEROCOPY completion
notifications through the standard inet ABI.

The splice filters by SO_EE_ORIGIN: TIMESTAMPING / ZEROCOPY / LOCAL
events forward to the parent because they are tied to user-handed data,
not to a specific path; subflow-level ICMP errors are dropped because
the legacy RECVERR ABI cannot meaningfully convey their per-subflow peer
identity to single-path-aware userspace. Such events will be carried by
a future MPTCP_RECERR channel.

mptcp_recv_error() retries the splice on the pull side: if
sock_queue_err_skb() previously failed under rmem pressure, the skb
stays on the subflow queue, and the next recvmsg(MSG_ERRQUEUE) splices
it once the parent's queue has been drained.

Suggested-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: David Carlier <devnexen@gmail.com>
---
 net/mptcp/protocol.c | 92 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 86 insertions(+), 6 deletions(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 93e7a42fc65c..53abb8dc2c0f 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -11,6 +11,7 @@
 #include <linux/netdevice.h>
 #include <linux/sched/signal.h>
 #include <linux/atomic.h>
+#include <linux/errqueue.h>
 #include <net/aligned_data.h>
 #include <net/rps.h>
 #include <net/sock.h>
@@ -815,21 +816,52 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
 	return moved;
 }
 
+static bool mptcp_errqueue_skb_forwardable(const struct sk_buff *skb)
+{
+	u8 origin = SKB_EXT_ERR(skb)->ee.ee_origin;
+
+	return origin == SO_EE_ORIGIN_TIMESTAMPING ||
+		origin == SO_EE_ORIGIN_ZEROCOPY ||
+		origin == SO_EE_ORIGIN_LOCAL;
+}
+
+static bool __mptcp_subflow_splice_errqueue(struct sock *sk, struct sock *ssk)
+{
+	struct sk_buff *skb;
+	bool moved = false;
+
+	while ((skb = skb_dequeue(&ssk->sk_error_queue))) {
+		if (!mptcp_errqueue_skb_forwardable(skb)) {
+			kfree_skb(skb);  /* path-specific (ICMP) — belongs in MPTCP_RECERR */
+			continue;
+		}
+		if (sock_queue_err_skb(sk, skb)) {
+			skb_queue_head(&ssk->sk_error_queue, skb);
+			break;
+		}
+		moved = true;
+	}
+
+	return moved;
+}
+
 static bool __mptcp_subflow_error_report(struct sock *sk, struct sock *ssk)
 {
 	int ssk_state;
+	bool report;
 	int err;
 
+	report = __mptcp_subflow_splice_errqueue(sk, ssk);
+
 	/* only propagate errors on fallen-back sockets or
 	 * on MPC connect
 	 */
 	if (sk->sk_state != TCP_SYN_SENT && !__mptcp_check_fallback(mptcp_sk(sk)))
-		return false;
+		goto out;
 
 	err = sock_error(ssk);
 	if (!err)
-		return false;
-
+		goto out;
 	/* We need to propagate only transition to CLOSE state.
 	 * Orphaned socket will see such state change via
 	 * subflow_sched_work_if_closed() and that path will properly
@@ -839,6 +871,11 @@ static bool __mptcp_subflow_error_report(struct sock *sk, struct sock *ssk)
 	if (ssk_state == TCP_CLOSE && !sock_flag(sk, SOCK_DEAD))
 		mptcp_set_state(sk, ssk_state);
 	WRITE_ONCE(sk->sk_err, -err);
+	report = true;
+
+out:
+	if (!report)
+		return false;
 
 	/* This barrier is coupled with smp_rmb() in mptcp_poll() */
 	smp_wmb();
@@ -2286,6 +2323,35 @@ static unsigned int mptcp_inq_hint(const struct sock *sk)
 	return 0;
 }
 
+static int mptcp_recv_error(struct sock *sk, struct msghdr *msg, int len)
+{
+	struct mptcp_sock *msk = mptcp_sk(sk);
+	struct mptcp_subflow_context *subflow;
+	bool moved = false;
+	int ret;
+
+	/* Drain the parent first: a previous splice may have failed under
+	 * rmem pressure and the skb stayed on a subflow. Freeing space here
+	 * lets the splice below succeed; sock_queue_err_skb() then re-asserts
+	 * EPOLLERR so userspace knows to drain again on the next poll.
+	 */
+	ret = inet_recv_error(sk, msg, len);
+
+	lock_sock(sk);
+	mptcp_for_each_subflow(msk, subflow) {
+		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
+
+		if (!skb_queue_empty_lockless(&ssk->sk_error_queue))
+			moved |= __mptcp_subflow_splice_errqueue(sk, ssk);
+	}
+	release_sock(sk);
+
+	if (ret == -EAGAIN && moved)
+		ret = inet_recv_error(sk, msg, len);
+
+	return ret;
+}
+
 static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 			 int flags)
 {
@@ -2295,9 +2361,8 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	int target;
 	long timeo;
 
-	/* MSG_ERRQUEUE is really a no-op till we support IP_RECVERR */
 	if (unlikely(flags & MSG_ERRQUEUE))
-		return inet_recv_error(sk, msg, len);
+		return mptcp_recv_error(sk, msg, len);
 
 	lock_sock(sk);
 	if (unlikely(sk->sk_state == TCP_LISTEN)) {
@@ -4298,6 +4363,19 @@ static __poll_t mptcp_check_writeable(struct mptcp_sock *msk)
 	return 0;
 }
 
+static bool mptcp_subflow_errqueue_pending(const struct mptcp_sock *msk)
+{
+	struct mptcp_subflow_context *subflow;
+
+	mptcp_for_each_subflow(msk, subflow) {
+		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
+
+		if (!skb_queue_empty_lockless(&ssk->sk_error_queue))
+			return true;
+	}
+	return false;
+}
+
 static __poll_t mptcp_poll(struct file *file, struct socket *sock,
 			   struct poll_table_struct *wait)
 {
@@ -4341,7 +4419,9 @@ static __poll_t mptcp_poll(struct file *file, struct socket *sock,
 
 	/* This barrier is coupled with smp_wmb() in __mptcp_error_report() */
 	smp_rmb();
-	if (READ_ONCE(sk->sk_err))
+	if (READ_ONCE(sk->sk_err) ||
+	    !skb_queue_empty_lockless(&sk->sk_error_queue) ||
+	    mptcp_subflow_errqueue_pending(msk))
 		mask |= EPOLLERR;
 
 	return mask;
-- 
2.53.0


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

* [PATCH mptcp-next v7 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
                   ` (2 preceding siblings ...)
  2026-05-09 21:16 ` [PATCH mptcp-next v7 3/4] mptcp: support MSG_ERRQUEUE on the parent socket David Carlier
@ 2026-05-09 21:16 ` David Carlier
  2026-05-09 22:24 ` [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket MPTCP CI
  2026-05-27  5:08 ` Matthieu Baerts
  5 siblings, 0 replies; 10+ messages in thread
From: David Carlier @ 2026-05-09 21:16 UTC (permalink / raw)
  To: mptcp; +Cc: matttbe, martineau, geliang, pabeni, David Carlier

Exercise setsockopt/getsockopt of IP_RECVERR and IPV6_RECVERR on the
MPTCP parent socket, including the empty-errqueue EAGAIN contract on
MSG_ERRQUEUE|MSG_DONTWAIT.

End-to-end errqueue delivery (ICMP, TX timestamps, zerocopy) depends on
subflow-side producers that are out of scope for this series and will be
covered by follow-up work.

Assisted-by: Codex:gpt-5
Signed-off-by: David Carlier <devnexen@gmail.com>
---
 .../selftests/net/mptcp/mptcp_sockopt.c       | 55 +++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
index b6e58d936ebe..95bb2cc8e2ff 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
@@ -769,6 +769,60 @@ static void test_ip_tos_sockopt(int fd)
 		xerror("expect socklen_t == -1");
 }
 
+static void test_ip_recverr_sockopt(int fd)
+{
+	struct iovec iov = {
+		.iov_base = &(char){ 0 },
+		.iov_len = 1,
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	int one = 1, zero = 0, val = -1;
+	socklen_t s = sizeof(val);
+	int level, optname, r;
+
+	switch (pf) {
+	case AF_INET:
+		level = SOL_IP;
+		optname = IP_RECVERR;
+		break;
+	case AF_INET6:
+		level = SOL_IPV6;
+		optname = IPV6_RECVERR;
+		break;
+	default:
+		xerror("Unknown pf %d\n", pf);
+	}
+
+	r = setsockopt(fd, level, optname, &one, sizeof(one));
+	if (r)
+		die_perror("setsockopt recverr on");
+
+	r = getsockopt(fd, level, optname, &val, &s);
+	if (r)
+		die_perror("getsockopt recverr on");
+	if (s != sizeof(val) || val != one)
+		xerror("recverr on mismatch val=%d len=%u", val, s);
+
+	r = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
+	if (r != -1 || errno != EAGAIN)
+		xerror("expected empty errqueue to return EAGAIN, ret=%d errno=%d", r, errno);
+
+	r = setsockopt(fd, level, optname, &zero, sizeof(zero));
+	if (r)
+		die_perror("setsockopt recverr off");
+
+	val = -1;
+	s = sizeof(val);
+	r = getsockopt(fd, level, optname, &val, &s);
+	if (r)
+		die_perror("getsockopt recverr off");
+	if (s != sizeof(val) || val != zero)
+		xerror("recverr off mismatch val=%d len=%u", val, s);
+}
+
 static int client(int pipefd)
 {
 	int fd = -1;
@@ -787,6 +841,7 @@ static int client(int pipefd)
 	}
 
 	test_ip_tos_sockopt(fd);
+	test_ip_recverr_sockopt(fd);
 
 	connect_one_server(fd, pipefd);
 
-- 
2.53.0


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

* Re: [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
                   ` (3 preceding siblings ...)
  2026-05-09 21:16 ` [PATCH mptcp-next v7 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation David Carlier
@ 2026-05-09 22:24 ` MPTCP CI
  2026-05-27  5:08 ` Matthieu Baerts
  5 siblings, 0 replies; 10+ messages in thread
From: MPTCP CI @ 2026-05-09 22:24 UTC (permalink / raw)
  To: David Carlier; +Cc: mptcp

Hi David,

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/25612442092

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


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

* Re: [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket
  2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
                   ` (4 preceding siblings ...)
  2026-05-09 22:24 ` [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket MPTCP CI
@ 2026-05-27  5:08 ` Matthieu Baerts
  2026-05-27  5:37   ` David CARLIER
  5 siblings, 1 reply; 10+ messages in thread
From: Matthieu Baerts @ 2026-05-27  5:08 UTC (permalink / raw)
  To: David Carlier, mptcp; +Cc: martineau, geliang, pabeni

Hi David,

On 10/05/2026 07:16, David Carlier wrote:
> This series adds MSG_ERRQUEUE support on the MPTCP parent socket, so
> poll() and recvmsg(MSG_ERRQUEUE) observe TX timestamps and MSG_ZEROCOPY
> completion notifications through the standard inet ABI. IP_RECVERR /
> IPV6_RECVERR (and their RFC4884 variants) are propagated to existing
> and future subflows.
> 
> Patch 1 factors per-flag inet_assign_bit() calls in
> sync_socket_options() into a mask-driven loop so future propagated
> flags only need to extend MPTCP_INET_FLAGS_MASK.
> 
> Patch 2 wires up RECVERR setsockopt/getsockopt: snapshot the value,
> apply it on the parent, and forward to every subflow under lock_sock()
> so concurrent setsockopt callers cannot leave parent and subflows
> desynchronized. Newly-joining subflows pick up the four RECVERR bits
> through sync_socket_options().
> 
> Patch 3 splices forwardable err skbs (TIMESTAMPING / ZEROCOPY / LOCAL)
> from each subflow's error queue onto the parent's, so pollers see
> EPOLLERR and recvmsg(MSG_ERRQUEUE) on the parent drains them. Subflow
> ICMP errors are dropped — they will be carried by a future
> MPTCP_RECERR channel.

Sorry for the delay: I saw Sashiko had some comments [1], and because I
noticed you checked it before, I thought you were going to send a reply
or a new version, and I forgot to ask here. So here it is: is the review
correct?

[1]
https://sashiko.dev/#/patchset/20260509211651.104934-1-devnexen@gmail.com

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


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

* Re: [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket
  2026-05-27  5:08 ` Matthieu Baerts
@ 2026-05-27  5:37   ` David CARLIER
  0 siblings, 0 replies; 10+ messages in thread
From: David CARLIER @ 2026-05-27  5:37 UTC (permalink / raw)
  To: Matthieu Baerts; +Cc: mptcp, martineau, geliang, pabeni

Hi,

On Wed, 27 May 2026 at 06:08, Matthieu Baerts <matttbe@kernel.org> wrote:
>
> Hi David,
>
> On 10/05/2026 07:16, David Carlier wrote:
> > This series adds MSG_ERRQUEUE support on the MPTCP parent socket, so
> > poll() and recvmsg(MSG_ERRQUEUE) observe TX timestamps and MSG_ZEROCOPY
> > completion notifications through the standard inet ABI. IP_RECVERR /
> > IPV6_RECVERR (and their RFC4884 variants) are propagated to existing
> > and future subflows.
> >
> > Patch 1 factors per-flag inet_assign_bit() calls in
> > sync_socket_options() into a mask-driven loop so future propagated
> > flags only need to extend MPTCP_INET_FLAGS_MASK.
> >
> > Patch 2 wires up RECVERR setsockopt/getsockopt: snapshot the value,
> > apply it on the parent, and forward to every subflow under lock_sock()
> > so concurrent setsockopt callers cannot leave parent and subflows
> > desynchronized. Newly-joining subflows pick up the four RECVERR bits
> > through sync_socket_options().
> >
> > Patch 3 splices forwardable err skbs (TIMESTAMPING / ZEROCOPY / LOCAL)
> > from each subflow's error queue onto the parent's, so pollers see
> > EPOLLERR and recvmsg(MSG_ERRQUEUE) on the parent drains them. Subflow
> > ICMP errors are dropped — they will be carried by a future
> > MPTCP_RECERR channel.
>
> Sorry for the delay: I saw Sashiko had some comments [1], and because I
> noticed you checked it before, I thought you were going to send a reply
> or a new version, and I forgot to ask here. So here it is: is the review
> correct?
>
> [1]
> https://sashiko.dev/#/patchset/20260509211651.104934-1-devnexen@gmail.com
>
> Cheers,
> Matt
> --
> Sponsored by the NGI0 Core fund.
>

Yes, both findings are real.

For v8 I'll drop the skb on splice failure (matches sock_queue_err_skb()'s
own behaviour under rmem pressure: -ENOMEM + sk_drops++, the skb is freed
by the caller). With nothing retained on subflow err queues,
mptcp_subflow_errqueue_pending() can go from mptcp_poll() — which fixes
the lockless conn_list walk too — and the recvmsg retry in
mptcp_recv_error() goes with it.

Cheers

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

* Re: [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows
  2026-05-09 21:16 ` [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows David Carlier
@ 2026-05-27  5:37   ` Matthieu Baerts
  2026-05-27  5:48     ` David CARLIER
  0 siblings, 1 reply; 10+ messages in thread
From: Matthieu Baerts @ 2026-05-27  5:37 UTC (permalink / raw)
  To: David Carlier, mptcp; +Cc: martineau, geliang, pabeni

Hi David,

On 10/05/2026 07:16, David Carlier wrote:
> Propagate IP_RECVERR/IP_RECVERR_RFC4884 and
> IPV6_RECVERR/IPV6_RECVERR_RFC4884 from the MPTCP socket to existing
> and future subflows.
> 
> mptcp_setsockopt_recverr() snapshots optval into a local int, applies
> it to the parent socket via ip_setsockopt() / ipv6_setsockopt(), bumps
> msk->setsockopt_seq, and forwards to every subflow via
> mptcp_setsockopt_all_sf(). Newly-joining subflows pick up the four
> RECVERR bits through sync_socket_options() now that
> MPTCP_INET_FLAGS_MASK covers them.
> 
> mptcp_setsockopt_all_sf() skips IPv4 subflows when called with
> SOL_IPV6 to avoid the -ENOPROTOOPT that ip_setsockopt() returns on
> level mismatch in AF_INET6 msks carrying IPv4 subflows.
> 
> Suggested-by: Paolo Abeni <pabeni@redhat.com>
> Assisted-by: Codex:gpt-5
> Signed-off-by: David Carlier <devnexen@gmail.com>
> ---
>  net/mptcp/sockopt.c | 133 ++++++++++++++++++++++++++++++++++++--------
>  1 file changed, 109 insertions(+), 24 deletions(-)
> 
> diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
> index 114436d2e401..fbbd1692af7e 100644
> --- a/net/mptcp/sockopt.c
> +++ b/net/mptcp/sockopt.c
> @@ -8,6 +8,8 @@
>  
>  #include <linux/kernel.h>
>  #include <linux/module.h>
> +#include <net/ip.h>
> +#include <net/ipv6.h>

Are these new "include" really needed?

>  #include <net/sock.h>
>  #include <net/protocol.h>
>  #include <net/tcp.h>
> @@ -19,7 +21,11 @@
>  #define MPTCP_INET_FLAGS_MASK \
>  	(BIT(INET_FLAGS_TRANSPARENT) | \
>  	 BIT(INET_FLAGS_FREEBIND) | \
> -	 BIT(INET_FLAGS_BIND_ADDRESS_NO_PORT))
> +	 BIT(INET_FLAGS_BIND_ADDRESS_NO_PORT) | \
> +	 BIT(INET_FLAGS_RECVERR) | \
> +	 BIT(INET_FLAGS_RECVERR_RFC4884) | \
> +	 BIT(INET_FLAGS_RECVERR6) | \
> +	 BIT(INET_FLAGS_RECVERR6_RFC4884))
>  
>  static struct sock *__mptcp_tcp_fallback(struct mptcp_sock *msk)
>  {
> @@ -394,6 +400,81 @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname,
>  	return -EOPNOTSUPP;
>  }
>  
> +static int mptcp_setsockopt_all_sf(struct mptcp_sock *msk, int level,
> +				   int optname, sockptr_t optval,
> +				   unsigned int optlen)
> +{
> +	struct mptcp_subflow_context *subflow;
> +	int ret = 0;
> +
> +	mptcp_for_each_subflow(msk, subflow) {
> +		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
> +
> +		if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
> +			continue;

This new check looks strange:

- Was it required before this patch? If yes, please create a dedicated
patch. If no, explain how it can happen in the commit message (+ here in
a short comment?).

- if the msk is in v6, all subflows are in v6 as well, no? Or did I miss
a case (easy to miss with v6-mapped-in-v4 sockets...)?

> +		ret = tcp_setsockopt(ssk, level, optname, optval, optlen);
> +		if (ret)
> +			break;
> +	}
> +
> +	if (!ret)
> +		sockopt_seq_inc(msk);
> +
> +	return ret;
> +}
> +
> +static int mptcp_setsockopt_recverr(struct mptcp_sock *msk, int level,
> +				    int optname, sockptr_t optval,
> +				    unsigned int optlen)
> +{
> +	struct sock *sk = (struct sock *)msk;
> +	int val, ret;
> +
> +	/* Let ip_setsockopt() / ipv6_setsockopt() validate optval and optlen
> +	 * (so 1-byte boolean writes keep the same ABI as plain TCP) and update
> +	 * the parent's RECVERR bit. Re-read that bit under lock_sock() and
> +	 * push it to the subflows: concurrent setsockopt callers cannot leave
> +	 * parent and subflows desynchronized this way.
> +	 */
> +	if (level == SOL_IP)
> +		ret = ip_setsockopt(sk, level, optname, optval, optlen);
> +#if IS_ENABLED(CONFIG_IPV6)
> +	else if (level == SOL_IPV6)
> +		ret = ipv6_setsockopt(sk, level, optname, optval, optlen);
> +#endif
> +	else
> +		return -EOPNOTSUPP;
> +	if (ret)
> +		return ret;
> +
> +	lock_sock(sk);
> +	switch (optname) {
> +	case IP_RECVERR:
> +		val = inet_test_bit(RECVERR, sk);
> +		break;
> +	case IP_RECVERR_RFC4884:
> +		val = inet_test_bit(RECVERR_RFC4884, sk);
> +		break;
> +#if IS_ENABLED(CONFIG_IPV6)
> +	case IPV6_RECVERR:
> +		val = inet6_test_bit(RECVERR6, sk);
> +		break;
> +	case IPV6_RECVERR_RFC4884:
> +		val = inet6_test_bit(RECVERR6_RFC4884, sk);
> +		break;
> +#endif
> +	default:
> +		release_sock(sk);
> +		return -EOPNOTSUPP;

When lock are used, we usually try to have one exit path: could you set
'ret' and use a "goto" here?

Also, this "default" case should never be used, right? Do we need it? Or
use a WARN?

> +	}
> +
> +	ret = mptcp_setsockopt_all_sf(msk, level, optname,
> +				      KERNEL_SOCKPTR(&val), sizeof(val));
> +	release_sock(sk);
> +	return ret;
> +}
> +
>  static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname,
>  			       sockptr_t optval, unsigned int optlen)
>  {
> @@ -436,6 +517,10 @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname,
>  
>  		release_sock(sk);
>  		break;
> +	case IPV6_RECVERR:
> +	case IPV6_RECVERR_RFC4884:
> +		ret = mptcp_setsockopt_recverr(msk, SOL_IPV6, optname, optval, optlen);
> +		break;
>  	}
>  
>  	return ret;
> @@ -781,6 +866,9 @@ static int mptcp_setsockopt_v4(struct mptcp_sock *msk, int optname,
>  		return mptcp_setsockopt_sol_ip_set(msk, optname, optval, optlen);
>  	case IP_TOS:
>  		return mptcp_setsockopt_v4_set_tos(msk, optname, optval, optlen);
> +	case IP_RECVERR:
> +	case IP_RECVERR_RFC4884:
> +		return mptcp_setsockopt_recverr(msk, SOL_IP, optname, optval, optlen);
>  	}
>  
>  	return -EOPNOTSUPP;
> @@ -808,27 +896,6 @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int
>  	return ret;
>  }
>  
> -static int mptcp_setsockopt_all_sf(struct mptcp_sock *msk, int level,
> -				   int optname, sockptr_t optval,
> -				   unsigned int optlen)
> -{
> -	struct mptcp_subflow_context *subflow;
> -	int ret = 0;
> -
> -	mptcp_for_each_subflow(msk, subflow) {
> -		struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
> -
> -		ret = tcp_setsockopt(ssk, level, optname, optval, optlen);
> -		if (ret)
> -			break;
> -	}
> -
> -	if (!ret)
> -		sockopt_seq_inc(msk);
> -
> -	return ret;
> -}
> -
>  static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname,
>  				    sockptr_t optval, unsigned int optlen)
>  {
> @@ -932,8 +999,11 @@ int mptcp_setsockopt(struct sock *sk, int level, int optname,
>  	if (level == SOL_IP)
>  		return mptcp_setsockopt_v4(msk, optname, optval, optlen);
>  
> -	if (level == SOL_IPV6)
> +	if (level == SOL_IPV6) {
> +		if (sk->sk_family != AF_INET6)
> +			return -ENOPROTOOPT;

Same here: Was it required before this patch? If yes, please create a
dedicated patch. If no, explain how it can happen in the commit message
(+ here in a short comment?).

>  		return mptcp_setsockopt_v6(msk, optname, optval, optlen);
> +	}
>  
>  	if (level == SOL_TCP)
>  		return mptcp_setsockopt_sol_tcp(msk, optname, optval, optlen);
> @@ -1473,6 +1543,12 @@ static int mptcp_getsockopt_v4(struct mptcp_sock *msk, int optname,
>  	case IP_LOCAL_PORT_RANGE:
>  		return mptcp_put_int_option(msk, optval, optlen,
>  				READ_ONCE(inet_sk(sk)->local_port_range));
> +	case IP_RECVERR:
> +		return mptcp_put_int_option(msk, optval, optlen,
> +				inet_test_bit(RECVERR, sk));
> +	case IP_RECVERR_RFC4884:
> +		return mptcp_put_int_option(msk, optval, optlen,
> +				inet_test_bit(RECVERR_RFC4884, sk));
>  	}
>  
>  	return -EOPNOTSUPP;
> @@ -1493,6 +1569,12 @@ static int mptcp_getsockopt_v6(struct mptcp_sock *msk, int optname,
>  	case IPV6_FREEBIND:
>  		return mptcp_put_int_option(msk, optval, optlen,
>  					    inet_test_bit(FREEBIND, sk));
> +	case IPV6_RECVERR:
> +		return mptcp_put_int_option(msk, optval, optlen,
> +					    inet6_test_bit(RECVERR6, sk));
> +	case IPV6_RECVERR_RFC4884:
> +		return mptcp_put_int_option(msk, optval, optlen,
> +					    inet6_test_bit(RECVERR6_RFC4884, sk));
>  	}
>  
>  	return -EOPNOTSUPP;
> @@ -1537,8 +1619,11 @@ int mptcp_getsockopt(struct sock *sk, int level, int optname,
>  
>  	if (level == SOL_IP)
>  		return mptcp_getsockopt_v4(msk, optname, optval, option);
> -	if (level == SOL_IPV6)
> +	if (level == SOL_IPV6) {
> +		if (sk->sk_family != AF_INET6)
> +			return -ENOPROTOOPT;

Same here.

>  		return mptcp_getsockopt_v6(msk, optname, optval, option);
> +	}
>  	if (level == SOL_TCP)
>  		return mptcp_getsockopt_sol_tcp(msk, optname, optval, option);
>  	if (level == SOL_MPTCP)

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


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

* Re: [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows
  2026-05-27  5:37   ` Matthieu Baerts
@ 2026-05-27  5:48     ` David CARLIER
  0 siblings, 0 replies; 10+ messages in thread
From: David CARLIER @ 2026-05-27  5:48 UTC (permalink / raw)
  To: Matthieu Baerts; +Cc: mptcp, martineau, geliang, pabeni

On 27/05/2026 06:37, Matthieu Baerts wrote:
>> +#include <net/ip.h>
>> +#include <net/ipv6.h>
>
> Are these new "include" really needed?

ip_setsockopt() was already used pre-patch (transitively pulled in), so
<net/ip.h> can go. <net/ipv6.h> I need for ipv6_setsockopt() — will
double-check at build time and drop if transitive too.

>> +        if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
>> +            continue;
>
> This new check looks strange: [...]

Not a fix for an existing bug — pre-patch, mptcp_setsockopt_all_sf()
was only called with SOL_TCP (TCP_MAXSEG), where family is irrelevant.
RECVERR is the first SOL_IPV6 caller, so the v4-subflow-on-v6-msk
case never gets exercised on this path without this patch.

For that case: an AF_INET6 listener accepts MP_JOINs from v4 peers
(subflow created with sk_family == AF_INET), and the userspace PM can
graft v4 subflows onto a v6 msk. Calling tcp_setsockopt(SOL_IPV6, ...)
on such a subflow hits ipv6_setsockopt()'s sk_family != AF_INET6 check
and returns -EAFNOSUPPORT, aborting the loop and leaving the remaining
subflows desynchronised.

Since it's only needed by the new caller, I'll keep the skip in this
patch with an inline comment + a note in the commit message.

>> +    default:
>> +        release_sock(sk);
>> +        return -EOPNOTSUPP;
>
> When lock are used, we usually try to have one exit path: could you set
> 'ret' and use a "goto" here?
>
> Also, this "default" case should never be used, right? Do we need it? Or
> use a WARN?

Right, unreachable — the caller filters to the four RECVERR optnames.
I'll drop the default and use a single 'ret' + goto out for the success
path.

>>      if (level == SOL_IP)
>>          return mptcp_setsockopt_v4(msk, optname, optval, optlen);
>>
>> -    if (level == SOL_IPV6)
>> +    if (level == SOL_IPV6) {
>> +        if (sk->sk_family != AF_INET6)
>> +            return -ENOPROTOOPT;
>
> Same here: Was it required before this patch? [...]

Not needed — pre-patch the v6 path would already error out with
-EAFNOSUPPORT from the v6 layer further down. This is an unrelated
behaviour change; I'll drop both hunks (setsockopt + getsockopt).

Cheers

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

end of thread, other threads:[~2026-05-27  5:48 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-09 21:16 [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket David Carlier
2026-05-09 21:16 ` [PATCH mptcp-next v7 1/4] mptcp: sockopt: factor inet_flags propagation into a mask David Carlier
2026-05-09 21:16 ` [PATCH mptcp-next v7 2/4] mptcp: propagate RECVERR sockopts to subflows David Carlier
2026-05-27  5:37   ` Matthieu Baerts
2026-05-27  5:48     ` David CARLIER
2026-05-09 21:16 ` [PATCH mptcp-next v7 3/4] mptcp: support MSG_ERRQUEUE on the parent socket David Carlier
2026-05-09 21:16 ` [PATCH mptcp-next v7 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation David Carlier
2026-05-09 22:24 ` [PATCH mptcp-next v7 0/4] mptcp: MSG_ERRQUEUE support on the parent socket MPTCP CI
2026-05-27  5:08 ` Matthieu Baerts
2026-05-27  5:37   ` David CARLIER

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