diff mbox series

[net-next,v2] selftests: net: Add support for testing SO_RCVMARK and SO_RCVPRIORITY

Message ID 20250210192216.37756-1-annaemesenyiri@gmail.com (mailing list archive)
State New
Headers show
Series [net-next,v2] selftests: net: Add support for testing SO_RCVMARK and SO_RCVPRIORITY | expand

Commit Message

Anna Emese Nyiri Feb. 10, 2025, 7:22 p.m. UTC
Introduce tests to verify the correct functionality of the SO_RCVMARK and 
SO_RCVPRIORITY socket options.

Key changes include:

- so_rcv_listener.c: Implements a receiver application to test the correct 
behavior of the SO_RCVMARK and SO_RCVPRIORITY options.
- test_so_rcv.sh: Provides a shell script to automate testing for these options.
- Makefile: Integrates test_so_rcv.sh into the kernel selftests.

v2:

- Add the C part to TEST_GEN_PROGS and .gitignore.
- Modify buffer space and add IPv6 testing option
in so_rcv_listener.c.
- Add IPv6 testing, remove unnecessary comment,
add kselftest exit codes, run both binaries in a namespace,
and add sleep in test_so_rcv.sh.
The sleep was added to ensure that the listener process has
enough time to start before the sender attempts to connect.
- Rebased on net-next.

v1:

https://lore.kernel.org/netdev/20250129143601.16035-2-annaemesenyiri@gmail.com/

Suggested-by: Jakub Kicinski <kuba@kernel.org>
Suggested-by: Ferenc Fejes <fejes@inf.elte.hu>
Signed-off-by: Anna Emese Nyiri <annaemesenyiri@gmail.com>
---
 tools/testing/selftests/net/.gitignore        |   1 +
 tools/testing/selftests/net/Makefile          |   2 +
 tools/testing/selftests/net/so_rcv_listener.c | 165 ++++++++++++++++++
 tools/testing/selftests/net/test_so_rcv.sh    |  73 ++++++++
 4 files changed, 241 insertions(+)
 create mode 100644 tools/testing/selftests/net/so_rcv_listener.c
 create mode 100755 tools/testing/selftests/net/test_so_rcv.sh

Comments

Paolo Abeni Feb. 11, 2025, 11:19 a.m. UTC | #1
On 2/10/25 8:22 PM, Anna Emese Nyiri wrote:
> Introduce tests to verify the correct functionality of the SO_RCVMARK and 
> SO_RCVPRIORITY socket options.
> 
> Key changes include:
> 
> - so_rcv_listener.c: Implements a receiver application to test the correct 
> behavior of the SO_RCVMARK and SO_RCVPRIORITY options.
> - test_so_rcv.sh: Provides a shell script to automate testing for these options.
> - Makefile: Integrates test_so_rcv.sh into the kernel selftests.
> 
> v2:
> 
> - Add the C part to TEST_GEN_PROGS and .gitignore.
> - Modify buffer space and add IPv6 testing option
> in so_rcv_listener.c.
> - Add IPv6 testing, remove unnecessary comment,
> add kselftest exit codes, run both binaries in a namespace,
> and add sleep in test_so_rcv.sh.
> The sleep was added to ensure that the listener process has
> enough time to start before the sender attempts to connect.
> - Rebased on net-next.
> 
> v1:
> 
> https://lore.kernel.org/netdev/20250129143601.16035-2-annaemesenyiri@gmail.com/

Unfortunately the added self-test does not run successfully in the CI:

https://netdev-3.bots.linux.dev/vmksft-net/results/987742/117-so-rcv-listener/stdout

Please have a look at:

https://github.com/linux-netdev/nipa/wiki/How-to-run-netdev-selftests-CI-style

to test the change locally in a CI-like way.

Cheers,

