mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE
@ 2026-09-18 18:38 Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket Matthieu Baerts (NGI0)
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-18 18:38 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, linux-kernel, Matthieu Baerts (NGI0),
	David Carlier, Shuah Khan, linux-kselftest

This series lets MPTCP applications use poll(EPOLLERR) and
recvmsg(MSG_ERRQUEUE) on the MPTCP socket to drain TX timestamps
through the standard inet ABI, the same way they would on a plain TCP
socket. ICMP-derived errors stay on the subflow queue: the legacy
RECVERR ABI cannot convey their per-subflow peer identity, and they
are intended for a future MPTCP_RECERR channel.

- Patch 1 splices subflow err-skbs onto the MPTCP's sk_error_queue at
  error-report time. All forwarded events go through sock_queue_err_skb,
  which re-homes skb->sk onto the MPTCP and charges sk_rmem_alloc, so
  the MPTCP's error queue stays bounded by sk_rcvbuf and is dropped under
  rmem pressure, matching tcp's tx-timestamp path and ip_icmp_error() /
  ipv6_icmp_error(). mptcp_recvmsg(MSG_ERRQUEUE) forwards directly to
  inet_recv_error(), and mptcp_poll() advertises EPOLLERR purely on the
  MPTCP's sk_err / sk_error_queue, matching tcp_poll().

- Patch 2 factors the existing inet_flags subflow-propagation hard-coded
  list into a mask, so the next patch can extend it without churn.

- Patch 3 makes IP_RECVERR / IPV6_RECVERR (and the RFC4884 variants)
  propagate to the subflows. The MPTCP stores the bit so MPTCP-aware
  helpers can branch on it.

- Patch 4 is a selftest covering the propagation path.

Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
David Carlier (4):
      mptcp: support MSG_ERRQUEUE on the parent socket
      mptcp: sockopt: factor inet_flags propagation into a mask
      mptcp: propagate RECVERR sockopts to subflows
      selftests: mptcp: cover IP_RECVERR sockopt propagation

 net/mptcp/protocol.c                              |  55 ++++++--
 net/mptcp/sockopt.c                               | 155 ++++++++++++++++++----
 tools/testing/selftests/net/mptcp/mptcp_sockopt.c |  70 ++++++++++
 3 files changed, 246 insertions(+), 34 deletions(-)
---
base-commit: 4bb9710c6a68d35207f123aef55dcd50e7195ec5
change-id: 20260918-net-next-mptcp-msg_errqueue-0e2049a30061

Best regards,
--  
Matthieu Baerts (NGI0) <matttbe@kernel.org>


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

