root/tools/testing/selftests/net/so_txtime.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. gettime_ns
  2. do_send_one
  3. do_recv_one
  4. do_recv_verify_empty
  5. do_recv_errqueue_timeout
  6. setsockopt_txtime
  7. setup_tx
  8. setup_rx
  9. do_test
  10. parse_io
  11. parse_opts
  12. main

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * Test the SO_TXTIME API
   4  *
   5  * Takes two streams of { payload, delivery time }[], one input and one output.
   6  * Sends the input stream and verifies arrival matches the output stream.
   7  * The two streams can differ due to out-of-order delivery and drops.
   8  */
   9 
  10 #define _GNU_SOURCE
  11 
  12 #include <arpa/inet.h>
  13 #include <error.h>
  14 #include <errno.h>
  15 #include <inttypes.h>
  16 #include <linux/net_tstamp.h>
  17 #include <linux/errqueue.h>
  18 #include <linux/ipv6.h>
  19 #include <linux/tcp.h>
  20 #include <stdbool.h>
  21 #include <stdlib.h>
  22 #include <stdio.h>
  23 #include <string.h>
  24 #include <sys/socket.h>
  25 #include <sys/stat.h>
  26 #include <sys/time.h>
  27 #include <sys/types.h>
  28 #include <time.h>
  29 #include <unistd.h>
  30 
  31 static int      cfg_clockid     = CLOCK_TAI;
  32 static bool     cfg_do_ipv4;
  33 static bool     cfg_do_ipv6;
  34 static uint16_t cfg_port        = 8000;
  35 static int      cfg_variance_us = 4000;
  36 
  37 static uint64_t glob_tstart;
  38 
  39 /* encode one timed transmission (of a 1B payload) */
  40 struct timed_send {
  41         char    data;
  42         int64_t delay_us;
  43 };
  44 
  45 #define MAX_NUM_PKT     8
  46 static struct timed_send cfg_in[MAX_NUM_PKT];
  47 static struct timed_send cfg_out[MAX_NUM_PKT];
  48 static int cfg_num_pkt;
  49 
  50 static int cfg_errq_level;
  51 static int cfg_errq_type;
  52 
  53 static uint64_t gettime_ns(void)
  54 {
  55         struct timespec ts;
  56 
  57         if (clock_gettime(cfg_clockid, &ts))
  58                 error(1, errno, "gettime");
  59 
  60         return ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec;
  61 }
  62 
  63 static void do_send_one(int fdt, struct timed_send *ts)
  64 {
  65         char control[CMSG_SPACE(sizeof(uint64_t))];
  66         struct msghdr msg = {0};
  67         struct iovec iov = {0};
  68         struct cmsghdr *cm;
  69         uint64_t tdeliver;
  70         int ret;
  71 
  72         iov.iov_base = &ts->data;
  73         iov.iov_len = 1;
  74 
  75         msg.msg_iov = &iov;
  76         msg.msg_iovlen = 1;
  77 
  78         if (ts->delay_us >= 0) {
  79                 memset(control, 0, sizeof(control));
  80                 msg.msg_control = &control;
  81                 msg.msg_controllen = sizeof(control);
  82 
  83                 tdeliver = glob_tstart + ts->delay_us * 1000;
  84 
  85                 cm = CMSG_FIRSTHDR(&msg);
  86                 cm->cmsg_level = SOL_SOCKET;
  87                 cm->cmsg_type = SCM_TXTIME;
  88                 cm->cmsg_len = CMSG_LEN(sizeof(tdeliver));
  89                 memcpy(CMSG_DATA(cm), &tdeliver, sizeof(tdeliver));
  90         }
  91 
  92         ret = sendmsg(fdt, &msg, 0);
  93         if (ret == -1)
  94                 error(1, errno, "write");
  95         if (ret == 0)
  96                 error(1, 0, "write: 0B");
  97 
  98 }
  99 
 100 static bool do_recv_one(int fdr, struct timed_send *ts)
 101 {
 102         int64_t tstop, texpect;
 103         char rbuf[2];
 104         int ret;
 105 
 106         ret = recv(fdr, rbuf, sizeof(rbuf), 0);
 107         if (ret == -1 && errno == EAGAIN)
 108                 return true;
 109         if (ret == -1)
 110                 error(1, errno, "read");
 111         if (ret != 1)
 112                 error(1, 0, "read: %dB", ret);
 113 
 114         tstop = (gettime_ns() - glob_tstart) / 1000;
 115         texpect = ts->delay_us >= 0 ? ts->delay_us : 0;
 116 
 117         fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n",
 118                         rbuf[0], (long long)tstop, (long long)texpect);
 119 
 120         if (rbuf[0] != ts->data)
 121                 error(1, 0, "payload mismatch. expected %c", ts->data);
 122 
 123         if (labs(tstop - texpect) > cfg_variance_us)
 124                 error(1, 0, "exceeds variance (%d us)", cfg_variance_us);
 125 
 126         return false;
 127 }
 128 
 129 static void do_recv_verify_empty(int fdr)
 130 {
 131         char rbuf[1];
 132         int ret;
 133 
 134         ret = recv(fdr, rbuf, sizeof(rbuf), 0);
 135         if (ret != -1 || errno != EAGAIN)
 136                 error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno);
 137 }
 138 
 139 static void do_recv_errqueue_timeout(int fdt)
 140 {
 141         char control[CMSG_SPACE(sizeof(struct sock_extended_err)) +
 142                      CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0};
 143         char data[sizeof(struct ipv6hdr) +
 144                   sizeof(struct tcphdr) + 1];
 145         struct sock_extended_err *err;
 146         struct msghdr msg = {0};
 147         struct iovec iov = {0};
 148         struct cmsghdr *cm;
 149         int64_t tstamp = 0;
 150         int ret;
 151 
 152         iov.iov_base = data;
 153         iov.iov_len = sizeof(data);
 154 
 155         msg.msg_iov = &iov;
 156         msg.msg_iovlen = 1;
 157 
 158         msg.msg_control = control;
 159         msg.msg_controllen = sizeof(control);
 160 
 161         while (1) {
 162                 ret = recvmsg(fdt, &msg, MSG_ERRQUEUE);
 163                 if (ret == -1 && errno == EAGAIN)
 164                         break;
 165                 if (ret == -1)
 166                         error(1, errno, "errqueue");
 167                 if (msg.msg_flags != MSG_ERRQUEUE)
 168                         error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags);
 169 
 170                 cm = CMSG_FIRSTHDR(&msg);
 171                 if (cm->cmsg_level != cfg_errq_level ||
 172                     cm->cmsg_type != cfg_errq_type)
 173                         error(1, 0, "errqueue: type 0x%x.0x%x\n",
 174                                     cm->cmsg_level, cm->cmsg_type);
 175 
 176                 err = (struct sock_extended_err *)CMSG_DATA(cm);
 177                 if (err->ee_origin != SO_EE_ORIGIN_TXTIME)
 178                         error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin);
 179                 if (err->ee_code != ECANCELED)
 180                         error(1, 0, "errqueue: code 0x%x\n", err->ee_code);
 181 
 182                 tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info;
 183                 tstamp -= (int64_t) glob_tstart;
 184                 tstamp /= 1000 * 1000;
 185                 fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped\n",
 186                                 data[ret - 1], tstamp);
 187 
 188                 msg.msg_flags = 0;
 189                 msg.msg_controllen = sizeof(control);
 190         }
 191 
 192         error(1, 0, "recv: timeout");
 193 }
 194 
 195 static void setsockopt_txtime(int fd)
 196 {
 197         struct sock_txtime so_txtime_val = { .clockid = cfg_clockid };
 198         struct sock_txtime so_txtime_val_read = { 0 };
 199         socklen_t vallen = sizeof(so_txtime_val);
 200 
 201         so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS;
 202 
 203         if (setsockopt(fd, SOL_SOCKET, SO_TXTIME,
 204                        &so_txtime_val, sizeof(so_txtime_val)))
 205                 error(1, errno, "setsockopt txtime");
 206 
 207         if (getsockopt(fd, SOL_SOCKET, SO_TXTIME,
 208                        &so_txtime_val_read, &vallen))
 209                 error(1, errno, "getsockopt txtime");
 210 
 211         if (vallen != sizeof(so_txtime_val) ||
 212             memcmp(&so_txtime_val, &so_txtime_val_read, vallen))
 213                 error(1, 0, "getsockopt txtime: mismatch");
 214 }
 215 
 216 static int setup_tx(struct sockaddr *addr, socklen_t alen)
 217 {
 218         int fd;
 219 
 220         fd = socket(addr->sa_family, SOCK_DGRAM, 0);
 221         if (fd == -1)
 222                 error(1, errno, "socket t");
 223 
 224         if (connect(fd, addr, alen))
 225                 error(1, errno, "connect");
 226 
 227         setsockopt_txtime(fd);
 228 
 229         return fd;
 230 }
 231 
 232 static int setup_rx(struct sockaddr *addr, socklen_t alen)
 233 {
 234         struct timeval tv = { .tv_usec = 100 * 1000 };
 235         int fd;
 236 
 237         fd = socket(addr->sa_family, SOCK_DGRAM, 0);
 238         if (fd == -1)
 239                 error(1, errno, "socket r");
 240 
 241         if (bind(fd, addr, alen))
 242                 error(1, errno, "bind");
 243 
 244         if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
 245                 error(1, errno, "setsockopt rcv timeout");
 246 
 247         return fd;
 248 }
 249 
 250 static void do_test(struct sockaddr *addr, socklen_t alen)
 251 {
 252         int fdt, fdr, i;
 253 
 254         fprintf(stderr, "\nSO_TXTIME ipv%c clock %s\n",
 255                         addr->sa_family == PF_INET ? '4' : '6',
 256                         cfg_clockid == CLOCK_TAI ? "tai" : "monotonic");
 257 
 258         fdt = setup_tx(addr, alen);
 259         fdr = setup_rx(addr, alen);
 260 
 261         glob_tstart = gettime_ns();
 262 
 263         for (i = 0; i < cfg_num_pkt; i++)
 264                 do_send_one(fdt, &cfg_in[i]);
 265         for (i = 0; i < cfg_num_pkt; i++)
 266                 if (do_recv_one(fdr, &cfg_out[i]))
 267                         do_recv_errqueue_timeout(fdt);
 268 
 269         do_recv_verify_empty(fdr);
 270 
 271         if (close(fdr))
 272                 error(1, errno, "close r");
 273         if (close(fdt))
 274                 error(1, errno, "close t");
 275 }
 276 
 277 static int parse_io(const char *optarg, struct timed_send *array)
 278 {
 279         char *arg, *tok;
 280         int aoff = 0;
 281 
 282         arg = strdup(optarg);
 283         if (!arg)
 284                 error(1, errno, "strdup");
 285 
 286         while ((tok = strtok(arg, ","))) {
 287                 arg = NULL;     /* only pass non-zero on first call */
 288 
 289                 if (aoff / 2 == MAX_NUM_PKT)
 290                         error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT);
 291 
 292                 if (aoff & 1) { /* parse delay */
 293                         array->delay_us = strtol(tok, NULL, 0) * 1000;
 294                         array++;
 295                 } else {        /* parse character */
 296                         array->data = tok[0];
 297                 }
 298 
 299                 aoff++;
 300         }
 301 
 302         free(arg);
 303 
 304         return aoff / 2;
 305 }
 306 
 307 static void parse_opts(int argc, char **argv)
 308 {
 309         int c, ilen, olen;
 310 
 311         while ((c = getopt(argc, argv, "46c:")) != -1) {
 312                 switch (c) {
 313                 case '4':
 314                         cfg_do_ipv4 = true;
 315                         break;
 316                 case '6':
 317                         cfg_do_ipv6 = true;
 318                         break;
 319                 case 'c':
 320                         if (!strcmp(optarg, "tai"))
 321                                 cfg_clockid = CLOCK_TAI;
 322                         else if (!strcmp(optarg, "monotonic") ||
 323                                  !strcmp(optarg, "mono"))
 324                                 cfg_clockid = CLOCK_MONOTONIC;
 325                         else
 326                                 error(1, 0, "unknown clock id %s", optarg);
 327                         break;
 328                 default:
 329                         error(1, 0, "parse error at %d", optind);
 330                 }
 331         }
 332 
 333         if (argc - optind != 2)
 334                 error(1, 0, "Usage: %s [-46] -c <clock> <in> <out>", argv[0]);
 335 
 336         ilen = parse_io(argv[optind], cfg_in);
 337         olen = parse_io(argv[optind + 1], cfg_out);
 338         if (ilen != olen)
 339                 error(1, 0, "i/o streams len mismatch (%d, %d)\n", ilen, olen);
 340         cfg_num_pkt = ilen;
 341 }
 342 
 343 int main(int argc, char **argv)
 344 {
 345         parse_opts(argc, argv);
 346 
 347         if (cfg_do_ipv6) {
 348                 struct sockaddr_in6 addr6 = {0};
 349 
 350                 addr6.sin6_family = AF_INET6;
 351                 addr6.sin6_port = htons(cfg_port);
 352                 addr6.sin6_addr = in6addr_loopback;
 353 
 354                 cfg_errq_level = SOL_IPV6;
 355                 cfg_errq_type = IPV6_RECVERR;
 356 
 357                 do_test((void *)&addr6, sizeof(addr6));
 358         }
 359 
 360         if (cfg_do_ipv4) {
 361                 struct sockaddr_in addr4 = {0};
 362 
 363                 addr4.sin_family = AF_INET;
 364                 addr4.sin_port = htons(cfg_port);
 365                 addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 366 
 367                 cfg_errq_level = SOL_IP;
 368                 cfg_errq_type = IP_RECVERR;
 369 
 370                 do_test((void *)&addr4, sizeof(addr4));
 371         }
 372 
 373         return 0;
 374 }

/* [<][>][^][v][top][bottom][index][help] */