Paolo
Willem de Bruijn Feb. 12, 2025, 1:41 a.m. UTC | #2
Anna Emese Nyiri wrote:
> Introduce tests to verify the correct functionality of the SO_RCVMARK and 
> SO_RCVPRIORITY socket options.
> 
> Key changes include:
> 
> - so_rcv_listener.c: Implements a receiver application to test the correct 
> behavior of the SO_RCVMARK and SO_RCVPRIORITY options.
> - test_so_rcv.sh: Provides a shell script to automate testing for these options.
> - Makefile: Integrates test_so_rcv.sh into the kernel selftests.
> 
> v2:
> 
> - Add the C part to TEST_GEN_PROGS and .gitignore.
> - Modify buffer space and add IPv6 testing option
> in so_rcv_listener.c.
> - Add IPv6 testing, remove unnecessary comment,
> add kselftest exit codes, run both binaries in a namespace,
> and add sleep in test_so_rcv.sh.
> The sleep was added to ensure that the listener process has
> enough time to start before the sender attempts to connect.
> - Rebased on net-next.
> 
> v1:
> 
> https://lore.kernel.org/netdev/20250129143601.16035-2-annaemesenyiri@gmail.com/
> 
> Suggested-by: Jakub Kicinski <kuba@kernel.org>
> Suggested-by: Ferenc Fejes <fejes@inf.elte.hu>
> Signed-off-by: Anna Emese Nyiri <annaemesenyiri@gmail.com>
> ---
>  tools/testing/selftests/net/.gitignore        |   1 +
>  tools/testing/selftests/net/Makefile          |   2 +
>  tools/testing/selftests/net/so_rcv_listener.c | 165 ++++++++++++++++++
>  tools/testing/selftests/net/test_so_rcv.sh    |  73 ++++++++
>  4 files changed, 241 insertions(+)
>  create mode 100644 tools/testing/selftests/net/so_rcv_listener.c
>  create mode 100755 tools/testing/selftests/net/test_so_rcv.sh
> 
> diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
> index 28a715a8ef2b..80dcae53ef55 100644
> --- a/tools/testing/selftests/net/.gitignore
> +++ b/tools/testing/selftests/net/.gitignore
> @@ -42,6 +42,7 @@ socket
>  so_incoming_cpu
>  so_netns_cookie
>  so_txtime
> +so_rcv_listener
>  stress_reuseport_listen
>  tap
>  tcp_fastopen_backup_key
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index 73ee88d6b043..fe3491dea7c5 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -33,6 +33,7 @@ TEST_PROGS += gro.sh
>  TEST_PROGS += gre_gso.sh
>  TEST_PROGS += cmsg_so_mark.sh
>  TEST_PROGS += cmsg_so_priority.sh
> +TEST_PROGS += test_so_rcv.sh
>  TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
>  TEST_PROGS += netns-name.sh
>  TEST_PROGS += nl_netdev.py
> @@ -89,6 +90,7 @@ TEST_GEN_FILES += sctp_hello
>  TEST_GEN_FILES += ip_local_port_range
>  TEST_GEN_PROGS += bind_wildcard
>  TEST_GEN_PROGS += bind_timewait
> +TEST_GEN_PROGS += so_rcv_listener
>  TEST_PROGS += test_vxlan_mdb.sh
>  TEST_PROGS += test_bridge_neigh_suppress.sh
>  TEST_PROGS += test_vxlan_nolocalbypass.sh
> diff --git a/tools/testing/selftests/net/so_rcv_listener.c b/tools/testing/selftests/net/so_rcv_listener.c
> new file mode 100644
> index 000000000000..693fc1e3d44d
> --- /dev/null
> +++ b/tools/testing/selftests/net/so_rcv_listener.c
> @@ -0,0 +1,165 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <errno.h>
> +#include <netdb.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <linux/types.h>
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <arpa/inet.h>
> +
> +#ifndef SO_RCVPRIORITY
> +#define SO_RCVPRIORITY 82
> +#endif
> +
> +struct options {
> +	__u32 val;
> +	int name;
> +	int rcvname;
> +	const char *host;
> +	const char *service;
> +} opt;
> +
> +static void __attribute__((noreturn)) usage(const char *bin)
> +{
> +	printf("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
> +	printf("Options:\n"
> +		"\t\t-M val  Test SO_RCVMARK\n"
> +		"\t\t-P val  Test SO_RCVPRIORITY\n"
> +		"");
> +	exit(EXIT_FAILURE);
> +}
> +
> +static void parse_args(int argc, char *argv[])
> +{
> +	int o;
> +
> +	while ((o = getopt(argc, argv, "M:P:")) != -1) {
> +		switch (o) {
> +		case 'M':
> +			opt.val = atoi(optarg);
> +			opt.name = SO_MARK;
> +			opt.rcvname = SO_RCVMARK;
> +			break;
> +		case 'P':
> +			opt.val = atoi(optarg);
> +			opt.name = SO_PRIORITY;
> +			opt.rcvname = SO_RCVPRIORITY;
> +			break;
> +		default:
> +			usage(argv[0]);
> +			break;
> +		}
> +	}
> +
> +	if (optind != argc - 2)
> +		usage(argv[0]);
> +
> +	opt.host = argv[optind];
> +	opt.service = argv[optind + 1];
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int err = 0;
> +	int recv_fd = -1;
> +	int ret_value = 0;
> +	__u32 recv_val;
> +	struct cmsghdr *cmsg;
> +	char cbuf[CMSG_SPACE(sizeof(__u32))];
> +	char recv_buf[CMSG_SPACE(sizeof(__u32))];
> +	struct iovec iov[1];
> +	struct msghdr msg;
> +	struct sockaddr_in recv_addr4;
> +	struct sockaddr_in6 recv_addr6;
> +
> +	parse_args(argc, argv);
> +
> +	int family = strchr(opt.host, ':') ? AF_INET6 : AF_INET;
> +
> +	recv_fd = socket(family, SOCK_DGRAM, IPPROTO_UDP);
> +	if (recv_fd < 0) {
> +		perror("Can't open recv socket");
> +		ret_value = -errno;
> +		goto cleanup;
> +	}
> +
> +	err = setsockopt(recv_fd, SOL_SOCKET, opt.rcvname, &opt.val, sizeof(opt.val));
> +	if (err < 0) {
> +		perror("Recv setsockopt error");
> +		ret_value = -errno;
> +		goto cleanup;
> +	}
> +
> +	if (family == AF_INET) {
> +		memset(&recv_addr4, 0, sizeof(recv_addr4));
> +		recv_addr4.sin_family = family;
> +		recv_addr4.sin_port = htons(atoi(opt.service));
> +
> +		if (inet_pton(family, opt.host, &recv_addr4.sin_addr) <= 0) {
> +			perror("Invalid IPV4 address");
> +			ret_value = -errno;
> +			goto cleanup;
> +		}
> +
> +		err = bind(recv_fd, (struct sockaddr *)&recv_addr4, sizeof(recv_addr4));
> +	} else {
> +		memset(&recv_addr6, 0, sizeof(recv_addr6));
> +		recv_addr6.sin6_family = family;
> +		recv_addr6.sin6_port = htons(atoi(opt.service));
> +
> +		if (inet_pton(family, opt.host, &recv_addr6.sin6_addr) <= 0) {
> +			perror("Invalid IPV6 address");
> +			ret_value = -errno;
> +			goto cleanup;
> +		}
> +
> +		err = bind(recv_fd, (struct sockaddr *)&recv_addr6, sizeof(recv_addr6));
> +	}
> +
> +	if (err < 0) {
> +		perror("Recv bind error");
> +		ret_value = -errno;
> +		goto cleanup;
> +	}
> +
> +	iov[0].iov_base = recv_buf;
> +	iov[0].iov_len = sizeof(recv_buf);
> +
> +	memset(&msg, 0, sizeof(msg));
> +	msg.msg_iov = iov;
> +	msg.msg_iovlen = 1;
> +	msg.msg_control = cbuf;
> +	msg.msg_controllen = sizeof(cbuf);
> +
> +	err = recvmsg(recv_fd, &msg, 0);
> +	if (err < 0) {
> +		perror("Message receive error");
> +		ret_value = -errno;
> +		goto cleanup;
> +	}
> +
> +	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
> +		if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == opt.name) {
> +			recv_val = *(__u32 *)CMSG_DATA(cmsg);
> +			printf("Received value: %u\n", recv_val);
> +
> +			if (recv_val != opt.val) {
> +				fprintf(stderr, "Error: expected value: %u, got: %u\n",
> +					opt.val, recv_val);
> +				ret_value = -EINVAL;

This test will return success if no cmsg of cmsg_type opt.name is
encountered. You probably want to fail in that case.

> +				goto cleanup;
> +			}
> +		}
> +	}
> +
Anna Emese Nyiri Feb. 13, 2025, 12:46 p.m. UTC | #3
Paolo Abeni <pabeni@redhat.com> ezt írta (időpont: 2025. febr. 11., K, 12:19):
>
> On 2/10/25 8:22 PM, Anna Emese Nyiri wrote:
> > Introduce tests to verify the correct functionality of the SO_RCVMARK and
> > SO_RCVPRIORITY socket options.
> >
> > Key changes include:
> >
> > - so_rcv_listener.c: Implements a receiver application to test the correct
> > behavior of the SO_RCVMARK and SO_RCVPRIORITY options.
> > - test_so_rcv.sh: Provides a shell script to automate testing for these options.
> > - Makefile: Integrates test_so_rcv.sh into the kernel selftests.
> >
> > v2:
> >
> > - Add the C part to TEST_GEN_PROGS and .gitignore.
> > - Modify buffer space and add IPv6 testing option
> > in so_rcv_listener.c.
> > - Add IPv6 testing, remove unnecessary comment,
> > add kselftest exit codes, run both binaries in a namespace,
> > and add sleep in test_so_rcv.sh.
> > The sleep was added to ensure that the listener process has
> > enough time to start before the sender attempts to connect.
> > - Rebased on net-next.
> >
> > v1:
> >
> > https://lore.kernel.org/netdev/20250129143601.16035-2-annaemesenyiri@gmail.com/
>
> Unfortunately the added self-test does not run successfully in the CI:

I think the test is not running because it is added to TEST_GEN_PROGS.
However, after reconsidering, I'm not sure it should be there, since
this test does not run on its own but is executed by the
test_so_rcv.sh shell script.
Wouldn't it be more appropriate to add so_rcv_listener to
TEST_GEN_FILES instead?

> https://netdev-3.bots.linux.dev/vmksft-net/results/987742/117-so-rcv-listener/stdout

> Please have a look at:
>
> https://github.com/linux-netdev/nipa/wiki/How-to-run-netdev-selftests-CI-style
>
> to test the change locally in a CI-like way.
>
> Cheers,
>
> Paolo
>
Willem de Bruijn Feb. 13, 2025, 3:06 p.m. UTC | #4
Anna Nyiri wrote:
> Paolo Abeni <pabeni@redhat.com> ezt írta (időpont: 2025. febr. 11., K, 12:19):
> >
> > On 2/10/25 8:22 PM, Anna Emese Nyiri wrote:
> > > Introduce tests to verify the correct functionality of the SO_RCVMARK and
> > > SO_RCVPRIORITY socket options.
> > >
> > > Key changes include:
> > >
> > > - so_rcv_listener.c: Implements a receiver application to test the correct
> > > behavior of the SO_RCVMARK and SO_RCVPRIORITY options.
> > > - test_so_rcv.sh: Provides a shell script to automate testing for these options.
> > > - Makefile: Integrates test_so_rcv.sh into the kernel selftests.
> > >
> > > v2:
> > >
> > > - Add the C part to TEST_GEN_PROGS and .gitignore.
> > > - Modify buffer space and add IPv6 testing option
> > > in so_rcv_listener.c.
> > > - Add IPv6 testing, remove unnecessary comment,
> > > add kselftest exit codes, run both binaries in a namespace,
> > > and add sleep in test_so_rcv.sh.
> > > The sleep was added to ensure that the listener process has
> > > enough time to start before the sender attempts to connect.
> > > - Rebased on net-next.
> > >
> > > v1:
> > >
> > > https://lore.kernel.org/netdev/20250129143601.16035-2-annaemesenyiri@gmail.com/
> >
> > Unfortunately the added self-test does not run successfully in the CI:
> 
> I think the test is not running because it is added to TEST_GEN_PROGS.
> However, after reconsidering, I'm not sure it should be there, since
> this test does not run on its own but is executed by the
> test_so_rcv.sh shell script.
> Wouldn't it be more appropriate to add so_rcv_listener to
> TEST_GEN_FILES instead?