* [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket
  2026-09-18 18:38 [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE Matthieu Baerts (NGI0)
@ 2026-09-18 18:38 ` Matthieu Baerts (NGI0)
  2026-09-21 18:39   ` netdev-bot+sashiko
  2026-09-18 18:38 ` [PATCH net-next 2/4] mptcp: sockopt: factor inet_flags propagation into a mask Matthieu Baerts (NGI0)
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-18 18:38 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, linux-kernel, Matthieu Baerts (NGI0), David Carlier

From: David Carlier <devnexen@gmail.com>

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 through the standard inet ABI.

The splice filters by SO_EE_ORIGIN: TIMESTAMPING 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.

Forwarded events all go through sock_queue_err_skb(), which re-homes
skb->sk onto the parent and charges sk_rmem_alloc, so the parent's error
queue stays bounded by sk_rcvbuf and is dropped under rmem pressure
(sk_rmem_alloc + truesize >= sk_rcvbuf), matching tcp's sk_rcvbuf-gated
tx-timestamp path and ip_icmp_error() / ipv6_icmp_error(). The
MSG_ERRQUEUE branch of mptcp_recvmsg() forwards to inet_recv_error()
directly, and poll() advertises EPOLLERR purely on the parent's sk_err /
sk_error_queue, matching tcp_poll().

Signed-off-by: David Carlier <devnexen@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
 net/mptcp/protocol.c | 55 +++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 46 insertions(+), 9 deletions(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index e89a69ab927c..15ae270ee6fd 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>
@@ -919,21 +920,52 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
 	return moved;
 }
 
+static bool mptcp_errqueue_skb_forwardable(const struct sk_buff *skb)
+{
+	/* Subflow-level ICMP errors are dropped: the legacy RECVERR ABI
+	 * cannot convey their per-subflow peer identity.
+	 */
+	return SKB_EXT_ERR(skb)->ee.ee_origin == SO_EE_ORIGIN_TIMESTAMPING;
+}
+
+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))) {
+		/* sock_queue_err_skb() re-homes skb->sk onto the parent and
+		 * charges sk_rmem_alloc, bounding the queue by sk_rcvbuf.
+		 */
+		if (!mptcp_errqueue_skb_forwardable(skb) ||
+		    sock_queue_err_skb(sk, skb)) {
+			kfree_skb(skb);
+			continue;
+		}
+		moved = true;
+	}
+
+	return moved;
+}
+
 static bool __mptcp_subflow_error_report(struct sock *sk, struct sock *ssk)
 {
+	bool propagated = false;
 	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
@@ -943,11 +975,16 @@ 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;
+	propagated = true;
 
-	/* This barrier is coupled with smp_rmb() in mptcp_poll() */
-	smp_wmb();
-	sk_error_report(sk);
-	return true;
+out:
+	if (report) {
+		/* This barrier is coupled with smp_rmb() in mptcp_poll() */
+		smp_wmb();
+		sk_error_report(sk);
+	}
+	return propagated;
 }
 
 void __mptcp_error_report(struct sock *sk)
@@ -2385,7 +2422,6 @@ 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);
 
@@ -4523,7 +4559,8 @@ 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))
 		mask |= EPOLLERR;
 
 	return mask;

-- 
2.55.0


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

* [PATCH net-next 2/4] mptcp: sockopt: factor inet_flags propagation into a mask
  2026-09-18 18:38 [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket Matthieu Baerts (NGI0)
@ 2026-09-18 18:38 ` Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation Matthieu Baerts (NGI0)
  3 siblings, 0 replies; 10+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-18 18:38 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, linux-kernel, Matthieu Baerts (NGI0), David Carlier

From: David Carlier <devnexen@gmail.com>

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.

Signed-off-by: David Carlier <devnexen@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
 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 fcf6feb2a9eb..2ba56832587e 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)
 {
@@ -1550,8 +1554,11 @@ int mptcp_getsockopt(struct sock *sk, int level, int optname,
 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;
+	unsigned long mask = MPTCP_INET_FLAGS_MASK;
 	struct sock *sk = (struct sock *)msk;
+	unsigned long src;
 	bool keep_open;
+	int b;
 
 	keep_open = sock_flag(sk, SOCK_KEEPOPEN);
 	if (ssk->sk_prot->keepalive)
@@ -1597,9 +1604,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.55.0


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

* [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows
  2026-09-18 18:38 [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket Matthieu Baerts (NGI0)
  2026-09-18 18:38 ` [PATCH net-next 2/4] mptcp: sockopt: factor inet_flags propagation into a mask Matthieu Baerts (NGI0)
@ 2026-09-18 18:38 ` Matthieu Baerts (NGI0)
  2026-09-21 18:39   ` netdev-bot+sashiko
  2026-09-18 18:38 ` [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation Matthieu Baerts (NGI0)
  3 siblings, 1 reply; 10+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-18 18:38 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, linux-kernel, Matthieu Baerts (NGI0), David Carlier

From: David Carlier <devnexen@gmail.com>

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: ipv6_setsockopt() on a sock with sk_family != AF_INET6
returns an error, which would abort the loop and leave the remaining
subflows desynchronised. This branch was unreachable before this
patch (the only caller was TCP_MAXSEG, family-agnostic); it becomes
live with the new IPV6_RECVERR / IPV6_RECVERR_RFC4884 caller and the
v4-subflow-on-AF_INET6-msk case (v4 MP_JOIN, or userspace PM grafting
a v4 subflow onto a v6 msk).

Assisted-by: Codex:gpt-5
Signed-off-by: David Carlier <devnexen@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
 net/mptcp/sockopt.c | 142 +++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 119 insertions(+), 23 deletions(-)

diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
index 2ba56832587e..922f6ae5c80c 100644
--- a/net/mptcp/sockopt.c
+++ b/net/mptcp/sockopt.c
@@ -8,6 +8,7 @@
 
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <net/ipv6.h>
 #include <net/sock.h>
 #include <net/protocol.h>
 #include <net/tcp.h>
@@ -19,7 +20,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)
 {
@@ -398,6 +403,86 @@ 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);
+		int err;
+
+		/* SOL_IPV6 options on a v4 subflow (v4 MP_JOIN, or userspace PM
+		 * grafting a v4 subflow onto an AF_INET6 msk) would otherwise
+		 * abort the loop with -EAFNOSUPPORT from ipv6_setsockopt().
+		 */
+		if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
+			continue;
+
+		err = tcp_setsockopt(ssk, level, optname, optval, optlen);
+		if (err < 0 && ret == 0)
+			ret = err;
+	}
+
+	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 = 0, 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) {
+		if (sk->sk_family != AF_INET6)
+			return -ENOPROTOOPT;
+		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
+	}
+
+	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)
 {
@@ -440,6 +525,11 @@ 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;
@@ -785,6 +875,10 @@ 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;
@@ -812,28 +906,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);
-		int err;
-
-		err = tcp_setsockopt(ssk, level, optname, optval, optlen);
-		if (err < 0 && ret == 0)
-			ret = err;
-	}
-
-	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)
 {
@@ -1478,6 +1550,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;
@@ -1498,6 +1576,16 @@ 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:
+		if (sk->sk_family != AF_INET6)
+			return -ENOPROTOOPT;
+		return mptcp_put_int_option(msk, optval, optlen,
+					    inet6_test_bit(RECVERR6, sk));
+	case IPV6_RECVERR_RFC4884:
+		if (sk->sk_family != AF_INET6)
+			return -ENOPROTOOPT;
+		return mptcp_put_int_option(msk, optval, optlen,
+					    inet6_test_bit(RECVERR6_RFC4884, sk));
 	}
 
 	return -EOPNOTSUPP;
