1 // SPDX-License-Identifier: GPL-2.0 2 3 #define _GNU_SOURCE 4 5 #include <errno.h> 6 #include <limits.h> 7 #include <fcntl.h> 8 #include <string.h> 9 #include <stdbool.h> 10 #include <stdint.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <strings.h> 14 #include <unistd.h> 15 16 #include <sys/poll.h> 17 #include <sys/sendfile.h> 18 #include <sys/stat.h> 19 #include <sys/socket.h> 20 #include <sys/types.h> 21 #include <sys/mman.h> 22 23 #include <netdb.h> 24 #include <netinet/in.h> 25 26 #include <linux/tcp.h> 27 28 extern int optind; 29 30 #ifndef IPPROTO_MPTCP 31 #define IPPROTO_MPTCP 262 32 #endif 33 #ifndef TCP_ULP 34 #define TCP_ULP 31 35 #endif 36 37 static int poll_timeout = 10 * 1000; 38 static bool listen_mode; 39 40 enum cfg_mode { 41 CFG_MODE_POLL, 42 CFG_MODE_MMAP, 43 CFG_MODE_SENDFILE, 44 }; 45 46 static enum cfg_mode cfg_mode = CFG_MODE_POLL; 47 static const char *cfg_host; 48 static const char *cfg_port = "12000"; 49 static int cfg_sock_proto = IPPROTO_MPTCP; 50 static bool tcpulp_audit; 51 static int pf = AF_INET; 52 static int cfg_sndbuf; 53 static int cfg_rcvbuf; 54 static bool cfg_join; 55 56 static void die_usage(void) 57 { 58 fprintf(stderr, "Usage: mptcp_connect [-6] [-u] [-s MPTCP|TCP] [-p port] [-m mode]" 59 "[-l] connect_address\n"); 60 fprintf(stderr, "\t-6 use ipv6\n"); 61 fprintf(stderr, "\t-t num -- set poll timeout to num\n"); 62 fprintf(stderr, "\t-S num -- set SO_SNDBUF to num\n"); 63 fprintf(stderr, "\t-R num -- set SO_RCVBUF to num\n"); 64 fprintf(stderr, "\t-p num -- use port num\n"); 65 fprintf(stderr, "\t-m [MPTCP|TCP] -- use tcp or mptcp sockets\n"); 66 fprintf(stderr, "\t-s [mmap|poll] -- use poll (default) or mmap\n"); 67 fprintf(stderr, "\t-u -- check mptcp ulp\n"); 68 exit(1); 69 } 70 71 static const char *getxinfo_strerr(int err) 72 { 73 if (err == EAI_SYSTEM) 74 return strerror(errno); 75 76 return gai_strerror(err); 77 } 78 79 static void xgetnameinfo(const struct sockaddr *addr, socklen_t addrlen, 80 char *host, socklen_t hostlen, 81 char *serv, socklen_t servlen) 82 { 83 int flags = NI_NUMERICHOST | NI_NUMERICSERV; 84 int err = getnameinfo(addr, addrlen, host, hostlen, serv, servlen, 85 flags); 86 87 if (err) { 88 const char *errstr = getxinfo_strerr(err); 89 90 fprintf(stderr, "Fatal: getnameinfo: %s\n", errstr); 91 exit(1); 92 } 93 } 94 95 static void xgetaddrinfo(const char *node, const char *service, 96 const struct addrinfo *hints, 97 struct addrinfo **res) 98 { 99 int err = getaddrinfo(node, service, hints, res); 100 101 if (err) { 102 const char *errstr = getxinfo_strerr(err); 103 104 fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n", 105 node ? node : "", service ? service : "", errstr); 106 exit(1); 107 } 108 } 109 110 static void set_rcvbuf(int fd, unsigned int size) 111 { 112 int err; 113 114 err = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); 115 if (err) { 116 perror("set SO_RCVBUF"); 117 exit(1); 118 } 119 } 120 121 static void set_sndbuf(int fd, unsigned int size) 122 { 123 int err; 124 125 err = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); 126 if (err) { 127 perror("set SO_SNDBUF"); 128 exit(1); 129 } 130 } 131 132 static int sock_listen_mptcp(const char * const listenaddr, 133 const char * const port) 134 { 135 int sock; 136 struct addrinfo hints = { 137 .ai_protocol = IPPROTO_TCP, 138 .ai_socktype = SOCK_STREAM, 139 .ai_flags = AI_PASSIVE | AI_NUMERICHOST 140 }; 141 142 hints.ai_family = pf; 143 144 struct addrinfo *a, *addr; 145 int one = 1; 146 147 xgetaddrinfo(listenaddr, port, &hints, &addr); 148 hints.ai_family = pf; 149 150 for (a = addr; a; a = a->ai_next) { 151 sock = socket(a->ai_family, a->ai_socktype, cfg_sock_proto); 152 if (sock < 0) 153 continue; 154 155 if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, 156 sizeof(one))) 157 perror("setsockopt"); 158 159 if (bind(sock, a->ai_addr, a->ai_addrlen) == 0) 160 break; /* success */ 161 162 perror("bind"); 163 close(sock); 164 sock = -1; 165 } 166 167 freeaddrinfo(addr); 168 169 if (sock < 0) { 170 fprintf(stderr, "Could not create listen socket\n"); 171 return sock; 172 } 173 174 if (listen(sock, 20)) { 175 perror("listen"); 176 close(sock); 177 return -1; 178 } 179 180 return sock; 181 } 182 183 static bool sock_test_tcpulp(const char * const remoteaddr, 184 const char * const port) 185 { 186 struct addrinfo hints = { 187 .ai_protocol = IPPROTO_TCP, 188 .ai_socktype = SOCK_STREAM, 189 }; 190 struct addrinfo *a, *addr; 191 int sock = -1, ret = 0; 192 bool test_pass = false; 193 194 hints.ai_family = AF_INET; 195 196 xgetaddrinfo(remoteaddr, port, &hints, &addr); 197 for (a = addr; a; a = a->ai_next) { 198 sock = socket(a->ai_family, a->ai_socktype, IPPROTO_TCP); 199 if (sock < 0) { 200 perror("socket"); 201 continue; 202 } 203 ret = setsockopt(sock, IPPROTO_TCP, TCP_ULP, "mptcp", 204 sizeof("mptcp")); 205 if (ret == -1 && errno == EOPNOTSUPP) 206 test_pass = true; 207 close(sock); 208 209 if (test_pass) 210 break; 211 if (!ret) 212 fprintf(stderr, 213 "setsockopt(TCP_ULP) returned 0\n"); 214 else 215 perror("setsockopt(TCP_ULP)"); 216 } 217 return test_pass; 218 } 219 220 static int sock_connect_mptcp(const char * const remoteaddr, 221 const char * const port, int proto) 222 { 223 struct addrinfo hints = { 224 .ai_protocol = IPPROTO_TCP, 225 .ai_socktype = SOCK_STREAM, 226 }; 227 struct addrinfo *a, *addr; 228 int sock = -1; 229 230 hints.ai_family = pf; 231 232 xgetaddrinfo(remoteaddr, port, &hints, &addr); 233 for (a = addr; a; a = a->ai_next) { 234 sock = socket(a->ai_family, a->ai_socktype, proto); 235 if (sock < 0) { 236 perror("socket"); 237 continue; 238 } 239 240 if (connect(sock, a->ai_addr, a->ai_addrlen) == 0) 241 break; /* success */ 242 243 perror("connect()"); 244 close(sock); 245 sock = -1; 246 } 247 248 freeaddrinfo(addr); 249 return sock; 250 } 251 252 static size_t do_rnd_write(const int fd, char *buf, const size_t len) 253 { 254 static bool first = true; 255 unsigned int do_w; 256 ssize_t bw; 257 258 do_w = rand() & 0xffff; 259 if (do_w == 0 || do_w > len) 260 do_w = len; 261 262 if (cfg_join && first && do_w > 100) 263 do_w = 100; 264 265 bw = write(fd, buf, do_w); 266 if (bw < 0) 267 perror("write"); 268 269 /* let the join handshake complete, before going on */ 270 if (cfg_join && first) { 271 usleep(200000); 272 first = false; 273 } 274 275 return bw; 276 } 277 278 static size_t do_write(const int fd, char *buf, const size_t len) 279 { 280 size_t offset = 0; 281 282 while (offset < len) { 283 size_t written; 284 ssize_t bw; 285 286 bw = write(fd, buf + offset, len - offset); 287 if (bw < 0) { 288 perror("write"); 289 return 0; 290 } 291 292 written = (size_t)bw; 293 offset += written; 294 } 295 296 return offset; 297 } 298 299 static ssize_t do_rnd_read(const int fd, char *buf, const size_t len) 300 { 301 size_t cap = rand(); 302 303 cap &= 0xffff; 304 305 if (cap == 0) 306 cap = 1; 307 else if (cap > len) 308 cap = len; 309 310 return read(fd, buf, cap); 311 } 312 313 static void set_nonblock(int fd) 314 { 315 int flags = fcntl(fd, F_GETFL); 316 317 if (flags == -1) 318 return; 319 320 fcntl(fd, F_SETFL, flags | O_NONBLOCK); 321 } 322 323 static int copyfd_io_poll(int infd, int peerfd, int outfd) 324 { 325 struct pollfd fds = { 326 .fd = peerfd, 327 .events = POLLIN | POLLOUT, 328 }; 329 unsigned int woff = 0, wlen = 0; 330 char wbuf[8192]; 331 332 set_nonblock(peerfd); 333 334 for (;;) { 335 char rbuf[8192]; 336 ssize_t len; 337 338 if (fds.events == 0) 339 break; 340 341 switch (poll(&fds, 1, poll_timeout)) { 342 case -1: 343 if (errno == EINTR) 344 continue; 345 perror("poll"); 346 return 1; 347 case 0: 348 fprintf(stderr, "%s: poll timed out (events: " 349 "POLLIN %u, POLLOUT %u)\n", __func__, 350 fds.events & POLLIN, fds.events & POLLOUT); 351 return 2; 352 } 353 354 if (fds.revents & POLLIN) { 355 len = do_rnd_read(peerfd, rbuf, sizeof(rbuf)); 356 if (len == 0) { 357 /* no more data to receive: 358 * peer has closed its write side 359 */ 360 fds.events &= ~POLLIN; 361 362 if ((fds.events & POLLOUT) == 0) 363 /* and nothing more to send */ 364 break; 365 366 /* Else, still have data to transmit */ 367 } else if (len < 0) { 368 perror("read"); 369 return 3; 370 } 371 372 do_write(outfd, rbuf, len); 373 } 374 375 if (fds.revents & POLLOUT) { 376 if (wlen == 0) { 377 woff = 0; 378 wlen = read(infd, wbuf, sizeof(wbuf)); 379 } 380 381 if (wlen > 0) { 382 ssize_t bw; 383 384 bw = do_rnd_write(peerfd, wbuf + woff, wlen); 385 if (bw < 0) 386 return 111; 387 388 woff += bw; 389 wlen -= bw; 390 } else if (wlen == 0) { 391 /* We have no more data to send. */ 392 fds.events &= ~POLLOUT; 393 394 if ((fds.events & POLLIN) == 0) 395 /* ... and peer also closed already */ 396 break; 397 398 /* ... but we still receive. 399 * Close our write side, ev. give some time 400 * for address notification 401 */ 402 if (cfg_join) 403 usleep(400000); 404 shutdown(peerfd, SHUT_WR); 405 } else { 406 if (errno == EINTR) 407 continue; 408 perror("read"); 409 return 4; 410 } 411 } 412 413 if (fds.revents & (POLLERR | POLLNVAL)) { 414 fprintf(stderr, "Unexpected revents: " 415 "POLLERR/POLLNVAL(%x)\n", fds.revents); 416 return 5; 417 } 418 } 419 420 /* leave some time for late join/announce */ 421 if (cfg_join) 422 usleep(400000); 423 424 close(peerfd); 425 return 0; 426 } 427 428 static int do_recvfile(int infd, int outfd) 429 { 430 ssize_t r; 431 432 do { 433 char buf[16384]; 434 435 r = do_rnd_read(infd, buf, sizeof(buf)); 436 if (r > 0) { 437 if (write(outfd, buf, r) != r) 438 break; 439 } else if (r < 0) { 440 perror("read"); 441 } 442 } while (r > 0); 443 444 return (int)r; 445 } 446 447 static int do_mmap(int infd, int outfd, unsigned int size) 448 { 449 char *inbuf = mmap(NULL, size, PROT_READ, MAP_SHARED, infd, 0); 450 ssize_t ret = 0, off = 0; 451 size_t rem; 452 453 if (inbuf == MAP_FAILED) { 454 perror("mmap"); 455 return 1; 456 } 457 458 rem = size; 459 460 while (rem > 0) { 461 ret = write(outfd, inbuf + off, rem); 462 463 if (ret < 0) { 464 perror("write"); 465 break; 466 } 467 468 off += ret; 469 rem -= ret; 470 } 471 472 munmap(inbuf, size); 473 return rem; 474 } 475 476 static int get_infd_size(int fd) 477 { 478 struct stat sb; 479 ssize_t count; 480 int err; 481 482 err = fstat(fd, &sb); 483 if (err < 0) { 484 perror("fstat"); 485 return -1; 486 } 487 488 if ((sb.st_mode & S_IFMT) != S_IFREG) { 489 fprintf(stderr, "%s: stdin is not a regular file\n", __func__); 490 return -2; 491 } 492 493 count = sb.st_size; 494 if (count > INT_MAX) { 495 fprintf(stderr, "File too large: %zu\n", count); 496 return -3; 497 } 498 499 return (int)count; 500 } 501 502 static int do_sendfile(int infd, int outfd, unsigned int count) 503 { 504 while (count > 0) { 505 ssize_t r; 506 507 r = sendfile(outfd, infd, NULL, count); 508 if (r < 0) { 509 perror("sendfile"); 510 return 3; 511 } 512 513 count -= r; 514 } 515 516 return 0; 517 } 518 519 static int copyfd_io_mmap(int infd, int peerfd, int outfd, 520 unsigned int size) 521 { 522 int err; 523 524 if (listen_mode) { 525 err = do_recvfile(peerfd, outfd); 526 if (err) 527 return err; 528 529 err = do_mmap(infd, peerfd, size); 530 } else { 531 err = do_mmap(infd, peerfd, size); 532 if (err) 533 return err; 534 535 shutdown(peerfd, SHUT_WR); 536 537 err = do_recvfile(peerfd, outfd); 538 } 539 540 return err; 541 } 542 543 static int copyfd_io_sendfile(int infd, int peerfd, int outfd, 544 unsigned int size) 545 { 546 int err; 547 548 if (listen_mode) { 549 err = do_recvfile(peerfd, outfd); 550 if (err) 551 return err; 552 553 err = do_sendfile(infd, peerfd, size); 554 } else { 555 err = do_sendfile(infd, peerfd, size); 556 if (err) 557 return err; 558 err = do_recvfile(peerfd, outfd); 559 } 560 561 return err; 562 } 563 564 static int copyfd_io(int infd, int peerfd, int outfd) 565 { 566 int file_size; 567 568 switch (cfg_mode) { 569 case CFG_MODE_POLL: 570 return copyfd_io_poll(infd, peerfd, outfd); 571 case CFG_MODE_MMAP: 572 file_size = get_infd_size(infd); 573 if (file_size < 0) 574 return file_size; 575 return copyfd_io_mmap(infd, peerfd, outfd, file_size); 576 case CFG_MODE_SENDFILE: 577 file_size = get_infd_size(infd); 578 if (file_size < 0) 579 return file_size; 580 return copyfd_io_sendfile(infd, peerfd, outfd, file_size); 581 } 582 583 fprintf(stderr, "Invalid mode %d\n", cfg_mode); 584 585 die_usage(); 586 return 1; 587 } 588 589 static void check_sockaddr(int pf, struct sockaddr_storage *ss, 590 socklen_t salen) 591 { 592 struct sockaddr_in6 *sin6; 593 struct sockaddr_in *sin; 594 socklen_t wanted_size = 0; 595 596 switch (pf) { 597 case AF_INET: 598 wanted_size = sizeof(*sin); 599 sin = (void *)ss; 600 if (!sin->sin_port) 601 fprintf(stderr, "accept: something wrong: ip connection from port 0"); 602 break; 603 case AF_INET6: 604 wanted_size = sizeof(*sin6); 605 sin6 = (void *)ss; 606 if (!sin6->sin6_port) 607 fprintf(stderr, "accept: something wrong: ipv6 connection from port 0"); 608 break; 609 default: 610 fprintf(stderr, "accept: Unknown pf %d, salen %u\n", pf, salen); 611 return; 612 } 613 614 if (salen != wanted_size) 615 fprintf(stderr, "accept: size mismatch, got %d expected %d\n", 616 (int)salen, wanted_size); 617 618 if (ss->ss_family != pf) 619 fprintf(stderr, "accept: pf mismatch, expect %d, ss_family is %d\n", 620 (int)ss->ss_family, pf); 621 } 622 623 static void check_getpeername(int fd, struct sockaddr_storage *ss, socklen_t salen) 624 { 625 struct sockaddr_storage peerss; 626 socklen_t peersalen = sizeof(peerss); 627 628 if (getpeername(fd, (struct sockaddr *)&peerss, &peersalen) < 0) { 629 perror("getpeername"); 630 return; 631 } 632 633 if (peersalen != salen) { 634 fprintf(stderr, "%s: %d vs %d\n", __func__, peersalen, salen); 635 return; 636 } 637 638 if (memcmp(ss, &peerss, peersalen)) { 639 char a[INET6_ADDRSTRLEN]; 640 char b[INET6_ADDRSTRLEN]; 641 char c[INET6_ADDRSTRLEN]; 642 char d[INET6_ADDRSTRLEN]; 643 644 xgetnameinfo((struct sockaddr *)ss, salen, 645 a, sizeof(a), b, sizeof(b)); 646 647 xgetnameinfo((struct sockaddr *)&peerss, peersalen, 648 c, sizeof(c), d, sizeof(d)); 649 650 fprintf(stderr, "%s: memcmp failure: accept %s vs peername %s, %s vs %s salen %d vs %d\n", 651 __func__, a, c, b, d, peersalen, salen); 652 } 653 } 654 655 static void check_getpeername_connect(int fd) 656 { 657 struct sockaddr_storage ss; 658 socklen_t salen = sizeof(ss); 659 char a[INET6_ADDRSTRLEN]; 660 char b[INET6_ADDRSTRLEN]; 661 662 if (getpeername(fd, (struct sockaddr *)&ss, &salen) < 0) { 663 perror("getpeername"); 664 return; 665 } 666 667 xgetnameinfo((struct sockaddr *)&ss, salen, 668 a, sizeof(a), b, sizeof(b)); 669 670 if (strcmp(cfg_host, a) || strcmp(cfg_port, b)) 671 fprintf(stderr, "%s: %s vs %s, %s vs %s\n", __func__, 672 cfg_host, a, cfg_port, b); 673 } 674 675 static void maybe_close(int fd) 676 { 677 unsigned int r = rand(); 678 679 if (!cfg_join && (r & 1)) 680 close(fd); 681 } 682 683 int main_loop_s(int listensock) 684 { 685 struct sockaddr_storage ss; 686 struct pollfd polls; 687 socklen_t salen; 688 int remotesock; 689 690 polls.fd = listensock; 691 polls.events = POLLIN; 692 693 switch (poll(&polls, 1, poll_timeout)) { 694 case -1: 695 perror("poll"); 696 return 1; 697 case 0: 698 fprintf(stderr, "%s: timed out\n", __func__); 699 close(listensock); 700 return 2; 701 } 702 703 salen = sizeof(ss); 704 remotesock = accept(listensock, (struct sockaddr *)&ss, &salen); 705 if (remotesock >= 0) { 706 maybe_close(listensock); 707 check_sockaddr(pf, &ss, salen); 708 check_getpeername(remotesock, &ss, salen); 709 710 return copyfd_io(0, remotesock, 1); 711 } 712 713 perror("accept"); 714 715 return 1; 716 } 717 718 static void init_rng(void) 719 { 720 int fd = open("/dev/urandom", O_RDONLY); 721 unsigned int foo; 722 723 if (fd > 0) { 724 int ret = read(fd, &foo, sizeof(foo)); 725 726 if (ret < 0) 727 srand(fd + foo); 728 close(fd); 729 } 730 731 srand(foo); 732 } 733 734 int main_loop(void) 735 { 736 int fd; 737 738 /* listener is ready. */ 739 fd = sock_connect_mptcp(cfg_host, cfg_port, cfg_sock_proto); 740 if (fd < 0) 741 return 2; 742 743 check_getpeername_connect(fd); 744 745 if (cfg_rcvbuf) 746 set_rcvbuf(fd, cfg_rcvbuf); 747 if (cfg_sndbuf) 748 set_sndbuf(fd, cfg_sndbuf); 749 750 return copyfd_io(0, fd, 1); 751 } 752 753 int parse_proto(const char *proto) 754 { 755 if (!strcasecmp(proto, "MPTCP")) 756 return IPPROTO_MPTCP; 757 if (!strcasecmp(proto, "TCP")) 758 return IPPROTO_TCP; 759 760 fprintf(stderr, "Unknown protocol: %s\n.", proto); 761 die_usage(); 762 763 /* silence compiler warning */ 764 return 0; 765 } 766 767 int parse_mode(const char *mode) 768 { 769 if (!strcasecmp(mode, "poll")) 770 return CFG_MODE_POLL; 771 if (!strcasecmp(mode, "mmap")) 772 return CFG_MODE_MMAP; 773 if (!strcasecmp(mode, "sendfile")) 774 return CFG_MODE_SENDFILE; 775 776 fprintf(stderr, "Unknown test mode: %s\n", mode); 777 fprintf(stderr, "Supported modes are:\n"); 778 fprintf(stderr, "\t\t\"poll\" - interleaved read/write using poll()\n"); 779 fprintf(stderr, "\t\t\"mmap\" - send entire input file (mmap+write), then read response (-l will read input first)\n"); 780 fprintf(stderr, "\t\t\"sendfile\" - send entire input file (sendfile), then read response (-l will read input first)\n"); 781 782 die_usage(); 783 784 /* silence compiler warning */ 785 return 0; 786 } 787 788 static int parse_int(const char *size) 789 { 790 unsigned long s; 791 792 errno = 0; 793 794 s = strtoul(size, NULL, 0); 795 796 if (errno) { 797 fprintf(stderr, "Invalid sndbuf size %s (%s)\n", 798 size, strerror(errno)); 799 die_usage(); 800 } 801 802 if (s > INT_MAX) { 803 fprintf(stderr, "Invalid sndbuf size %s (%s)\n", 804 size, strerror(ERANGE)); 805 die_usage(); 806 } 807 808 return (int)s; 809 } 810 811 static void parse_opts(int argc, char **argv) 812 { 813 int c; 814 815 while ((c = getopt(argc, argv, "6jlp:s:hut:m:S:R:")) != -1) { 816 switch (c) { 817 case 'j': 818 cfg_join = true; 819 cfg_mode = CFG_MODE_POLL; 820 break; 821 case 'l': 822 listen_mode = true; 823 break; 824 case 'p': 825 cfg_port = optarg; 826 break; 827 case 's': 828 cfg_sock_proto = parse_proto(optarg); 829 break; 830 case 'h': 831 die_usage(); 832 break; 833 case 'u': 834 tcpulp_audit = true; 835 break; 836 case '6': 837 pf = AF_INET6; 838 break; 839 case 't': 840 poll_timeout = atoi(optarg) * 1000; 841 if (poll_timeout <= 0) 842 poll_timeout = -1; 843 break; 844 case 'm': 845 cfg_mode = parse_mode(optarg); 846 break; 847 case 'S': 848 cfg_sndbuf = parse_int(optarg); 849 break; 850 case 'R': 851 cfg_rcvbuf = parse_int(optarg); 852 break; 853 } 854 } 855 856 if (optind + 1 != argc) 857 die_usage(); 858 cfg_host = argv[optind]; 859 860 if (strchr(cfg_host, ':')) 861 pf = AF_INET6; 862 } 863 864 int main(int argc, char *argv[]) 865 { 866 init_rng(); 867 868 parse_opts(argc, argv); 869 870 if (tcpulp_audit) 871 return sock_test_tcpulp(cfg_host, cfg_port) ? 0 : 1; 872 873 if (listen_mode) { 874 int fd = sock_listen_mptcp(cfg_host, cfg_port); 875 876 if (fd < 0) 877 return 1; 878 879 if (cfg_rcvbuf) 880 set_rcvbuf(fd, cfg_rcvbuf); 881 if (cfg_sndbuf) 882 set_sndbuf(fd, cfg_sndbuf); 883 884 return main_loop_s(fd); 885 } 886 887 return main_loop(); 888 } 889