1// Copyright 2008 Google Inc.  Released under the GPL v2.
2//
3// This test performs numerous connects (with auto-binding), to a server
4// listening on all local addresses using an IPv6 socket, by connecting to
5// 127.0.0.1, ::ffff:127.0.0.1 and ::1.
6//
7// The code is really three tests:
8//
9//   - RunWithOneServer, using CreateServer and ConnectAndAccept,
10//     uses one server socket and repeatedly connects to it.
11//
12//   - RunWithOneShotServers, using CreateServerConnectAndAccept,
13//     creates servers, connects to them and then discards them.
14//
15//   - RunMultiThreaded, using ThreadedCreateServerConnectAndAccept,
16//     ThreadedStartServer and ThreadedGetServerFD, is equivalent to
17//     RunWithOneShotServers but uses multiple threads, one for the
18//     server and one for the client.
19//
20// Each of these tests triggers error conditions on different kernels
21// to a different extent.
22
23#include <arpa/inet.h>
24#include <netinet/in.h>
25#include <pthread.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <sys/socket.h>
30#include <sys/time.h>
31#include <time.h>
32#include <unistd.h>
33
34// Which loopback address to connect to.
35enum LoopbackAddr { V4_LOOPBACK, V6_LOOPBACK, V6_MAPPED_V4_LOOPBACK };
36
37// Connect to a listening TCP socket, and accept the connection.
38static void ConnectAndAccept(enum LoopbackAddr addr, int server_fd, int port) {
39  struct sockaddr_in6 sa;
40  socklen_t addr_len;
41  int client_fd, accepted_fd;
42
43  if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) {
44    char buf[INET6_ADDRSTRLEN];
45
46    memset(&sa, 0, sizeof(sa));
47    if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
48      perror("socket");
49      exit(1);
50    }
51    if (addr == V6_LOOPBACK) {
52      inet_pton(AF_INET6, "::1", &sa.sin6_addr);
53    } else if (addr == V6_MAPPED_V4_LOOPBACK) {
54      inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr);
55    }
56    if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) {
57      perror("inet_ntop");
58      exit(1);
59    }
60    addr_len = sizeof(sa);
61    sa.sin6_family = AF_INET6;
62    sa.sin6_port = port;
63    if (connect(client_fd, (struct sockaddr*)(&sa),
64                sizeof(struct sockaddr_in6)) == -1) {
65      perror("connect");
66      exit(1);
67    }
68    write(2, (addr == V6_LOOPBACK) ? "+" : "-", 1);
69  } else {
70    struct sockaddr_in sa4;
71
72    if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
73      perror("socket");
74      exit(1);
75    }
76    memset(&sa4, 0, sizeof(sa4));
77    sa4.sin_family = AF_INET;
78    inet_pton(AF_INET, "127.0.0.1", &sa4.sin_addr);
79    sa4.sin_port = port;
80    if (connect(client_fd, (struct sockaddr*)(&sa4),
81                sizeof(struct sockaddr_in)) == -1) {
82      perror("connect");
83      exit(1);
84    }
85    write(2, ".", 1);
86  }
87  addr_len = sizeof(sa);
88  if ((accepted_fd = accept(server_fd,
89                            (struct sockaddr*)(&sa), &addr_len)) == -1) {
90    perror("accept");
91    exit(1);
92  }
93  close(client_fd);
94  close(accepted_fd);
95}
96
97// Create a listening TCP socket.
98static void CreateServer(int* server_fd, int* port) {
99  struct sockaddr_in6 sa;
100  socklen_t addr_len;
101
102  memset(&sa, 0, sizeof(sa));
103  if ((*server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
104    perror("socket");
105    exit(1);
106  }
107  addr_len = sizeof(sa);
108  sa.sin6_family = AF_INET6;
109  sa.sin6_addr = in6addr_any;
110  sa.sin6_port = 0;
111  if (bind(*server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) {
112    perror("bind");
113    exit(1);
114  }
115  if (getsockname(*server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) {
116    perror("getsockname");
117    exit(1);
118  }
119  if (listen(*server_fd, 10) == -1) {
120    perror("listen");
121    exit(1);
122  }
123  *port = sa.sin6_port;
124}
125
126// Create a socket, connect to it, accept, and discard both.
127static void CreateServerConnectAndAccept(enum LoopbackAddr addr) {
128  struct sockaddr_in6 sa;
129  socklen_t addr_len;
130  int server_fd, client_fd, accepted_fd, connect_rc;
131
132  if ((server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
133    perror("socket");
134    exit(1);
135  }
136  addr_len = sizeof(sa);
137  memset(&sa, 0, sizeof(sa));
138  sa.sin6_family = AF_INET6;
139  sa.sin6_addr = in6addr_any;
140  sa.sin6_port = 0;
141  if (bind(server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) {
142    perror("bind");
143    exit(1);
144  }
145  if (getsockname(server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) {
146    perror("getsockname");
147    exit(1);
148  }
149  if (listen(server_fd, 10) == -1) {
150    perror("listen");
151    exit(1);
152  }
153  if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) {
154    char buf[INET6_ADDRSTRLEN];
155
156    if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
157      perror("socket");
158      exit(1);
159    }
160    if (addr == V6_LOOPBACK) {
161      inet_pton(AF_INET6, "::1", &sa.sin6_addr);
162    } else if (addr == V6_MAPPED_V4_LOOPBACK) {
163      inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr);
164    }
165    if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) {
166      perror("inet_ntop");
167      exit(1);
168    }
169    connect_rc = connect(client_fd, (struct sockaddr*)(&sa),
170                         sizeof(struct sockaddr_in6));
171    write(2, (addr == V6_MAPPED_V4_LOOPBACK) ? "-" : "+", 1);
172  } else {
173    struct sockaddr_in sa4;
174
175    if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
176      perror("socket");
177      exit(1);
178    }
179    memset(&sa4, 0, sizeof(sa4));
180    sa4.sin_family = AF_INET;
181    inet_pton(AF_INET, "127.0.0.1", &sa4.sin_addr);
182    sa4.sin_port = sa.sin6_port;
183    connect_rc = connect(client_fd, (struct sockaddr*)(&sa4),
184                         sizeof(struct sockaddr_in));
185    write(2, ".", 1);
186  }
187  if (connect_rc == -1) {
188    perror("connect");
189    exit(1);
190  }
191  addr_len = sizeof(sa);
192  if ((accepted_fd = accept(server_fd,
193                            (struct sockaddr*)(&sa), &addr_len)) == -1) {
194    perror("accept");
195    exit(1);
196  }
197  close(accepted_fd);
198  close(client_fd);
199  close(server_fd);
200}
201
202// Globals for threaded version.
203static volatile int threaded_listening = 0;
204static int threaded_server_fd;
205static pthread_mutex_t threaded_mutex = PTHREAD_MUTEX_INITIALIZER;
206static pthread_cond_t threaded_cond = PTHREAD_COND_INITIALIZER;
207
208// Block until listening, then return server address.
209static int ThreadedGetServerFD() {
210  pthread_mutex_lock(&threaded_mutex);
211  while (!threaded_listening) {
212    pthread_cond_wait(&threaded_cond, &threaded_mutex);
213  }
214  pthread_mutex_unlock(&threaded_mutex);
215  return threaded_server_fd;
216}
217
218// Start a server which accepts one connection.
219static void* ThreadedStartServer(void* unused) {
220  struct sockaddr_in6 sa;
221  socklen_t addr_len = sizeof(sa);
222  int accept_fd;
223
224  if ((threaded_server_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
225    perror("socket");
226    exit(1);
227  }
228
229  // Any IP, unused port.
230  memset(&sa, 0, sizeof(sa));
231  sa.sin6_family = AF_INET6;
232  sa.sin6_addr = in6addr_any;
233  sa.sin6_port = 0;
234
235  // Bind.
236  if (bind(threaded_server_fd, (struct sockaddr*)(&sa), sizeof(sa)) == -1) {
237    perror("bind");
238    exit(1);
239  }
240
241  // Listen.
242  if (listen(threaded_server_fd, 10) == -1) {
243    perror("listen");
244    exit(1);
245  }
246  pthread_mutex_lock(&threaded_mutex);
247  threaded_listening = 1;
248  pthread_cond_signal(&threaded_cond);
249  pthread_mutex_unlock(&threaded_mutex);
250
251  // Try to accept.
252  if ((accept_fd = accept(threaded_server_fd, (struct sockaddr*)(&sa),
253                          &addr_len)) == -1) {
254    perror("accept");
255    exit(1);
256  }
257
258  // All done.
259  close(threaded_server_fd);
260  close(accept_fd);
261  threaded_listening = 0;
262  return NULL;
263}
264
265// Start a server thread, then connect to it via TCP.
266static void ThreadedCreateServerConnectAndAccept(enum LoopbackAddr addr) {
267  pthread_t pthread;
268  int server_fd, client_fd;
269  struct sockaddr_in6 sa;
270  socklen_t addr_len = sizeof(sa);
271
272  pthread_create(&pthread, NULL, ThreadedStartServer, NULL);
273
274  // Get the server address information -- this call will block until
275  // the server is listening.
276  server_fd = ThreadedGetServerFD();
277  memset(&sa, 0, sizeof(sa));
278  if (getsockname(server_fd, (struct sockaddr*)(&sa), &addr_len) == -1) {
279    perror("getsockname");
280    exit(1);
281  }
282
283  if (addr == V6_LOOPBACK || addr == V6_MAPPED_V4_LOOPBACK) {
284    char buf[INET6_ADDRSTRLEN];
285
286    if ((client_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) {
287      perror("socket");
288      exit(1);
289    }
290
291    // Check that we are listening on ::
292    if (!inet_ntop(AF_INET6, &sa.sin6_addr, buf, INET6_ADDRSTRLEN)) {
293      fprintf(stderr, "inet_ntop failed\n");
294      exit(1);
295    }
296    if (strlen(buf) != 2) {
297      fprintf(stderr, "Expected to listen on ::, instead listening on %s", buf);
298      exit(1);
299    }
300
301    if (addr == V6_LOOPBACK) {
302      inet_pton(AF_INET6, "::1", &sa.sin6_addr);
303    } else if (addr == V6_MAPPED_V4_LOOPBACK) {
304      inet_pton(AF_INET6, "::ffff:127.0.0.1", &sa.sin6_addr);
305    }
306    if (connect(client_fd, (struct sockaddr*)(&sa),
307                sizeof(struct sockaddr_in6)) == -1) {
308      perror("connect");
309      exit(1);
310    }
311  } else {
312    struct sockaddr_in sa4;
313
314    if ((client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
315      perror("socket");
316      exit(1);
317    }
318
319    memset(&sa4, 0, sizeof(sa4));
320    sa4.sin_family = AF_INET;
321    inet_aton("127.0.0.1", &sa4.sin_addr);
322    sa4.sin_port = sa.sin6_port;
323
324    if (connect(client_fd, (struct sockaddr*)(&sa4),
325                sizeof(struct sockaddr_in)) == -1) {
326      perror("connect");
327      exit(1);
328    }
329  }
330
331  // Update progress.
332  switch (addr) {
333    case V4_LOOPBACK:
334      write(2, ".", 1);
335      break;
336    case V6_MAPPED_V4_LOOPBACK:
337      write(2, "-", 1);
338      break;
339    case V6_LOOPBACK:
340      write(2, "+", 1);
341      break;
342  }
343
344  // Close our connection and wait for the server thread to shutdown.
345  close(client_fd);
346  pthread_join(pthread, NULL);
347}
348
349static void RunWithOneServer(int outer, int inner) {
350  int i, j, server_fd, port;
351  fprintf(stderr, "Starting test with one server port for all connects\n");
352  for (i = 0; i < outer; ++i) {
353    CreateServer(&server_fd, &port);
354    for (j = 0; j < inner; ++j) {
355      ConnectAndAccept(V4_LOOPBACK, server_fd, port);
356    }
357    write(2, "\n", 1);
358    for (j = 0; j < inner; ++j) {
359      ConnectAndAccept(V6_MAPPED_V4_LOOPBACK, server_fd, port);
360    }
361    write(2, "\n", 1);
362    for (j = 0; j < inner; ++j) {
363      ConnectAndAccept(V6_LOOPBACK, server_fd, port);
364    }
365    write(2, "\n", 1);
366    close(server_fd);
367  }
368}
369
370static void RunWithOneShotServers(int outer, int inner) {
371  int i, j;
372  fprintf(stderr, "Starting test with one server port per connect\n");
373  for (i = 0; i < outer; ++i) {
374    for (j = 0; j < inner; ++j) {
375      CreateServerConnectAndAccept(V4_LOOPBACK);
376    }
377    write(2, "\n", 1);
378    for (j = 0; j < inner; ++j) {
379      CreateServerConnectAndAccept(V6_MAPPED_V4_LOOPBACK);
380    }
381    write(2, "\n", 1);
382    for (j = 0; j < inner; ++j) {
383      CreateServerConnectAndAccept(V6_LOOPBACK);
384    }
385    write(2, "\n", 1);
386  }
387}
388
389static void RunMultiThreaded(int outer, int inner) {
390  int i, j;
391  fprintf(stderr, "Starting multi-threaded test\n");
392  for (i = 0; i < outer; ++i) {
393    for (j = 0; j < inner; ++j) {
394      ThreadedCreateServerConnectAndAccept(V4_LOOPBACK);
395    }
396    write(2, "\n", 1);
397    for (j = 0; j < inner; ++j) {
398      ThreadedCreateServerConnectAndAccept(V6_MAPPED_V4_LOOPBACK);
399    }
400    write(2, "\n", 1);
401    for (j = 0; j < inner; ++j) {
402      ThreadedCreateServerConnectAndAccept(V6_LOOPBACK);
403    }
404    write(2, "\n", 1);
405  }
406}
407
408static const char* usage =
409    "Usage: %s [types [outer [inner]]]\n"
410    "Arguments:\n"
411    "\ttypes: String consisting of [OMT], for the test types to run\n"
412    "\t       O: One server, multiple connects\n"
413    "\t       M: One server per connect (multiple server ports)\n"
414    "\t       T: Multi-threaded version of \'M\'\n"
415    "\touter: Number of passes through the outer loops, default 10\n"
416    "\tinner: Number of passes through the inner loops, default 75\n";
417
418static void Usage(char *argv0) {
419  fprintf(stderr, usage, argv0);
420  exit(2);
421}
422
423int main(int argc, char** argv) {
424  char *types = "OMT";
425  int i, inner = 75, outer = 10, timediff;
426  struct timeval tv0, tv1;
427
428  // Parse the options.
429  if (argc == 4) {
430    inner = atoi(argv[3]);
431    if (inner <= 0) {
432      Usage(argv[0]);
433    }
434    argc--;
435  }
436  if (argc == 3) {
437    outer = atoi(argv[2]);
438    if (outer <= 0) {
439      Usage(argv[0]);
440    }
441    argc--;
442  }
443  if (argc == 2) {
444    types = argv[1];
445    if (strspn(types, "OMT") != strlen(types)) {
446      Usage(argv[0]);
447    }
448    argc--;
449  }
450  if (argc != 1) {
451    Usage(argv[0]);
452  }
453
454  // Run the tests.
455  gettimeofday(&tv0, NULL);
456  for (i = 0; i < strlen(types); ++i) {
457    switch (types[i]) {
458      case 'O':
459        RunWithOneServer(outer, inner);
460        break;
461      case 'M':
462        RunWithOneShotServers(outer, inner);
463        break;
464      case 'T':
465        RunMultiThreaded(outer, inner);
466        break;
467    }
468  }
469  gettimeofday(&tv1, NULL);
470  timediff = (tv1.tv_sec - tv0.tv_sec) * 1000000 + tv1.tv_usec - tv0.tv_usec;
471  fprintf(stderr, "Total time = %d.%06ds\n", timediff / 1000000,
472          timediff % 1000000);
473  exit(0);
474}
475