Commit c935af42 authored by Anna Emese Nyiri's avatar Anna Emese Nyiri Committed by Jakub Kicinski
Browse files

selftests: net: add support for testing SO_RCVMARK and SO_RCVPRIORITY



Introduce tests to verify the correct functionality of the SO_RCVMARK and
SO_RCVPRIORITY socket options.

Suggested-by: default avatarJakub Kicinski <kuba@kernel.org>
Suggested-by: default avatarFerenc Fejes <fejes@inf.elte.hu>
Signed-off-by: default avatarAnna Emese Nyiri <annaemesenyiri@gmail.com>
Reviewed-by: default avatarWillem de Bruijn <willemb@google.com>
Reviewed-by: default avatarIdo Schimmel <idosch@nvidia.com>
Tested-by: default avatarIdo Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20250214205828.48503-1-annaemesenyiri@gmail.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent b9d75210
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ socket
so_incoming_cpu
so_netns_cookie
so_txtime
so_rcv_listener
stress_reuseport_listen
tap
tcp_fastopen_backup_key
+2 −0
Original line number Diff line number Diff line
@@ -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
+168 −0
Original line number Diff line number Diff line
// 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;
}
+73 −0
Original line number Diff line number Diff line
#!/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