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