summaryrefslogtreecommitdiffstats
path: root/ipv6connect/ipv6connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipv6connect/ipv6connect.c')
-rw-r--r--ipv6connect/ipv6connect.c474
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);
+}