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: 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>
This commit is contained in:
Anna Emese Nyiri 2025-02-14 21:58:28 +01:00 committed by Jakub Kicinski
parent b9d752105e
commit c935af429e
4 changed files with 244 additions and 0 deletions

View file

@ -42,6 +42,7 @@ socket
so_incoming_cpu so_incoming_cpu
so_netns_cookie so_netns_cookie
so_txtime so_txtime
so_rcv_listener
stress_reuseport_listen stress_reuseport_listen
tap tap
tcp_fastopen_backup_key tcp_fastopen_backup_key

View file

@ -33,6 +33,7 @@ TEST_PROGS += gro.sh
TEST_PROGS += gre_gso.sh TEST_PROGS += gre_gso.sh
TEST_PROGS += cmsg_so_mark.sh TEST_PROGS += cmsg_so_mark.sh
TEST_PROGS += cmsg_so_priority.sh TEST_PROGS += cmsg_so_priority.sh
TEST_PROGS += test_so_rcv.sh
TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
TEST_PROGS += netns-name.sh TEST_PROGS += netns-name.sh
TEST_PROGS += nl_netdev.py 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 += toeplitz
TEST_GEN_FILES += cmsg_sender TEST_GEN_FILES += cmsg_sender
TEST_GEN_FILES += stress_reuseport_listen TEST_GEN_FILES += stress_reuseport_listen
TEST_GEN_FILES += so_rcv_listener
TEST_PROGS += test_vxlan_vnifiltering.sh TEST_PROGS += test_vxlan_vnifiltering.sh
TEST_GEN_FILES += io_uring_zerocopy_tx TEST_GEN_FILES += io_uring_zerocopy_tx
TEST_PROGS += io_uring_zerocopy_tx.sh TEST_PROGS += io_uring_zerocopy_tx.sh

View file

@ -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;
}

View file

@ -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