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
die_perror(const char * msg)90 static void die_perror(const char *msg)
91 {
92 perror(msg);
93 exit(1);
94 }
95
die_usage(int r)96 static void die_usage(int r)
97 {
98 fprintf(stderr, "Usage: mptcp_sockopt [-6]\n");
99 exit(r);
100 }
101
xerror(const char * fmt,...)102 static 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
getxinfo_strerr(int err)113 static const char *getxinfo_strerr(int err)
114 {
115 if (err == EAI_SYSTEM)
116 return strerror(errno);
117
118 return gai_strerror(err);
119 }
120
xgetaddrinfo(const char * node,const char * service,const struct addrinfo * hints,struct addrinfo ** res)121 static 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
sock_listen_mptcp(const char * const listenaddr,const char * const port)136 static 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
sock_connect_mptcp(const char * const remoteaddr,const char * const port,int proto)182 static 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
parse_opts(int argc,char ** argv)213 static 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
do_getsockopt_bogus_sf_data(int fd,int optname)232 static 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
do_getsockopt_mptcp_info(struct so_state * s,int fd,size_t w)309 static 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
do_getsockopt_tcp_info(struct so_state * s,int fd,size_t r,size_t w)331 static 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
377 done:
378 do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO);
379 }
380
do_getsockopt_subflow_addrs(int fd)381 static 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
do_getsockopts(struct so_state * s,int fd,size_t r,size_t w)448 static 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
connect_one_server(int fd,int pipefd)457 static 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
process_one_client(int fd,int pipefd)526 static 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
xaccept(int s)562 static 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
server(int pipefd)572 static 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
test_ip_tos_sockopt(int fd)599 static 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
client(int pipefd)637 static 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
xfork(void)661 static 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
rcheck(int wstatus,const char * what)671 static 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
init_rng(void)687 static 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
main(int argc,char * argv[])706 int 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