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
66struct trip_ts {
67    struct timespec sent;
68    struct timespec recvd;
69};
70
71struct test_pkt {
72    int pnum;
73    struct trip_ts tss[2];
74    int lost;
75    unsigned char data[PKT_SIZE];
76};
77
78struct 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
94struct 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
112static const struct timespec zero_ts;
113/* 0.01s, should be more than enough for the loopback communication  */
114static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};
115
116enum 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
120static clockid_t
121get_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
137static int
138get_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
158static size_t
159get_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
177static void
178setup_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
224static void
225setup_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
260static char *
261inet_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
269static void
270setup_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
307static void
308teardown_udp(struct test_ctx *tcp)
309{
310
311    close(tcp->fds[0]);
312    close(tcp->fds[1]);
313}
314
315static void
316send_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
336static void
337hdr_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
382static void
383recv_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
415static void
416recv_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
432static int
433recv_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
470static void
471test_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
486static void
487test_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
507static void
508calc_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
517static void
518test_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
591int
592main(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