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 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 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 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 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 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 * 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 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 308 teardown_udp(struct test_ctx *tcp) 309 { 310 311 close(tcp->fds[0]); 312 close(tcp->fds[1]); 313 } 314 315 static void 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 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 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 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 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 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 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 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 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 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