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
get_clock_type(struct test_ctx * tcp)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
get_scm_type(struct test_ctx * tcp)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
get_scm_size(struct test_ctx * tcp)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
setup_ts_sockopt(struct test_ctx * tcp,int fd)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
setup_udp(struct test_ctx * tcp)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 *
inet_ntoa6(const void * sin6_addr)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
setup_udp6(struct test_ctx * tcp)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
teardown_udp(struct test_ctx * tcp)309 teardown_udp(struct test_ctx *tcp)
310 {
311
312 close(tcp->fds[0]);
313 close(tcp->fds[1]);
314 }
315
316 static void
send_pkt(struct test_ctx * tcp,int pnum,int fdidx,const char * face)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
hdr_extract_ts(struct test_ctx * tcp,struct msghdr * mhp,struct timespec * tp)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
recv_pkt_recvmsg(struct test_ctx * tcp,int fdidx,const char * face,void * buf,size_t rlen,struct timespec * tp)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
recv_pkt_recv(struct test_ctx * tcp,int fdidx,const char * face,void * buf,size_t rlen,struct timespec * tp)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
recv_pkt(struct test_ctx * tcp,int fdidx,const char * face,int tout)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
test_server(struct test_ctx * tcp)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
test_client(struct test_ctx * tcp)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
calc_rtt(struct test_pkt * tpp,struct rtt * rttp)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
test_run(int ts_type,int use_ipv6,int use_recvmsg,const char * name)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
main(void)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