1 // SPDX-License-Identifier: GPL-2.0
3 * Test the SO_TXTIME API
5 * Takes a stream of { payload, delivery time }[], to be sent across two
6 * processes. Start this program on two separate network namespaces or
7 * connected hosts, one instance in transmit mode and the other in receive
8 * mode using the '-r' option. Receiver will compare arrival timestamps to
9 * the expected stream. Sender will read transmit timestamps from the error
10 * queue. The streams can differ due to out-of-order delivery and drops.
15 #include <arpa/inet.h>
19 #include <linux/net_tstamp.h>
20 #include <linux/errqueue.h>
21 #include <linux/if_ether.h>
22 #include <linux/ipv6.h>
23 #include <linux/udp.h>
28 #include <sys/socket.h>
31 #include <sys/types.h>
36 static int cfg_clockid
= CLOCK_TAI
;
37 static uint16_t cfg_port
= 8000;
38 static int cfg_variance_us
= 4000;
39 static uint64_t cfg_start_time_ns
;
43 static uint64_t glob_tstart
;
44 static uint64_t tdeliver_max
;
46 /* encode one timed transmission (of a 1B payload) */
53 static struct timed_send cfg_buf
[MAX_NUM_PKT
];
54 static int cfg_num_pkt
;
56 static int cfg_errq_level
;
57 static int cfg_errq_type
;
59 static struct sockaddr_storage cfg_dst_addr
;
60 static struct sockaddr_storage cfg_src_addr
;
61 static socklen_t cfg_alen
;
63 static uint64_t gettime_ns(clockid_t clock
)
67 if (clock_gettime(clock
, &ts
))
68 error(1, errno
, "gettime");
70 return ts
.tv_sec
* (1000ULL * 1000 * 1000) + ts
.tv_nsec
;
73 static void do_send_one(int fdt
, struct timed_send
*ts
)
75 char control
[CMSG_SPACE(sizeof(uint64_t))];
76 struct msghdr msg
= {0};
77 struct iovec iov
= {0};
82 iov
.iov_base
= &ts
->data
;
87 msg
.msg_name
= (struct sockaddr
*)&cfg_dst_addr
;
88 msg
.msg_namelen
= cfg_alen
;
90 if (ts
->delay_us
>= 0) {
91 memset(control
, 0, sizeof(control
));
92 msg
.msg_control
= &control
;
93 msg
.msg_controllen
= sizeof(control
);
95 tdeliver
= glob_tstart
+ ts
->delay_us
* 1000;
96 tdeliver_max
= tdeliver_max
> tdeliver
?
97 tdeliver_max
: tdeliver
;
99 cm
= CMSG_FIRSTHDR(&msg
);
100 cm
->cmsg_level
= SOL_SOCKET
;
101 cm
->cmsg_type
= SCM_TXTIME
;
102 cm
->cmsg_len
= CMSG_LEN(sizeof(tdeliver
));
103 memcpy(CMSG_DATA(cm
), &tdeliver
, sizeof(tdeliver
));
106 ret
= sendmsg(fdt
, &msg
, 0);
108 error(1, errno
, "write");
110 error(1, 0, "write: 0B");
114 static void do_recv_one(int fdr
, struct timed_send
*ts
)
116 int64_t tstop
, texpect
;
120 ret
= recv(fdr
, rbuf
, sizeof(rbuf
), 0);
121 if (ret
== -1 && errno
== EAGAIN
)
122 error(1, EAGAIN
, "recv: timeout");
124 error(1, errno
, "read");
126 error(1, 0, "read: %dB", ret
);
128 tstop
= (gettime_ns(cfg_clockid
) - glob_tstart
) / 1000;
129 texpect
= ts
->delay_us
>= 0 ? ts
->delay_us
: 0;
131 fprintf(stderr
, "payload:%c delay:%lld expected:%lld (us)\n",
132 rbuf
[0], (long long)tstop
, (long long)texpect
);
134 if (rbuf
[0] != ts
->data
)
135 error(1, 0, "payload mismatch. expected %c", ts
->data
);
137 if (llabs(tstop
- texpect
) > cfg_variance_us
) {
138 fprintf(stderr
, "exceeds variance (%d us)\n", cfg_variance_us
);
139 if (!getenv("KSFT_MACHINE_SLOW"))
144 static void do_recv_verify_empty(int fdr
)
149 ret
= recv(fdr
, rbuf
, sizeof(rbuf
), 0);
150 if (ret
!= -1 || errno
!= EAGAIN
)
151 error(1, 0, "recv: not empty as expected (%d, %d)", ret
, errno
);
154 static int do_recv_errqueue_timeout(int fdt
)
156 char control
[CMSG_SPACE(sizeof(struct sock_extended_err
)) +
157 CMSG_SPACE(sizeof(struct sockaddr_in6
))] = {0};
158 char data
[sizeof(struct ethhdr
) + sizeof(struct ipv6hdr
) +
159 sizeof(struct udphdr
) + 1];
160 struct sock_extended_err
*err
;
161 int ret
, num_tstamp
= 0;
162 struct msghdr msg
= {0};
163 struct iovec iov
= {0};
168 iov
.iov_len
= sizeof(data
);
173 msg
.msg_control
= control
;
174 msg
.msg_controllen
= sizeof(control
);
179 ret
= recvmsg(fdt
, &msg
, MSG_ERRQUEUE
);
180 if (ret
== -1 && errno
== EAGAIN
)
183 error(1, errno
, "errqueue");
184 if (msg
.msg_flags
!= MSG_ERRQUEUE
)
185 error(1, 0, "errqueue: flags 0x%x\n", msg
.msg_flags
);
187 cm
= CMSG_FIRSTHDR(&msg
);
188 if (cm
->cmsg_level
!= cfg_errq_level
||
189 cm
->cmsg_type
!= cfg_errq_type
)
190 error(1, 0, "errqueue: type 0x%x.0x%x\n",
191 cm
->cmsg_level
, cm
->cmsg_type
);
193 err
= (struct sock_extended_err
*)CMSG_DATA(cm
);
194 if (err
->ee_origin
!= SO_EE_ORIGIN_TXTIME
)
195 error(1, 0, "errqueue: origin 0x%x\n", err
->ee_origin
);
197 switch (err
->ee_errno
) {
199 if (err
->ee_code
!= SO_EE_CODE_TXTIME_MISSED
)
200 error(1, 0, "errqueue: unknown ECANCELED %u\n",
202 reason
= "missed txtime";
205 if (err
->ee_code
!= SO_EE_CODE_TXTIME_INVALID_PARAM
)
206 error(1, 0, "errqueue: unknown EINVAL %u\n",
208 reason
= "invalid txtime";
211 error(1, 0, "errqueue: errno %u code %u\n",
212 err
->ee_errno
, err
->ee_code
);
215 tstamp
= ((int64_t) err
->ee_data
) << 32 | err
->ee_info
;
216 tstamp
-= (int64_t) glob_tstart
;
217 tstamp
/= 1000 * 1000;
218 fprintf(stderr
, "send: pkt %c at %" PRId64
"ms dropped: %s\n",
219 data
[ret
- 1], tstamp
, reason
);
222 msg
.msg_controllen
= sizeof(control
);
229 static void recv_errqueue_msgs(int fdt
)
231 struct pollfd pfd
= { .fd
= fdt
, .events
= POLLERR
};
232 const int timeout_ms
= 10;
233 int ret
, num_tstamp
= 0;
236 ret
= poll(&pfd
, 1, timeout_ms
);
238 error(1, errno
, "poll");
240 if (ret
&& (pfd
.revents
& POLLERR
))
241 num_tstamp
+= do_recv_errqueue_timeout(fdt
);
243 if (num_tstamp
== cfg_num_pkt
)
246 } while (gettime_ns(cfg_clockid
) < tdeliver_max
);
249 static void start_time_wait(void)
254 if (!cfg_start_time_ns
)
257 now
= gettime_ns(CLOCK_REALTIME
);
258 if (cfg_start_time_ns
< now
)
261 err
= usleep((cfg_start_time_ns
- now
) / 1000);
263 error(1, errno
, "usleep");
266 static void setsockopt_txtime(int fd
)
268 struct sock_txtime so_txtime_val
= { .clockid
= cfg_clockid
};
269 struct sock_txtime so_txtime_val_read
= { 0 };
270 socklen_t vallen
= sizeof(so_txtime_val
);
272 so_txtime_val
.flags
= SOF_TXTIME_REPORT_ERRORS
;
274 if (setsockopt(fd
, SOL_SOCKET
, SO_TXTIME
,
275 &so_txtime_val
, sizeof(so_txtime_val
)))
276 error(1, errno
, "setsockopt txtime");
278 if (getsockopt(fd
, SOL_SOCKET
, SO_TXTIME
,
279 &so_txtime_val_read
, &vallen
))
280 error(1, errno
, "getsockopt txtime");
282 if (vallen
!= sizeof(so_txtime_val
) ||
283 memcmp(&so_txtime_val
, &so_txtime_val_read
, vallen
))
284 error(1, 0, "getsockopt txtime: mismatch");
287 static int setup_tx(struct sockaddr
*addr
, socklen_t alen
)
291 fd
= socket(addr
->sa_family
, SOCK_DGRAM
, 0);
293 error(1, errno
, "socket t");
295 if (connect(fd
, addr
, alen
))
296 error(1, errno
, "connect");
298 setsockopt_txtime(fd
);
301 setsockopt(fd
, SOL_SOCKET
, SO_MARK
, &cfg_mark
, sizeof(cfg_mark
)))
302 error(1, errno
, "setsockopt mark");
307 static int setup_rx(struct sockaddr
*addr
, socklen_t alen
)
309 struct timeval tv
= { .tv_usec
= 100 * 1000 };
312 fd
= socket(addr
->sa_family
, SOCK_DGRAM
, 0);
314 error(1, errno
, "socket r");
316 if (bind(fd
, addr
, alen
))
317 error(1, errno
, "bind");
319 if (setsockopt(fd
, SOL_SOCKET
, SO_RCVTIMEO
, &tv
, sizeof(tv
)))
320 error(1, errno
, "setsockopt rcv timeout");
325 static void do_test_tx(struct sockaddr
*addr
, socklen_t alen
)
329 fprintf(stderr
, "\nSO_TXTIME ipv%c clock %s\n",
330 addr
->sa_family
== PF_INET
? '4' : '6',
331 cfg_clockid
== CLOCK_TAI
? "tai" : "monotonic");
333 fdt
= setup_tx(addr
, alen
);
336 glob_tstart
= gettime_ns(cfg_clockid
);
338 for (i
= 0; i
< cfg_num_pkt
; i
++)
339 do_send_one(fdt
, &cfg_buf
[i
]);
341 recv_errqueue_msgs(fdt
);
344 error(1, errno
, "close t");
347 static void do_test_rx(struct sockaddr
*addr
, socklen_t alen
)
351 fdr
= setup_rx(addr
, alen
);
354 glob_tstart
= gettime_ns(cfg_clockid
);
356 for (i
= 0; i
< cfg_num_pkt
; i
++)
357 do_recv_one(fdr
, &cfg_buf
[i
]);
359 do_recv_verify_empty(fdr
);
362 error(1, errno
, "close r");
365 static void setup_sockaddr(int domain
, const char *str_addr
,
366 struct sockaddr_storage
*sockaddr
)
368 struct sockaddr_in6
*addr6
= (void *) sockaddr
;
369 struct sockaddr_in
*addr4
= (void *) sockaddr
;
373 memset(addr4
, 0, sizeof(*addr4
));
374 addr4
->sin_family
= AF_INET
;
375 addr4
->sin_port
= htons(cfg_port
);
377 inet_pton(AF_INET
, str_addr
, &(addr4
->sin_addr
)) != 1)
378 error(1, 0, "ipv4 parse error: %s", str_addr
);
381 memset(addr6
, 0, sizeof(*addr6
));
382 addr6
->sin6_family
= AF_INET6
;
383 addr6
->sin6_port
= htons(cfg_port
);
385 inet_pton(AF_INET6
, str_addr
, &(addr6
->sin6_addr
)) != 1)
386 error(1, 0, "ipv6 parse error: %s", str_addr
);
391 static int parse_io(const char *optarg
, struct timed_send
*array
)
396 arg
= strdup(optarg
);
398 error(1, errno
, "strdup");
400 while ((tok
= strtok(arg
, ","))) {
401 arg
= NULL
; /* only pass non-zero on first call */
403 if (aoff
/ 2 == MAX_NUM_PKT
)
404 error(1, 0, "exceeds max pkt count (%d)", MAX_NUM_PKT
);
406 if (aoff
& 1) { /* parse delay */
407 array
->delay_us
= strtol(tok
, NULL
, 0) * 1000;
409 } else { /* parse character */
410 array
->data
= tok
[0];
421 static void usage(const char *progname
)
423 fprintf(stderr
, "\nUsage: %s [options] <payload>\n"
427 " -c <clock> monotonic or tai (default)\n"
428 " -D <addr> destination IP address (server)\n"
429 " -S <addr> source IP address (client)\n"
431 " -t <nsec> start time (UTC nanoseconds)\n"
432 " -m <mark> socket mark\n"
438 static void parse_opts(int argc
, char **argv
)
440 char *daddr
= NULL
, *saddr
= NULL
;
441 int domain
= PF_UNSPEC
;
444 while ((c
= getopt(argc
, argv
, "46c:S:D:rt:m:")) != -1) {
447 if (domain
!= PF_UNSPEC
)
448 error(1, 0, "Pass one of -4 or -6");
450 cfg_alen
= sizeof(struct sockaddr_in
);
451 cfg_errq_level
= SOL_IP
;
452 cfg_errq_type
= IP_RECVERR
;
455 if (domain
!= PF_UNSPEC
)
456 error(1, 0, "Pass one of -4 or -6");
458 cfg_alen
= sizeof(struct sockaddr_in6
);
459 cfg_errq_level
= SOL_IPV6
;
460 cfg_errq_type
= IPV6_RECVERR
;
463 if (!strcmp(optarg
, "tai"))
464 cfg_clockid
= CLOCK_TAI
;
465 else if (!strcmp(optarg
, "monotonic") ||
466 !strcmp(optarg
, "mono"))
467 cfg_clockid
= CLOCK_MONOTONIC
;
469 error(1, 0, "unknown clock id %s", optarg
);
481 cfg_start_time_ns
= strtoll(optarg
, NULL
, 0);
484 cfg_mark
= strtol(optarg
, NULL
, 0);
491 if (argc
- optind
!= 1)
494 if (domain
== PF_UNSPEC
)
495 error(1, 0, "Pass one of -4 or -6");
497 error(1, 0, "-D <server addr> required\n");
498 if (!cfg_rx
&& !saddr
)
499 error(1, 0, "-S <client addr> required\n");
501 setup_sockaddr(domain
, daddr
, &cfg_dst_addr
);
502 setup_sockaddr(domain
, saddr
, &cfg_src_addr
);
504 cfg_num_pkt
= parse_io(argv
[optind
], cfg_buf
);
507 int main(int argc
, char **argv
)
509 parse_opts(argc
, argv
);
512 do_test_rx((void *)&cfg_dst_addr
, cfg_alen
);
514 do_test_tx((void *)&cfg_src_addr
, cfg_alen
);