diff options
Diffstat (limited to 'tests/network/test2.c')
-rw-r--r-- | tests/network/test2.c | 506 |
1 files changed, 506 insertions, 0 deletions
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 |