summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/port/TODOS7
-rw-r--r--include/wolf/network/network.h65
-rw-r--r--tests/network/GNUmakefile3
-rw-r--r--tests/network/test1.c20
-rw-r--r--tests/network/test2.c506
5 files changed, 583 insertions, 18 deletions
diff --git a/docs/port/TODOS b/docs/port/TODOS
index d603d72..41593f9 100644
--- a/docs/port/TODOS
+++ b/docs/port/TODOS
@@ -1,8 +1,7 @@
-- fix the gcc -std=c99 problem on solaris 10 (header file
- /usr/include/sys/feature_tests.h), this also means we
- must clean up the compiler flags in the makefiles
+- the HAVE_XXX flags could produce a name clash with some autoconf'ed
+ software/library we will be using in production code. Rename to
+ WOLF_SYS_HAVE_XXX or similar
- feedback to the c99 snprintf guy, errors like:
- -1 instead of buffer size on boffer overflow
- extern errno in mutli-threaded code
- add a lot more tests
-- go to Win32
diff --git a/include/wolf/network/network.h b/include/wolf/network/network.h
new file mode 100644
index 0000000..34f5511
--- /dev/null
+++ b/include/wolf/network/network.h
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) 2008 Andreas Baumann <abaumann@yahoo.com>
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef WOLF_NETWORK_H
+#define WOLF_NETWORK_H
+
+/**
+ * @addtogroup wolf_networking support for networking
+ * @{
+ */
+
+/**
+ * @file network.h
+ * @brief Portable helper functions for networking
+ * @author Andreas Baumann <abaumann@yahoo.com>
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "port/sys.h"
+
+#if defined HAVE_IPV6
+/**
+ * @brief defined when we have proper IPv6 ready on the system
+ */
+#define WOLF_NETWORK_HAVE_IPV6
+#endif
+
+/**
+ * helper union to avoid anti-aliasing warnings in old network functions like getpeername,
+ * use instead of struct sockaddr_storage when declaring socket address variables which need
+ * proper alignment
+ */
+typedef union wolf_network_sockaddr_union_t {
+ struct sockaddr_storage storage;
+ struct sockaddr_in in;
+#ifdef WOLF_NETWORK_HAVE_IPV6
+ struct sockaddr_in6 in6;
+#endif
+ struct sockaddr addr;
+} wolf_network_sockaddr_union_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */ /* @addtogroup wolf_networking */
+
+#endif /* ifndef WOLF_NETWORK_H */
diff --git a/tests/network/GNUmakefile b/tests/network/GNUmakefile
index 0b4f44d..13dceb7 100644
--- a/tests/network/GNUmakefile
+++ b/tests/network/GNUmakefile
@@ -7,7 +7,8 @@ INCLUDE_LIBS = \
$(TOPDIR)/src/libwolf.a
TEST_BINS = \
- test1$(EXE)
+ test1$(EXE) \
+ test2$(EXE)
-include $(TOPDIR)/makefiles/gmake/sub.mk
diff --git a/tests/network/test1.c b/tests/network/test1.c
index 954b665..79e9935 100644
--- a/tests/network/test1.c
+++ b/tests/network/test1.c
@@ -5,6 +5,7 @@
*/
#include "port/netdb.h" /* for getaddrinfo, getnameinfo */
+#include "network/network.h" /* for networking stuff */
#include "port/stdlib.h" /* for EXIT_XXX */
#include "port/stdio.h" /* for fprintf */
#include "port/string.h" /* for memset */
@@ -65,16 +66,6 @@ static void term_handler( int sig ) {
WOLF_UNUSED( sig );
terminate = true;
}
-
-/* helper union to avoid anti-aliasing warnings in old network functions like getpeername */
-union sockaddr_union {
- struct sockaddr_storage storage;
- struct sockaddr_in in;
-#ifdef HAVE_IPV6
- struct sockaddr_in6 in6;
-#endif
- struct sockaddr addr;
-};
int main( int argc, char *argv[] ) {
char *host;
@@ -104,7 +95,7 @@ int main( int argc, char *argv[] ) {
fd_set write_set;
struct timeval timeout;
socklen_t peer_addr_len;
- union sockaddr_union peer_addr;
+ wolf_network_sockaddr_union_t peer_addr;
char peer_hostname[NI_MAXHOST] = "";
char peer_service[NI_MAXSERV] = "";
@@ -476,11 +467,11 @@ END:
goto FAIL;
}
- freeaddrinfo( result );
-
goto OK;
FAIL:
+ freeaddrinfo( result );
+
#ifdef _WIN32
WSACleanup( );
#endif
@@ -488,5 +479,8 @@ FAIL:
return EXIT_FAILURE;
OK:
+
+ freeaddrinfo( result );
+
return EXIT_SUCCESS;
}
diff --git a/tests/network/test2.c b/tests/network/test2.c
new file mode 100644
index 0000000..9b30a9d
--- /dev/null
+++ b/tests/network/test2.c
@@ -0,0 +1,506 @@
+/* Unix traditional and most portable way of a TCP/IP server using select().
+ *
+ * Also tests asynchrononous accept (to avoid DoSAs on blocking accepts).
+ */
+
+#include "port/netdb.h" /* for getaddrinfo, getnameinfo */
+#include "network/network.h" /* for networking stuff */
+#include "port/stdlib.h" /* for EXIT_XXX */
+#include "port/stdio.h" /* for fprintf */
+#include "port/string.h" /* for memset */
+#include "port/stdbool.h" /* for bool, true, false */
+#include "port/unused.h" /* for WOLF_UNUSED */
+
+#include <unistd.h> /* for close */
+#include <errno.h> /* for errno */
+#include <fcntl.h> /* for fcntl */
+#include <sys/select.h> /* for select */
+#include <assert.h> /* for assertions */
+#include <signal.h> /* for signal */
+
+#define DEBUG 0
+
+static bool wolf_network_sock_nonblocking( int fd ) {
+ int flags;
+
+ flags = fcntl( fd, F_GETFL, 0 /* ignored */ );
+ if( flags < 0 ) return false;
+ flags |= O_NONBLOCK;
+ flags = fcntl( fd, F_SETFL, flags );
+ if( flags < 0 ) return false;
+ return true;
+}
+
+int main( int argc, char* argv[] ) {
+ int error;
+ int serv_fd;
+ int res;
+ char *host;
+ char *service;
+ struct addrinfo hints;
+ struct addrinfo *result;
+ wolf_network_sockaddr_union_t client_addr;
+ socklen_t client_addr_len;
+
+ if( argc != 3 ) {
+ fprintf( stderr, "usage: test2 <host> <port>\n" );
+ goto FAIL;
+ }
+
+ host = argv[1];
+ service = argv[2];
+
+#ifdef _WIN32
+ WSADATA wsa_data;
+ WSAStartup( MAKEWORD( 2, 2 ), &wsa_data );
+#endif
+
+ /* tell getaddrinfo what we want */
+ memset( &hints, 0, sizeof( struct addrinfo ) );
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ /* resolve the domain name into a list of addresses */
+ error = getaddrinfo( host, service, &hints, &result );
+ if( error != 0 ) {
+ fprintf( stderr, "getaddrinfo failed: %s (%d)\n",
+ gai_strerror( error ), error );
+ goto FAIL;
+ }
+
+ /* open the server endpoint */
+ serv_fd = socket( result->ai_family, result->ai_socktype, result->ai_protocol );
+ if( serv_fd < 0 ) {
+ fprintf( stderr, "socket failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+
+ /* setsockopt SO_REUSEADDR */
+
+ /* bind server socket */
+ res = bind( serv_fd, result->ai_addr, result->ai_addrlen );
+ if( res < 0 ) {
+ (void)close( serv_fd );
+ fprintf( stderr, "bind failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+
+ /* listen to the socket */
+ res = listen( serv_fd, 10 );
+ if( res < 0 ) {
+ (void)close( serv_fd );
+ fprintf( stderr, "listen failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+
+ /* set socket non-blocking for accepts (Stevens 15.6) */
+ if( !wolf_network_sock_nonblocking( serv_fd ) ) {
+ (void)close( serv_fd );
+ fprintf( stderr, "set nonblocking failed for server socket: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+
+ /* accept the connection in the kernel, select ready for ready tells us there
+ * is a connection ready. Then we have to call accept again and check the result
+ */
+ client_addr_len = sizeof( client_addr );
+ res = accept( serv_fd, &client_addr.addr, &client_addr_len );
+ if( res < 0 ) {
+ if( errno == EAGAIN || errno == EWOULDBLOCK ) {
+ /* as expected, skip */
+ } else {
+ (void)close( serv_fd );
+ fprintf( stderr, "first non-blocking accept failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+ }
+
+#if 0
+ FD_ZERO( &read_set );
+ FD_ZERO( &write_set );
+ FD_SET( fd, &read_set );
+ FD_SET( fd, &write_set );
+ timeout.tv_sec = CONNECT_TIMEOUT;
+ timeout.tv_usec = 0;
+ res = select( fd + 1, &read_set, &write_set, NULL, &timeout );
+ if( res < 0 ) {
+ if( errno == EINTR || errno == EAGAIN ) {
+ goto CONNECT_SELECT_AGAIN;
+ } else {
+ /* fatal errors */
+ fprintf( stderr, "select failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else if( res == 0 ) {
+ /* connect timeout */
+ fprintf( stderr, "Connect timeout..terminating\n" );
+ goto FAIL;
+ } else {
+ if( FD_ISSET( fd, &read_set ) || FD_ISSET( fd, &write_set ) ) {
+ socklen_t sock_error_len;
+ int sock_error;
+#endif
+
+ goto OK;
+
+FAIL:
+ freeaddrinfo( result );
+
+#ifdef _WIN32
+ WSACleanup( );
+#endif
+
+ return EXIT_FAILURE;
+
+OK:
+ freeaddrinfo( result );
+
+#ifdef _WIN32
+ WSACleanup( );
+#endif
+
+ return EXIT_SUCCESS;
+}
+
+#if 0
+
+#if DEBUG
+static char *fd_set_to_string( fd_set *fd, int min_fd, int max_fd, char *buf, size_t buflen ) {
+ int i;
+ size_t j;
+
+ assert( min_fd <= max_fd );
+
+ for( i = min_fd, j = 0; i <= max_fd && j < buflen; i++, j++ ) {
+ if( FD_ISSET( i, fd ) ) {
+ buf[j] = '1';
+ } else {
+ buf[j] = '0';
+ }
+ }
+
+ buf[j] = '\0';
+
+ return buf;
+}
+#endif
+
+#define MAX_IDLE_TIMEOUT 10
+#define WRITE_BUFFER_SIZE 4096
+#define READ_BUFFER_SIZE 4096
+#define CONNECT_TIMEOUT 2
+
+#define max(a,b) ((a) < (b) ? (b) : (a))
+
+static volatile bool terminate = false;
+
+static void term_handler( int sig ) {
+ WOLF_UNUSED( sig );
+ terminate = true;
+}
+
+
+int main( int argc, char *argv[] ) {
+ int res;
+ int idle_secs;
+ char read_buffer[READ_BUFFER_SIZE];
+ char write_buffer[WRITE_BUFFER_SIZE];
+ size_t stdout_to_write;
+ size_t stdout_written;
+ size_t fd_to_write;
+ size_t fd_written;
+ size_t total_stdin_read;
+ size_t total_fd_read;
+ size_t total_fd_written;
+ size_t total_stdout_written;
+ int count;
+ bool stdin_eof;
+ bool fd_write_closed;
+ bool fd_eof;
+ int max_fd;
+ fd_set read_set;
+ fd_set write_set;
+ struct timeval timeout;
+ socklen_t peer_addr_len;
+ union sockaddr_union peer_addr;
+ char peer_hostname[NI_MAXHOST] = "";
+ char peer_service[NI_MAXSERV] = "";
+
+CONNECT_SELECT_AGAIN:
+ sock_error_len = sizeof( int );
+ res = getsockopt( fd, SOL_SOCKET, SO_ERROR, &sock_error, &sock_error_len );
+ if( res < 0 ) {
+ fprintf( stderr, "getsockopt for connection check failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ } else {
+ if( error == 0 ) {
+ goto CONNECTED;
+ } else {
+ fprintf( stderr, "SO_ERROR is not ok: %s (%d)\n",
+ strerror( error ), error );
+ (void)close( fd );
+ goto FAIL;
+ }
+ }
+ } else {
+ fprintf( stderr, "Socket not ready after select\n" );
+ (void)close( fd );
+ goto FAIL;
+ }
+ }
+
+CONNECTED:
+ /* determine the address of the other end of the connection */
+ peer_addr_len = sizeof( peer_addr );
+ res = getsockname( fd, &peer_addr.addr, &peer_addr_len );
+ if( res < 0 ) {
+ (void)close( fd );
+ fprintf( stderr, "getsockname failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+ res = getnameinfo( &peer_addr.addr, peer_addr_len,
+ peer_hostname, NI_MAXHOST,
+ peer_service, NI_MAXSERV,
+ NI_NUMERICSERV | NI_NUMERICHOST );
+ if( res < 0 ) {
+ (void)close( fd );
+ fprintf( stderr, "getnameinfo failed: %s (%d)\n",
+ gai_strerror( error ), error );
+ goto FAIL;
+ }
+
+ fprintf( stderr, "Connected to %s, port %s (peer: %s, %s)\n", host, service,
+ peer_hostname, peer_service );
+
+ idle_secs = 0;
+ count = 0;
+ stdout_to_write = 0;
+ stdout_written = 0;
+ fd_to_write = 0;
+ fd_written = 0;
+ total_stdin_read = 0;
+ total_fd_read = 0;
+ total_fd_written = 0;
+ total_stdout_written = 0;
+ stdin_eof = false;
+ fd_write_closed = false;
+ fd_eof = false;
+
+ signal( SIGTERM, term_handler );
+ signal( SIGINT, term_handler );
+ signal( SIGPIPE, SIG_IGN );
+
+ do {
+ ssize_t rres;
+#if DEBUG
+ char buf[10];
+ char buf2[10];
+#endif
+
+ FD_ZERO( &read_set );
+ FD_ZERO( &write_set );
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ /* set up select mask and compute highest file descriptor number */
+ max_fd = 0;
+ max_fd = max( max_fd, STDIN_FILENO );
+ max_fd = max( max_fd, STDOUT_FILENO );
+ max_fd = max( max_fd, fd );
+
+ if( !stdin_eof && fd_to_write == 0 ) {
+ FD_SET( STDIN_FILENO, &read_set );
+ }
+ if( stdout_to_write > 0 ) {
+ FD_SET( STDOUT_FILENO, &write_set );
+ }
+ if( stdout_to_write == 0 ) {
+ FD_SET( fd, &read_set );
+ }
+ if( fd_to_write > 0 ) {
+ FD_SET( fd, &write_set );
+ }
+
+ /* wait for events or timeout */
+#if DEBUG
+fprintf( stderr, "select call read_fd: %s write_fd: %s wbuf: %d rbuf: %d stdin_eof: %d fdwcl: %d\n",
+ fd_set_to_string( &read_set, 0, max_fd, buf, 10 ),
+ fd_set_to_string( &write_set, 0, max_fd, buf2, 10 ),
+ fd_to_write, stdout_to_write, stdin_eof, fd_write_closed );
+#endif
+
+ res = select( max_fd + 1, &read_set, &write_set, NULL, &timeout );
+
+#if DEBUG
+fprintf( stderr, "select %04d read_fd: %s write_fd: %s wbuf: %d rbuf: %d stdin_eof: %d fdwcl: %d\n",
+ res, fd_set_to_string( &read_set, 0, max_fd, buf, 10 ),
+ fd_set_to_string( &write_set, 0, max_fd, buf2, 10 ),
+ fd_to_write, stdout_to_write, stdin_eof, fd_write_closed );
+#endif
+
+ if( res < 0 ) {
+ if( errno == EINTR || errno == EAGAIN ) {
+ /* skip */
+ } else {
+ /* fatal errors */
+ fprintf( stderr, "select failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else if( res == 0 ) {
+ /* timeout */
+ idle_secs++;
+ if( idle_secs > MAX_IDLE_TIMEOUT ) {
+ fprintf( stderr, "Idle connection after %d seconds..terminating\n",
+ idle_secs );
+ goto END;
+ }
+ } else {
+ /* something happened */
+ idle_secs = 0;
+
+ /* empty the socket read buffer to stdout */
+ if( FD_ISSET( STDOUT_FILENO, &write_set ) ) {
+ assert( stdout_to_write > 0 );
+ rres = write( STDOUT_FILENO, read_buffer + stdout_written, stdout_to_write );
+ if( rres < 0 ) {
+ if( errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) {
+ /* skip */
+ } else {
+ fprintf( stderr, "write to stdout failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else {
+ assert( rres >= 0 );
+ stdout_written += (size_t)rres;
+ assert( stdout_to_write >= (size_t)rres );
+ stdout_to_write -= (size_t)rres;
+ total_stdout_written += (size_t)rres;
+ }
+ }
+
+ /* empty the socket write buffer writing to fd */
+ if( FD_ISSET( fd, &write_set ) ) {
+ assert( fd_to_write > 0 );
+ rres = write( fd, write_buffer + fd_written, fd_to_write );
+ if( rres < 0 ) {
+ if( errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) {
+ /* skip */
+ } else {
+ fprintf( stderr, "write to socket failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else {
+ assert( rres >= 0 );
+ fd_written += (size_t)rres;
+ assert( fd_to_write >= (size_t)rres );
+ fd_to_write -= (size_t)rres;
+ total_fd_written += (size_t)rres;
+ }
+ }
+
+ /* read from socket, fill socket read buffer */
+ if( FD_ISSET( fd, &read_set ) ) {
+ assert( stdout_to_write == 0 );
+ rres = read( fd, read_buffer, READ_BUFFER_SIZE );
+ if( rres < 0 ) {
+ if( errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) {
+ /* skip */
+ } else {
+ fprintf( stderr, "read from socket failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else if( rres == 0 ) {
+ /* EOF on socket */
+ shutdown( fd, SHUT_RD );
+ fd_eof = true;
+#if DEBUG
+ fprintf( stderr, "EOF on socket\n" );
+#endif
+ } else {
+ assert( rres > 0 );
+ stdout_written = 0;
+ stdout_to_write = (size_t)rres;
+ total_fd_read += (size_t)rres;
+ }
+ }
+
+ /* read from stdin, fill socket write buffer */
+ if( FD_ISSET( STDIN_FILENO, &read_set ) ) {
+ assert( fd_to_write == 0 );
+ rres = read( STDIN_FILENO, write_buffer, WRITE_BUFFER_SIZE );
+ if( rres < 0 ) {
+ if( errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) {
+ /* skip */
+ } else {
+ fprintf( stderr, "read from stdin failed: %s (%d)\n",
+ strerror( errno ), errno );
+ (void)close( fd );
+ goto FAIL;
+ }
+ } else if( rres == 0 ) {
+ /* EOF on STDIN */
+ stdin_eof = true;
+ fclose( stdin );
+
+#if DEBUG
+ fprintf( stderr, "EOF on stdin\n" );
+#endif
+ } else {
+ assert( rres > 0 );
+ fd_written = 0;
+ fd_to_write = (size_t)rres;
+ total_stdin_read += (size_t)rres;
+ }
+ }
+
+ /* close writing side of the socket if we have emptied
+ * the socket write buffer and there is nothing more on
+ * the input (stdin)
+ */
+ if( fd_to_write == 0 && stdin_eof && !fd_write_closed ) {
+#if DEBUG
+ fprintf( stderr, "socket SHUT_WR\n" );
+#endif
+ shutdown( fd, SHUT_WR );
+ fd_write_closed = true;
+ }
+ }
+
+ count++;
+ if( count % 10000 == 0 ) {
+ fprintf( stderr, "Transfered nof_selects: %d, stdin: %zd, stdout: %zd, fd-in: %zd, fd-out: %zd\n",
+ count, total_stdin_read, total_stdout_written, total_fd_read, total_fd_written );
+ }
+ } while( !terminate && !( stdin_eof && fd_eof ) );
+
+END:
+ fprintf( stderr, "Terminated stdin: %zd, stdout: %zd, fd-in: %zd, fd-out: %zd\n",
+ total_stdin_read, total_stdout_written, total_fd_read, total_fd_written );
+
+ res = close( fd );
+ if( res < 0 ) {
+ fprintf( stderr, "close failed: %s (%d)\n", strerror( errno ), errno );
+ goto FAIL;
+ }
+
+ freeaddrinfo( result );
+
+ goto OK;
+
+
+}
+#endif