diff --git a/ChangeLog b/ChangeLog index f3b26f5a2cbaf81a61080249bb4581aa89c509c4..f4a86894985328c8ca73a03d1455a434b870ad19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2012-07-08 Niels Möller <nisse@lysator.liu.se> + + * src/testsuite/Makefile.in (TARGETS): Added tcpconnnect. + + * src/testsuite/tcpconnect.c: New file. Reimplemented the program + with the same name in tcputils. + 2012-06-19 Niels Möller <nisse@lysator.liu.se> * src/transport.c (transport_process_packet): Disconnect on diff --git a/src/testsuite/Makefile.in b/src/testsuite/Makefile.in index 85408b532bbd610f72bc9ffe636972ca8ca2ef0d..a31a746a71edfb323d400cf4c5bc2e077634ccd6 100644 --- a/src/testsuite/Makefile.in +++ b/src/testsuite/Makefile.in @@ -46,7 +46,8 @@ TS_SH = conv-1-test conv-2-test conv-3-test \ TS_C = $(TS_SOURCES:.c=$(EXEEXT)) TS_ALL = $(TS_C) $(TS_SH) -TARGETS = $(TS_C) prgrp-timeout$(EXEEXT) mini-inetd$(EXEEXT) $(PASSWD_PRELOAD) +TARGETS = $(TS_C) prgrp-timeout$(EXEEXT) $(PASSWD_PRELOAD) \ + mini-inetd$(EXEEXT) tcpconnect$(EXEEXT) TEST_OBJS = testutils.$(OBJEXT) \ ../algorithms.$(OBJEXT) \ diff --git a/src/testsuite/tcpconnect.c b/src/testsuite/tcpconnect.c new file mode 100644 index 0000000000000000000000000000000000000000..2c156c88330ab3cd7a4fe001d6fae3501140227b --- /dev/null +++ b/src/testsuite/tcpconnect.c @@ -0,0 +1,368 @@ +/* tcpconnect + * + * This program is an total reimplementation of the old tcpconnect + * from Thomas Bellman's tcputils. + * + * Copyright (C) 2012 Möller + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netdb.h> + +#include <arpa/inet.h> + +#include "getopt.h" + +static void NORETURN PRINTF_STYLE(1, 2) +die(const char *format, ...) +{ + va_list args; + fprintf(stderr, "tcpconnect: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + exit(EXIT_FAILURE); +} + +static void PRINTF_STYLE(1, 2) +werror(const char *format, ...) +{ + va_list args; + fprintf(stderr, "tcpconnect: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +static void +usage (FILE *f) +{ + fprintf(f, + "tcpconnect [OPTIONS] host post\n" + "Options:\n" + " -i Terminate at end-of-file on standard input;\n" + " don't wait for the server to close the connection.\n" + " -r Terminate when the remote server closes the connection;\n" + " don't wait for end-of-file on standard input.\n" + " -v Verbose mode. Prints a message to standard error when\n" + " the connection has been established.\n" + /* FIXME: ':' is a bad separator for literal IPv6 addresses. + Support [] around the address? */ + " -l ADDR:PORT\n" + " Bind the local end-point of the connection to IP address\n" + " ADDR, TCP port PORT. Either the IP address or the port,\n" + " but not both, may be left out, meaning that the operating\n" + " system gets to choose that part by itself.\n" + " -4 Only use IPv4.\n" + " -6 Only use IPv6.\n" + " --help Display this help.\n"); +} + +#define BUF_SIZE 4096 + +int +main (int argc, char **argv) +{ + int family = AF_UNSPEC; + int wait_stdin_eof = 1; + int wait_remote_eof = 1; + int verbose = 0; +#if 0 + const char *local_ip = NULL; + const char *local_port = NULL; +#endif + int c; + + struct addrinfo hints; + struct addrinfo *list, *p; + int flags; + int fd; + + int seen_remote_eof; + int seen_stdin_eof; + + char buf_stdin[BUF_SIZE]; + unsigned buf_size; + unsigned buf_pos; + int err; + + enum { OPT_HELP = 300 }; + static const struct option options[] = + { + /* Name, args, flag, val */ + + { "help", no_argument, NULL, OPT_HELP }, + { NULL, 0, NULL, 0 } + }; + + while ( (c = getopt_long (argc, argv, "46irvl:", options, NULL)) != -1) + switch (c) + { + case '?': + return EXIT_FAILURE; + case OPT_HELP: + usage(stdout); + return EXIT_SUCCESS; + case '4': + family = AF_INET; + break; + case '6': +#ifdef AF_INET6 + family = AF_INET6; + break; +#else + die ("IP version 6 not supported.\n"); +#endif + case 'i': + wait_remote_eof = 0; + break; + case 'r': + wait_stdin_eof = 0; + break; + case 'v': + verbose = 1; + break; + case 'l': + die ("Not implemented.\n"); + break; + } + + argc -= optind; + argv += optind; + + if (argc < 2) + { + usage(stderr); + return EXIT_FAILURE; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + err = getaddrinfo (argv[0], argv[1], &hints, &list); + if (err) + die("Failed to look up address: %s\n", gai_strerror(err)); + + for (p = list, fd = -1; p; p = p->ai_next) + { + fd = socket (p->ai_family, p->ai_socktype, p->ai_protocol); + if (fd < 0) + continue; + if (connect (fd, p->ai_addr, p->ai_addrlen) >= 0) + break; + + close (fd); + fd = -1; + } + + if (fd < 0) + die("Connection failed.\n"); + + if (verbose) + switch (p->ai_family) + { +#ifdef AF_INET6 + case AF_INET6: + { + const struct sockaddr_in6 *sin6 + = (struct sockaddr_in6 *) p->ai_addr; + + char buf[INET6_ADDRSTRLEN]; + if (!inet_ntop (p->ai_family, &sin6->sin6_addr, buf, sizeof(buf))) + die ("inet_ntop failed: %s\n", strerror(errno)); + fprintf(stderr, "[Connected to %s port %d]\n", + buf, ntohs (sin6->sin6_port)); + break; + } +#endif + case AF_INET: + { + const struct sockaddr_in *sin + = (struct sockaddr_in *) p->ai_addr; + + char buf[INET_ADDRSTRLEN]; + if (!inet_ntop (p->ai_family, &sin->sin_addr, buf, sizeof(buf))) + die ("inet_ntop failed: %s\n", strerror(errno)); + fprintf(stderr, "[Connected to %s port %d]\n", + buf, ntohs (sin->sin_port)); + break; + } + default: + die ("Unknown address family %d.\n", p->ai_family); + } + freeaddrinfo (list); + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + werror("fcntl F_GETFL failed: %s\n", strerror(errno)); + else if (fcntl(fd, F_SETFL, flags | 1 | O_NONBLOCK) < 0) + werror("fcntl F_SETFL failed: %s\n", strerror(errno)); + + buf_size = buf_pos = 0; + seen_stdin_eof = seen_remote_eof = 0; + + /* We do blocking reads of stdin and writes to stdout, and use + FIONREAD to avoid blocking on read. We do non-blocking reads and + writes on the sockets. If we can't write, we keep buffered data + in buf_stdin, and don't read any more stdin data until the + buffered data has been written to the socket. */ + for (;;) + { + fd_set read_fds; + fd_set write_fds; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + + if (buf_size > buf_pos) + FD_SET (fd, &write_fds); + else if (!seen_stdin_eof) + FD_SET (STDIN_FILENO, &read_fds); + + if (!seen_remote_eof) + FD_SET (fd, &read_fds); + + if (select (fd + 1, &read_fds, &write_fds, NULL, NULL) < 0) + { + if (errno == EINTR) + continue; + die ("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET (fd, &write_fds)) + { + int res = write(fd, buf_stdin + buf_pos, buf_size - buf_pos); + if (res < 0) + { + if (errno != EINTR) + { + if (verbose) + werror ("write to socket failed: %s\n", strerror(errno)); + break; + } + } + else + buf_pos += res; + } + if (FD_ISSET (STDIN_FILENO, &read_fds)) + { + int nbytes, res; + + if (ioctl (STDIN_FILENO, FIONREAD, &nbytes) < 0 + || nbytes <= 0 + || nbytes > (int) sizeof(buf_stdin)) + nbytes = sizeof(buf_stdin); + + res = read(STDIN_FILENO, buf_stdin, nbytes); + if (res < 0) + { + if (errno != EINTR) + die("read from stdin failed: %s\n", strerror(errno)); + } + else if (res == 0) + { + wait_stdin_eof = 0; + if (!wait_remote_eof) + break; + if (shutdown (fd, SHUT_WR) < 0 && errno != ENOTCONN) + die("shutdown failed: %s\n", strerror(errno)); + } + else + { + nbytes = res; + res = write(fd, buf_stdin, nbytes); + if (res < 0) + { + if (errno != EINTR) + { + if (verbose) + werror ("write to socket failed: %s\n", strerror(errno)); + break; + } + res = 0; + } + if (res < nbytes) + { + buf_size = nbytes; + buf_pos = res; + } + } + } + if (FD_ISSET (fd, &read_fds)) + { + char buf_remote[BUF_SIZE]; + int res = read (fd, buf_remote, sizeof (buf_remote)); + + if (res <= 0) + { + /* Treat read errors in the same way as EOF. */ + if (res < 0 && (errno == EINTR || errno == EWOULDBLOCK)) + ; /* Do nothing */ + else + { + if (verbose && res < 0) + werror("read from socket failed: %s\n", strerror(errno)); + + wait_remote_eof = 0; + if (!wait_stdin_eof) + break; + + /* Useful only in case stdout happens to be a socket. */ + shutdown (STDOUT_FILENO, SHUT_WR); + } + } + else + { + int size = res; + int done = 0; + while (done < size) + { + res = write(STDOUT_FILENO, buf_remote + done, size - done); + if (res < 0) + { + if (errno != EINTR) + die ("write to stdout failed: %s\n", strerror(errno)); + } + else + done += res; + } + } + } + } + close (fd); + + if (verbose) + fprintf(stderr, "[Connection closed]"); + + return EXIT_SUCCESS; +}