0001
0002
0003 #define _GNU_SOURCE
0004
0005 #include <assert.h>
0006 #include <errno.h>
0007 #include <fcntl.h>
0008 #include <limits.h>
0009 #include <string.h>
0010 #include <stdarg.h>
0011 #include <stdbool.h>
0012 #include <stdint.h>
0013 #include <inttypes.h>
0014 #include <stdio.h>
0015 #include <stdlib.h>
0016 #include <strings.h>
0017 #include <time.h>
0018 #include <unistd.h>
0019
0020 #include <sys/socket.h>
0021 #include <sys/types.h>
0022 #include <sys/wait.h>
0023
0024 #include <netdb.h>
0025 #include <netinet/in.h>
0026
0027 #include <linux/tcp.h>
0028
0029 static int pf = AF_INET;
0030
0031 #ifndef IPPROTO_MPTCP
0032 #define IPPROTO_MPTCP 262
0033 #endif
0034 #ifndef SOL_MPTCP
0035 #define SOL_MPTCP 284
0036 #endif
0037
0038 #ifndef MPTCP_INFO
0039 struct mptcp_info {
0040 __u8 mptcpi_subflows;
0041 __u8 mptcpi_add_addr_signal;
0042 __u8 mptcpi_add_addr_accepted;
0043 __u8 mptcpi_subflows_max;
0044 __u8 mptcpi_add_addr_signal_max;
0045 __u8 mptcpi_add_addr_accepted_max;
0046 __u32 mptcpi_flags;
0047 __u32 mptcpi_token;
0048 __u64 mptcpi_write_seq;
0049 __u64 mptcpi_snd_una;
0050 __u64 mptcpi_rcv_nxt;
0051 __u8 mptcpi_local_addr_used;
0052 __u8 mptcpi_local_addr_max;
0053 __u8 mptcpi_csum_enabled;
0054 };
0055
0056 struct mptcp_subflow_data {
0057 __u32 size_subflow_data;
0058 __u32 num_subflows;
0059 __u32 size_kernel;
0060 __u32 size_user;
0061 } __attribute__((aligned(8)));
0062
0063 struct mptcp_subflow_addrs {
0064 union {
0065 __kernel_sa_family_t sa_family;
0066 struct sockaddr sa_local;
0067 struct sockaddr_in sin_local;
0068 struct sockaddr_in6 sin6_local;
0069 struct __kernel_sockaddr_storage ss_local;
0070 };
0071 union {
0072 struct sockaddr sa_remote;
0073 struct sockaddr_in sin_remote;
0074 struct sockaddr_in6 sin6_remote;
0075 struct __kernel_sockaddr_storage ss_remote;
0076 };
0077 };
0078
0079 #define MPTCP_INFO 1
0080 #define MPTCP_TCPINFO 2
0081 #define MPTCP_SUBFLOW_ADDRS 3
0082 #endif
0083
0084 struct so_state {
0085 struct mptcp_info mi;
0086 uint64_t mptcpi_rcv_delta;
0087 uint64_t tcpi_rcv_delta;
0088 };
0089
0090 static void die_perror(const char *msg)
0091 {
0092 perror(msg);
0093 exit(1);
0094 }
0095
0096 static void die_usage(int r)
0097 {
0098 fprintf(stderr, "Usage: mptcp_sockopt [-6]\n");
0099 exit(r);
0100 }
0101
0102 static void xerror(const char *fmt, ...)
0103 {
0104 va_list ap;
0105
0106 va_start(ap, fmt);
0107 vfprintf(stderr, fmt, ap);
0108 va_end(ap);
0109 fputc('\n', stderr);
0110 exit(1);
0111 }
0112
0113 static const char *getxinfo_strerr(int err)
0114 {
0115 if (err == EAI_SYSTEM)
0116 return strerror(errno);
0117
0118 return gai_strerror(err);
0119 }
0120
0121 static void xgetaddrinfo(const char *node, const char *service,
0122 const struct addrinfo *hints,
0123 struct addrinfo **res)
0124 {
0125 int err = getaddrinfo(node, service, hints, res);
0126
0127 if (err) {
0128 const char *errstr = getxinfo_strerr(err);
0129
0130 fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n",
0131 node ? node : "", service ? service : "", errstr);
0132 exit(1);
0133 }
0134 }
0135
0136 static int sock_listen_mptcp(const char * const listenaddr,
0137 const char * const port)
0138 {
0139 int sock = -1;
0140 struct addrinfo hints = {
0141 .ai_protocol = IPPROTO_TCP,
0142 .ai_socktype = SOCK_STREAM,
0143 .ai_flags = AI_PASSIVE | AI_NUMERICHOST
0144 };
0145
0146 hints.ai_family = pf;
0147
0148 struct addrinfo *a, *addr;
0149 int one = 1;
0150
0151 xgetaddrinfo(listenaddr, port, &hints, &addr);
0152 hints.ai_family = pf;
0153
0154 for (a = addr; a; a = a->ai_next) {
0155 sock = socket(a->ai_family, a->ai_socktype, IPPROTO_MPTCP);
0156 if (sock < 0)
0157 continue;
0158
0159 if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one,
0160 sizeof(one)))
0161 perror("setsockopt");
0162
0163 if (bind(sock, a->ai_addr, a->ai_addrlen) == 0)
0164 break;
0165
0166 perror("bind");
0167 close(sock);
0168 sock = -1;
0169 }
0170
0171 freeaddrinfo(addr);
0172
0173 if (sock < 0)
0174 xerror("could not create listen socket");
0175
0176 if (listen(sock, 20))
0177 die_perror("listen");
0178
0179 return sock;
0180 }
0181
0182 static int sock_connect_mptcp(const char * const remoteaddr,
0183 const char * const port, int proto)
0184 {
0185 struct addrinfo hints = {
0186 .ai_protocol = IPPROTO_TCP,
0187 .ai_socktype = SOCK_STREAM,
0188 };
0189 struct addrinfo *a, *addr;
0190 int sock = -1;
0191
0192 hints.ai_family = pf;
0193
0194 xgetaddrinfo(remoteaddr, port, &hints, &addr);
0195 for (a = addr; a; a = a->ai_next) {
0196 sock = socket(a->ai_family, a->ai_socktype, proto);
0197 if (sock < 0)
0198 continue;
0199
0200 if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
0201 break;
0202
0203 die_perror("connect");
0204 }
0205
0206 if (sock < 0)
0207 xerror("could not create connect socket");
0208
0209 freeaddrinfo(addr);
0210 return sock;
0211 }
0212
0213 static void parse_opts(int argc, char **argv)
0214 {
0215 int c;
0216
0217 while ((c = getopt(argc, argv, "h6")) != -1) {
0218 switch (c) {
0219 case 'h':
0220 die_usage(0);
0221 break;
0222 case '6':
0223 pf = AF_INET6;
0224 break;
0225 default:
0226 die_usage(1);
0227 break;
0228 }
0229 }
0230 }
0231
0232 static void do_getsockopt_bogus_sf_data(int fd, int optname)
0233 {
0234 struct mptcp_subflow_data good_data;
0235 struct bogus_data {
0236 struct mptcp_subflow_data d;
0237 char buf[2];
0238 } bd;
0239 socklen_t olen, _olen;
0240 int ret;
0241
0242 memset(&bd, 0, sizeof(bd));
0243 memset(&good_data, 0, sizeof(good_data));
0244
0245 olen = sizeof(good_data);
0246 good_data.size_subflow_data = olen;
0247
0248 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0249 assert(ret < 0);
0250 assert(olen == sizeof(good_data));
0251
0252 bd.d = good_data;
0253
0254 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0255 assert(ret == 0);
0256 assert(olen == sizeof(good_data));
0257 assert(bd.d.num_subflows == 1);
0258 assert(bd.d.size_kernel > 0);
0259 assert(bd.d.size_user == 0);
0260
0261 bd.d = good_data;
0262 _olen = rand() % olen;
0263 olen = _olen;
0264 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0265 assert(ret < 0);
0266 assert(olen == _olen);
0267
0268 bd.d = good_data;
0269 olen = sizeof(good_data);
0270 bd.d.size_kernel = 1;
0271 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0272 assert(ret < 0);
0273
0274 bd.d = good_data;
0275 olen = sizeof(good_data);
0276 bd.d.num_subflows = 1;
0277 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0278 assert(ret < 0);
0279
0280
0281 bd.d = good_data;
0282 olen = sizeof(bd);
0283 bd.d.size_subflow_data = sizeof(bd);
0284
0285 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
0286 assert(ret == 0);
0287
0288
0289 assert(olen == sizeof(good_data));
0290
0291 assert(bd.d.size_subflow_data == sizeof(bd));
0292
0293 bd.d = good_data;
0294 bd.d.size_subflow_data += 1;
0295 bd.d.size_user = 1;
0296 olen = bd.d.size_subflow_data + 1;
0297 _olen = olen;
0298
0299 ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &_olen);
0300 assert(ret == 0);
0301
0302
0303 assert(olen == _olen);
0304
0305 assert(bd.d.size_subflow_data == sizeof(good_data) + 1);
0306 assert(bd.buf[0] == 0);
0307 }
0308
0309 static void do_getsockopt_mptcp_info(struct so_state *s, int fd, size_t w)
0310 {
0311 struct mptcp_info i;
0312 socklen_t olen;
0313 int ret;
0314
0315 olen = sizeof(i);
0316 ret = getsockopt(fd, SOL_MPTCP, MPTCP_INFO, &i, &olen);
0317
0318 if (ret < 0)
0319 die_perror("getsockopt MPTCP_INFO");
0320
0321 assert(olen == sizeof(i));
0322
0323 if (s->mi.mptcpi_write_seq == 0)
0324 s->mi = i;
0325
0326 assert(s->mi.mptcpi_write_seq + w == i.mptcpi_write_seq);
0327
0328 s->mptcpi_rcv_delta = i.mptcpi_rcv_nxt - s->mi.mptcpi_rcv_nxt;
0329 }
0330
0331 static void do_getsockopt_tcp_info(struct so_state *s, int fd, size_t r, size_t w)
0332 {
0333 struct my_tcp_info {
0334 struct mptcp_subflow_data d;
0335 struct tcp_info ti[2];
0336 } ti;
0337 int ret, tries = 5;
0338 socklen_t olen;
0339
0340 do {
0341 memset(&ti, 0, sizeof(ti));
0342
0343 ti.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
0344 ti.d.size_user = sizeof(struct tcp_info);
0345 olen = sizeof(ti);
0346
0347 ret = getsockopt(fd, SOL_MPTCP, MPTCP_TCPINFO, &ti, &olen);
0348 if (ret < 0)
0349 xerror("getsockopt MPTCP_TCPINFO (tries %d, %m)");
0350
0351 assert(olen <= sizeof(ti));
0352 assert(ti.d.size_user == ti.d.size_kernel);
0353 assert(ti.d.size_user == sizeof(struct tcp_info));
0354 assert(ti.d.num_subflows == 1);
0355
0356 assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
0357 olen -= sizeof(struct mptcp_subflow_data);
0358 assert(olen == sizeof(struct tcp_info));
0359
0360 if (ti.ti[0].tcpi_bytes_sent == w &&
0361 ti.ti[0].tcpi_bytes_received == r)
0362 goto done;
0363
0364 if (r == 0 && ti.ti[0].tcpi_bytes_sent == w &&
0365 ti.ti[0].tcpi_bytes_received) {
0366 s->tcpi_rcv_delta = ti.ti[0].tcpi_bytes_received;
0367 goto done;
0368 }
0369
0370
0371 sleep(1);
0372 } while (tries-- > 0);
0373
0374 xerror("tcpi_bytes_sent %" PRIu64 ", want %zu. tcpi_bytes_received %" PRIu64 ", want %zu",
0375 ti.ti[0].tcpi_bytes_sent, w, ti.ti[0].tcpi_bytes_received, r);
0376
0377 done:
0378 do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO);
0379 }
0380
0381 static void do_getsockopt_subflow_addrs(int fd)
0382 {
0383 struct sockaddr_storage remote, local;
0384 socklen_t olen, rlen, llen;
0385 int ret;
0386 struct my_addrs {
0387 struct mptcp_subflow_data d;
0388 struct mptcp_subflow_addrs addr[2];
0389 } addrs;
0390
0391 memset(&addrs, 0, sizeof(addrs));
0392 memset(&local, 0, sizeof(local));
0393 memset(&remote, 0, sizeof(remote));
0394
0395 addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
0396 addrs.d.size_user = sizeof(struct mptcp_subflow_addrs);
0397 olen = sizeof(addrs);
0398
0399 ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
0400 if (ret < 0)
0401 die_perror("getsockopt MPTCP_SUBFLOW_ADDRS");
0402
0403 assert(olen <= sizeof(addrs));
0404 assert(addrs.d.size_user == addrs.d.size_kernel);
0405 assert(addrs.d.size_user == sizeof(struct mptcp_subflow_addrs));
0406 assert(addrs.d.num_subflows == 1);
0407
0408 assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
0409 olen -= sizeof(struct mptcp_subflow_data);
0410 assert(olen == sizeof(struct mptcp_subflow_addrs));
0411
0412 llen = sizeof(local);
0413 ret = getsockname(fd, (struct sockaddr *)&local, &llen);
0414 if (ret < 0)
0415 die_perror("getsockname");
0416 rlen = sizeof(remote);
0417 ret = getpeername(fd, (struct sockaddr *)&remote, &rlen);
0418 if (ret < 0)
0419 die_perror("getpeername");
0420
0421 assert(rlen > 0);
0422 assert(rlen == llen);
0423
0424 assert(remote.ss_family == local.ss_family);
0425
0426 assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) == 0);
0427 assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) == 0);
0428
0429 memset(&addrs, 0, sizeof(addrs));
0430
0431 addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
0432 addrs.d.size_user = sizeof(sa_family_t);
0433 olen = sizeof(addrs.d) + sizeof(sa_family_t);
0434
0435 ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
0436 assert(ret == 0);
0437 assert(olen == sizeof(addrs.d) + sizeof(sa_family_t));
0438
0439 assert(addrs.addr[0].sa_family == pf);
0440 assert(addrs.addr[0].sa_family == local.ss_family);
0441
0442 assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) != 0);
0443 assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) != 0);
0444
0445 do_getsockopt_bogus_sf_data(fd, MPTCP_SUBFLOW_ADDRS);
0446 }
0447
0448 static void do_getsockopts(struct so_state *s, int fd, size_t r, size_t w)
0449 {
0450 do_getsockopt_mptcp_info(s, fd, w);
0451
0452 do_getsockopt_tcp_info(s, fd, r, w);
0453
0454 do_getsockopt_subflow_addrs(fd);
0455 }
0456
0457 static void connect_one_server(int fd, int pipefd)
0458 {
0459 char buf[4096], buf2[4096];
0460 size_t len, i, total;
0461 struct so_state s;
0462 bool eof = false;
0463 ssize_t ret;
0464
0465 memset(&s, 0, sizeof(s));
0466
0467 len = rand() % (sizeof(buf) - 1);
0468
0469 if (len < 128)
0470 len = 128;
0471
0472 for (i = 0; i < len ; i++) {
0473 buf[i] = rand() % 26;
0474 buf[i] += 'A';
0475 }
0476
0477 buf[i] = '\n';
0478
0479 do_getsockopts(&s, fd, 0, 0);
0480
0481
0482 ret = read(pipefd, buf2, 4);
0483 assert(ret == 4);
0484 close(pipefd);
0485
0486 assert(strncmp(buf2, "xmit", 4) == 0);
0487
0488 ret = write(fd, buf, len);
0489 if (ret < 0)
0490 die_perror("write");
0491
0492 if (ret != (ssize_t)len)
0493 xerror("short write");
0494
0495 total = 0;
0496 do {
0497 ret = read(fd, buf2 + total, sizeof(buf2) - total);
0498 if (ret < 0)
0499 die_perror("read");
0500 if (ret == 0) {
0501 eof = true;
0502 break;
0503 }
0504
0505 total += ret;
0506 } while (total < len);
0507
0508 if (total != len)
0509 xerror("total %lu, len %lu eof %d\n", total, len, eof);
0510
0511 if (memcmp(buf, buf2, len))
0512 xerror("data corruption");
0513
0514 if (s.tcpi_rcv_delta)
0515 assert(s.tcpi_rcv_delta <= total);
0516
0517 do_getsockopts(&s, fd, ret, ret);
0518
0519 if (eof)
0520 total += 1;
0521
0522 assert(s.mptcpi_rcv_delta == (uint64_t)total);
0523 close(fd);
0524 }
0525
0526 static void process_one_client(int fd, int pipefd)
0527 {
0528 ssize_t ret, ret2, ret3;
0529 struct so_state s;
0530 char buf[4096];
0531
0532 memset(&s, 0, sizeof(s));
0533 do_getsockopts(&s, fd, 0, 0);
0534
0535 ret = write(pipefd, "xmit", 4);
0536 assert(ret == 4);
0537
0538 ret = read(fd, buf, sizeof(buf));
0539 if (ret < 0)
0540 die_perror("read");
0541
0542 assert(s.mptcpi_rcv_delta <= (uint64_t)ret);
0543
0544 if (s.tcpi_rcv_delta)
0545 assert(s.tcpi_rcv_delta == (uint64_t)ret);
0546
0547 ret2 = write(fd, buf, ret);
0548 if (ret2 < 0)
0549 die_perror("write");
0550
0551
0552 ret3 = read(fd, buf, 1);
0553 if (ret3 != 0)
0554 xerror("expected EOF, got %lu", ret3);
0555
0556 do_getsockopts(&s, fd, ret, ret2);
0557 if (s.mptcpi_rcv_delta != (uint64_t)ret + 1)
0558 xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64, s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - ret);
0559 close(fd);
0560 }
0561
0562 static int xaccept(int s)
0563 {
0564 int fd = accept(s, NULL, 0);
0565
0566 if (fd < 0)
0567 die_perror("accept");
0568
0569 return fd;
0570 }
0571
0572 static int server(int pipefd)
0573 {
0574 int fd = -1, r;
0575
0576 switch (pf) {
0577 case AF_INET:
0578 fd = sock_listen_mptcp("127.0.0.1", "15432");
0579 break;
0580 case AF_INET6:
0581 fd = sock_listen_mptcp("::1", "15432");
0582 break;
0583 default:
0584 xerror("Unknown pf %d\n", pf);
0585 break;
0586 }
0587
0588 r = write(pipefd, "conn", 4);
0589 assert(r == 4);
0590
0591 alarm(15);
0592 r = xaccept(fd);
0593
0594 process_one_client(r, pipefd);
0595
0596 return 0;
0597 }
0598
0599 static void test_ip_tos_sockopt(int fd)
0600 {
0601 uint8_t tos_in, tos_out;
0602 socklen_t s;
0603 int r;
0604
0605 tos_in = rand() & 0xfc;
0606 r = setsockopt(fd, SOL_IP, IP_TOS, &tos_in, sizeof(tos_out));
0607 if (r != 0)
0608 die_perror("setsockopt IP_TOS");
0609
0610 tos_out = 0;
0611 s = sizeof(tos_out);
0612 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
0613 if (r != 0)
0614 die_perror("getsockopt IP_TOS");
0615
0616 if (tos_in != tos_out)
0617 xerror("tos %x != %x socklen_t %d\n", tos_in, tos_out, s);
0618
0619 if (s != 1)
0620 xerror("tos should be 1 byte");
0621
0622 s = 0;
0623 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
0624 if (r != 0)
0625 die_perror("getsockopt IP_TOS 0");
0626 if (s != 0)
0627 xerror("expect socklen_t == 0");
0628
0629 s = -1;
0630 r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
0631 if (r != -1 && errno != EINVAL)
0632 die_perror("getsockopt IP_TOS did not indicate -EINVAL");
0633 if (s != -1)
0634 xerror("expect socklen_t == -1");
0635 }
0636
0637 static int client(int pipefd)
0638 {
0639 int fd = -1;
0640
0641 alarm(15);
0642
0643 switch (pf) {
0644 case AF_INET:
0645 fd = sock_connect_mptcp("127.0.0.1", "15432", IPPROTO_MPTCP);
0646 break;
0647 case AF_INET6:
0648 fd = sock_connect_mptcp("::1", "15432", IPPROTO_MPTCP);
0649 break;
0650 default:
0651 xerror("Unknown pf %d\n", pf);
0652 }
0653
0654 test_ip_tos_sockopt(fd);
0655
0656 connect_one_server(fd, pipefd);
0657
0658 return 0;
0659 }
0660
0661 static pid_t xfork(void)
0662 {
0663 pid_t p = fork();
0664
0665 if (p < 0)
0666 die_perror("fork");
0667
0668 return p;
0669 }
0670
0671 static int rcheck(int wstatus, const char *what)
0672 {
0673 if (WIFEXITED(wstatus)) {
0674 if (WEXITSTATUS(wstatus) == 0)
0675 return 0;
0676 fprintf(stderr, "%s exited, status=%d\n", what, WEXITSTATUS(wstatus));
0677 return WEXITSTATUS(wstatus);
0678 } else if (WIFSIGNALED(wstatus)) {
0679 xerror("%s killed by signal %d\n", what, WTERMSIG(wstatus));
0680 } else if (WIFSTOPPED(wstatus)) {
0681 xerror("%s stopped by signal %d\n", what, WSTOPSIG(wstatus));
0682 }
0683
0684 return 111;
0685 }
0686
0687 static void init_rng(void)
0688 {
0689 int fd = open("/dev/urandom", O_RDONLY);
0690
0691 if (fd >= 0) {
0692 unsigned int foo;
0693 ssize_t ret;
0694
0695
0696 ret = read(fd, &foo, sizeof(foo));
0697 assert(ret == sizeof(foo));
0698
0699 close(fd);
0700 srand(foo);
0701 } else {
0702 srand(time(NULL));
0703 }
0704 }
0705
0706 int main(int argc, char *argv[])
0707 {
0708 int e1, e2, wstatus;
0709 pid_t s, c, ret;
0710 int pipefds[2];
0711
0712 parse_opts(argc, argv);
0713
0714 init_rng();
0715
0716 e1 = pipe(pipefds);
0717 if (e1 < 0)
0718 die_perror("pipe");
0719
0720 s = xfork();
0721 if (s == 0)
0722 return server(pipefds[1]);
0723
0724 close(pipefds[1]);
0725
0726
0727 e1 = read(pipefds[0], &e1, 4);
0728 assert(e1 == 4);
0729
0730 c = xfork();
0731 if (c == 0)
0732 return client(pipefds[0]);
0733
0734 close(pipefds[0]);
0735
0736 ret = waitpid(s, &wstatus, 0);
0737 if (ret == -1)
0738 die_perror("waitpid");
0739 e1 = rcheck(wstatus, "server");
0740 ret = waitpid(c, &wstatus, 0);
0741 if (ret == -1)
0742 die_perror("waitpid");
0743 e2 = rcheck(wstatus, "client");
0744
0745 return e1 ? e1 : e2;
0746 }