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