cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

mptcp_sockopt.c (15812B)


      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 = -1;
    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}