| // Copyright 2008 Google Inc. Released under the GPL v2. |
| // |
| // This test performs numerous connects (with auto-binding), to a server |
| // listening on all local addresses using an IPv6 socket, by connecting to |
| // 127.0.0.1, ::ffff:127.0.0.1 and ::1. |
| // |
| // The code is really three tests: |
| // |
| // - RunWithOneServer, using CreateServer and ConnectAndAccept, |
| // uses one server socket and repeatedly connects to it. |
| // |
| // - RunWithOneShotServers, using CreateServerConnectAndAccept, |
| // creates servers, connects to them and then discards them. |
| // |
| // - RunMultiThreaded, using ThreadedCreateServerConnectAndAccept, |
| // ThreadedStartServer and ThreadedGetServerFD, is equivalent to |
| // RunWithOneShotServers but uses multiple threads, one for the |
| // server and one for the client. |
| // |
| // Each of these tests triggers error conditions on different kernels |
| // to a different extent. |
| |
| #include <arpa/inet.h> |
| #include <netinet/in.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| // Which loopback address to connect to. |
| enum LoopbackAddr { V4_LOOPBACK, V6_LOOPBACK, V6_MAPPED_V4_LOOPBACK }; |
| |
| // Connect to a listening TCP socket, and accept the connection. |
| static void ConnectAndAccept(enum LoopbackAddr addr, int server_fd, int port) { |
| struct sockaddr_in6 sa; |
| socklen_t addr_len; |
| int client_fd, accepted_fd; |
| |
| if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) { |
| char buf[INET6_ADDRSTRLEN]; |
| |
| memset(&sa, 0, sizeof(sa)); |
| if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| if (addr == V6_LOOPBACK) { |
| inet_pton(AF_INET6, "::1", &sa.sin6_addr); |
| } else if (addr == V6_MAPPED_V4_LOOPBACK) { |
| inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr); |
| } |
| if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) { |
| perror("inet_ntop"); |
| exit(1); |
| } |
| addr_len = sizeof(sa); |
| sa.sin6_family = AF_INET6; |
| sa.sin6_port = port; |
| if (connect(client_fd, (struct sockaddr*)(&sa), |
| sizeof(struct sockaddr_in6)) == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| write(2, (addr == V6_LOOPBACK) ? "+" : "-", 1); |
| } else { |
| struct sockaddr_in sa4; |
| |
| if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| memset(&sa4, 0, sizeof(sa4)); |
| sa4.sin_family = AF_INET; |
| inet_pton(AF_INET, "127.0.0.1", &sa4.sin_addr); |
| sa4.sin_port = port; |
| if (connect(client_fd, (struct sockaddr*)(&sa4), |
| sizeof(struct sockaddr_in)) == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| write(2, ".", 1); |
| } |
| addr_len = sizeof(sa); |
| if ((accepted_fd = accept(server_fd, |
| (struct sockaddr*)(&sa), &addr_len)) == -1) { |
| perror("accept"); |
| exit(1); |
| } |
| close(client_fd); |
| close(accepted_fd); |
| } |
| |
| // Create a listening TCP socket. |
| static void CreateServer(int* server_fd, int* port) { |
| struct sockaddr_in6 sa; |
| socklen_t addr_len; |
| |
| memset(&sa, 0, sizeof(sa)); |
| if ((*server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| addr_len = sizeof(sa); |
| sa.sin6_family = AF_INET6; |
| sa.sin6_addr = in6addr_any; |
| sa.sin6_port = 0; |
| if (bind(*server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) { |
| perror("bind"); |
| exit(1); |
| } |
| if (getsockname(*server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) { |
| perror("getsockname"); |
| exit(1); |
| } |
| if (listen(*server_fd, 10) == -1) { |
| perror("listen"); |
| exit(1); |
| } |
| *port = sa.sin6_port; |
| } |
| |
| // Create a socket, connect to it, accept, and discard both. |
| static void CreateServerConnectAndAccept(enum LoopbackAddr addr) { |
| struct sockaddr_in6 sa; |
| socklen_t addr_len; |
| int server_fd, client_fd, accepted_fd, connect_rc; |
| |
| if ((server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| addr_len = sizeof(sa); |
| memset(&sa, 0, sizeof(sa)); |
| sa.sin6_family = AF_INET6; |
| sa.sin6_addr = in6addr_any; |
| sa.sin6_port = 0; |
| if (bind(server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) { |
| perror("bind"); |
| exit(1); |
| } |
| if (getsockname(server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) { |
| perror("getsockname"); |
| exit(1); |
| } |
| if (listen(server_fd, 10) == -1) { |
| perror("listen"); |
| exit(1); |
| } |
| if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) { |
| char buf[INET6_ADDRSTRLEN]; |
| |
| if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| if (addr == V6_LOOPBACK) { |
| inet_pton(AF_INET6, "::1", &sa.sin6_addr); |
| } else if (addr == V6_MAPPED_V4_LOOPBACK) { |
| inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr); |
| } |
| if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) { |
| perror("inet_ntop"); |
| exit(1); |
| } |
| connect_rc = connect(client_fd, (struct sockaddr*)(&sa), |
| sizeof(struct sockaddr_in6)); |
| write(2, (addr == V6_MAPPED_V4_LOOPBACK) ? "-" : "+", 1); |
| } else { |
| struct sockaddr_in sa4; |
| |
| if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| memset(&sa4, 0, sizeof(sa4)); |
| sa4.sin_family = AF_INET; |
| inet_pton(AF_INET, "127.0.0.1", &sa4.sin_addr); |
| sa4.sin_port = sa.sin6_port; |
| connect_rc = connect(client_fd, (struct sockaddr*)(&sa4), |
| sizeof(struct sockaddr_in)); |
| write(2, ".", 1); |
| } |
| if (connect_rc == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| addr_len = sizeof(sa); |
| if ((accepted_fd = accept(server_fd, |
| (struct sockaddr*)(&sa), &addr_len)) == -1) { |
| perror("accept"); |
| exit(1); |
| } |
| close(accepted_fd); |
| close(client_fd); |
| close(server_fd); |
| } |
| |
| // Globals for threaded version. |
| static volatile int threaded_listening = 0; |
| static int threaded_server_fd; |
| static pthread_mutex_t threaded_mutex = PTHREAD_MUTEX_INITIALIZER; |
| static pthread_cond_t threaded_cond = PTHREAD_COND_INITIALIZER; |
| |
| // Block until listening, then return server address. |
| static int ThreadedGetServerFD() { |
| pthread_mutex_lock(&threaded_mutex); |
| while (!threaded_listening) { |
| pthread_cond_wait(&threaded_cond, &threaded_mutex); |
| } |
| pthread_mutex_unlock(&threaded_mutex); |
| return threaded_server_fd; |
| } |
| |
| // Start a server which accepts one connection. |
| static void* ThreadedStartServer(void* unused) { |
| struct sockaddr_in6 sa; |
| socklen_t addr_len = sizeof(sa); |
| int accept_fd; |
| |
| if ((threaded_server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| |
| // Any IP, unused port. |
| memset(&sa, 0, sizeof(sa)); |
| sa.sin6_family = AF_INET6; |
| sa.sin6_addr = in6addr_any; |
| sa.sin6_port = 0; |
| |
| // Bind. |
| if (bind(threaded_server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) { |
| perror("bind"); |
| exit(1); |
| } |
| |
| // Listen. |
| if (listen(threaded_server_fd, 10) == -1) { |
| perror("listen"); |
| exit(1); |
| } |
| pthread_mutex_lock(&threaded_mutex); |
| threaded_listening = 1; |
| pthread_cond_signal(&threaded_cond); |
| pthread_mutex_unlock(&threaded_mutex); |
| |
| // Try to accept. |
| if ((accept_fd = accept(threaded_server_fd, (struct sockaddr*)(&sa), |
| &addr_len)) == -1) { |
| perror("accept"); |
| exit(1); |
| } |
| |
| // All done. |
| close(threaded_server_fd); |
| close(accept_fd); |
| threaded_listening = 0; |
| return NULL; |
| } |
| |
| // Start a server thread, then connect to it via TCP. |
| static void ThreadedCreateServerConnectAndAccept(enum LoopbackAddr addr) { |
| pthread_t pthread; |
| int server_fd, client_fd; |
| struct sockaddr_in6 sa; |
| socklen_t addr_len = sizeof(sa); |
| |
| pthread_create(&pthread, NULL, ThreadedStartServer, NULL); |
| |
| // Get the server address information -- this call will block until |
| // the server is listening. |
| server_fd = ThreadedGetServerFD(); |
| memset(&sa, 0, sizeof(sa)); |
| if (getsockname(server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) { |
| perror("getsockname"); |
| exit(1); |
| } |
| |
| if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) { |
| char buf[INET6_ADDRSTRLEN]; |
| |
| if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| |
| // Check that we are listening on :: |
| if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) { |
| fprintf(stderr, "inet_ntop failed\n"); |
| exit(1); |
| } |
| if (strlen(buf) != 2) { |
| fprintf(stderr, "Expected to listen on ::, instead listening on %s", buf); |
| exit(1); |
| } |
| |
| if (addr == V6_LOOPBACK) { |
| inet_pton(AF_INET6, "::1", &sa.sin6_addr); |
| } else if (addr == V6_MAPPED_V4_LOOPBACK) { |
| inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr); |
| } |
| if (connect(client_fd, (struct sockaddr*)(&sa), |
| sizeof(struct sockaddr_in6)) == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| } else { |
| struct sockaddr_in sa4; |
| |
| if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { |
| perror("socket"); |
| exit(1); |
| } |
| |
| memset(&sa4, 0, sizeof(sa4)); |
| sa4.sin_family = AF_INET; |
| inet_aton("127.0.0.1", &sa4.sin_addr); |
| sa4.sin_port = sa.sin6_port; |
| |
| if (connect(client_fd, (struct sockaddr*)(&sa4), |
| sizeof(struct sockaddr_in)) == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| } |
| |
| // Update progress. |
| switch (addr) { |
| case V4_LOOPBACK: |
| write(2, ".", 1); |
| break; |
| case V6_MAPPED_V4_LOOPBACK: |
| write(2, "-", 1); |
| break; |
| case V6_LOOPBACK: |
| write(2, "+", 1); |
| break; |
| } |
| |
| // Close our connection and wait for the server thread to shutdown. |
| close(client_fd); |
| pthread_join(pthread, NULL); |
| } |
| |
| static void RunWithOneServer(int outer, int inner) { |
| int i, j, server_fd, port; |
| fprintf(stderr, "Starting test with one server port for all connects\n"); |
| for (i = 0; i < outer; ++i) { |
| CreateServer(&server_fd, &port); |
| for (j = 0; j < inner; ++j) { |
| ConnectAndAccept(V4_LOOPBACK, server_fd, port); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| ConnectAndAccept(V6_MAPPED_V4_LOOPBACK, server_fd, port); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| ConnectAndAccept(V6_LOOPBACK, server_fd, port); |
| } |
| write(2, "\n", 1); |
| close(server_fd); |
| } |
| } |
| |
| static void RunWithOneShotServers(int outer, int inner) { |
| int i, j; |
| fprintf(stderr, "Starting test with one server port per connect\n"); |
| for (i = 0; i < outer; ++i) { |
| for (j = 0; j < inner; ++j) { |
| CreateServerConnectAndAccept(V4_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| CreateServerConnectAndAccept(V6_MAPPED_V4_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| CreateServerConnectAndAccept(V6_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| } |
| } |
| |
| static void RunMultiThreaded(int outer, int inner) { |
| int i, j; |
| fprintf(stderr, "Starting multi-threaded test\n"); |
| for (i = 0; i < outer; ++i) { |
| for (j = 0; j < inner; ++j) { |
| ThreadedCreateServerConnectAndAccept(V4_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| ThreadedCreateServerConnectAndAccept(V6_MAPPED_V4_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| for (j = 0; j < inner; ++j) { |
| ThreadedCreateServerConnectAndAccept(V6_LOOPBACK); |
| } |
| write(2, "\n", 1); |
| } |
| } |
| |
| static const char* usage = |
| "Usage: %s [types [outer [inner]]]\n" |
| "Arguments:\n" |
| "\ttypes: String consisting of [OMT], for the test types to run\n" |
| "\t O: One server, multiple connects\n" |
| "\t M: One server per connect (multiple server ports)\n" |
| "\t T: Multi-threaded version of \'M\'\n" |
| "\touter: Number of passes through the outer loops, default 10\n" |
| "\tinner: Number of passes through the inner loops, default 75\n"; |
| |
| static void Usage(char *argv0) { |
| fprintf(stderr, usage, argv0); |
| exit(2); |
| } |
| |
| int main(int argc, char** argv) { |
| char *types = "OMT"; |
| int i, inner = 75, outer = 10, timediff; |
| struct timeval tv0, tv1; |
| |
| // Parse the options. |
| if (argc == 4) { |
| inner = atoi(argv[3]); |
| if (inner <= 0) { |
| Usage(argv[0]); |
| } |
| argc--; |
| } |
| if (argc == 3) { |
| outer = atoi(argv[2]); |
| if (outer <= 0) { |
| Usage(argv[0]); |
| } |
| argc--; |
| } |
| if (argc == 2) { |
| types = argv[1]; |
| if (strspn(types, "OMT") != strlen(types)) { |
| Usage(argv[0]); |
| } |
| argc--; |
| } |
| if (argc != 1) { |
| Usage(argv[0]); |
| } |
| |
| // Run the tests. |
| gettimeofday(&tv0, NULL); |
| for (i = 0; i < strlen(types); ++i) { |
| switch (types[i]) { |
| case 'O': |
| RunWithOneServer(outer, inner); |
| break; |
| case 'M': |
| RunWithOneShotServers(outer, inner); |
| break; |
| case 'T': |
| RunMultiThreaded(outer, inner); |
| break; |
| } |
| } |
| gettimeofday(&tv1, NULL); |
| timediff = (tv1.tv_sec - tv0.tv_sec) * 1000000 + tv1.tv_usec - tv0.tv_usec; |
| fprintf(stderr, "Total time = %d.%06ds\n", timediff / 1000000, |
| timediff % 1000000); |
| exit(0); |
| } |