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