// 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 #include #include #include #include #include #include #include #include #include // 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); }