@@ -1606,6 +1694,14 @@ static void sync_socket_options(struct mptcp_sock *msk, struct sock *ssk)
 
 	src = READ_ONCE(inet_sk(sk)->inet_flags);
 
+	/* RECVERR6 bits are only read on AF_INET6 sockets; copying them onto a
+	 * v4 subflow is dead state and diverges from the SOL_IPV6 skip in
+	 * mptcp_setsockopt_all_sf().
+	 */
+	if (ssk->sk_family != AF_INET6)
+		mask &= ~(BIT(INET_FLAGS_RECVERR6) |
+			BIT(INET_FLAGS_RECVERR6_RFC4884));
+
 	for_each_set_bit(b, &mask, BITS_PER_LONG)
 		assign_bit(b, &inet_sk(ssk)->inet_flags, src & BIT(b));
 

-- 
2.55.0


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

* [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation
  2026-09-18 18:38 [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE Matthieu Baerts (NGI0)
                   ` (2 preceding siblings ...)
  2026-09-18 18:38 ` [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows Matthieu Baerts (NGI0)
@ 2026-09-18 18:38 ` Matthieu Baerts (NGI0)
  2026-09-21 18:39   ` netdev-bot+sashiko
  3 siblings, 1 reply; 10+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-18 18:38 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, linux-kernel, Matthieu Baerts (NGI0),
	David Carlier, Shuah Khan, linux-kselftest

From: David Carlier <devnexen@gmail.com>

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>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
To: Shuah Khan <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
---
 tools/testing/selftests/net/mptcp/mptcp_sockopt.c | 70 +++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
index b6e58d936ebe..d68515b7903b 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
@@ -183,6 +183,13 @@ static void xgetaddrinfo(const char *node, const char *service,
 	}
 }
 
+static bool expect_all_features(void)
+{
+	char *env = getenv("SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES");
+
+	return env && strcmp(env, "1") == 0;
+}
+
 static int sock_listen_mptcp(const char * const listenaddr,
 			     const char * const port)
 {
@@ -769,6 +776,68 @@ 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) {
+		/* For older kernels not supporting IP(V6)_RECVERR yet */
+		if (errno == EOPNOTSUPP && !expect_all_features()) {
+			fprintf(stderr, "IP(V6)_RECVERR not supported, SKIP\n");
+			return;
+		}
+
+		die_perror("setsockopt IP(V6)_RECVERR on");
+	}
+
+	r = getsockopt(fd, level, optname, &val, &s);
+	if (r)
+		die_perror("getsockopt IP(V6)_RECVERR on");
+	if (s != sizeof(val) || val != one)
+		xerror("IP(V6)_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, r=%d err=%d",
+		       r, errno);
+
+	r = setsockopt(fd, level, optname, &zero, sizeof(zero));
+	if (r)
+		die_perror("setsockopt IP(V6)_RECVERR off");
+
+	val = -1;
+	s = sizeof(val);
+	r = getsockopt(fd, level, optname, &val, &s);
+	if (r)
+		die_perror("getsockopt IP(V6)_RECVERR off");
+	if (s != sizeof(val) || val != zero)
+		xerror("IP(V6)_RECVERR off mismatch val=%d len=%u", val, s);
+}
+
 static int client(int pipefd)
 {
 	int fd = -1;
@@ -787,6 +856,7 @@ static int client(int pipefd)
 	}
 
 	test_ip_tos_sockopt(fd);
+	test_ip_recverr_sockopt(fd);
 
 	connect_one_server(fd, pipefd);
 

-- 
2.55.0


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

* Re: [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket
  2026-09-18 18:38 ` [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket Matthieu Baerts (NGI0)
@ 2026-09-21 18:39   ` netdev-bot+sashiko
  2026-09-22 13:30     ` Paolo Abeni
  0 siblings, 1 reply; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 18:39 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, pabeni, horms, netdev,
	mptcp, linux-kernel, devnexen, shuah, linux-kselftest

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The new error-queue splice is dead code for MPTCP-native data:
  its only accepted input (SO_EE_ORIGIN_TIMESTAMPING skbs on a subflow…
- [Medium] Forwarded TX timestamps expose per-subflow identifiers instead
  of parent-socket byte-stream identifiers, breaking the…

--

mptcp: support MSG_ERRQUEUE on the parent socket

This moves pending error skbs from each subflow's error queue onto the
parent msk error queue at error-report time, keeping only
SO_EE_ORIGIN_TIMESTAMPING events and dropping subflow ICMP errors.  It
also makes mptcp_poll() report EPOLLERR when the parent error queue is
non-empty, and drops the stale comment in front of the MSG_ERRQUEUE
branch of mptcp_recvmsg().

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index e89a69ab927c..15ae270ee6fd 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c

[ ... ]

> @@ -919,21 +920,52 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
>  	return moved;
>  }
>  
> +static bool mptcp_errqueue_skb_forwardable(const struct sk_buff *skb)
> +{
> +	/* Subflow-level ICMP errors are dropped: the legacy RECVERR ABI
> +	 * cannot convey their per-subflow peer identity.
> +	 */
> +	return SKB_EXT_ERR(skb)->ee.ee_origin == SO_EE_ORIGIN_TIMESTAMPING;
> +}
> +
> +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))) {

[Severity: Medium]
Is there anything today that can put a SO_EE_ORIGIN_TIMESTAMPING skb on a
subflow's sk_error_queue?

mptcp_errqueue_skb_forwardable() accepts only ee_origin ==
SO_EE_ORIGIN_TIMESTAMPING, and the producers of those events
(skb_tx_timestamp() in drivers, the SKBTX_SCHED_TSTAMP check in
__dev_queue_xmit(), and tcp_ack_tstamp() -> __skb_tstamp_tx()) all key off
per-skb state: skb_shinfo(skb)->tx_flags, skb_shinfo(skb)->tskey and
TCP_SKB_CB(skb)->txstamp_ack.

Those bits are set by tcp_tx_timestamp(), which is only reached from
tcp_sendmsg_locked().  mptcp_sendmsg_frag() builds the subflow skbs itself:

	skb = mptcp_alloc_tx_skb(sk, ssk, info->data_lock_held);
	...
	TCP_SKB_CB(skb)->end_seq += copy;

so it never goes through tcp_sendmsg_locked() and never arms timestamping.
The fastopen path is not an exception either, since
mptcp_sendmsg_fastopen() calls tcp_sendmsg_fastopen() directly.  A
"git grep -E 'tx_timestamp|tskey|txstamp_ack|tsflags' net/mptcp/" finds no
arming site.

If that reading is right, then __mptcp_subflow_splice_errqueue() always
returns false for real traffic, and the commit message statement

  "so poll() and recvmsg(MSG_ERRQUEUE) on the parent socket observe TX
   timestamps through the standard inet ABI"

does not hold yet: recvmsg(MSG_ERRQUEUE) on the parent still returns
-EAGAIN.  Note that mptcp_setsockopt_sol_socket_timestamping() in
net/mptcp/sockopt.c does accept SO_TIMESTAMPING_OLD/NEW and replicates it
to every subflow via sock_set_timestamping(), so the configuration side
advertises something the transmit side does not implement.

The last patch in this series ("selftests: mptcp: cover IP_RECVERR sockopt
propagation") acknowledges this, saying end-to-end errqueue delivery
depends on subflow-side producers that are out of scope for the series.
Could this commit message be reworded the same way, so it describes the
plumbing rather than claiming the observable behaviour?

> +		/* sock_queue_err_skb() re-homes skb->sk onto the parent and
> +		 * charges sk_rmem_alloc, bounding the queue by sk_rcvbuf.
> +		 */
> +		if (!mptcp_errqueue_skb_forwardable(skb) ||
> +		    sock_queue_err_skb(sk, skb)) {

[Severity: Medium]
Once producers do exist, will the forwarded ee_data still mean anything on
the parent socket?

__skb_complete_tx_timestamp() fills the identifier from the originating
socket's counters:

	if (READ_ONCE(sk->sk_tsflags) & SOF_TIMESTAMPING_OPT_ID) {
		serr->ee.ee_data = skb_shinfo(skb)->tskey;
		if (sk_is_tcp(sk))
			serr->ee.ee_data -= atomic_read(&sk->sk_tskey);
	}

so ee_data is relative to the subflow's sk_tskey and TCP sequence space.
mptcp_setsockopt_sol_socket_timestamping() enables timestamping on the msk
and on each subflow separately, giving every subflow its own baseline.

__mptcp_subflow_splice_errqueue() re-homes the skb without translating
ee_data into the msk write_seq space.  With two or more subflows, does
userspace reading MSG_ERRQUEUE on the parent then see duplicate or
unrelated identifiers for one socket?  Documentation/networking/
timestamping.rst describes SOF_TIMESTAMPING_OPT_ID as unique among
outstanding timestamp requests for that socket, and byte-incrementing for
stream sockets.

Even with a single subflow the value is relative to the subflow's TCP
sequence space rather than msk->write_seq.  Would remapping ee_data here
require replacing the plain re-home rather than extending it later?

> +			kfree_skb(skb);
> +			continue;
> +		}
> +		moved = true;
> +	}
> +
> +	return moved;
> +}
> +

[ ... ]

> @@ -4523,7 +4559,8 @@ 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))
>  		mask |= EPOLLERR;
>  
>  	return mask;

With the splice unable to fire for MPTCP-native data, can this new
sk_error_queue term ever become true through the path described in the
commit message?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260918-net-next-mptcp-msg_errqueue-v1-0-dd77e1738248%40kernel.org

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

* Re: [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows
  2026-09-18 18:38 ` [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows Matthieu Baerts (NGI0)
@ 2026-09-21 18:39   ` netdev-bot+sashiko
  0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 18:39 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, pabeni, horms, netdev,
	mptcp, linux-kernel, devnexen, shuah, linux-kselftest

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 1 (1)

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The new in-code comment in mptcp_setsockopt_all_sf()
  (net/mptcp/sockopt.c:417-420) and the commit changelog both justify…
- [Low] Short-buffer getsockopt ABI mismatch for the two newly supported
  IPv6 options: mptcp_getsockopt_v6() (net/mptcp/sockopt.c:1579 and…

Pre-existing issues:
- [Low] Inconsistent SOL_IPV6 getsockopt surface inside
  mptcp_getsockopt_v6() (net/mptcp/sockopt.c): the two newly added cases…

--

mptcp: propagate RECVERR sockopts to subflows

This adds IP_RECVERR/IP_RECVERR_RFC4884 and IPV6_RECVERR/
IPV6_RECVERR_RFC4884 handling to the MPTCP setsockopt and getsockopt
paths, pushing the value to all existing subflows and extending
MPTCP_INET_FLAGS_MASK so new subflows inherit the bits through
sync_socket_options().

A few questions below about the new comment, the changelog wording and the
getsockopt surface.

> diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c
> index 2ba56832587e..922f6ae5c80c 100644
> --- a/net/mptcp/sockopt.c
> +++ b/net/mptcp/sockopt.c

[ ... ]

> @@ -398,6 +403,86 @@ 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);
> +		int err;
> +
> +		/* SOL_IPV6 options on a v4 subflow (v4 MP_JOIN, or userspace PM
> +		 * grafting a v4 subflow onto an AF_INET6 msk) would otherwise
> +		 * abort the loop with -EAFNOSUPPORT from ipv6_setsockopt().
> +		 */
> +		if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
> +			continue;
> +
> +		err = tcp_setsockopt(ssk, level, optname, optval, optlen);
> +		if (err < 0 && ret == 0)
> +			ret = err;
> +	}
> +
> +	if (!ret)
> +		sockopt_seq_inc(msk);
> +
> +	return ret;
> +}

[Severity: Medium]
Does the comment above the new skip describe what the loop actually does?

The loop records only the first error and keeps going:

	err = tcp_setsockopt(ssk, level, optname, optval, optlen);
	if (err < 0 && ret == 0)
		ret = err;

There is no break or return, so the remaining subflows are still visited.
Without the guard the visible effects would be the errno returned to
userspace plus the skipped sockopt_seq_inc(msk), not an aborted loop.

Is -EAFNOSUPPORT from ipv6_setsockopt() the right errno to name here?  For
a v4 subflow, tcp_setsockopt() with level != SOL_TCP dispatches through
icsk_af_ops->setsockopt, and net/mptcp/subflow.c sets

	subflow_specific = ipv4_specific;

so the call lands in ip_setsockopt(), which starts with:

	if (level != SOL_IP)
		return -ENOPROTOOPT;

ipv6_setsockopt() is never entered in that case, and grepping
net/ipv6/ipv6_sockglue.c for EAFNOSUPPORT gives no match; do_ipv6_setsockopt()
does not check sk_family either.  Could the comment say -ENOPROTOOPT from
ip_setsockopt() and describe the suppressed sockopt_seq_inc() instead of a
loop abort?  The guard itself looks correct and needed.

The changelog repeats the same reasoning:

    mptcp_setsockopt_all_sf() skips IPv4 subflows when called with
    SOL_IPV6: ipv6_setsockopt() on a sock with sk_family != AF_INET6
    returns an error, which would abort the loop and leave the remaining
    subflows desynchronised.

Could that be reworded for the same reasons?

The changelog also says:

    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().

Does that match the order in the new helper?  mptcp_setsockopt_recverr()
passes the caller's sockptr and optlen straight to ip_setsockopt() /
ipv6_setsockopt() first, then reads the resulting bit into val, and
setsockopt_seq is bumped inside mptcp_setsockopt_all_sf() after a
successful forward, not before it.

[ ... ]

> @@ -1498,6 +1576,16 @@ 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:
> +		if (sk->sk_family != AF_INET6)
> +			return -ENOPROTOOPT;
> +		return mptcp_put_int_option(msk, optval, optlen,
> +					    inet6_test_bit(RECVERR6, sk));
> +	case IPV6_RECVERR_RFC4884:
> +		if (sk->sk_family != AF_INET6)
> +			return -ENOPROTOOPT;
> +		return mptcp_put_int_option(msk, optval, optlen,
> +					    inet6_test_bit(RECVERR6_RFC4884, sk));

[Severity: Low]
This is a pre-existing inconsistency rather than something the patch
introduces, but the two new cases in mptcp_getsockopt_v6() now behave
differently from their neighbours in the same switch.

IPV6_V6ONLY, IPV6_TRANSPARENT and IPV6_FREEBIND answer a SOL_IPV6
getsockopt on an AF_INET msk by returning the IPv4 inet_flags bit, with no
family gate, while the new IPV6_RECVERR and IPV6_RECVERR_RFC4884 cases
return -ENOPROTOOPT there.

Plain TCP on an AF_INET socket rejects every SOL_IPV6 getsockopt through
ip_getsockopt()'s level != SOL_IP check, so the new cases look like the
stricter and more correct side.  Would it make sense to align the older
cases with the new gate, or is the divergence intentional for now?

[Severity: Low]
Does routing these two options through mptcp_put_int_option() change the
short-buffer behaviour compared to plain TCP?

mptcp_put_int_option() collapses any requested length of 1 to 3 bytes for a
0/1 value down to one byte:

	if (len < sizeof(int) && len > 0 && val >= 0 && val <= 255) {
		unsigned char ucval = (unsigned char)val;

		len = 1;

do_ipv6_getsockopt() instead keeps the requested length:

	len = min_t(unsigned int, sizeof(int), len);
	if (copy_to_sockptr(optlen, &len, sizeof(int)))
		return -EFAULT;
	if (copy_to_sockptr(optval, &val, len))
		return -EFAULT;

So getsockopt(SOL_IPV6, IPV6_RECVERR, buf, &len) with len of 2 or 3 reports
optlen 1 on an MPTCP socket and 2 or 3 on a TCP socket, and with len 1 the
returned byte comes from an unsigned char conversion rather than the first
byte of the native int, which differs on big-endian.

This matches what mptcp already does for IPV6_FREEBIND and
IPV6_TRANSPARENT, so the helper behaviour is not new here, only its use for
these two options.  Is the difference acceptable, or should these follow the
IPv6 stack's length handling?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260918-net-next-mptcp-msg_errqueue-v1-0-dd77e1738248%40kernel.org

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

* Re: [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation
  2026-09-18 18:38 ` [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation Matthieu Baerts (NGI0)
@ 2026-09-21 18:39   ` netdev-bot+sashiko
  0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 18:39 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, pabeni, horms, netdev,
	mptcp, linux-kernel, devnexen, shuah, linux-kselftest

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The commit subject claims the patch covers "IP_RECVERR sockopt
  propagation", but the new selftest `test_ip_recverr_sockopt()`…
- [Low] The "empty-errqueue EAGAIN contract" assertion added at
  tools/testing/selftests/net/mptcp/mptcp_sockopt.c:823-826 (`r =…

--

selftests: mptcp: cover IP_RECVERR sockopt propagation

This adds test_ip_recverr_sockopt() to the mptcp_sockopt selftest, which
sets and reads back IP_RECVERR / IPV6_RECVERR on the MPTCP parent socket
and checks that recvmsg() with MSG_ERRQUEUE|MSG_DONTWAIT on an empty
error queue returns EAGAIN.

> diff --git a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
> index b6e58d936ebe..d68515b7903b 100644
> --- a/tools/testing/selftests/net/mptcp/mptcp_sockopt.c
> +++ b/tools/testing/selftests/net/mptcp/mptcp_sockopt.c

[ ... ]

> @@ -769,6 +776,68 @@ static void test_ip_tos_sockopt(int fd)
>  		xerror("expect socklen_t == -1");
>  }
>  
> +static void test_ip_recverr_sockopt(int fd)
> +{

[Severity: Medium]
The subject says "cover IP_RECVERR sockopt propagation", but does this
test observe any propagation?  Everything here operates on the parent fd
only, and no subflow state is ever queried.

The MPTCP-specific work happens in mptcp_setsockopt_recverr() ->
mptcp_setsockopt_all_sf(), including the skip for IPv6 options on v4
subflows:

net/mptcp/sockopt.c:mptcp_setsockopt_all_sf() {
	...
	if (level == SOL_IPV6 && ssk->sk_family != AF_INET6)
		continue;
	...
}

and in sync_socket_options() for subflows that join later:

net/mptcp/sockopt.c:sync_socket_options() {
	...
	src = READ_ONCE(inet_sk(sk)->inet_flags);
	if (ssk->sk_family != AF_INET6)
		mask &= ~(BIT(INET_FLAGS_RECVERR6) |
			BIT(INET_FLAGS_RECVERR6_RFC4884));
	...
}

The read-back path, however, only reports the parent msk's own flag bit:

net/mptcp/sockopt.c:mptcp_getsockopt_v4() {
	...
	case IP_RECVERR:
		return mptcp_put_int_option(msk, optval, optlen,
				inet_test_bit(RECVERR, sk));
	...
}

with mptcp_getsockopt_v6() doing the same for inet6_test_bit(RECVERR6, sk).

So if the subflow forwarding step and the sync-on-join masking were both
deleted, would this test still pass?  Would it be more accurate to say the
test covers the parent-socket set/get round trip, and to note that subflow
propagation is not asserted?  IP_RECVERR_RFC4884 / IPV6_RECVERR_RFC4884 go
through the same kernel paths and are also not touched here.

> +	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) {
> +		/* For older kernels not supporting IP(V6)_RECVERR yet */
> +		if (errno == EOPNOTSUPP && !expect_all_features()) {
> +			fprintf(stderr, "IP(V6)_RECVERR not supported, SKIP\n");
> +			return;
> +		}
> +
> +		die_perror("setsockopt IP(V6)_RECVERR on");
> +	}
> +
> +	r = getsockopt(fd, level, optname, &val, &s);
> +	if (r)
> +		die_perror("getsockopt IP(V6)_RECVERR on");
> +	if (s != sizeof(val) || val != one)
> +		xerror("IP(V6)_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, r=%d err=%d",
> +		       r, errno);

[Severity: Low]
Can this check tell the errqueue path apart from the ordinary non-blocking
empty receive queue path?

The branch it aims at is:

net/mptcp/protocol.c:mptcp_recvmsg() {
	...
	if (unlikely(flags & MSG_ERRQUEUE))
		return inet_recv_error(sk, msg, len);
	...
}

If that branch were gone, the normal receive loop in the same function
would still return -EAGAIN for a connected socket with nothing queued:

net/mptcp/protocol.c:mptcp_recvmsg() {
	...
		if (!timeo) {
			copied = -EAGAIN;
			break;
		}
	...
}

Since test_ip_recverr_sockopt() runs before connect_one_server() moves any
payload, the data queue is empty by construction and MSG_DONTWAIT is set,
so both paths return -1/EAGAIN.  Would something like checking that a
queued TX timestamp is actually returned, or that the one byte data buffer
is not consumed, make the assertion discriminating?

> +
> +	r = setsockopt(fd, level, optname, &zero, sizeof(zero));
> +	if (r)
> +		die_perror("setsockopt IP(V6)_RECVERR off");
> +
> +	val = -1;
> +	s = sizeof(val);
> +	r = getsockopt(fd, level, optname, &val, &s);
> +	if (r)
> +		die_perror("getsockopt IP(V6)_RECVERR off");
> +	if (s != sizeof(val) || val != zero)
> +		xerror("IP(V6)_RECVERR off mismatch val=%d len=%u", val, s);
> +}
> +
>  static int client(int pipefd)
>  {
>  	int fd = -1;
> @@ -787,6 +856,7 @@ static int client(int pipefd)
>  	}
>  
>  	test_ip_tos_sockopt(fd);
> +	test_ip_recverr_sockopt(fd);
>  
>  	connect_one_server(fd, pipefd);

Since the option is switched back off here before connect_one_server()
runs, no later part of the test can observe the option state on subflows
either.  Was leaving RECVERR enabled for the rest of the run considered?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260918-net-next-mptcp-msg_errqueue-v1-0-dd77e1738248%40kernel.org

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

* Re: [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket
  2026-09-21 18:39   ` netdev-bot+sashiko
@ 2026-09-22 13:30     ` Paolo Abeni
  2026-09-22 20:59       ` David CARLIER
  0 siblings, 1 reply; 10+ messages in thread
From: Paolo Abeni @ 2026-09-22 13:30 UTC (permalink / raw)
  To: netdev-bot+sashiko, matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, horms, netdev, mptcp,
	linux-kernel, devnexen, shuah, linux-kselftest

On 9/21/26 20:39, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 0 · Medium: 2 · Low: 0
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [Medium] The new error-queue splice is dead code for MPTCP-native data:
>    its only accepted input (SO_EE_ORIGIN_TIMESTAMPING skbs on a subflow…
> - [Medium] Forwarded TX timestamps expose per-subflow identifiers instead
>    of parent-socket byte-stream identifiers, breaking the…
Sashiko comments look relevant to me. Basically this work makes sense
if proper support for mptcp-level tx timestamping is enabled and the
and sk_tskey is resolved at the msk level.

I underlook at the above during reviews on the mptcp ML.

IDK if someone is currently working on the missing work/bits.

/P


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

* Re: [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket
  2026-09-22 13:30     ` Paolo Abeni
@ 2026-09-22 20:59       ` David CARLIER
  0 siblings, 0 replies; 10+ messages in thread
From: David CARLIER @ 2026-09-22 20:59 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: netdev-bot+sashiko, matttbe, martineau, geliang, davem, edumazet,
	kuba, horms, netdev, mptcp, linux-kernel, shuah, linux-kselftest

On Tue, 22 Sept 2026 at 14:30, Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 9/21/26 20:39, netdev-bot+sashiko@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 2 potential
> > issue(s) to consider.
> >
> > Critical: 0 · High: 0 · Medium: 2 · Low: 0
> >
> > If you believe none of the issues are legitimate, please reply to each
> > issue of Medium severity or higher and explain why the AI is wrong.
> > If any of them are legitimate and you plan to respin, please let patchwork
> > know by including "pw-bot: cr" as a separate line at the end of your reply
> > (one such reply per series is enough).
> >
> > - [Medium] The new error-queue splice is dead code for MPTCP-native data:
> >    its only accepted input (SO_EE_ORIGIN_TIMESTAMPING skbs on a subflow…
> > - [Medium] Forwarded TX timestamps expose per-subflow identifiers instead
> >    of parent-socket byte-stream identifiers, breaking the…
> Sashiko comments look relevant to me. Basically this work makes sense
> if proper support for mptcp-level tx timestamping is enabled and the
> and sk_tskey is resolved at the msk level.
>
> I underlook at the above during reviews on the mptcp ML.
>
> IDK if someone is currently working on the missing work/bits.
>
> /P
>

Agreed, nothing arms tx timestamps on the msk today. Let's drop the
series for now.

I can look into it.

pw-bot: cr

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

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

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 18:38 [PATCH net-next 0/4] mptcp: support MSG_ERRQUEUE Matthieu Baerts (NGI0)
2026-09-18 18:38 ` [PATCH net-next 1/4] mptcp: support MSG_ERRQUEUE on the parent socket Matthieu Baerts (NGI0)
2026-09-21 18:39   ` netdev-bot+sashiko
2026-09-22 13:30     ` Paolo Abeni
2026-09-22 20:59       ` David CARLIER
2026-09-18 18:38 ` [PATCH net-next 2/4] mptcp: sockopt: factor inet_flags propagation into a mask Matthieu Baerts (NGI0)
2026-09-18 18:38 ` [PATCH net-next 3/4] mptcp: propagate RECVERR sockopts to subflows Matthieu Baerts (NGI0)
2026-09-21 18:39   ` netdev-bot+sashiko
2026-09-18 18:38 ` [PATCH net-next 4/4] selftests: mptcp: cover IP_RECVERR sockopt propagation Matthieu Baerts (NGI0)
2026-09-21 18:39   ` netdev-bot+sashiko

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

all inboxes | Powered by JetHome®