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