Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tools/regression/sockets/udp_pingpong/udp_pingpong.c
108623 views
1
/*-
2
* Copyright (c) 2017 Maksym Sobolyev <[email protected]>
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
test_run(TT_BINTIME, i, 1,
605
"send()/recvmsg(), setsockopt(SO_BINTIME, 1)");
606
printf("OK\n");
607
test_run(TT_REALTIME_MICRO, i, 1,
608
"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)");
609
printf("OK\n");
610
test_run(TT_TS_BINTIME, i, 1,
611
"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)");
612
printf("OK\n");
613
test_run(TT_REALTIME, i, 1,
614
"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)");
615
printf("OK\n");
616
test_run(TT_MONOTONIC, i, 1,
617
"send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)");
618
printf("OK\n");
619
}
620
exit(0);
621
}
622
623