summaryrefslogtreecommitdiff
path: root/docs/network/connect_test.c
blob: 5c38f4f0fdff144ef72938d330b071d63cd29fcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/* Test of connect() behavior when interrupted */

/* David Madore <david.madore@ens.fr> - 2003-04-25 - Public Domain */

/* This program forks to two processes: the father process does
 * nothing but continuously send USR1 signals to the child process,
 * and dies when the latter exits.  The child process ignores USR1
 * signals, but they will likely cause interrupted system calls.  The
 * child process attempts to connect to CONNECT_ADDRESS on
 * CONNECT_PORT.  The goal is to produce an interrupted system call on
 * connect() to check its behavior.  Therefore, as long as this does
 * not occurr, the child process will close the connection as soon as
 * it succeeds, and try again (up to GIVEUP attempts will be made).
 * Once connect() is interrupted, there are two possible tests: if
 * TEST_TWO is set to 1, the program will poll() for completion of the
 * asynchronous connection attempt that SUSv3 prescribes will then
 * take place; if TEST_TWO is undefined or set to 0, the program will
 * retry the connect() call with the same arguments, and persist while
 * the latter returns EINTR (if it does). */

/* Please define CONNECT_ADDRESS to the IP address of some web server
 * (if possible, one which will not respond too rapidly - but not too
 * slowly either). */

/* If we read the SUSv3 to the letter, the following output should be
 * produced (assuming CONNECT_ADDRESS has been set to 216.239.33.99):
 * with TEST_TWO, "Will try to connect to 216.239.33.99 on port
 * 80\n(connect has been interrupted and now completed
 * successfully)\n"; and without TEST_TWO, "Will try to connect to
 * 216.239.33.99 on port 80\n(connect had been interrupted and now
 * produced an error)\nconnect: Operation already in progress\n" (I
 * think this behavior is utterly stupid, but SUSv3 does seem to
 * require it!). */

/* On Linux, without TEST_TWO, we get "Will try to connect to
 * 216.239.33.99 on port 80\n(connect has been interrupted and now
 * completed successfully)\n" (the same as with TEST_TWO).  On FreeBSD
 * and OpenBSD, we get "Will try to connect to 216.239.33.99 on port
 * 80\n(connect had been interrupted and now produced an
 * error)\nconnect: Adress already in use\n".  On Solaris, we get the
 * per-spec required behavior described above.  All systems function
 * as expected when TEST_TWO is set. */

/* Also see
 * <URL: http://www.eleves.ens.fr:8080/home/madore/computers/connect-intr.html >
 */

#ifndef CONNECT_ADDRESS
#define CONNECT_ADDRESS "127.0.0.1"
#endif

#ifndef CONNECT_PORT
#define CONNECT_PORT 80
#endif

#ifndef GIVEUP
/* 0 means "never". */
#define GIVEUP 100
#endif

#ifndef VERBOSE
#define VERBOSE 0
#endif

#ifndef TEST_TWO
#define TEST_TWO 0
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#if TEST_TWO
#include <sys/poll.h>
#endif

#include <errno.h>

static void
ignore_handler (int useless)
{
  /* Nothing! */
}

static volatile sig_atomic_t terminate;

static void
terminate_handler (int useless)
{
  terminate = 1;
}

static pid_t child;

static void
killing_loop (void)
{
  while ( ! terminate )
    {
      int retval;

      /* Note race condition here.  Too annoying to fix. */
      retval = kill (child, SIGUSR1);
      if ( retval == -1 )
	{
	  if ( errno == EINTR )
	    continue;
	  perror ("kill");
	  kill (child, SIGTERM);
	  exit (EXIT_FAILURE);
	}
    }
}

static long nbtries;
static long nbsubtries;

