474 lines
13 KiB
C
474 lines
13 KiB
C
// 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);
|
|
}
|