diff options
Diffstat (limited to 'ipv6connect/ipv6connect.c')
-rw-r--r-- | ipv6connect/ipv6connect.c | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/ipv6connect/ipv6connect.c b/ipv6connect/ipv6connect.c new file mode 100644 index 0000000..34bd2d8 --- /dev/null +++ b/ipv6connect/ipv6connect.c @@ -0,0 +1,474 @@ +// 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); +} |