diff options
Diffstat (limited to 'tests/network/test2_unix.c')
-rw-r--r-- | tests/network/test2_unix.c | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/tests/network/test2_unix.c b/tests/network/test2_unix.c new file mode 100644 index 0000000..3964c93 --- /dev/null +++ b/tests/network/test2_unix.c @@ -0,0 +1,404 @@ +/* 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 + +#define MAX_ACCEPT_IDLE_TIMEOUT 4 +#define MAX_IDLE_TIMEOUT 10 + +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; +} + +#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 BUFFER_SIZE 4096 + +static volatile bool terminate = false; + +static void term_handler( int sig ) { + WOLF_UNUSED( sig ); + terminate = true; +} + +int main( int argc, char* argv[] ) { + int error; + int serv_fd = -1; + int res; + char *host; + char *service; + struct addrinfo hints; + struct addrinfo *result = NULL; + wolf_network_sockaddr_union_t client_addr; + socklen_t client_addr_len; + fd_set read_set; + fd_set write_set; + struct timeval timeout; + char client_hostname[NI_MAXHOST] = ""; + char client_service[NI_MAXSERV] = ""; + int on; + int client_fd = -1; + int idle_secs; + char buffer[BUFFER_SIZE]; + size_t fd_to_write; + size_t fd_written; + size_t total_fd_read; + size_t total_fd_written; + int count; + bool fd_eof; + + 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 + + signal( SIGTERM, term_handler ); + signal( SIGINT, term_handler ); + signal( SIGPIPE, SIG_IGN ); + + /* 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, this is handy while testing or deployment, in + * production in can lead to a certain type of rerouting packets to wrong + * servers + */ + on = 1; + res = setsockopt( serv_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on ) ); + if( res < 0 ) { + (void)close( serv_fd ); + fprintf( stderr, "setsockopt(SO_REUSEADDR) failed: %s (%d)\n", strerror( errno ), errno ); + goto FAIL; + } + + /* bind server socket */ + res = bind( serv_fd, result->ai_addr, result->ai_addrlen ); + if( res < 0 ) { + fprintf( stderr, "bind failed: %s (%d)\n", strerror( errno ), errno ); + goto FAIL; + } + + /* listen to the socket */ + res = listen( serv_fd, 10 ); + if( res < 0 ) { + 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 ) ) { + 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 + */ +FIRST_ACCEPT_AGAIN: + client_addr_len = sizeof( client_addr ); + client_fd = accept( serv_fd, &client_addr.addr, &client_addr_len ); + if( client_fd < 0 ) { + if( errno == EINTR ) { + if( terminate ) goto OK; + goto FIRST_ACCEPT_AGAIN; + } else if( errno == EAGAIN || errno == EWOULDBLOCK ) { + /* as expected, skip */ + } else { + fprintf( stderr, "first non-blocking accept failed: %s (%d)\n", strerror( errno ), errno ); + goto FAIL; + } + } + + /* go into a select and wait till we are ready for read (this indicates a + * possibly new connection) + */ +ACCEPT_SELECT_AGAIN: + FD_ZERO( &read_set ); + FD_SET( serv_fd, &read_set ); + timeout.tv_sec = MAX_ACCEPT_IDLE_TIMEOUT; + timeout.tv_usec = 0; + + res = select( serv_fd + 1, &read_set, NULL, NULL, &timeout ); + if( res < 0 ) { + if( errno == EINTR ) { + if( terminate ) goto OK; + goto ACCEPT_SELECT_AGAIN; + } else if( errno == EAGAIN ) { + goto ACCEPT_SELECT_AGAIN; + } else { + /* fatal errors */ + fprintf( stderr, "accept/select failed: %s (%d)\n", + strerror( errno ), errno ); + goto FAIL; + } + } else if( res == 0 ) { + /* accept timeout, here we could to periodic stuff, also if we + * get blocked somewhere above we could recover.. + */ + fprintf( stderr, "Idle. No new connections..\n" ); + goto ACCEPT_SELECT_AGAIN; + } else { + if( FD_ISSET( serv_fd, &read_set ) ) { + /* this is a new connection */ + } else { + /* can't happen! */ + assert( false ); + } + } + + /* second accept, now we have to check for possible "short-and-run-away" + * connects. + */ +ACCEPT_AGAIN: + client_addr_len = sizeof( client_addr ); + client_fd = accept( serv_fd, &client_addr.addr, &client_addr_len ); + if( client_fd < 0 ) { + if( errno == EINTR ) { + if( terminate ) goto OK; + /* interrupted, again */ + goto ACCEPT_AGAIN; + } else if( errno == ECONNABORTED ) { + /* connection run away */ + goto FIRST_ACCEPT_AGAIN; + } else { + fprintf( stderr, "second non-blocking accept failed: %s (%d)\n", strerror( errno ), errno ); + goto FAIL; + } + } + + /* determine where the request came from */ + res = getnameinfo( &client_addr.addr, client_addr_len, + client_hostname, NI_MAXHOST, + client_service, NI_MAXSERV, + NI_NUMERICSERV | NI_NUMERICHOST ); + if( res < 0 ) { + fprintf( stderr, "getnameinfo failed: %s (%d)\n", + gai_strerror( error ), error ); + goto FAIL; + } + + fprintf( stderr, "New connection from %s, port %s\n", client_hostname, client_service ); + + /* handle connection input/output as echo server */ + + idle_secs = 0; + count = 0; + fd_to_write = 0; + fd_written = 0; + total_fd_read = 0; + total_fd_written = 0; + fd_eof = false; + + 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; + if( fd_to_write == 0 ) { + FD_SET( client_fd, &read_set ); + } + if( fd_to_write > 0 ) { + FD_SET( client_fd, &write_set ); + } + + /* wait for events or timeout */ +#if DEBUG +fprintf( stderr, "select call read_fd: %s write_fd: %s buf: %d eof: %d\n", + fd_set_to_string( &read_set, client_fd, client_fd, buf, 10 ), + fd_set_to_string( &write_set, client_fd, client_fd, buf2, 10 ), + fd_to_write, fd_eof ); +#endif + + res = select( client_fd + 1, &read_set, &write_set, NULL, &timeout ); + +#if DEBUG +fprintf( stderr, "select %04d read_fd: %s write_fd: %s buf: %d eof: %d\n", + res, fd_set_to_string( &read_set, client_fd, client_fd, buf, 10 ), + fd_set_to_string( &write_set, client_fd, client_fd, buf2, 10 ), + fd_to_write, fd_eof ); +#endif + + if( res < 0 ) { + if( errno == EINTR || errno == EAGAIN ) { + /* skip */ + } else { + /* fatal errors */ + fprintf( stderr, "select failed: %s (%d)\n", + strerror( errno ), errno ); + 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; + + /* if we have something to write back to the client, do that */ + if( FD_ISSET( client_fd, &write_set ) ) { + assert( fd_to_write > 0 ); + rres = write( client_fd, buffer + fd_written, fd_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 ); + goto FAIL; + } + } else { + assert( rres >= 0 ); + fd_written += (size_t)rres; + assert( fd_to_write >= (size_t)rres ); + fd_to_write -= (size_t)rres; + fd_written += (size_t)rres; + total_fd_written += (size_t)rres; + } + } + + /* if there is space in the read buffer and something to be read, do so */ + if( FD_ISSET( client_fd, &read_set ) ) { + assert( fd_to_write == 0 ); + rres = read( client_fd, buffer, 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 ); + goto FAIL; + } + } else if( rres == 0 ) { + /* EOF on socket */ + shutdown( client_fd, SHUT_RD ); + fd_eof = true; +#if DEBUG + fprintf( stderr, "EOF on socket\n" ); +#endif + } else { + assert( rres > 0 ); + fd_written = 0; + fd_to_write = (size_t)rres; + total_fd_read += (size_t)rres; + } + } + } + + count++; + if( count % 10000 == 0 ) { + fprintf( stderr, "Transfered nof_selects: %d, fd-in: %zd, fd-out: %zd\n", + count, total_fd_read, total_fd_written ); + } + } while( !terminate && !( fd_eof && fd_to_write == 0 ) ); + +END: + + fprintf( stderr, "Terminated nof_selects: %d, fd-in: %zd, fd-out: %zd\n", + count, total_fd_read, total_fd_written ); + + (void)close( client_fd ); + (void)close( serv_fd ); + freeaddrinfo( result ); + + goto OK; + +FAIL: + + if( client_fd >= 0 ) (void)close( client_fd ); + if( serv_fd >= 0 ) (void)close( serv_fd ); + if( result != NULL ) freeaddrinfo( result ); + +#ifdef _WIN32 + WSACleanup( ); +#endif + + return EXIT_FAILURE; + +OK: + +#ifdef _WIN32 + WSACleanup( ); +#endif + + return EXIT_SUCCESS; +} |