Files
linux/tools/testing/selftests/net/cmsg_sender.c
Anna Emese Nyiri cda7d5abe0 selftests: net: test SO_PRIORITY ancillary data with cmsg_sender
Extend cmsg_sender.c with a new option '-Q' to send SO_PRIORITY
ancillary data.

cmsg_so_priority.sh script added to validate SO_PRIORITY behavior
by creating VLAN device with egress QoS mapping and testing packet
priorities using flower filters. Verify that packets with different
priorities are correctly matched and counted by filters for multiple
protocols and IP versions.

Reviewed-by: Willem de Bruijn <willemb@google.com>
Acked-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Tested-by: Ido Schimmel <idosch@nvidia.com>
Suggested-by: Ido Schimmel <idosch@idosch.org>
Signed-off-by: Anna Emese Nyiri <annaemesenyiri@gmail.com>
Link: https://patch.msgid.link/20241213084457.45120-4-annaemesenyiri@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2024-12-16 18:14:12 -08:00

546 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
#include <errno.h>
#include <error.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/errqueue.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/net_tstamp.h>
#include <linux/types.h>
#include <linux/udp.h>
#include <sys/socket.h>
#include "../kselftest.h"
enum {
ERN_SUCCESS = 0,
/* Well defined errors, callers may depend on these */
ERN_SEND = 1,
/* Informational, can reorder */
ERN_HELP,
ERN_SEND_SHORT,
ERN_SOCK_CREATE,
ERN_RESOLVE,
ERN_CMSG_WR,
ERN_SOCKOPT,
ERN_GETTIME,
ERN_RECVERR,
ERN_CMSG_RD,
ERN_CMSG_RCV,
};
struct option_cmsg_u32 {
bool ena;
unsigned int val;
};
struct options {
bool silent_send;
const char *host;
const char *service;
unsigned int size;
unsigned int num_pkt;
struct {
unsigned int mark;
unsigned int dontfrag;
unsigned int tclass;
unsigned int hlimit;
unsigned int priority;
} sockopt;
struct {
unsigned int family;
unsigned int type;
unsigned int proto;
} sock;
struct option_cmsg_u32 mark;
struct option_cmsg_u32 priority;
struct {
bool ena;
unsigned int delay;
} txtime;
struct {
bool ena;
} ts;
struct {
struct option_cmsg_u32 dontfrag;
struct option_cmsg_u32 tclass;
struct option_cmsg_u32 hlimit;
struct option_cmsg_u32 exthdr;
} v6;
} opt = {
.size = 13,
.num_pkt = 1,
.sock = {
.family = AF_UNSPEC,
.type = SOCK_DGRAM,
.proto = IPPROTO_UDP,
},
};
static struct timespec time_start_real;
static struct timespec time_start_mono;
static void __attribute__((noreturn)) cs_usage(const char *bin)
{
printf("Usage: %s [opts] <dst host> <dst port / service>\n", bin);
printf("Options:\n"
"\t\t-s Silent send() failures\n"
"\t\t-S send() size\n"
"\t\t-4/-6 Force IPv4 / IPv6 only\n"
"\t\t-p prot Socket protocol\n"
"\t\t (u = UDP (default); i = ICMP; r = RAW)\n"
"\n"
"\t\t-m val Set SO_MARK with given value\n"
"\t\t-M val Set SO_MARK via setsockopt\n"
"\t\t-P val Set SO_PRIORITY via setsockopt\n"
"\t\t-Q val Set SO_PRIORITY via cmsg\n"
"\t\t-d val Set SO_TXTIME with given delay (usec)\n"
"\t\t-t Enable time stamp reporting\n"
"\t\t-f val Set don't fragment via cmsg\n"
"\t\t-F val Set don't fragment via setsockopt\n"
"\t\t-c val Set TCLASS via cmsg\n"
"\t\t-C val Set TCLASS via setsockopt\n"
"\t\t-l val Set HOPLIMIT via cmsg\n"
"\t\t-L val Set HOPLIMIT via setsockopt\n"
"\t\t-H type Add an IPv6 header option\n"
"\t\t (h = HOP; d = DST; r = RTDST)"
"");
exit(ERN_HELP);
}
static void cs_parse_args(int argc, char *argv[])
{
int o;
while ((o = getopt(argc, argv, "46sS:p:P:m:M:n:d:tf:F:c:C:l:L:H:Q:")) != -1) {
switch (o) {
case 's':
opt.silent_send = true;
break;
case 'S':
opt.size = atoi(optarg);
break;
case '4':
opt.sock.family = AF_INET;
break;
case '6':
opt.sock.family = AF_INET6;
break;
case 'p':
if (*optarg == 'u' || *optarg == 'U') {
opt.sock.proto = IPPROTO_UDP;
} else if (*optarg == 'i' || *optarg == 'I') {
opt.sock.proto = IPPROTO_ICMP;
} else if (*optarg == 'r') {
opt.sock.type = SOCK_RAW;
} else {
printf("Error: unknown protocol: %s\n", optarg);
cs_usage(argv[0]);
}
break;
case 'P':
opt.sockopt.priority = atoi(optarg);
break;
case 'm':
opt.mark.ena = true;
opt.mark.val = atoi(optarg);
break;
case 'Q':
opt.priority.ena = true;
opt.priority.val = atoi(optarg);
break;
case 'M':
opt.sockopt.mark = atoi(optarg);
break;
case 'n':
opt.num_pkt = atoi(optarg);
break;
case 'd':
opt.txtime.ena = true;
opt.txtime.delay = atoi(optarg);
break;
case 't':
opt.ts.ena = true;
break;
case 'f':
opt.v6.dontfrag.ena = true;
opt.v6.dontfrag.val = atoi(optarg);
break;
case 'F':
opt.sockopt.dontfrag = atoi(optarg);
break;
case 'c':
opt.v6.tclass.ena = true;
opt.v6.tclass.val = atoi(optarg);
break;
case 'C':
opt.sockopt.tclass = atoi(optarg);
break;
case 'l':
opt.v6.hlimit.ena = true;
opt.v6.hlimit.val = atoi(optarg);
break;
case 'L':
opt.sockopt.hlimit = atoi(optarg);
break;
case 'H':
opt.v6.exthdr.ena = true;
switch (optarg[0]) {
case 'h':
opt.v6.exthdr.val = IPV6_HOPOPTS;
break;
case 'd':
opt.v6.exthdr.val = IPV6_DSTOPTS;
break;
case 'r':
opt.v6.exthdr.val = IPV6_RTHDRDSTOPTS;
break;
default:
printf("Error: hdr type: %s\n", optarg);
break;
}
break;
}
}
if (optind != argc - 2)
cs_usage(argv[0]);
opt.host = argv[optind];
opt.service = argv[optind + 1];
}
static void memrnd(void *s, size_t n)
{
int *dword = s;
char *byte;
for (; n >= 4; n -= 4)
*dword++ = rand();
byte = (void *)dword;
while (n--)
*byte++ = rand();
}
static void
ca_write_cmsg_u32(char *cbuf, size_t cbuf_sz, size_t *cmsg_len,
int level, int optname, struct option_cmsg_u32 *uopt)
{
struct cmsghdr *cmsg;
if (!uopt->ena)
return;
cmsg = (struct cmsghdr *)(cbuf + *cmsg_len);
*cmsg_len += CMSG_SPACE(sizeof(__u32));
if (cbuf_sz < *cmsg_len)
error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");
cmsg->cmsg_level = level;
cmsg->cmsg_type = optname;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*(__u32 *)CMSG_DATA(cmsg) = uopt->val;
}
static void
cs_write_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
{
struct cmsghdr *cmsg;
size_t cmsg_len;
msg->msg_control = cbuf;
cmsg_len = 0;
ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
SOL_SOCKET, SO_MARK, &opt.mark);
ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
SOL_SOCKET, SO_PRIORITY, &opt.priority);
ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
SOL_IPV6, IPV6_DONTFRAG, &opt.v6.dontfrag);
ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
SOL_IPV6, IPV6_TCLASS, &opt.v6.tclass);
ca_write_cmsg_u32(cbuf, cbuf_sz, &cmsg_len,
SOL_IPV6, IPV6_HOPLIMIT, &opt.v6.hlimit);
if (opt.txtime.ena) {
__u64 txtime;
txtime = time_start_mono.tv_sec * (1000ULL * 1000 * 1000) +
time_start_mono.tv_nsec +
opt.txtime.delay * 1000;
cmsg = (struct cmsghdr *)(cbuf + cmsg_len);
cmsg_len += CMSG_SPACE(sizeof(txtime));
if (cbuf_sz < cmsg_len)
error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TXTIME;
cmsg->cmsg_len = CMSG_LEN(sizeof(txtime));
memcpy(CMSG_DATA(cmsg), &txtime, sizeof(txtime));
}
if (opt.ts.ena) {
cmsg = (struct cmsghdr *)(cbuf + cmsg_len);
cmsg_len += CMSG_SPACE(sizeof(__u32));
if (cbuf_sz < cmsg_len)
error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMPING;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u32));
*(__u32 *)CMSG_DATA(cmsg) = SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_TX_SOFTWARE;
}
if (opt.v6.exthdr.ena) {
cmsg = (struct cmsghdr *)(cbuf + cmsg_len);
cmsg_len += CMSG_SPACE(8);
if (cbuf_sz < cmsg_len)
error(ERN_CMSG_WR, EFAULT, "cmsg buffer too small");
cmsg->cmsg_level = SOL_IPV6;
cmsg->cmsg_type = opt.v6.exthdr.val;
cmsg->cmsg_len = CMSG_LEN(8);
*(__u64 *)CMSG_DATA(cmsg) = 0;
}
if (cmsg_len)
msg->msg_controllen = cmsg_len;
else
msg->msg_control = NULL;
}
static const char *cs_ts_info2str(unsigned int info)
{
static const char *names[] = {
[SCM_TSTAMP_SND] = "SND",
[SCM_TSTAMP_SCHED] = "SCHED",
[SCM_TSTAMP_ACK] = "ACK",
};
if (info < ARRAY_SIZE(names))
return names[info];
return "unknown";
}
static unsigned long
cs_read_cmsg(int fd, struct msghdr *msg, char *cbuf, size_t cbuf_sz)
{
struct sock_extended_err *see;
struct scm_timestamping *ts;
unsigned long ts_seen = 0;
struct cmsghdr *cmsg;
int i, err;
if (!opt.ts.ena)
return 0;
msg->msg_control = cbuf;
msg->msg_controllen = cbuf_sz;
while (true) {
ts = NULL;
see = NULL;
memset(cbuf, 0, cbuf_sz);
err = recvmsg(fd, msg, MSG_ERRQUEUE);
if (err < 0) {
if (errno == EAGAIN)
break;
error(ERN_RECVERR, errno, "recvmsg ERRQ");
}
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMPING_OLD) {
if (cmsg->cmsg_len < sizeof(*ts))
error(ERN_CMSG_RD, EINVAL, "TS cmsg");
ts = (void *)CMSG_DATA(cmsg);
}
if ((cmsg->cmsg_level == SOL_IP &&
cmsg->cmsg_type == IP_RECVERR) ||
(cmsg->cmsg_level == SOL_IPV6 &&
cmsg->cmsg_type == IPV6_RECVERR)) {
if (cmsg->cmsg_len < sizeof(*see))
error(ERN_CMSG_RD, EINVAL, "sock_err cmsg");
see = (void *)CMSG_DATA(cmsg);
}
}
if (!ts)
error(ERN_CMSG_RCV, ENOENT, "TS cmsg not found");
if (!see)
error(ERN_CMSG_RCV, ENOENT, "sock_err cmsg not found");
for (i = 0; i < 3; i++) {
unsigned long long rel_time;
if (!ts->ts[i].tv_sec && !ts->ts[i].tv_nsec)
continue;
rel_time = (ts->ts[i].tv_sec - time_start_real.tv_sec) *
(1000ULL * 1000) +
(ts->ts[i].tv_nsec - time_start_real.tv_nsec) /
1000;
printf(" %5s ts%d %lluus\n",
cs_ts_info2str(see->ee_info),
i, rel_time);
ts_seen |= 1 << see->ee_info;
}
}
return ts_seen;
}
static void ca_set_sockopts(int fd)
{
if (opt.sockopt.mark &&
setsockopt(fd, SOL_SOCKET, SO_MARK,
&opt.sockopt.mark, sizeof(opt.sockopt.mark)))
error(ERN_SOCKOPT, errno, "setsockopt SO_MARK");
if (opt.sockopt.dontfrag &&
setsockopt(fd, SOL_IPV6, IPV6_DONTFRAG,
&opt.sockopt.dontfrag, sizeof(opt.sockopt.dontfrag)))
error(ERN_SOCKOPT, errno, "setsockopt IPV6_DONTFRAG");
if (opt.sockopt.tclass &&
setsockopt(fd, SOL_IPV6, IPV6_TCLASS,
&opt.sockopt.tclass, sizeof(opt.sockopt.tclass)))
error(ERN_SOCKOPT, errno, "setsockopt IPV6_TCLASS");
if (opt.sockopt.hlimit &&
setsockopt(fd, SOL_IPV6, IPV6_UNICAST_HOPS,
&opt.sockopt.hlimit, sizeof(opt.sockopt.hlimit)))
error(ERN_SOCKOPT, errno, "setsockopt IPV6_HOPLIMIT");
if (opt.sockopt.priority &&
setsockopt(fd, SOL_SOCKET, SO_PRIORITY,
&opt.sockopt.priority, sizeof(opt.sockopt.priority)))
error(ERN_SOCKOPT, errno, "setsockopt SO_PRIORITY");
if (opt.txtime.ena) {
struct sock_txtime so_txtime = {
.clockid = CLOCK_MONOTONIC,
};
if (setsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime, sizeof(so_txtime)))
error(ERN_SOCKOPT, errno, "setsockopt TXTIME");
}
if (opt.ts.ena) {
__u32 val = SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_OPT_TSONLY;
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
&val, sizeof(val)))
error(ERN_SOCKOPT, errno, "setsockopt TIMESTAMPING");
}
}
int main(int argc, char *argv[])
{
struct addrinfo hints, *ai;
struct iovec iov[1];
unsigned char *buf;
struct msghdr msg;
char cbuf[1024];
int err;
int fd;
int i;
cs_parse_args(argc, argv);
buf = malloc(opt.size);
memrnd(buf, opt.size);
memset(&hints, 0, sizeof(hints));
hints.ai_family = opt.sock.family;
ai = NULL;
err = getaddrinfo(opt.host, opt.service, &hints, &ai);
if (err) {
fprintf(stderr, "Can't resolve address [%s]:%s\n",
opt.host, opt.service);
return ERN_SOCK_CREATE;
}
if (ai->ai_family == AF_INET6 && opt.sock.proto == IPPROTO_ICMP)
opt.sock.proto = IPPROTO_ICMPV6;
fd = socket(ai->ai_family, opt.sock.type, opt.sock.proto);
if (fd < 0) {
fprintf(stderr, "Can't open socket: %s\n", strerror(errno));
freeaddrinfo(ai);
return ERN_RESOLVE;
}
if (opt.sock.proto == IPPROTO_ICMP) {
buf[0] = ICMP_ECHO;
buf[1] = 0;
} else if (opt.sock.proto == IPPROTO_ICMPV6) {
buf[0] = ICMPV6_ECHO_REQUEST;
buf[1] = 0;
} else if (opt.sock.type == SOCK_RAW) {
struct udphdr hdr = { 1, 2, htons(opt.size), 0 };
struct sockaddr_in6 *sin6 = (void *)ai->ai_addr;
memcpy(buf, &hdr, sizeof(hdr));
sin6->sin6_port = htons(opt.sock.proto);
}
ca_set_sockopts(fd);
if (clock_gettime(CLOCK_REALTIME, &time_start_real))
error(ERN_GETTIME, errno, "gettime REALTIME");
if (clock_gettime(CLOCK_MONOTONIC, &time_start_mono))
error(ERN_GETTIME, errno, "gettime MONOTONIC");
iov[0].iov_base = buf;
iov[0].iov_len = opt.size;
memset(&msg, 0, sizeof(msg));
msg.msg_name = ai->ai_addr;
msg.msg_namelen = ai->ai_addrlen;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
cs_write_cmsg(fd, &msg, cbuf, sizeof(cbuf));
for (i = 0; i < opt.num_pkt; i++) {
err = sendmsg(fd, &msg, 0);
if (err < 0) {
if (!opt.silent_send)
fprintf(stderr, "send failed: %s\n", strerror(errno));
err = ERN_SEND;
goto err_out;
} else if (err != (int)opt.size) {
fprintf(stderr, "short send\n");
err = ERN_SEND_SHORT;
goto err_out;
}
}
err = ERN_SUCCESS;
if (opt.ts.ena) {
unsigned long seen;
int i;
/* Make sure all timestamps have time to loop back */
for (i = 0; i < 40; i++) {
seen = cs_read_cmsg(fd, &msg, cbuf, sizeof(cbuf));
if (seen & (1 << SCM_TSTAMP_SND))
break;
usleep(opt.txtime.delay / 20);
}
}
err_out:
close(fd);
freeaddrinfo(ai);
return err;
}