selftests: net: add support for testing SO_RCVMARK and SO_RCVPRIORITY
authorAnna Emese Nyiri <annaemesenyiri@gmail.com>
Fri, 14 Feb 2025 20:58:28 +0000 (21:58 +0100)
committerJakub Kicinski <kuba@kernel.org>
Tue, 18 Feb 2025 00:45:19 +0000 (16:45 -0800)
Introduce tests to verify the correct functionality of the SO_RCVMARK and
SO_RCVPRIORITY socket options.

Suggested-by: Jakub Kicinski <kuba@kernel.org>
Suggested-by: Ferenc Fejes <fejes@inf.elte.hu>
Signed-off-by: Anna Emese Nyiri <annaemesenyiri@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Tested-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20250214205828.48503-1-annaemesenyiri@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/testing/selftests/net/.gitignore
tools/testing/selftests/net/Makefile
tools/testing/selftests/net/so_rcv_listener.c [new file with mode: 0644]
tools/testing/selftests/net/test_so_rcv.sh [new file with mode: 0755]

index 28a715a8ef2b18cadbde5abbd576fb98727131e0..80dcae53ef55e4bc93bfe1cafaba182516e8f811 100644 (file)
@@ -42,6 +42,7 @@ socket
 so_incoming_cpu
 so_netns_cookie
 so_txtime
+so_rcv_listener
 stress_reuseport_listen
 tap
 tcp_fastopen_backup_key
index b6271714504d991f0299ecce8b9743f4e79a7725..8d6116b80cf1a2c507b1ed619e027e54144aad0b 100644 (file)
@@ -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
@@ -76,6 +77,7 @@ TEST_GEN_PROGS += reuseport_dualstack reuseaddr_conflict tls tun tap epoll_busy_
 TEST_GEN_FILES += toeplitz
 TEST_GEN_FILES += cmsg_sender
 TEST_GEN_FILES += stress_reuseport_listen
+TEST_GEN_FILES += so_rcv_listener
 TEST_PROGS += test_vxlan_vnifiltering.sh
 TEST_GEN_FILES += io_uring_zerocopy_tx
 TEST_PROGS += io_uring_zerocopy_tx.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 (file)
index 0000000..bc58411
--- /dev/null
@@ -0,0 +1,168 @@
+// 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;
+               }
+       }
+
+       fprintf(stderr, "Error: No matching cmsg received\n");
+       ret_value = -ENOMSG;
+
+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 (executable)
index 0000000..d8aa436
--- /dev/null
@@ -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