Path: blob/main/tools/regression/sockets/udp_pingpong/udp_pingpong.c
108623 views
/*-1* Copyright (c) 2017 Maksym Sobolyev <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND14* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE15* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE17* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF23* SUCH DAMAGE.24*/2526/*27* The test that setups two processes A and B and make A sending28* B UDP packet(s) and B send it back. The time of sending is recorded29* in the payload and time of the arrival is either determined by30* reading clock after recv() completes or using kernel-supplied31* via recvmsg(). End-to-end time t(A->B->A) is then calculated32* and compared against time for both t(A->B) + t(B->A) to make33* sure it makes sense.34*/3536#include <sys/types.h>37#include <sys/socket.h>38#include <sys/wait.h>39#include <sys/time.h>40#include <netinet/in.h>41#include <arpa/inet.h>42#include <err.h>43#include <poll.h>44#include <stdio.h>45#include <stdlib.h>46#include <string.h>47#include <strings.h>48#include <time.h>49#include <unistd.h>5051#define NPKTS 100052#define PKT_SIZE 12853/* Timeout to receive pong on the side A, 100ms */54#define SRECV_TIMEOUT (1 * 100)55/*56* Timeout to receive ping on the side B. 4x as large as on the side A,57* so that in the case of packet loss the side A will have a chance to58* realize that and send few more before B bails out.59*/60#define RRECV_TIMEOUT (SRECV_TIMEOUT * 4)61#define MIN_NRECV ((NPKTS * 99) / 100) /* 99% */6263//#define SIMULATE_PLOSS6465struct trip_ts {66struct timespec sent;67struct timespec recvd;68};6970struct test_pkt {71int pnum;72struct trip_ts tss[2];73int lost;74unsigned char data[PKT_SIZE];75};7677struct test_ctx {78const char *name;79int fds[2];80struct pollfd pfds[2];81union {82struct sockaddr_in v4;83struct sockaddr_in6 v6;84} sin[2];85struct test_pkt test_pkts[NPKTS];86int nsent;87int nrecvd;88clockid_t clock;89int use_recvmsg;90int ts_type;91};9293struct rtt {94struct timespec a2b;95struct timespec b2a;96struct timespec e2e;97struct timespec a2b_b2a;98};99100#define SEC(x) ((x)->tv_sec)101#define NSEC(x) ((x)->tv_nsec)102#define NSEC_MAX 1000000000L103#define NSEC_IN_USEC 1000L104105#define timeval2timespec(tv, ts) \106do { \107SEC(ts) = (tv)->tv_sec; \108NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC; \109} while (0);110111static const struct timespec zero_ts;112/* 0.01s, should be more than enough for the loopback communication */113static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)};114115enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1,116TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME,117TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC};118119static clockid_t120get_clock_type(struct test_ctx *tcp)121{122switch (tcp->ts_type) {123case TT_TIMESTAMP:124case TT_BINTIME:125case TT_REALTIME_MICRO:126case TT_TS_BINTIME:127case TT_REALTIME:128return (CLOCK_REALTIME);129130case TT_MONOTONIC:131return (CLOCK_MONOTONIC);132}133abort();134}135136static int137get_scm_type(struct test_ctx *tcp)138{139switch (tcp->ts_type) {140case TT_TIMESTAMP:141case TT_REALTIME_MICRO:142return (SCM_TIMESTAMP);143144case TT_BINTIME:145case TT_TS_BINTIME:146return (SCM_BINTIME);147148case TT_REALTIME:149return (SCM_REALTIME);150151case TT_MONOTONIC:152return (SCM_MONOTONIC);153}154abort();155}156157static size_t158get_scm_size(struct test_ctx *tcp)159{160switch (tcp->ts_type) {161case TT_TIMESTAMP:162case TT_REALTIME_MICRO:163return (sizeof(struct timeval));164165case TT_BINTIME:166case TT_TS_BINTIME:167return (sizeof(struct bintime));168169case TT_REALTIME:170case TT_MONOTONIC:171return (sizeof(struct timespec));172}173abort();174}175176static void177setup_ts_sockopt(struct test_ctx *tcp, int fd)178{179int rval, oname1, oname2, sval1, sval2;180181oname1 = SO_TIMESTAMP;182oname2 = -1;183sval2 = -1;184185switch (tcp->ts_type) {186case TT_REALTIME_MICRO:187case TT_TS_BINTIME:188case TT_REALTIME:189case TT_MONOTONIC:190oname2 = SO_TS_CLOCK;191sval2 = tcp->ts_type;192break;193194case TT_TIMESTAMP:195break;196197case TT_BINTIME:198oname1 = SO_BINTIME;199break;200201default:202abort();203}204205sval1 = 1;206rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1,207sizeof(sval1));208if (rval != 0) {209err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name,210fd, oname1);211}212if (oname2 == -1)213return;214rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2,215sizeof(sval2));216if (rval != 0) {217err(1, "%s: setup_udp: setsockopt(%d, %d, %d)",218tcp->name, fd, oname2, sval2);219}220}221222223static void224setup_udp(struct test_ctx *tcp)225{226int i;227socklen_t sin_len, af_len;228229af_len = sizeof(tcp->sin[0].v4);230for (i = 0; i < 2; i++) {231tcp->sin[i].v4.sin_len = af_len;232tcp->sin[i].v4.sin_family = AF_INET;233tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);234tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0);235if (tcp->fds[i] < 0)236err(1, "%s: setup_udp: socket", tcp->name);237if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)238err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,239inet_ntoa(tcp->sin[i].v4.sin_addr), 0);240sin_len = af_len;241if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)242err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);243if (tcp->use_recvmsg != 0) {244setup_ts_sockopt(tcp, tcp->fds[i]);245}246247tcp->pfds[i].fd = tcp->fds[i];248tcp->pfds[i].events = POLLIN;249}250251if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)252err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,253inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port));254if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)255err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,256inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port));257}258259static char *260inet_ntoa6(const void *sin6_addr)261{262static char straddr[INET6_ADDRSTRLEN];263264inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr));265return (straddr);266}267268static void269setup_udp6(struct test_ctx *tcp)270{271int i;272socklen_t sin_len, af_len;273274af_len = sizeof(tcp->sin[0].v6);275for (i = 0; i < 2; i++) {276tcp->sin[i].v6.sin6_len = af_len;277tcp->sin[i].v6.sin6_family = AF_INET6;278tcp->sin[i].v6.sin6_addr = in6addr_loopback;279tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0);280if (tcp->fds[i] < 0)281err(1, "%s: setup_udp: socket", tcp->name);282if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0)283err(1, "%s: setup_udp: bind(%s, %d)", tcp->name,284inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0);285sin_len = af_len;286if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0)287err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]);288if (tcp->use_recvmsg != 0) {289setup_ts_sockopt(tcp, tcp->fds[i]);290}291292tcp->pfds[i].fd = tcp->fds[i];293tcp->pfds[i].events = POLLIN;294}295296if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0)297err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,298inet_ntoa6(&tcp->sin[1].v6.sin6_addr),299ntohs(tcp->sin[1].v6.sin6_port));300if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0)301err(1, "%s: setup_udp: connect(%s, %d)", tcp->name,302inet_ntoa6(&tcp->sin[0].v6.sin6_addr),303ntohs(tcp->sin[0].v6.sin6_port));304}305306static void307teardown_udp(struct test_ctx *tcp)308{309310close(tcp->fds[0]);311close(tcp->fds[1]);312}313314static void315send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face)316{317ssize_t r;318size_t slen;319320slen = sizeof(tcp->test_pkts[pnum]);321clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent);322r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0);323if (r < 0) {324err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]);325}326if (r < (ssize_t)slen) {327errx(1, "%s: %s: send(%d): short send", tcp->name, face,328tcp->fds[fdidx]);329}330tcp->nsent += 1;331}332333#define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data)334335static void336hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp)337{338int scm_type;339size_t scm_size;340union {341struct timespec ts;342struct bintime bt;343struct timeval tv;344} tdata;345struct cmsghdr *cmsg;346347scm_type = get_scm_type(tcp);348scm_size = get_scm_size(tcp);349for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL;350cmsg = CMSG_NXTHDR(mhp, cmsg)) {351if ((cmsg->cmsg_level == SOL_SOCKET) &&352(cmsg->cmsg_type == scm_type)) {353memcpy(&tdata, CMSG_DATA(cmsg), scm_size);354break;355}356}357if (cmsg == NULL) {358abort();359}360switch (tcp->ts_type) {361case TT_REALTIME:362case TT_MONOTONIC:363*tp = tdata.ts;364break;365366case TT_TIMESTAMP:367case TT_REALTIME_MICRO:368timeval2timespec(&tdata.tv, tp);369break;370371case TT_BINTIME:372case TT_TS_BINTIME:373bintime2timespec(&tdata.bt, tp);374break;375376default:377abort();378}379}380381static void382recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf,383size_t rlen, struct timespec *tp)384{385/* We use a union to make sure hdr is aligned */386union {387struct cmsghdr hdr;388unsigned char buf[CMSG_SPACE(1024)];389} cmsgbuf;390struct msghdr msg;391struct iovec iov;392ssize_t rval;393394memset(&msg, '\0', sizeof(msg));395iov.iov_base = buf;396iov.iov_len = rlen;397msg.msg_iov = &iov;398msg.msg_iovlen = 1;399msg.msg_control = cmsgbuf.buf;400msg.msg_controllen = sizeof(cmsgbuf.buf);401402rval = recvmsg(tcp->fds[fdidx], &msg, 0);403if (rval < 0) {404err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]);405}406if (rval < (ssize_t)rlen) {407errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face,408tcp->fds[fdidx]);409}410411hdr_extract_ts(tcp, &msg, tp);412}413414static void415recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf,416size_t rlen, struct timespec *tp)417{418ssize_t rval;419420rval = recv(tcp->fds[fdidx], buf, rlen, 0);421clock_gettime(get_clock_type(tcp), tp);422if (rval < 0) {423err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]);424}425if (rval < (ssize_t)rlen) {426errx(1, "%s: %s: recv(%d): short recv", tcp->name, face,427tcp->fds[fdidx]);428}429}430431static int432recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout)433{434int pr;435struct test_pkt recv_buf;436size_t rlen;437438pr = poll(&tcp->pfds[fdidx], 1, tout);439if (pr < 0) {440err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]);441}442if (pr == 0) {443return (-1);444}445if(tcp->pfds[fdidx].revents != POLLIN) {446errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face,447tcp->fds[fdidx]);448}449rlen = sizeof(recv_buf);450if (tcp->use_recvmsg == 0) {451recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen,452&recv_buf.tss[fdidx].recvd);453} else {454recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen,455&recv_buf.tss[fdidx].recvd);456}457if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS ||458memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) {459errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name,460face, tcp->fds[fdidx], recv_buf.pnum);461}462tcp->nrecvd += 1;463memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss,464sizeof(recv_buf.tss));465tcp->test_pkts[recv_buf.pnum].lost = 0;466return (recv_buf.pnum);467}468469static void470test_server(struct test_ctx *tcp)471{472int i, j;473474for (i = 0; i < NPKTS; i++) {475send_pkt(tcp, i, 0, __FUNCTION__);476j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT);477if (j < 0) {478warnx("packet %d is lost", i);479/* timeout */480continue;481}482}483}484485static void486test_client(struct test_ctx *tcp)487{488int i, j;489490for (i = 0; i < NPKTS; i++) {491j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT);492if (j < 0) {493/* timeout */494return;495}496#if defined(SIMULATE_PLOSS)497if ((i % 99) == 0) {498warnx("dropping packet %d", i);499continue;500}501#endif502send_pkt(tcp, j, 1, __FUNCTION__);503}504}505506static void507calc_rtt(struct test_pkt *tpp, struct rtt *rttp)508{509510timespecsub(&tpp->tss[1].recvd, &tpp->tss[0].sent, &rttp->a2b);511timespecsub(&tpp->tss[0].recvd, &tpp->tss[1].sent, &rttp->b2a);512timespecadd(&rttp->a2b, &rttp->b2a, &rttp->a2b_b2a);513timespecsub(&tpp->tss[0].recvd, &tpp->tss[0].sent, &rttp->e2e);514}515516static void517test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name)518{519struct test_ctx test_ctx;520pid_t pid, cpid;521int i, j, status;522523printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6");524fflush(stdout);525bzero(&test_ctx, sizeof(test_ctx));526test_ctx.name = name;527test_ctx.use_recvmsg = use_recvmsg;528test_ctx.ts_type = ts_type;529if (use_ipv6 == 0) {530setup_udp(&test_ctx);531} else {532setup_udp6(&test_ctx);533}534for (i = 0; i < NPKTS; i++) {535test_ctx.test_pkts[i].pnum = i;536test_ctx.test_pkts[i].lost = 1;537for (j = 0; j < PKT_SIZE; j++) {538test_ctx.test_pkts[i].data[j] = (unsigned char)random();539}540}541cpid = fork();542if (cpid < 0) {543err(1, "%s: fork()", test_ctx.name);544}545if (cpid == 0) {546test_client(&test_ctx);547exit(0);548}549test_server(&test_ctx);550pid = waitpid(cpid, &status, 0);551if (pid == (pid_t)-1) {552err(1, "%s: waitpid(%d)", test_ctx.name, cpid);553}554555if (WIFEXITED(status)) {556if (WEXITSTATUS(status) != EXIT_SUCCESS) {557errx(1, "client exit status is %d",558WEXITSTATUS(status));559}560} else {561if (WIFSIGNALED(status))562errx(1, "abnormal termination of client, signal %d%s",563WTERMSIG(status), WCOREDUMP(status) ?564" (core file generated)" : "");565else566errx(1, "termination of client, unknown status");567}568if (test_ctx.nrecvd < MIN_NRECV) {569errx(1, "packet loss is too high %d received out of %d, min %d",570test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV);571}572for (i = 0; i < NPKTS; i++) {573struct rtt rtt;574if (test_ctx.test_pkts[i].lost != 0) {575continue;576}577calc_rtt(&test_ctx.test_pkts[i], &rtt);578if (!timespeccmp(&rtt.e2e, &rtt.a2b_b2a, >))579errx(1, "end-to-end trip time is too small");580if (!timespeccmp(&rtt.e2e, &max_ts, <))581errx(1, "end-to-end trip time is too large");582if (!timespeccmp(&rtt.a2b, &zero_ts, >))583errx(1, "A2B trip time is not positive");584if (!timespeccmp(&rtt.b2a, &zero_ts, >))585errx(1, "B2A trip time is not positive");586}587teardown_udp(&test_ctx);588}589590int591main(void)592{593int i;594595srandomdev();596597for (i = 0; i < 2; i++) {598test_run(0, i, 0, "send()/recv()");599printf("OK\n");600test_run(TT_TIMESTAMP, i, 1,601"send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)");602printf("OK\n");603test_run(TT_BINTIME, i, 1,604"send()/recvmsg(), setsockopt(SO_BINTIME, 1)");605printf("OK\n");606test_run(TT_REALTIME_MICRO, i, 1,607"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");608printf("OK\n");609test_run(TT_TS_BINTIME, i, 1,610"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");611printf("OK\n");612test_run(TT_REALTIME, i, 1,613"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");614printf("OK\n");615test_run(TT_MONOTONIC, i, 1,616"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");617printf("OK\n");618}619exit(0);620}621622623