Yes,

+TEST_PROGS += test_so_rcv.sh

is correct, as this needs to be called from kselftest.

+TEST_GEN_PROGS += so_rcv_listener

needs to be TEST_GEN_FILES as it is not intended to be called directly.

Documentation/dev-tools/kselftest.rst for details.
diff mbox series

Patch

diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index 28a715a8ef2b..80dcae53ef55 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -42,6 +42,7 @@  socket
 so_incoming_cpu
 so_netns_cookie
 so_txtime
+so_rcv_listener
 stress_reuseport_listen
 tap
 tcp_fastopen_backup_key
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 73ee88d6b043..fe3491dea7c5 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -33,6 +33,7 @@  TEST_PROGS += gro.sh
 TEST_PROGS += gre_gso.sh
 TEST_PROGS += cmsg_so_mark.sh
 TEST_PROGS += cmsg_so_priority.sh
+TEST_PROGS += test_so_rcv.sh
 TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
 TEST_PROGS += netns-name.sh
 TEST_PROGS += nl_netdev.py
@@ -89,6 +90,7 @@  TEST_GEN_FILES += sctp_hello
 TEST_GEN_FILES += ip_local_port_range
 TEST_GEN_PROGS += bind_wildcard
 TEST_GEN_PROGS += bind_timewait
