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__FBSDID("$FreeBSD$");
39
40#include <sys/types.h>
41#include <sys/socket.h>
42#include <sys/wait.h>
43#include <sys/time.h>
44#include <netinet/in.h>
45#include <arpa/inet.h>
46#include <err.h>
47#include <poll.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <strings.h>
52#include <time.h>
53#include <unistd.h>
54
55#define	NPKTS		1000
56#define	PKT_SIZE	128
57/* Timeout to receive pong on the side A, 100ms */
58#define SRECV_TIMEOUT	(1 * 100)
59/*
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.
63 */
64#define RRECV_TIMEOUT	(SRECV_TIMEOUT * 4)
65#define MIN_NRECV	((NPKTS * 99) / 100) /* 99% */
66
67//#define	SIMULATE_PLOSS
68
69struct trip_ts {
70    struct timespec sent;
71    struct timespec recvd;
72};
73
74struct test_pkt {
75    int pnum;
76    struct trip_ts tss[2];
77    int lost;
78    unsigned char data[PKT_SIZE];
79};
80
81struct test_ctx {
82    const char *name;
83    int fds[2];
84    struct pollfd pfds[2];
85    union {
86        struct sockaddr_in v4;
87        struct sockaddr_in6 v6;
88    } sin[2];
89    struct test_pkt test_pkts[NPKTS];
90    int nsent;
91    int nrecvd;
92    clockid_t clock;
93    int use_recvmsg;
94    int ts_type;
95};
96
97struct rtt {
98    struct timespec a2b;
99    struct timespec b2a;
100    struct timespec e2e;
101    struct timespec a2b_b2a;
102};
103
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
108
109#define timeval2timespec(tv, ts)                                   \
110    do {                                                           \
111        SEC(ts) = (tv)->tv_sec;                                    \
112        NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC;                   \
113    } while (0);
114
115static const struct timespec zero_ts;
116/* 0.01s, should be more than enough for the loopback communication  */
117static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};
118
119enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1,
120  TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME,
121  TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC};
122
123static clockid_t
124get_clock_type(struct test_ctx *tcp)
125{
126    switch (tcp->ts_type) {
127    case TT_TIMESTAMP:
128    case TT_BINTIME:
129    case TT_REALTIME_MICRO:
130    case TT_TS_BINTIME:
131    case TT_REALTIME:
132        return (CLOCK_REALTIME);
133
134    case TT_MONOTONIC:
135        return (CLOCK_MONOTONIC);
136    }
137    abort();
138}
139
140static int
141get_scm_type(struct test_ctx *tcp)
142{
143    switch (tcp->ts_type) {
144    case TT_TIMESTAMP:
145    case TT_REALTIME_MICRO:
146        return (SCM_TIMESTAMP);
147
148    case TT_BINTIME:
149    case TT_TS_BINTIME:
150        return (SCM_BINTIME);
151
152    case TT_REALTIME:
153        return (SCM_REALTIME);
154
155    case TT_MONOTONIC:
156        return (SCM_MONOTONIC);
157    }
158    abort();
159}
160
161static size_t
162get_scm_size(struct test_ctx *tcp)
163{
164    switch (tcp->ts_type) {
165    case TT_TIMESTAMP:
166    case TT_REALTIME_MICRO:
167        return (sizeof(struct timeval));
168
169    case TT_BINTIME:
170    case TT_TS_BINTIME:
171        return (sizeof(struct bintime));
172
173    case TT_REALTIME:
174    case TT_MONOTONIC:
175        return (sizeof(struct timespec));
176    }
177    abort();
178}
179
180static void
181setup_ts_sockopt(struct test_ctx *tcp, int fd)
182{
183    int rval, oname1, oname2, sval1, sval2;
184
185    oname1 = SO_TIMESTAMP;
186    oname2 = -1;
187    sval2 = -1;
188
189    switch (tcp->ts_type) {
190    case TT_REALTIME_MICRO:
191    case TT_TS_BINTIME:
192    case TT_REALTIME:
193    case TT_MONOTONIC:
194        oname2 = SO_TS_CLOCK;
195        sval2 = tcp->ts_type;
196        break;
197
198    case TT_TIMESTAMP:
199        break;
200
201    case TT_BINTIME:
202        oname1 = SO_BINTIME;
203        break;
204
205    default:
206        abort();
207    }
208
209    sval1 = 1;
210    rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1,
211      sizeof(sval1));
212    if (rval != 0) {
213        err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name,
214          fd, oname1);
215    }
216    if (oname2 == -1)
217        return;
218    rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2,
219      sizeof(sval2));
220    if (rval != 0) {
221        err(1, "%s: setup_udp: setsockopt(%d, %d, %d)",
222          tcp->name, fd, oname2, sval2);
223    }
224}
225
226
227static void
228setup_udp(struct test_ctx *tcp)
229{
230    int i;
231    socklen_t sin_len, af_len;
232
233    af_len = sizeof(tcp->sin[0].v4);
234    for (i = 0; i < 2; i++) {
235        tcp->sin[i].v4.sin_len = af_len;
236        tcp->sin[i].v4.sin_family = AF_INET;
237        tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
238        tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0);
239        if (tcp->fds[i] < 0)
240            err(1, "%s: setup_udp: socket", tcp->name);
241        if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
242            err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
243              inet_ntoa(tcp->sin[i].v4.sin_addr), 0);
244        sin_len = af_len;
245        if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
246            err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
247        if (tcp->use_recvmsg != 0) {
248            setup_ts_sockopt(tcp, tcp->fds[i]);
249        }
250
251        tcp->pfds[i].fd = tcp->fds[i];
252        tcp->pfds[i].events = POLLIN;
253    }
254
255    if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
256        err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
257          inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port));
258    if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
259        err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
260          inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port));
261}
262
263static char *
264inet_ntoa6(const void *sin6_addr)
265{
266    static char straddr[INET6_ADDRSTRLEN];
267
268    inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr));
269    return (straddr);
270}
271
272static void
273setup_udp6(struct test_ctx *tcp)
274{
275    int i;
276    socklen_t sin_len, af_len;
277
278    af_len = sizeof(tcp->sin[0].v6);
279    for (i = 0; i < 2; i++) {
280        tcp->sin[i].v6.sin6_len = af_len;
281        tcp->sin[i].v6.sin6_family = AF_INET6;
282        tcp->sin[i].v6.sin6_addr = in6addr_loopback;
283        tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0);
284        if (tcp->fds[i] < 0)
285            err(1, "%s: setup_udp: socket", tcp->name);
286        if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)
287            err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,
288              inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0);
289        sin_len = af_len;
290        if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)
291            err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);
292        if (tcp->use_recvmsg != 0) {
293            setup_ts_sockopt(tcp, tcp->fds[i]);
294        }
295
296        tcp->pfds[i].fd = tcp->fds[i];
297        tcp->pfds[i].events = POLLIN;
298    }
299
300    if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)
301        err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
302          inet_ntoa6(&tcp->sin[1].v6.sin6_addr),
303          ntohs(tcp->sin[1].v6.sin6_port));
304    if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)
305        err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,
306          inet_ntoa6(&tcp->sin[0].v6.sin6_addr),
307          ntohs(tcp->sin[0].v6.sin6_port));
308}
309
310static void
311teardown_udp(struct test_ctx *tcp)
312{
313
314    close(tcp->fds[0]);
315    close(tcp->fds[1]);
316}
317
318static void
319send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face)
320{
321    ssize_t r;
322    size_t slen;
323
324    slen = sizeof(tcp->test_pkts[pnum]);
325    clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent);
326    r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0);
327    if (r < 0) {
328        err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]);
329    }
330    if (r < (ssize_t)slen) {
331        errx(1, "%s: %s: send(%d): short send", tcp->name, face,
332          tcp->fds[fdidx]);
333    }
334    tcp->nsent += 1;
335}
336
337#define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data)
338
339static void
340hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp)
341{
342    int scm_type;
343    size_t scm_size;
344    union {
345        struct timespec ts;
346        struct bintime bt;
347        struct timeval tv;
348    } tdata;
349    struct cmsghdr *cmsg;
350
351    scm_type = get_scm_type(tcp);
352    scm_size = get_scm_size(tcp);
353    for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL;
354      cmsg = CMSG_NXTHDR(mhp, cmsg)) {
355        if ((cmsg->cmsg_level == SOL_SOCKET) &&
356          (cmsg->cmsg_type == scm_type)) {
357            memcpy(&tdata, CMSG_DATA(cmsg), scm_size);
358            break;
359        }
360    }
361    if (cmsg == NULL) {
362        abort();
363    }
364    switch (tcp->ts_type) {
365    case TT_REALTIME:
366    case TT_MONOTONIC:
367        *tp = tdata.ts;
368        break;
369
370    case TT_TIMESTAMP:
371    case TT_REALTIME_MICRO:
372        timeval2timespec(&tdata.tv, tp);
373        break;
374
375    case TT_BINTIME:
376    case TT_TS_BINTIME:
377        bintime2timespec(&tdata.bt, tp);
378        break;
379
380    default:
381        abort();
382    }
383}
384
385static void
386recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
387  size_t rlen, struct timespec *tp)
388{
389    /* We use a union to make sure hdr is aligned */
390    union {
391        struct cmsghdr hdr;
392        unsigned char buf[CMSG_SPACE(1024)];
393    } cmsgbuf;
394    struct msghdr msg;
395    struct iovec iov;
396    ssize_t rval;
397
398    memset(&msg, '\0', sizeof(msg));
399    iov.iov_base = buf;
400    iov.iov_len = rlen;
401    msg.msg_iov = &iov;
402    msg.msg_iovlen = 1;
403    msg.msg_control = cmsgbuf.buf;
404    msg.msg_controllen = sizeof(cmsgbuf.buf);
405
406    rval = recvmsg(tcp->fds[fdidx], &msg, 0);
407    if (rval < 0) {
408        err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]);
409    }
410    if (rval < (ssize_t)rlen) {
411        errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face,
412          tcp->fds[fdidx]);
413    }
414
415    hdr_extract_ts(tcp, &msg, tp);
416}
417
418static void
419recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf,
420  size_t rlen, struct timespec *tp)
421{
422    ssize_t rval;
423
424    rval = recv(tcp->fds[fdidx], buf, rlen, 0);
425    clock_gettime(get_clock_type(tcp), tp);
426    if (rval < 0) {
427        err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]);
428    }
429    if (rval < (ssize_t)rlen) {
430        errx(1, "%s: %s: recv(%d): short recv", tcp->name, face,
431            tcp->fds[fdidx]);
432    }
433}
434
435static int
436recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout)
437{
438    int pr;
439    struct test_pkt recv_buf;
440    size_t rlen;
441
442    pr = poll(&tcp->pfds[fdidx], 1, tout);
443    if (pr < 0) {
444        err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]);
445    }
446    if (pr == 0) {
447        return (-1);
448    }
449    if(tcp->pfds[fdidx].revents != POLLIN) {
450        errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face,
451          tcp->fds[fdidx]);
452    }
453    rlen = sizeof(recv_buf);
454    if (tcp->use_recvmsg == 0) {
455        recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen,
456          &recv_buf.tss[fdidx].recvd);
457    } else {
458        recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen,
459          &recv_buf.tss[fdidx].recvd);
460    }
461    if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS ||
462      memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) {
463        errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name,
464          face, tcp->fds[fdidx], recv_buf.pnum);
465    }
466    tcp->nrecvd += 1;
467    memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss,
468      sizeof(recv_buf.tss));
469    tcp->test_pkts[recv_buf.pnum].lost = 0;
470    return (recv_buf.pnum);
471}
472
473static void
474test_server(struct test_ctx *tcp)
475{
476    int i, j;
477
478    for (i = 0; i < NPKTS; i++) {
479        send_pkt(tcp, i, 0, __FUNCTION__);
480        j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT);
481        if (j < 0) {
482            warnx("packet %d is lost", i);
483            /* timeout */
484            continue;
485        }
486    }
487}
488
489static void
490test_client(struct test_ctx *tcp)
491{
492    int i, j;
493
494    for (i = 0; i < NPKTS; i++) {
495        j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT);
496        if (j < 0) {
497            /* timeout */
498            return;
499        }
500#if defined(SIMULATE_PLOSS)
501        if ((i % 99) == 0) {
502            warnx("dropping packet %d", i);
503            continue;
504        }
505#endif
506        send_pkt(tcp, j, 1, __FUNCTION__);
507    }
508}
509
510static void
511calc_rtt(struct test_pkt *tpp, struct rtt *rttp)
512{
513
514    timespecsub(&tpp->tss[1].recvd, &tpp->tss[0].sent, &rttp->a2b);
515    timespecsub(&tpp->tss[0].recvd, &tpp->tss[1].sent, &rttp->b2a);
516    timespecadd(&rttp->a2b, &rttp->b2a, &rttp->a2b_b2a);
517    timespecsub(&tpp->tss[0].recvd, &tpp->tss[0].sent, &rttp->e2e);
518}
519
520static void
521test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name)
522{
523    struct test_ctx test_ctx;
524    pid_t pid, cpid;
525    int i, j, status;
526
527    printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6");
528    fflush(stdout);
529    bzero(&test_ctx, sizeof(test_ctx));
530    test_ctx.name = name;
531    test_ctx.use_recvmsg = use_recvmsg;
532    test_ctx.ts_type = ts_type;
533    if (use_ipv6 == 0) {
534        setup_udp(&test_ctx);
535    } else {
536        setup_udp6(&test_ctx);
537    }
538    for (i = 0; i < NPKTS; i++) {
539        test_ctx.test_pkts[i].pnum = i;
540        test_ctx.test_pkts[i].lost = 1;
541        for (j = 0; j < PKT_SIZE; j++) {
542            test_ctx.test_pkts[i].data[j] = (unsigned char)random();
543        }
544    }
545    cpid = fork();
546    if (cpid < 0) {
547        err(1, "%s: fork()", test_ctx.name);
548    }
549    if (cpid == 0) {
550        test_client(&test_ctx);
551        exit(0);
552    }
553    test_server(&test_ctx);
554    pid = waitpid(cpid, &status, 0);
555    if (pid == (pid_t)-1) {
556        err(1, "%s: waitpid(%d)", test_ctx.name, cpid);
557    }
558
559    if (WIFEXITED(status)) {
560        if (WEXITSTATUS(status) != EXIT_SUCCESS) {
561            errx(1, "client exit status is %d",
562              WEXITSTATUS(status));
563        }
564    } else {
565        if (WIFSIGNALED(status))
566            errx(1, "abnormal termination of client, signal %d%s",
567              WTERMSIG(status), WCOREDUMP(status) ?
568              " (core file generated)" : "");
569        else
570            errx(1, "termination of client, unknown status");
571    }
572    if (test_ctx.nrecvd < MIN_NRECV) {
573        errx(1, "packet loss is too high %d received out of %d, min %d",
574          test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV);
575    }
576    for (i = 0; i < NPKTS; i++) {
577        struct rtt rtt;
578        if (test_ctx.test_pkts[i].lost != 0) {
579            continue;
580        }
581        calc_rtt(&test_ctx.test_pkts[i], &rtt);
582        if (!timespeccmp(&rtt.e2e, &rtt.a2b_b2a, >))
583            errx(1, "end-to-end trip time is too small");
584        if (!timespeccmp(&rtt.e2e, &max_ts, <))
585            errx(1, "end-to-end trip time is too large");
586        if (!timespeccmp(&rtt.a2b, &zero_ts, >))
587            errx(1, "A2B trip time is not positive");
588        if (!timespeccmp(&rtt.b2a, &zero_ts, >))
589            errx(1, "B2A trip time is not positive");
590    }
591    teardown_udp(&test_ctx);
592}
593
594int
595main(void)
596{
597    int i;
598
599    srandomdev();
600
601    for (i = 0; i < 2; i++) {
602        test_run(0, i, 0, "send()/recv()");
603        printf("OK\n");
604        test_run(TT_TIMESTAMP, i, 1,
605          "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)");
606        printf("OK\n");
607        if (i == 0) {
608            test_run(TT_BINTIME, i, 1,
609              "send()/recvmsg(), setsockopt(SO_BINTIME, 1)");
610            printf("OK\n");
611        }
612        test_run(TT_REALTIME_MICRO, i, 1,
613          "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");
614        printf("OK\n");
615        test_run(TT_TS_BINTIME, i, 1,
616          "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");
617        printf("OK\n");
618        test_run(TT_REALTIME, i, 1,
619          "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");
620        printf("OK\n");
621        test_run(TT_MONOTONIC, i, 1,
622          "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");
623        printf("OK\n");
624    }
625    exit(0);
626}
627