static void
child_loop (void)
{
  int socketd;
  struct sockaddr_in addr;
  struct protoent *proto;
  int retval;
  char have_testcase;

  proto = getprotobyname ("tcp");
  if ( ! proto )
    {
      fprintf (stderr, "getprotobyname: Protocol not found\n");
      exit (EXIT_FAILURE);
    }
  memset (&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  retval = inet_pton (AF_INET, CONNECT_ADDRESS, &addr.sin_addr);
  if ( retval == -1 )
    {
      perror ("inet_pton");
      exit (EXIT_FAILURE);
    }
  else if ( retval == 0 )
    {
      fprintf (stderr, "inet_pton: Failed to convert address\n");
      exit (EXIT_FAILURE);
    }
  addr.sin_port = htons (CONNECT_PORT);
  nbtries = 0;
  have_testcase = 0;
  while ( ! have_testcase )
    {
      socketd = socket (PF_INET, SOCK_STREAM, proto->p_proto);
      if ( socketd == -1 )
	{
	  perror ("socket");
	  exit (EXIT_FAILURE);
	}
#if VERBOSE
      fprintf (stderr, "socket: Success\n");
#endif
      nbsubtries = 0;
#if TEST_TWO
      if ( connect (socketd, (struct sockaddr *)&addr,
		    sizeof(addr)) == -1 )
	{
	  if ( errno == EINTR )
	    {
	      struct pollfd unix_really_sucks;
	      int some_more_junk;
	      socklen_t yet_more_useless_junk;

	      have_testcase = 1;
#if VERBOSE
	      fprintf (stderr, "connect: Interrupted system call "
		       "- waiting for asynchronous completion\n");
#endif
	      unix_really_sucks.fd = socketd;
	      unix_really_sucks.events = POLLOUT;
	      while ( poll (&unix_really_sucks, 1, -1) == -1 )
		{
		  if ( errno == EINTR )
		    continue;
		  perror ("poll");
		  exit (EXIT_FAILURE);
		}
	      yet_more_useless_junk = sizeof(some_more_junk);
	      if ( getsockopt (socketd, SOL_SOCKET, SO_ERROR,
			       &some_more_junk,
			       &yet_more_useless_junk) == -1 )
		{
		  perror ("getsockopt");
		  exit (EXIT_FAILURE);
		}
	      if ( some_more_junk != 0 )
		{
		  fprintf (stderr, "(connect had been interrupted, "
			   "and now polling produced an error)\n");
		  fprintf (stderr, "connect: %s\n",
			   strerror (some_more_junk));
		  exit (EXIT_FAILURE);
		}
	    }
	  else
	    {
	      perror ("connect");
	      exit (EXIT_FAILURE);
	    }
	}
#else
      while ( connect (socketd, (struct sockaddr *)&addr,
		       sizeof(addr)) == -1 )
	{
	  if ( errno == EINTR )
	    {
	      have_testcase = 1;
	      nbsubtries++;
#if VERBOSE
	      fprintf (stderr, "connect: Interrupted system call "
		       "- retrying\n");
#endif
	      if ( GIVEUP && nbsubtries >= GIVEUP )
		{
		  fprintf (stderr, "connect: Cannot complete without "
			   "interruption - giving up\n");
		  exit (EXIT_FAILURE);
		}
	      continue;
	    }
	  if ( have_testcase )
	    fprintf (stderr, "(connect had been interrupted "
		     "and now produced an error)\n");
	  perror ("connect");
	  exit (EXIT_FAILURE);
	}
#endif
      if ( have_testcase )
	fprintf (stderr, "(connect has been interrupted "
		 "and now completed successfully)\n");
#if VERBOSE
      fprintf (stderr, "connect: Success\n");
#endif
      while ( close (socketd) == -1 )
	{
	  if ( errno == EINTR )
	    continue;
	  perror ("close");
	  exit (EXIT_FAILURE);
	}
#if VERBOSE
      fprintf (stderr, "close: Success\n");
#endif
      nbtries++;
      if ( GIVEUP && nbtries >= GIVEUP )
	{
	  fprintf (stderr, "connect: Never interrupted "
		   "- giving up\n");
	  exit (EXIT_FAILURE);
	}
    }
}

int
main (void)
{
  fprintf (stderr, "Will try to connect to %s on port %d\n",
	   CONNECT_ADDRESS, CONNECT_PORT);
  if ( 1 )
    {
      struct sigaction catchsig;

      memset (&catchsig, 0, sizeof(catchsig));
      catchsig.sa_handler = ignore_handler;
      if ( sigaction (SIGUSR1, &catchsig, NULL) == -1 )
	{
	  perror ("sigaction");
	  exit (EXIT_FAILURE);
	}
    }
  child = fork ();
  if ( child == -1 )
    {
      perror ("fork");
      exit (EXIT_FAILURE);
    }
  if ( child )
    {
      struct sigaction childsig;

      memset (&childsig, 0, sizeof(childsig));
      childsig.sa_handler = terminate_handler;
      if ( sigaction (SIGCHLD, &childsig, NULL) == -1 )
	{
	  perror ("sigaction");
	  kill (child, SIGTERM);
	  exit (EXIT_FAILURE);
	}
      killing_loop ();
    }
  else
    child_loop ();
  exit (EXIT_SUCCESS);
  return 0;
}