1 // SPDX-License-Identifier: GPL-2.0
2
3 #define _GNU_SOURCE
4
5 #include <assert.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <string.h>
10 #include <stdarg.h>
11 #include <stdbool.h>
12 #include <stdint.h>
13 #include <inttypes.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <strings.h>
17 #include <time.h>
18 #include <unistd.h>
19
20 #include <sys/socket.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23
24 #include <netdb.h>
25 #include <netinet/in.h>
26
27 #include <linux/tcp.h>
28
29 static int pf = AF_INET;
30
31 #ifndef IPPROTO_MPTCP
32 #define IPPROTO_MPTCP 262
33 #endif
34 #ifndef SOL_MPTCP
35 #define SOL_MPTCP 284
36 #endif
37
38 #ifndef MPTCP_INFO
39 struct mptcp_info {
40 __u8 mptcpi_subflows;
41 __u8 mptcpi_add_addr_signal;
42 __u8 mptcpi_add_addr_accepted;
43 __u8 mptcpi_subflows_max;
44 __u8 mptcpi_add_addr_signal_max;
45 __u8 mptcpi_add_addr_accepted_max;
46 __u32 mptcpi_flags;
47 __u32 mptcpi_token;
48 __u64 mptcpi_write_seq;
49 __u64 mptcpi_snd_una;
50 __u64 mptcpi_rcv_nxt;
51 __u8 mptcpi_local_addr_used;
52 __u8 mptcpi_local_addr_max;
53 __u8 mptcpi_csum_enabled;
54 };
55
56 struct mptcp_subflow_data {
57 __u32 size_subflow_data; /* size of this structure in userspace */
58 __u32 num_subflows; /* must be 0, set by kernel */
59 __u32 size_kernel; /* must be 0, set by kernel */
60 __u32 size_user; /* size of one element in data[] */
61 } __attribute__((aligned(8)));
62
63 struct mptcp_subflow_addrs {
64 union {
65 __kernel_sa_family_t sa_family;
66 struct sockaddr sa_local;
67 struct sockaddr_in sin_local;
68 struct sockaddr_in6 sin6_local;
69 struct __kernel_sockaddr_storage ss_local;
70 };
71 union {
72 struct sockaddr sa_remote;
73 struct sockaddr_in sin_remote;
74 struct sockaddr_in6 sin6_remote;
75 struct __kernel_sockaddr_storage ss_remote;
76 };
77 };
78
79 #define MPTCP_INFO 1
80 #define MPTCP_TCPINFO 2
81 #define MPTCP_SUBFLOW_ADDRS 3
82 #endif
83
84 struct so_state {
85 struct mptcp_info mi;
86 uint64_t mptcpi_rcv_delta;
87 uint64_t tcpi_rcv_delta;
88 };
89
90 #ifndef MIN
91 #define MIN(a, b) ((a) < (b) ? (a) : (b))
92 #endif
93
die_perror(const char * msg)94 static void die_perror(const char *msg)
95 {
96 perror(msg);
97 exit(1);
98 }
99
die_usage(int r)100 static void die_usage(int r)
101 {
102 fprintf(stderr, "Usage: mptcp_sockopt [-6]\n");
103 exit(r);
104 }
105
xerror(const char * fmt,...)106 static void xerror(const char *fmt, ...)
107 {
108 va_list ap;
109
110 va_start(ap, fmt);
111 vfprintf(stderr, fmt, ap);
112 va_end(ap);
113 fputc('\n', stderr);
114 exit(1);
115 }
116
getxinfo_strerr(int err)117 static const char *getxinfo_strerr(int err)
118 {
119 if (err == EAI_SYSTEM)
120 return strerror(errno);
121
122 return gai_strerror(err);
123 }
124
xgetaddrinfo(const char * node,const char * service,const struct addrinfo * hints,struct addrinfo ** res)125 static void xgetaddrinfo(const char *node, const char *service,
126 const struct addrinfo *hints,
127 struct addrinfo **res)
128 {
129 int err = getaddrinfo(node, service, hints, res);
130
131 if (err) {
132 const char *errstr = getxinfo_strerr(err);
133
134 fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n",
135 node ? node : "", service ? service : "", errstr);
136 exit(1);
137 }
138 }
139
sock_listen_mptcp(const char * const listenaddr,const char * const port)140 static int sock_listen_mptcp(const char * const listenaddr,
141 const char * const port)
142 {
143 int sock = -1;
144 struct addrinfo hints = {
145 .ai_protocol = IPPROTO_TCP,
146 .ai_socktype = SOCK_STREAM,
147 .ai_flags = AI_PASSIVE | AI_NUMERICHOST
148 };
149
150 hints.ai_family = pf;
151
152 struct addrinfo *a, *addr;
153 int one = 1;
154
155 xgetaddrinfo(listenaddr, port, &hints, &addr);
156 hints.ai_family = pf;
157
158 for (a = addr; a; a = a->ai_next) {
159 sock = socket(a->ai_family, a->ai_socktype, IPPROTO_MPTCP);
160 if (sock < 0)
161 continue;
162
163 if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one,
164 sizeof(one)))
165 perror("setsockopt");
166
167 if (bind(sock, a->ai_addr, a->ai_addrlen) == 0)
168 break; /* success */
169
170 perror("bind");
171 close(sock);
172 sock = -1;
173 }
174
175 freeaddrinfo(addr);
176
177 if (sock < 0)
178 xerror("could not create listen socket");
179
180 if (listen(sock, 20))
181 die_perror("listen");
182
183 return sock;
184 }
185
sock_connect_mptcp(const char * const remoteaddr,const char * const port,int proto)186 static int sock_connect_mptcp(const char * const remoteaddr,
187 const char * const port, int proto)
188 {
189 struct addrinfo hints = {
190 .ai_protocol = IPPROTO_TCP,
191 .ai_socktype = SOCK_STREAM,
192 };
193 struct addrinfo *a, *addr;
194 int sock = -1;
195
196 hints.ai_family = pf;
197
198 xgetaddrinfo(remoteaddr, port, &hints, &addr);
199 for (a = addr; a; a = a->ai_next) {
200 sock = socket(a->ai_family, a->ai_socktype, proto);
201 if (sock < 0)
202 continue;
203
204 if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
205 break; /* success */
206
207 die_perror("connect");
208 }
209
210 if (sock < 0)
211 xerror("could not create connect socket");
212
213 freeaddrinfo(addr);
214 return sock;
215 }
216
parse_opts(int argc,char ** argv)217 static void parse_opts(int argc, char **argv)
218 {
219 int c;
220
221 while ((c = getopt(argc, argv, "h6")) != -1) {
222 switch (c) {
223 case 'h':
224 die_usage(0);
225 break;
226 case '6':
227 pf = AF_INET6;
228 break;
229 default:
230 die_usage(1);
231 break;
232 }
233 }
234 }
235
do_getsockopt_bogus_sf_data(int fd,int optname)236 static void do_getsockopt_bogus_sf_data(int fd, int optname)
237 {
238 struct mptcp_subflow_data good_data;
239 struct bogus_data {
240 struct mptcp_subflow_data d;
241 char buf[2];
242 } bd;
243 socklen_t olen, _olen;
244 int ret;
245
246 memset(&bd, 0, sizeof(bd));
247 memset(&good_data, 0, sizeof(good_data));
248
249 olen = sizeof(good_data);
250 good_data.size_subflow_data = olen;
251
252 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
253 assert(ret < 0); /* 0 size_subflow_data */
254 assert(olen == sizeof(good_data));
255
256 bd.d = good_data;
257
258 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
259 assert(ret == 0);
260 assert(olen == sizeof(good_data));
261 assert(bd.d.num_subflows == 1);
262 assert(bd.d.size_kernel > 0);
263 assert(bd.d.size_user == 0);
264
265 bd.d = good_data;
266 _olen = rand() % olen;
267 olen = _olen;
268 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
269 assert(ret < 0); /* bogus olen */
270 assert(olen == _olen); /* must be unchanged */
271
272 bd.d = good_data;
273 olen = sizeof(good_data);
274 bd.d.size_kernel = 1;
275 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
276 assert(ret < 0); /* size_kernel not 0 */
277
278 bd.d = good_data;
279 olen = sizeof(good_data);
280 bd.d.num_subflows = 1;
281 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
282 assert(ret < 0); /* num_subflows not 0 */
283
284 /* forward compat check: larger struct mptcp_subflow_data on 'old' kernel */
285 bd.d = good_data;
286 olen = sizeof(bd);
287 bd.d.size_subflow_data = sizeof(bd);
288
289 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
290 assert(ret == 0);
291
292 /* olen must be truncated to real data size filled by kernel: */
293 assert(olen == sizeof(good_data));
294
295 assert(bd.d.size_subflow_data == sizeof(bd));
296
297 bd.d = good_data;
298 bd.d.size_subflow_data += 1;
299 bd.d.size_user = 1;
300 olen = bd.d.size_subflow_data + 1;
301 _olen = olen;
302
303 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &_olen);
304 assert(ret == 0);
305
306 /* no truncation, kernel should have filled 1 byte of optname payload in buf[1]: */
307 assert(olen == _olen);
308
309 assert(bd.d.size_subflow_data == sizeof(good_data) + 1);
310 assert(bd.buf[0] == 0);
311 }
312
do_getsockopt_mptcp_info(struct so_state * s,int fd,size_t w)313 static void do_getsockopt_mptcp_info(struct so_state *s, int fd, size_t w)
314 {
315 struct mptcp_info i;
316 socklen_t olen;
317 int ret;
318
319 olen = sizeof(i);
320 ret = getsockopt(fd, SOL_MPTCP, MPTCP_INFO, &i, &olen);
321
322 if (ret < 0)
323 die_perror("getsockopt MPTCP_INFO");
324
325 assert(olen == sizeof(i));
326
327 if (s->mi.mptcpi_write_seq == 0)
328 s->mi = i;
329
330 assert(s->mi.mptcpi_write_seq + w == i.mptcpi_write_seq);
331
332 s->mptcpi_rcv_delta = i.mptcpi_rcv_nxt - s->mi.mptcpi_rcv_nxt;
333 }
334
do_getsockopt_tcp_info(struct so_state * s,int fd,size_t r,size_t w)335 static void do_getsockopt_tcp_info(struct so_state *s, int fd, size_t r, size_t w)
336 {
337 struct my_tcp_info {
338 struct mptcp_subflow_data d;
339 struct tcp_info ti[2];
340 } ti;
341 int ret, tries = 5;
342 socklen_t olen;
343
344 do {
345 memset(&ti, 0, sizeof(ti));
346
347 ti.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
348 ti.d.size_user = sizeof(struct tcp_info);
349 olen = sizeof(ti);
350
351 ret = getsockopt(fd, SOL_MPTCP, MPTCP_TCPINFO, &ti, &olen);
352 if (ret < 0)
353 xerror("getsockopt MPTCP_TCPINFO (tries %d, %m)");
354
355 assert(olen <= sizeof(ti));
356 assert(ti.d.size_kernel > 0);
357 assert(ti.d.size_user ==
358 MIN(ti.d.size_kernel, sizeof(struct tcp_info)));
359 assert(ti.d.num_subflows == 1);
360
361 assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
362 olen -= sizeof(struct mptcp_subflow_data);
363 assert(olen == ti.d.size_user);
364
365 if (ti.ti[0].tcpi_bytes_sent == w &&
366 ti.ti[0].tcpi_bytes_received == r)
367 goto done;
368
369 if (r == 0 && ti.ti[0].tcpi_bytes_sent == w &&
370 ti.ti[0].tcpi_bytes_received) {
371 s->tcpi_rcv_delta = ti.ti[0].tcpi_bytes_received;
372 goto done;
373 }
374
375 /* wait and repeat, might be that tx is still ongoing */
376 sleep(1);
377 } while (tries-- > 0);
378
379 xerror("tcpi_bytes_sent %" PRIu64 ", want %zu. tcpi_bytes_received %" PRIu64 ", want %zu",
380 ti.ti[0].tcpi_bytes_sent, w, ti.ti[0].tcpi_bytes_received, r);
381
382 done:
383 do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO);
384 }
385
do_getsockopt_subflow_addrs(int fd)386 static void do_getsockopt_subflow_addrs(int fd)
387 {
388 struct sockaddr_storage remote, local;
389 socklen_t olen, rlen, llen;
390 int ret;
391 struct my_addrs {
392 struct mptcp_subflow_data d;
393 struct mptcp_subflow_addrs addr[2];
394 } addrs;
395
396 memset(&addrs, 0, sizeof(addrs));
397 memset(&local, 0, sizeof(local));
398 memset(&remote, 0, sizeof(remote));
399
400 addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
401 addrs.d.size_user = sizeof(struct mptcp_subflow_addrs);
402 olen = sizeof(addrs);
403
404 ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
405 if (ret < 0)
406 die_perror("getsockopt MPTCP_SUBFLOW_ADDRS");
407
408 assert(olen <= sizeof(addrs));
409 assert(addrs.d.size_kernel > 0);
410 assert(addrs.d.size_user ==
411 MIN(addrs.d.size_kernel, sizeof(struct mptcp_subflow_addrs)));
412 assert(addrs.d.num_subflows == 1);
413
414 assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
415 olen -= sizeof(struct mptcp_subflow_data);
416 assert(olen == addrs.d.size_user);
417
418 llen = sizeof(local);
419 ret = getsockname(fd, (struct sockaddr *)&local, &llen);
420 if (ret < 0)
421 die_perror("getsockname");
422 rlen = sizeof(remote);
423 ret = getpeername(fd, (struct sockaddr *)&remote, &rlen);
424 if (ret < 0)
425 die_perror("getpeername");
426
427 assert(rlen > 0);
428 assert(rlen == llen);
429
430 assert(remote.ss_family == local.ss_family);
431
432 assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) == 0);
433 assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) == 0);
434
435 memset(&addrs, 0, sizeof(addrs));
436
437 addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
438 addrs.d.size_user = sizeof(sa_family_t);
439 olen = sizeof(addrs.d) + sizeof(sa_family_t);
440
441 ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
442 assert(ret == 0);
443 assert(olen == sizeof(addrs.d) + sizeof(sa_family_t));
444
445 assert(addrs.addr[0].sa_family == pf);
446 assert(addrs.addr[0].sa_family == local.ss_family);
447
448 assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) != 0);
449 assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) != 0);
450
451 do_getsockopt_bogus_sf_data(fd, MPTCP_SUBFLOW_ADDRS);
452 }
453
do_getsockopts(struct so_state * s,int fd,size_t r,size_t w)454 static void do_getsockopts(struct so_state *s, int fd, size_t r, size_t w)
455 {
456 do_getsockopt_mptcp_info(s, fd, w);
457
458 do_getsockopt_tcp_info(s, fd, r, w);
459
460 do_getsockopt_subflow_addrs(fd);
461 }
462
connect_one_server(int fd,int pipefd)463 static void connect_one_server(int fd, int pipefd)
464 {
465 char buf[4096], buf2[4096];
466 size_t len, i, total;
467 struct so_state s;
468 bool eof = false;
469 ssize_t ret;
470
471 memset(&s, 0, sizeof(s));
472
473 len = rand() % (sizeof(buf) - 1);
474
475 if (len < 128)
476 len = 128;
477
478 for (i = 0; i < len ; i++) {
479 buf[i] = rand() % 26;
480 buf[i] += 'A';
481 }
482
483 buf[i] = '\n';
484
485 do_getsockopts(&s, fd, 0, 0);
486
487 /* un-block server */
488 ret = read(pipefd, buf2, 4);
489 assert(ret == 4);
490 close(pipefd);
491
492 assert(strncmp(buf2, "xmit", 4) == 0);
493
494 ret = write(fd, buf, len);
495 if (ret < 0)
496 die_perror("write");
497
498 if (ret != (ssize_t)len)
499 xerror("short write");
500
501 total = 0;
502 do {
503 ret = read(fd, buf2 + total, sizeof(buf2) - total);
504 if (ret < 0)
505 die_perror("read");
506 if (ret == 0) {
507 eof = true;
508 break;
509 }
510
511 total += ret;
512 } while (total < len);
513
514 if (total != len)
515 xerror("total %lu, len %lu eof %d\n", total, len, eof);
516
517 if (memcmp(buf, buf2, len))
518 xerror("data corruption");
519
520 if (s.tcpi_rcv_delta)
521 assert(s.tcpi_rcv_delta <= total);
522
523 do_getsockopts(&s, fd, ret, ret);
524
525 if (eof)
526 total += 1; /* sequence advances due to FIN */
527
528 assert(s.mptcpi_rcv_delta == (uint64_t)total);
529 close(fd);
530 }
531
process_one_client(int fd,int pipefd)532 static void process_one_client(int fd, int pipefd)
533 {
534 ssize_t ret, ret2, ret3;
535 struct so_state s;
536 char buf[4096];
537
538 memset(&s, 0, sizeof(s));
539 do_getsockopts(&s, fd, 0, 0);
540
541 ret = write(pipefd, "xmit", 4);
542 assert(ret == 4);
543
544 ret = read(fd, buf, sizeof(buf));
545 if (ret < 0)
546 die_perror("read");
547
548 assert(s.mptcpi_rcv_delta <= (uint64_t)ret);
549
550 if (s.tcpi_rcv_delta)
551 assert(s.tcpi_rcv_delta == (uint64_t)ret);
552
553 ret2 = write(fd, buf, ret);
554 if (ret2 < 0)
555 die_perror("write");
556
557 /* wait for hangup */
558 ret3 = read(fd, buf, 1);
559 if (ret3 != 0)
560 xerror("expected EOF, got %lu", ret3);
561
562 do_getsockopts(&s, fd, ret, ret2);
563 if (s.mptcpi_rcv_delta != (uint64_t)ret + 1)
564 xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64, s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - ret);
565 close(fd);
566 }
567
xaccept(int s)568 static int xaccept(int s)
569 {
570 int fd = accept(s, NULL, 0);
571
572 if (fd < 0)
573 die_perror("accept");
574
575 return fd;
576 }
577
server(int pipefd)578 static int server(int pipefd)
579 {
580 int fd = -1, r;
581
582 switch (pf) {
583 case AF_INET:
584 fd = sock_listen_mptcp("127.0.0.1", "15432");
585 break;
586 case AF_INET6:
587 fd = sock_listen_mptcp("::1", "15432");
588 break;
589 default:
590 xerror("Unknown pf %d\n", pf);
591 break;
592 }
593
594 r = write(pipefd, "conn", 4);
595 assert(r == 4);
596
597 alarm(15);
598 r = xaccept(fd);
599
600 process_one_client(r, pipefd);
601
602 return 0;
603 }
604
test_ip_tos_sockopt(int fd)605 static void test_ip_tos_sockopt(int fd)
606 {
607 uint8_t tos_in, tos_out;
608 socklen_t s;
609 int r;
610
611 tos_in = rand() & 0xfc;
612 r = setsockopt(fd, SOL_IP, IP_TOS, &tos_in, sizeof(tos_out));
613 if (r != 0)
614 die_perror("setsockopt IP_TOS");
615
616 tos_out = 0;
617 s = sizeof(tos_out);
618 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
619 if (r != 0)
620 die_perror("getsockopt IP_TOS");
621
622 if (tos_in != tos_out)
623 xerror("tos %x != %x socklen_t %d\n", tos_in, tos_out, s);
624
625 if (s != 1)
626 xerror("tos should be 1 byte");
627
628 s = 0;
629 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
630 if (r != 0)
631 die_perror("getsockopt IP_TOS 0");
632 if (s != 0)
633 xerror("expect socklen_t == 0");
634
635 s = -1;
636 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
637 if (r != -1 && errno != EINVAL)
638 die_perror("getsockopt IP_TOS did not indicate -EINVAL");
639 if (s != -1)
640 xerror("expect socklen_t == -1");
641 }
642
client(int pipefd)643 static int client(int pipefd)
644 {
645 int fd = -1;
646
647 alarm(15);
648
649 switch (pf) {
650 case AF_INET:
651 fd = sock_connect_mptcp("127.0.0.1", "15432", IPPROTO_MPTCP);
652 break;
653 case AF_INET6:
654 fd = sock_connect_mptcp("::1", "15432", IPPROTO_MPTCP);
655 break;
656 default:
657 xerror("Unknown pf %d\n", pf);
658 }
659
660 test_ip_tos_sockopt(fd);
661
662 connect_one_server(fd, pipefd);
663
664 return 0;
665 }
666
xfork(void)667 static pid_t xfork(void)
668 {
669 pid_t p = fork();
670
671 if (p < 0)
672 die_perror("fork");
673
674 return p;
675 }
676
rcheck(int wstatus,const char * what)677 static int rcheck(int wstatus, const char *what)
678 {
679 if (WIFEXITED(wstatus)) {
680 if (WEXITSTATUS(wstatus) == 0)
681 return 0;
682 fprintf(stderr, "%s exited, status=%d\n", what, WEXITSTATUS(wstatus));
683 return WEXITSTATUS(wstatus);
684 } else if (WIFSIGNALED(wstatus)) {
685 xerror("%s killed by signal %d\n", what, WTERMSIG(wstatus));
686 } else if (WIFSTOPPED(wstatus)) {
687 xerror("%s stopped by signal %d\n", what, WSTOPSIG(wstatus));
688 }
689
690 return 111;
691 }
692
init_rng(void)693 static void init_rng(void)
694 {
695 int fd = open("/dev/urandom", O_RDONLY);
696
697 if (fd >= 0) {
698 unsigned int foo;
699 ssize_t ret;
700
701 /* can't fail */
702 ret = read(fd, &foo, sizeof(foo));
703 assert(ret == sizeof(foo));
704
705 close(fd);
706 srand(foo);
707 } else {
708 srand(time(NULL));
709 }
710 }
711
main(int argc,char * argv[])712 int main(int argc, char *argv[])
713 {
714 int e1, e2, wstatus;
715 pid_t s, c, ret;
716 int pipefds[2];
717
718 parse_opts(argc, argv);
719
720 init_rng();
721
722 e1 = pipe(pipefds);
723 if (e1 < 0)
724 die_perror("pipe");
725
726 s = xfork();
727 if (s == 0)
728 return server(pipefds[1]);
729
730 close(pipefds[1]);
731
732 /* wait until server bound a socket */
733 e1 = read(pipefds[0], &e1, 4);
734 assert(e1 == 4);
735
736 c = xfork();
737 if (c == 0)
738 return client(pipefds[0]);
739
740 close(pipefds[0]);
741
742 ret = waitpid(s, &wstatus, 0);
743 if (ret == -1)
744 die_perror("waitpid");
745 e1 = rcheck(wstatus, "server");
746 ret = waitpid(c, &wstatus, 0);
747 if (ret == -1)
748 die_perror("waitpid");
749 e2 = rcheck(wstatus, "client");
750
751 return e1 ? e1 : e2;
752 }
753