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$"); 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 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 140 static const struct timespec zero_ts; 141 /* 0.01s, should be more than enough for the loopback communication */ 142 static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)}; 143 144 enum 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 148 static clockid_t 149 get_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 165 static int 166 get_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 186 static size_t 187 get_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 205 static void 206 setup_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 252 static void 253 setup_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 288 static char * 289 inet_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 297 static void 298 setup_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 335 static void 336 teardown_udp(struct test_ctx *tcp) 337 { 338 339 close(tcp->fds[0]); 340 close(tcp->fds[1]); 341 } 342 343 static void 344 send_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 364 static void 365 hdr_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 410 static void 411 recv_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 443 static void 444 recv_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 460 static int 461 recv_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 498 static void 499 test_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 514 static void 515 test_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 535 static void 536 calc_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 545 static void 546 test_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 619 int 620 main(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