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