+TEST_GEN_PROGS += so_rcv_listener
 TEST_PROGS += test_vxlan_mdb.sh
 TEST_PROGS += test_bridge_neigh_suppress.sh
 TEST_PROGS += test_vxlan_nolocalbypass.sh
diff --git a/tools/testing/selftests/net/so_rcv_listener.c b/tools/testing/selftests/net/so_rcv_listener.c
new file mode 100644
index 000000000000..693fc1e3d44d
--- /dev/null
+++ b/tools/testing/selftests/net/so_rcv_listener.c
@@ -0,0 +1,165 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef SO_RCVPRIORITY
+#define SO_RCVPRIORITY 82
+#endif
+
+struct options {
+	__u32 val;
+	int name;
+	int rcvname;
+	const char *host;
+	const char *service;
+} opt;
+
+static void __attribute__((noreturn)) usage(const char *bin)
+{
+	printf("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
+	printf("Options:\n"
+		"\t\t-M val  Test SO_RCVMARK\n"
+		"\t\t-P val  Test SO_RCVPRIORITY\n"
+		"");
+	exit(EXIT_FAILURE);
+}
+
+static void parse_args(int argc, char *argv[])
+{
+	int o;
+
+	while ((o = getopt(argc, argv, "M:P:")) != -1) {
+		switch (o) {
+		case 'M':
+			opt.val = atoi(optarg);
+			opt.name = SO_MARK;
+			opt.rcvname = SO_RCVMARK;
+			break;
+		case 'P':
+			opt.val = atoi(optarg);
+			opt.name = SO_PRIORITY;
+			opt.rcvname = SO_RCVPRIORITY;
+			break;
+		default:
+			usage(argv[0]);
+			break;
+		}
+	}
+
+	if (optind != argc - 2)
+		usage(argv[0]);
+
+	opt.host = argv[optind];
+	opt.service = argv[optind + 1];
+}
+
+int main(int argc, char *argv[])
+{
+	int err = 0;
+	int recv_fd = -1;
+	int ret_value = 0;
+	__u32 recv_val;
+	struct cmsghdr *cmsg;
+	char cbuf[CMSG_SPACE(sizeof(__u32))];
+	char recv_buf[CMSG_SPACE(sizeof(__u32))];
+	struct iovec iov[1];
+	struct msghdr msg;
+	struct sockaddr_in recv_addr4;
+	struct sockaddr_in6 recv_addr6;
+
+	parse_args(argc, argv);
+
+	int family = strchr(opt.host, ':') ? AF_INET6 : AF_INET;
+
+	recv_fd = socket(family, SOCK_DGRAM, IPPROTO_UDP);
+	if (recv_fd < 0) {
+		perror("Can't open recv socket");
+		ret_value = -errno;
+		goto cleanup;
+	}
+
+	err = setsockopt(recv_fd, SOL_SOCKET, opt.rcvname, &opt.val, sizeof(opt.val));
+	if (err < 0) {
+		perror("Recv setsockopt error");
+		ret_value = -errno;
+		goto cleanup;
+	}
+
+	if (family == AF_INET) {
+		memset(&recv_addr4, 0, sizeof(recv_addr4));
+		recv_addr4.sin_family = family;
+		recv_addr4.sin_port = htons(atoi(opt.service));
+
+		if (inet_pton(family, opt.host, &recv_addr4.sin_addr) <= 0) {
+			perror("Invalid IPV4 address");
+			ret_value = -errno;
+			goto cleanup;
+		}
+
+		err = bind(recv_fd, (struct sockaddr *)&recv_addr4, sizeof(recv_addr4));
+	} else {
+		memset(&recv_addr6, 0, sizeof(recv_addr6));
+		recv_addr6.sin6_family = family;
+		recv_addr6.sin6_port = htons(atoi(opt.service));
+
+		if (inet_pton(family, opt.host, &recv_addr6.sin6_addr) <= 0) {
+			perror("Invalid IPV6 address");
+			ret_value = -errno;
+			goto cleanup;
+		}
+
+		err = bind(recv_fd, (struct sockaddr *)&recv_addr6, sizeof(recv_addr6));
+	}
+
+	if (err < 0) {
+		perror("Recv bind error");
+		ret_value = -errno;
+		goto cleanup;
+	}
+
+	iov[0].iov_base = recv_buf;
+	iov[0].iov_len = sizeof(recv_buf);
+
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = cbuf;
+	msg.msg_controllen = sizeof(cbuf);
+
+	err = recvmsg(recv_fd, &msg, 0);
+	if (err < 0) {
+		perror("Message receive error");
+		ret_value = -errno;
+		goto cleanup;
+	}
+
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == opt.name) {
+			recv_val = *(__u32 *)CMSG_DATA(cmsg);
+			printf("Received value: %u\n", recv_val);
+
+			if (recv_val != opt.val) {
+				fprintf(stderr, "Error: expected value: %u, got: %u\n",
+					opt.val, recv_val);
+				ret_value = -EINVAL;
+				goto cleanup;
+			}
+		}
+	}
+
+cleanup:
+	if (recv_fd >= 0)
+		close(recv_fd);
+
+	return ret_value;
+}
diff --git a/tools/testing/selftests/net/test_so_rcv.sh b/tools/testing/selftests/net/test_so_rcv.sh
new file mode 100755
index 000000000000..d8aa4362879d
--- /dev/null
+++ b/tools/testing/selftests/net/test_so_rcv.sh
@@ -0,0 +1,73 @@ 
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source lib.sh
+
+HOSTS=("127.0.0.1" "::1")
+PORT=1234
+TOTAL_TESTS=0
+FAILED_TESTS=0
+
+declare -A TESTS=(
+	["SO_RCVPRIORITY"]="-P 2"
+	["SO_RCVMARK"]="-M 3"
+)
+
+check_result() {
+	((TOTAL_TESTS++))
+	if [ "$1" -ne 0 ]; then
+		((FAILED_TESTS++))
+	fi
+}
+
+cleanup()
+{
+	cleanup_ns $NS
+}
+
+trap cleanup EXIT
+
+setup_ns NS
+
+for HOST in "${HOSTS[@]}"; do
+	PROTOCOL="IPv4"
+	if [[ "$HOST" == "::1" ]]; then
+		PROTOCOL="IPv6"
+	fi
+	for test_name in "${!TESTS[@]}"; do
+		echo "Running $test_name test, $PROTOCOL"
+		arg=${TESTS[$test_name]}
+
+		ip netns exec $NS ./so_rcv_listener $arg $HOST $PORT &
+		LISTENER_PID=$!
+
+		sleep 0.5
+
+		if ! ip netns exec $NS ./cmsg_sender $arg $HOST $PORT; then
+			echo "Sender failed for $test_name, $PROTOCOL"
+			kill "$LISTENER_PID" 2>/dev/null
+			wait "$LISTENER_PID"
+			check_result 1
+			continue
+		fi
+
+		wait "$LISTENER_PID"
+		LISTENER_EXIT_CODE=$?
+
+		if [ "$LISTENER_EXIT_CODE" -eq 0 ]; then
+			echo "Rcv test OK for $test_name, $PROTOCOL"
+			check_result 0
+		else
+			echo "Rcv test FAILED for $test_name, $PROTOCOL"
+			check_result 1
+		fi
+	done
+done
+
+if [ "$FAILED_TESTS" -ne 0 ]; then
+	echo "FAIL - $FAILED_TESTS/$TOTAL_TESTS tests failed"
+	exit ${KSFT_FAIL}
+else
+	echo "OK - All $TOTAL_TESTS tests passed"
+	exit ${KSFT_PASS}
+fi