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