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 <linux/net_tstamp.h> 16 #include <stdbool.h> 17 #include <stdlib.h> 18 #include <stdio.h> 19 #include <string.h> 20 #include <sys/socket.h> 21 #include <sys/stat.h> 22 #include <sys/time.h> 23 #include <sys/types.h> 24 #include <time.h> 25 #include <unistd.h> 26 27 static int cfg_clockid = CLOCK_TAI; 28 static bool cfg_do_ipv4; 29 static bool cfg_do_ipv6; 30 static uint16_t cfg_port = 8000; 31 static int cfg_variance_us = 2000; 32 33 static uint64_t glob_tstart; 34 35 /* encode one timed transmission (of a 1B payload) */ 36 struct timed_send { 37 char data; 38 int64_t delay_us; 39 }; 40 41 #define MAX_NUM_PKT 8 42 static struct timed_send cfg_in[MAX_NUM_PKT]; 43 static struct timed_send cfg_out[MAX_NUM_PKT]; 44 static int cfg_num_pkt; 45 46 static uint64_t gettime_ns(void) 47 { 48 struct timespec ts; 49 50 if (clock_gettime(cfg_clockid, &ts)) 51 error(1, errno, "gettime"); 52 53 return ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec; 54 } 55 56 static void do_send_one(int fdt, struct timed_send *ts) 57 { 58 char control[CMSG_SPACE(sizeof(uint64_t))]; 59 struct msghdr msg = {0}; 60 struct iovec iov = {0}; 61 struct cmsghdr *cm; 62 uint64_t tdeliver; 63 int ret; 64 65 iov.iov_base = &ts->data; 66 iov.iov_len = 1; 67 68 msg.msg_iov = &iov; 69 msg.msg_iovlen = 1; 70 71 if (ts->delay_us >= 0) { 72 memset(control, 0, sizeof(control)); 73 msg.msg_control = &control; 74 msg.msg_controllen = sizeof(control); 75 76 tdeliver = glob_tstart + ts->delay_us * 1000; 77 78 cm = CMSG_FIRSTHDR(&msg); 79 cm->cmsg_level = SOL_SOCKET; 80 cm->cmsg_type = SCM_TXTIME; 81 cm->cmsg_len = CMSG_LEN(sizeof(tdeliver)); 82 memcpy(CMSG_DATA(cm), &tdeliver, sizeof(tdeliver)); 83 } 84 85 ret = sendmsg(fdt, &msg, 0); 86 if (ret == -1) 87 error(1, errno, "write"); 88 if (ret == 0) 89 error(1, 0, "write: 0B"); 90 91 } 92 93 static void do_recv_one(int fdr, struct timed_send *ts) 94 { 95 int64_t tstop, texpect; 96 char rbuf[2]; 97 int ret; 98 99 ret = recv(fdr, rbuf, sizeof(rbuf), 0); 100 if (ret == -1) 101 error(1, errno, "read"); 102 if (ret != 1) 103 error(1, 0, "read: %dB", ret); 104 105 tstop = (gettime_ns() - glob_tstart) / 1000; 106 texpect = ts->delay_us >= 0 ? ts->delay_us : 0; 107 108 fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n", 109 rbuf[0], (long long)tstop, (long long)texpect); 110 111 if (rbuf[0] != ts->data) 112 error(1, 0, "payload mismatch. expected %c", ts->data); 113 114 if (labs(tstop - texpect) > cfg_variance_us) 115 error(1, 0, "exceeds variance (%d us)", cfg_variance_us); 116 } 117 118 static void do_recv_verify_empty(int fdr) 119 { 120 char rbuf[1]; 121 int ret; 122 123 ret = recv(fdr, rbuf, sizeof(rbuf), 0); 124 if (ret != -1 || errno != EAGAIN) 125 error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); 126 } 127 128 static void setsockopt_txtime(int fd) 129 { 130 struct sock_txtime so_txtime_val = { .clockid = cfg_clockid }; 131 struct sock_txtime so_txtime_val_read = { 0 }; 132 socklen_t vallen = sizeof(so_txtime_val); 133 134 if (setsockopt(fd, SOL_SOCKET, SO_TXTIME, 135 &so_txtime_val, sizeof(so_txtime_val))) 136 error(1, errno, "setsockopt txtime"); 137 138 if (getsockopt(fd, SOL_SOCKET, SO_TXTIME, 139 &so_txtime_val_read, &vallen)) 140 error(1, errno, "getsockopt txtime"); 141 142 if (vallen != sizeof(so_txtime_val) || 143 memcmp(&so_txtime_val, &so_txtime_val_read, vallen)) 144 error(1, 0, "getsockopt txtime: mismatch"); 145 } 146 147 static int setup_tx(struct sockaddr *addr, socklen_t alen) 148 { 149 int fd; 150 151 fd = socket(addr->sa_family, SOCK_DGRAM, 0); 152 if (fd == -1) 153 error(1, errno, "socket t"); 154 155 if (connect(fd, addr, alen)) 156 error(1, errno, "connect"); 157 158 setsockopt_txtime(fd); 159 160 return fd; 161 } 162 163 static int setup_rx(struct sockaddr *addr, socklen_t alen) 164 { 165 struct timeval tv = { .tv_usec = 100 * 1000 }; 166 int fd; 167 168 fd = socket(addr->sa_family, SOCK_DGRAM, 0); 169 if (fd == -1) 170 error(1, errno, "socket r"); 171 172 if (bind(fd, addr, alen)) 173 error(1, errno, "bind"); 174 175 if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) 176 error(1, errno, "setsockopt rcv timeout"); 177 178 return fd; 179 } 180 181 static void do_test(struct sockaddr *addr, socklen_t alen) 182 { 183 int fdt, fdr, i; 184 185 fprintf(stderr, "\nSO_TXTIME ipv%c clock %s\n", 186 addr->sa_family == PF_INET ? '4' : '6', 187 cfg_clockid == CLOCK_TAI ? "tai" : "monotonic"); 188 189 fdt = setup_tx(addr, alen); 190 fdr = setup_rx(addr, alen); 191 192 glob_tstart = gettime_ns(); 193 194 for (i = 0; i < cfg_num_pkt; i++) 195 do_send_one(fdt, &cfg_in[i]); 196 for (i = 0; i < cfg_num_pkt; i++) 197 do_recv_one(fdr, &cfg_out[i]); 198 199 do_recv_verify_empty(fdr); 200 201 if (close(fdr)) 202 error(1, errno, "close r"); 203 if (close(fdt)) 204 error(1, errno, "close t"); 205 } 206 207 static int parse_io(const char *optarg, struct timed_send *array) 208 { 209 char *arg, *tok; 210 int aoff = 0; 211 212 arg = strdup(optarg); 213 if (!arg) 214 error(1, errno, "strdup"); 215 216 while ((tok = strtok(arg, ","))) { 217 arg = NULL; /* only pass non-zero on first call */ 218 219 if (aoff / 2 == MAX_NUM_PKT) 220 error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT); 221 222 if (aoff & 1) { /* parse delay */ 223 array->delay_us = strtol(tok, NULL, 0) * 1000; 224 array++; 225 } else { /* parse character */ 226 array->data = tok[0]; 227 } 228 229 aoff++; 230 } 231 232 free(arg); 233 234 return aoff / 2; 235 } 236 237 static void parse_opts(int argc, char **argv) 238 { 239 int c, ilen, olen; 240 241 while ((c = getopt(argc, argv, "46c:")) != -1) { 242 switch (c) { 243 case '4': 244 cfg_do_ipv4 = true; 245 break; 246 case '6': 247 cfg_do_ipv6 = true; 248 break; 249 case 'c': 250 if (!strcmp(optarg, "tai")) 251 cfg_clockid = CLOCK_TAI; 252 else if (!strcmp(optarg, "monotonic") || 253 !strcmp(optarg, "mono")) 254 cfg_clockid = CLOCK_MONOTONIC; 255 else 256 error(1, 0, "unknown clock id %s", optarg); 257 break; 258 default: 259 error(1, 0, "parse error at %d", optind); 260 } 261 } 262 263 if (argc - optind != 2) 264 error(1, 0, "Usage: %s [-46] -c <clock> <in> <out>", argv[0]); 265 266 ilen = parse_io(argv[optind], cfg_in); 267 olen = parse_io(argv[optind + 1], cfg_out); 268 if (ilen != olen) 269 error(1, 0, "i/o streams len mismatch (%d, %d)\n", ilen, olen); 270 cfg_num_pkt = ilen; 271 } 272 273 int main(int argc, char **argv) 274 { 275 parse_opts(argc, argv); 276 277 if (cfg_do_ipv6) { 278 struct sockaddr_in6 addr6 = {0}; 279 280 addr6.sin6_family = AF_INET6; 281 addr6.sin6_port = htons(cfg_port); 282 addr6.sin6_addr = in6addr_loopback; 283 do_test((void *)&addr6, sizeof(addr6)); 284 } 285 286 if (cfg_do_ipv4) { 287 struct sockaddr_in addr4 = {0}; 288 289 addr4.sin_family = AF_INET; 290 addr4.sin_port = htons(cfg_port); 291 addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 292 do_test((void *)&addr4, sizeof(addr4)); 293 } 294 295 return 0; 296 } 297