2 * Copyright (c) 2017 Maksym Sobolyev <sobomax@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
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.
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
40 #include <sys/types.h>
41 #include <sys/socket.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
57 /* Timeout to receive pong on the side A, 100ms */
58 #define SRECV_TIMEOUT (1 * 100)
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.
64 #define RRECV_TIMEOUT (SRECV_TIMEOUT * 4)
65 #define MIN_NRECV ((NPKTS * 99) / 100) /* 99% */
67 //#define SIMULATE_PLOSS
71 struct timespec recvd;
76 struct trip_ts tss[2];
78 unsigned char data[PKT_SIZE];
84 struct pollfd pfds[2];
86 struct sockaddr_in v4;
87 struct sockaddr_in6 v6;
89 struct test_pkt test_pkts[NPKTS];
101 struct timespec a2b_b2a;
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
109 #define timespecsub2(r, v, u) \
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)) { \
115 NSEC(r) += NSEC_MAX; \
119 #define timespecadd2(r, v, u) \
121 SEC(r) = SEC(v) + SEC(u); \
122 NSEC(r) = NSEC(v) + NSEC(u); \
123 if (NSEC(r) >= NSEC_MAX) { \
125 NSEC(r) -= NSEC_MAX; \
129 #define timespeccmp(t, c, u) \
130 ((SEC(t) == SEC(u)) ? \
131 (NSEC(t) c NSEC(u)) : \
134 #define timeval2timespec(tv, ts) \
136 SEC(ts) = (tv)->tv_sec; \
137 NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC; \
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)};
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};
149 get_clock_type(struct test_ctx *tcp)
151 switch (tcp->ts_type) {
154 case TT_REALTIME_MICRO:
157 return (CLOCK_REALTIME);
160 return (CLOCK_MONOTONIC);
166 get_scm_type(struct test_ctx *tcp)
168 switch (tcp->ts_type) {
170 case TT_REALTIME_MICRO:
171 return (SCM_TIMESTAMP);
175 return (SCM_BINTIME);
178 return (SCM_REALTIME);
181 return (SCM_MONOTONIC);
187 get_scm_size(struct test_ctx *tcp)
189 switch (tcp->ts_type) {
191 case TT_REALTIME_MICRO:
192 return (sizeof(struct timeval));
196 return (sizeof(struct bintime));
200 return (sizeof(struct timespec));
206 setup_ts_sockopt(struct test_ctx *tcp, int fd)
208 int rval, oname1, oname2, sval1, sval2;
210 oname1 = SO_TIMESTAMP;
214 switch (tcp->ts_type) {
215 case TT_REALTIME_MICRO:
219 oname2 = SO_TS_CLOCK;
220 sval2 = tcp->ts_type;
235 rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1,
238 err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name,
243 rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2,
246 err(1, "%s: setup_udp: setsockopt(%d, %d, %d)",
247 tcp->name, fd, oname2, sval2);
253 setup_udp(struct test_ctx *tcp)
256 socklen_t sin_len, af_len;
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);
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);
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]);
276 tcp->pfds[i].fd = tcp->fds[i];
277 tcp->pfds[i].events = POLLIN;
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));
289 inet_ntoa6(const void *sin6_addr)
291 static char straddr[INET6_ADDRSTRLEN];
293 inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr));
298 setup_udp6(struct test_ctx *tcp)
301 socklen_t sin_len, af_len;
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);
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);
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]);
321 tcp->pfds[i].fd = tcp->fds[i];
322 tcp->pfds[i].events = POLLIN;
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));
336 teardown_udp(struct test_ctx *tcp)
344 send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face)
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);
353 err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]);
355 if (r < (ssize_t)slen) {
356 errx(1, "%s: %s: send(%d): short send", tcp->name, face,
362 #define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data)
365 hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp)
374 struct cmsghdr *cmsg;
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);
389 switch (tcp->ts_type) {
396 case TT_REALTIME_MICRO:
397 timeval2timespec(&tdata.tv, tp);
402 bintime2timespec(&tdata.bt, tp);
411 recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
412 size_t rlen, struct timespec *tp)
414 /* We use a union to make sure hdr is aligned */
417 unsigned char buf[CMSG_SPACE(1024)];
423 memset(&msg, '\0', sizeof(msg));
428 msg.msg_control = cmsgbuf.buf;
429 msg.msg_controllen = sizeof(cmsgbuf.buf);
431 rval = recvmsg(tcp->fds[fdidx], &msg, 0);
433 err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]);
435 if (rval < (ssize_t)rlen) {
436 errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face,
440 hdr_extract_ts(tcp, &msg, tp);
444 recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
445 size_t rlen, struct timespec *tp)
449 rval = recv(tcp->fds[fdidx], buf, rlen, 0);
450 clock_gettime(get_clock_type(tcp), tp);
452 err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]);
454 if (rval < (ssize_t)rlen) {
455 errx(1, "%s: %s: recv(%d): short recv", tcp->name, face,
461 recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout)
464 struct test_pkt recv_buf;
467 pr = poll(&tcp->pfds[fdidx], 1, tout);
469 err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]);
474 if(tcp->pfds[fdidx].revents != POLLIN) {
475 errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face,
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);
483 recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen,
484 &recv_buf.tss[fdidx].recvd);
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);
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);
499 test_server(struct test_ctx *tcp)
503 for (i = 0; i < NPKTS; i++) {
504 send_pkt(tcp, i, 0, __FUNCTION__);
505 j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT);
507 warnx("packet %d is lost", i);
515 test_client(struct test_ctx *tcp)
519 for (i = 0; i < NPKTS; i++) {
520 j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT);
525 #if defined(SIMULATE_PLOSS)
527 warnx("dropping packet %d", i);
531 send_pkt(tcp, j, 1, __FUNCTION__);
536 calc_rtt(struct test_pkt *tpp, struct rtt *rttp)
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);
546 test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name)
548 struct test_ctx test_ctx;
552 printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6");
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;
559 setup_udp(&test_ctx);
561 setup_udp6(&test_ctx);
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();
572 err(1, "%s: fork()", test_ctx.name);
575 test_client(&test_ctx);
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);
584 if (WIFEXITED(status)) {
585 if (WEXITSTATUS(status) != EXIT_SUCCESS) {
586 errx(1, "client exit status is %d",
587 WEXITSTATUS(status));
590 if (WIFSIGNALED(status))
591 errx(1, "abnormal termination of client, signal %d%s",
592 WTERMSIG(status), WCOREDUMP(status) ?
593 " (core file generated)" : "");
595 errx(1, "termination of client, unknown status");
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);
601 for (i = 0; i < NPKTS; i++) {
603 if (test_ctx.test_pkts[i].lost != 0) {
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");
616 teardown_udp(&test_ctx);
626 for (i = 0; i < 2; i++) {
627 test_run(0, i, 0, "send()/recv()");
629 test_run(TT_TIMESTAMP, i, 1,
630 "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)");
633 test_run(TT_BINTIME, i, 1,
634 "send()/recvmsg(), setsockopt(SO_BINTIME, 1)");
637 test_run(TT_REALTIME_MICRO, i, 1,
638 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");
640 test_run(TT_TS_BINTIME, i, 1,
641 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");
643 test_run(TT_REALTIME, i, 1,
644 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");
646 test_run(TT_MONOTONIC, i, 1,
647 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");