1 /*-
2  * Copyright (c) 2017 Maksym Sobolyev <sobomax@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 /*
28  * The test that setups two processes A and B and make A sending
29  * B UDP packet(s) and B send it back. The time of sending is recorded
30  * in the payload and time of the arrival is either determined by
31  * reading clock after recv() completes or using kernel-supplied
32  * via recvmsg(). End-to-end time t(A->B->A) is then calculated
33  * and compared against time for both t(A->B) + t(B->A) to make
34  * sure it makes sense.
35  */
36 
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
39 
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <sys/wait.h>
43 #include <sys/time.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 #include <err.h>
47 #include <poll.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <strings.h>
52 #include <time.h>
53 #include <unistd.h>
54 
55 #define	NPKTS		1000
56 #define	PKT_SIZE	128
57 /* Timeout to receive pong on the side A, 100ms */
58 #define SRECV_TIMEOUT	(1 * 100)
59 /*
60  * Timeout to receive ping on the side B. 4x as large as on the side A,
61  * so that in the case of packet loss the side A will have a chance to
62  * realize that and send few more before B bails out.
63  */
64 #define RRECV_TIMEOUT	(SRECV_TIMEOUT * 4)
65 #define MIN_NRECV	((NPKTS * 99) / 100) /* 99% */
66 
67 //#define	SIMULATE_PLOSS
68 
69 struct trip_ts {
70     struct timespec sent;
71     struct timespec recvd;
72 };
73 
74 struct test_pkt {
75     int pnum;
76     struct trip_ts tss[2];
77     int lost;
78     unsigned char data[PKT_SIZE];
79 };
80 
81 struct test_ctx {
82     const char *name;
83     int fds[2];
84     struct pollfd pfds[2];
85     union {
86         struct sockaddr_in v4;
87         struct sockaddr_in6 v6;
88     } sin[2];
89     struct test_pkt test_pkts[NPKTS];
90     int nsent;
91     int nrecvd;
92     clockid_t clock;
93     int use_recvmsg;
94     int ts_type;
95 };
96 
97 struct rtt {
98     struct timespec a2b;
99     struct timespec b2a;
100     struct timespec e2e;
101     struct timespec a2b_b2a;
102 };
103 
104 #define SEC(x)		((x)->tv_sec)
105 #define NSEC(x)		((x)->tv_nsec)
106 #define NSEC_MAX	1000000000L
107 #define NSEC_IN_USEC	1000L
108 
109 #define timespecsub2(r, v, u)                                      \
110     do {                                                           \
111         SEC(r) = SEC(v) - SEC(u);                                  \
112         NSEC(r) = NSEC(v) - NSEC(u);                               \
113         if (NSEC(r) < 0 && (SEC(r) > 0 || NSEC(r) <= -NSEC_MAX)) { \
114             SEC(r)--;                                              \
115             NSEC(r) += NSEC_MAX;                                   \
116         }                                                          \
117     } while (0);
118 
119 #define timespecadd2(r, v, u)                                      \
120     do {                                                           \
121         SEC(r) = SEC(v) + SEC(u);                                  \
122         NSEC(r) = NSEC(v) + NSEC(u);                               \
123         if (NSEC(r) >= NSEC_MAX) {                                 \
124             SEC(r)++;                                              \
125             NSEC(r) -= NSEC_MAX;                                   \
126         }                                                          \
127     } while (0);
128 
129 #define timespeccmp(t, c, u)                                       \
130     ((SEC(t) == SEC(u)) ?                                          \
131       (NSEC(t) c NSEC(u)) :                                        \
132       (SEC(t) c SEC(u)))
133 
134 #define timeval2timespec(tv, ts)                                   \
135     do {                                                           \
136         SEC(ts) = (tv)->tv_sec;                                    \
137         NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC;                   \
138     } while (0);
139 
140 static const struct timespec zero_ts;
141 /* 0.01s, should be more than enough for the loopback communication  */
142 static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};
143 
144 enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1,
145   TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME,
146   TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC};
147 
148 static clockid_t
149 get_clock_type(struct test_ctx *tcp)
150 {
151     switch (tcp->ts_type) {
152     case TT_TIMESTAMP:
153     case TT_BINTIME:
154     case TT_REALTIME_MICRO:
155     case TT_TS_BINTIME:
156     case TT_REALTIME:
157         return (CLOCK_REALTIME);
158 
159     case TT_MONOTONIC:
160         return (CLOCK_MONOTONIC);
161     }
162     abort();
163 }
164 
165 static int
166 get_scm_type(struct test_ctx *tcp)
167 {
168     switch (tcp->ts_type) {
169     case TT_TIMESTAMP:
170     case TT_REALTIME_MICRO:
171         return (SCM_TIMESTAMP);
172 
173     case TT_BINTIME:
174     case TT_TS_BINTIME:
175         return (SCM_BINTIME);
176 
177     case TT_REALTIME:
178         return (SCM_REALTIME);
179 
180     case TT_MONOTONIC:
181         return (SCM_MONOTONIC);
182     }
183     abort();
184 }
185 
186 static size_t
187 get_scm_size(struct test_ctx *tcp)
188 {
189     switch (tcp->ts_type) {
190     case TT_TIMESTAMP:
191     case TT_REALTIME_MICRO:
192         return (sizeof(struct timeval));
193 
194     case TT_BINTIME:
195     case TT_TS_BINTIME:
196         return (sizeof(struct bintime));
197 
198     case TT_REALTIME:
199     case TT_MONOTONIC:
200         return (sizeof(struct timespec));
201     }
202     abort();
203 }
204 
205 static void
206 setup_ts_sockopt(struct test_ctx *tcp, int fd)
207 {
208     int rval, oname1, oname2, sval1, sval2;
209 
210     oname1 = SO_TIMESTAMP;
211     oname2 = -1;
212     sval2 = -1;
213 
214     switch (tcp->ts_type) {
215     case TT_REALTIME_MICRO:
216     case TT_TS_BINTIME:
217     case TT_REALTIME:
218     case TT_MONOTONIC:
219         oname2 = SO_TS_CLOCK;
220         sval2 = tcp->ts_type;
221         break;
222 
223     case TT_TIMESTAMP:
224         break;
225 
226     case TT_BINTIME:
227         oname1 = SO_BINTIME;
228         break;
229 
230     default:
231         abort();
232     }
233 
234     sval1 = 1;
235     rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1,
236       sizeof(sval1));
237     if (rval != 0) {
238         err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name,
239           fd, oname1);
240     }
241     if (oname2 == -1)
242         return;
243     rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2,
244       sizeof(sval2));
245     if (rval != 0) {
246         err(1, "%s: setup_udp: setsockopt(%d, %d, %d)",
247           tcp->name, fd, oname2, sval2);
248     }
249 }
250 
251 
252 static void
253 setup_udp(struct test_ctx *tcp)
254 {
255     int i;
256     socklen_t sin_len, af_len;
257 
258     af_len = sizeof(tcp->sin[0].v4);
259     for (i = 0; i < 2; i++) {
260         tcp->sin[i].v4.sin_len = af_len;
261         tcp->sin[i].v4.sin_family = AF_INET;
262         tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
263         tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0);
264         if (tcp->fds[i] < 0)
265             err(1, "%s: setup_udp: socket", tcp->name);
266         if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
267             err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
268               inet_ntoa(tcp->sin[i].v4.sin_addr), 0);
269         sin_len = af_len;
270         if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
271             err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
272         if (tcp->use_recvmsg != 0) {
273             setup_ts_sockopt(tcp, tcp->fds[i]);
274         }
275 
276         tcp->pfds[i].fd = tcp->fds[i];
277         tcp->pfds[i].events = POLLIN;
278     }
279 
280     if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
281         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
282           inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port));
283     if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
284         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
285           inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port));
286 }
287 
288 static char *
289 inet_ntoa6(const void *sin6_addr)
290 {
291     static char straddr[INET6_ADDRSTRLEN];
292 
293     inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr));
294     return (straddr);
295 }
296 
297 static void
298 setup_udp6(struct test_ctx *tcp)
299 {
300     int i;
301     socklen_t sin_len, af_len;
302 
303     af_len = sizeof(tcp->sin[0].v6);
304     for (i = 0; i < 2; i++) {
305         tcp->sin[i].v6.sin6_len = af_len;
306         tcp->sin[i].v6.sin6_family = AF_INET6;
307         tcp->sin[i].v6.sin6_addr = in6addr_loopback;
308         tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0);
309         if (tcp->fds[i] < 0)
310             err(1, "%s: setup_udp: socket", tcp->name);
311         if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
312             err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
313               inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0);
314         sin_len = af_len;
315         if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
316             err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
317         if (tcp->use_recvmsg != 0) {
318             setup_ts_sockopt(tcp, tcp->fds[i]);
319         }
320 
321         tcp->pfds[i].fd = tcp->fds[i];
322         tcp->pfds[i].events = POLLIN;
323     }
324 
325     if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
326         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
327           inet_ntoa6(&tcp->sin[1].v6.sin6_addr),
328           ntohs(tcp->sin[1].v6.sin6_port));
329     if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
330         err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
331           inet_ntoa6(&tcp->sin[0].v6.sin6_addr),
332           ntohs(tcp->sin[0].v6.sin6_port));
333 }
334 
335 static void
336 teardown_udp(struct test_ctx *tcp)
337 {
338 
339     close(tcp->fds[0]);
340     close(tcp->fds[1]);
341 }
342 
343 static void
344 send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face)
345 {
346     ssize_t r;
347     size_t slen;
348 
349     slen = sizeof(tcp->test_pkts[pnum]);
350     clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent);
351     r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0);
352     if (r < 0) {
353         err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]);
354     }
355     if (r < (ssize_t)slen) {
356         errx(1, "%s: %s: send(%d): short send", tcp->name, face,
357           tcp->fds[fdidx]);
358     }
359     tcp->nsent += 1;
360 }
361 
362 #define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data)
363 
364 static void
365 hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp)
366 {
367     int scm_type;
368     size_t scm_size;
369     union {
370         struct timespec ts;
371         struct bintime bt;
372         struct timeval tv;
373     } tdata;
374     struct cmsghdr *cmsg;
375 
376     scm_type = get_scm_type(tcp);
377     scm_size = get_scm_size(tcp);
378     for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL;
379       cmsg = CMSG_NXTHDR(mhp, cmsg)) {
380         if ((cmsg->cmsg_level == SOL_SOCKET) &&
381           (cmsg->cmsg_type == scm_type)) {
382             memcpy(&tdata, CMSG_DATA(cmsg), scm_size);
383             break;
384         }
385     }
386     if (cmsg == NULL) {
387         abort();
388     }
389     switch (tcp->ts_type) {
390     case TT_REALTIME:
391     case TT_MONOTONIC:
392         *tp = tdata.ts;
393         break;
394 
395     case TT_TIMESTAMP:
396     case TT_REALTIME_MICRO:
397         timeval2timespec(&tdata.tv, tp);
398         break;
399 
400     case TT_BINTIME:
401     case TT_TS_BINTIME:
402         bintime2timespec(&tdata.bt, tp);
403         break;
404 
405     default:
406         abort();
407     }
408 }
409 
410 static void
411 recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
412   size_t rlen, struct timespec *tp)
413 {
414     /* We use a union to make sure hdr is aligned */
415     union {
416         struct cmsghdr hdr;
417         unsigned char buf[CMSG_SPACE(1024)];
418     } cmsgbuf;
419     struct msghdr msg;
420     struct iovec iov;
421     ssize_t rval;
422 
423     memset(&msg, '\0', sizeof(msg));
424     iov.iov_base = buf;
425     iov.iov_len = rlen;
426     msg.msg_iov = &iov;
427     msg.msg_iovlen = 1;
428     msg.msg_control = cmsgbuf.buf;
429     msg.msg_controllen = sizeof(cmsgbuf.buf);
430 
431     rval = recvmsg(tcp->fds[fdidx], &msg, 0);
432     if (rval < 0) {
433         err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]);
434     }
435     if (rval < (ssize_t)rlen) {
436         errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face,
437           tcp->fds[fdidx]);
438     }
439 
440     hdr_extract_ts(tcp, &msg, tp);
441 }
442 
443 static void
444 recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
445   size_t rlen, struct timespec *tp)
446 {
447     ssize_t rval;
448 
449     rval = recv(tcp->fds[fdidx], buf, rlen, 0);
450     clock_gettime(get_clock_type(tcp), tp);
451     if (rval < 0) {
452         err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]);
453     }
454     if (rval < (ssize_t)rlen) {
455         errx(1, "%s: %s: recv(%d): short recv", tcp->name, face,
456             tcp->fds[fdidx]);
457     }
458 }
459 
460 static int
461 recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout)
462 {
463     int pr;
464     struct test_pkt recv_buf;
465     size_t rlen;
466 
467     pr = poll(&tcp->pfds[fdidx], 1, tout);
468     if (pr < 0) {
469         err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]);
470     }
471     if (pr == 0) {
472         return (-1);
473     }
474     if(tcp->pfds[fdidx].revents != POLLIN) {
475         errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face,
476           tcp->fds[fdidx]);
477     }
478     rlen = sizeof(recv_buf);
479     if (tcp->use_recvmsg == 0) {
480         recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen,
481           &recv_buf.tss[fdidx].recvd);
482     } else {
483         recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen,
484           &recv_buf.tss[fdidx].recvd);
485     }
486     if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS ||
487       memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) {
488         errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name,
489           face, tcp->fds[fdidx], recv_buf.pnum);
490     }
491     tcp->nrecvd += 1;
492     memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss,
493       sizeof(recv_buf.tss));
494     tcp->test_pkts[recv_buf.pnum].lost = 0;
495     return (recv_buf.pnum);
496 }
497 
498 static void
499 test_server(struct test_ctx *tcp)
500 {
501     int i, j;
502 
503     for (i = 0; i < NPKTS; i++) {
504         send_pkt(tcp, i, 0, __FUNCTION__);
505         j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT);
506         if (j < 0) {
507             warnx("packet %d is lost", i);
508             /* timeout */
509             continue;
510         }
511     }
512 }
513 
514 static void
515 test_client(struct test_ctx *tcp)
516 {
517     int i, j;
518 
519     for (i = 0; i < NPKTS; i++) {
520         j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT);
521         if (j < 0) {
522             /* timeout */
523             return;
524         }
525 #if defined(SIMULATE_PLOSS)
526         if ((i % 99) == 0) {
527             warnx("dropping packet %d", i);
528             continue;
529         }
530 #endif
531         send_pkt(tcp, j, 1, __FUNCTION__);
532     }
533 }
534 
535 static void
536 calc_rtt(struct test_pkt *tpp, struct rtt *rttp)
537 {
538 
539     timespecsub2(&rttp->a2b, &tpp->tss[1].recvd, &tpp->tss[0].sent);
540     timespecsub2(&rttp->b2a, &tpp->tss[0].recvd, &tpp->tss[1].sent);
541     timespecadd2(&rttp->a2b_b2a, &rttp->a2b, &rttp->b2a);
542     timespecsub2(&rttp->e2e, &tpp->tss[0].recvd, &tpp->tss[0].sent);
543 }
544 
545 static void
546 test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name)
547 {
548     struct test_ctx test_ctx;
549     pid_t pid, cpid;
550     int i, j, status;
551 
552     printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6");
553     fflush(stdout);
554     bzero(&test_ctx, sizeof(test_ctx));
555     test_ctx.name = name;
556     test_ctx.use_recvmsg = use_recvmsg;
557     test_ctx.ts_type = ts_type;
558     if (use_ipv6 == 0) {
559         setup_udp(&test_ctx);
560     } else {
561         setup_udp6(&test_ctx);
562     }
563     for (i = 0; i < NPKTS; i++) {
564         test_ctx.test_pkts[i].pnum = i;
565         test_ctx.test_pkts[i].lost = 1;
566         for (j = 0; j < PKT_SIZE; j++) {
567             test_ctx.test_pkts[i].data[j] = (unsigned char)random();
568         }
569     }
570     cpid = fork();
571     if (cpid < 0) {
572         err(1, "%s: fork()", test_ctx.name);
573     }
574     if (cpid == 0) {
575         test_client(&test_ctx);
576         exit(0);
577     }
578     test_server(&test_ctx);
579     pid = waitpid(cpid, &status, 0);
580     if (pid == (pid_t)-1) {
581         err(1, "%s: waitpid(%d)", test_ctx.name, cpid);
582     }
583 
584     if (WIFEXITED(status)) {
585         if (WEXITSTATUS(status) != EXIT_SUCCESS) {
586             errx(1, "client exit status is %d",
587               WEXITSTATUS(status));
588         }
589     } else {
590         if (WIFSIGNALED(status))
591             errx(1, "abnormal termination of client, signal %d%s",
592               WTERMSIG(status), WCOREDUMP(status) ?
593               " (core file generated)" : "");
594         else
595             errx(1, "termination of client, unknown status");
596     }
597     if (test_ctx.nrecvd < MIN_NRECV) {
598         errx(1, "packet loss is too high %d received out of %d, min %d",
599           test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV);
600     }
601     for (i = 0; i < NPKTS; i++) {
602         struct rtt rtt;
603         if (test_ctx.test_pkts[i].lost != 0) {
604             continue;
605         }
606         calc_rtt(&test_ctx.test_pkts[i], &rtt);
607         if (!timespeccmp(&rtt.e2e, >, &rtt.a2b_b2a))
608             errx(1, "end-to-end trip time is too small");
609         if (!timespeccmp(&rtt.e2e, <, &max_ts))
610             errx(1, "end-to-end trip time is too large");
611         if (!timespeccmp(&rtt.a2b, >, &zero_ts))
612             errx(1, "A2B trip time is not positive");
613         if (!timespeccmp(&rtt.b2a, >, &zero_ts))
614             errx(1, "B2A trip time is not positive");
615     }
616     teardown_udp(&test_ctx);
617 }
618 
619 int
620 main(void)
621 {
622     int i;
623 
624     srandomdev();
625 
626     for (i = 0; i < 2; i++) {
627         test_run(0, i, 0, "send()/recv()");
628         printf("OK\n");
629         test_run(TT_TIMESTAMP, i, 1,
630           "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)");
631         printf("OK\n");
632         if (i == 0) {
633             test_run(TT_BINTIME, i, 1,
634               "send()/recvmsg(), setsockopt(SO_BINTIME, 1)");
635             printf("OK\n");
636         }
637         test_run(TT_REALTIME_MICRO, i, 1,
638           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");
639         printf("OK\n");
640         test_run(TT_TS_BINTIME, i, 1,
641           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");
642         printf("OK\n");
643         test_run(TT_REALTIME, i, 1,
644           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");
645         printf("OK\n");
646         test_run(TT_MONOTONIC, i, 1,
647           "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");
648         printf("OK\n");
649     }
650     exit(0);
651 }
652