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: stable/11/tools/regression/sockets/udp_pingpong/udp_pingpong.c 312296 2017-01-16 17:46:38Z sobomax $");
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
69struct trip_ts {
70    struct timespec sent;
71    struct timespec recvd;
72};
73
74struct test_pkt {
75    int pnum;
76    struct trip_ts tss[2];
77    int lost;
78    unsigned char data[PKT_SIZE];
79};
80
81struct 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
97struct 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
140static const struct timespec zero_ts;
141/* 0.01s, should be more than enough for the loopback communication  */
142static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};
143
144enum 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
148static clockid_t
149get_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
165static int
166get_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
186static size_t
187get_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
205static void
206setup_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
252static void
253setup_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
288static char *
289inet_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
297static void
298setup_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
335static void
336teardown_udp(struct test_ctx *tcp)
337{
338
339    close(tcp->fds[0]);
340    close(tcp->fds[1]);
341}
342
343static void
344send_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
364static void
365hdr_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
410static void
411recv_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
443static void
444recv_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
460static int
461recv_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
498static void
499test_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
514static void
515test_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
535static void
536calc_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
545static void
546test_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
619int
620main(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