1/*
2 * tcpcheck.c - checks to see if a TCP connection can be established
3 *              to the specified host/port.  Prints a success message
4 *              or a failure message for each host.
5 *
6 * Think of it as 'fping' for TCP connections.  Maximum
7 * parallelism.
8 *
9 * Designed to be as non-blocking as possible, but DNS FAILURES WILL
10 * CAUSE IT TO PAUSE.  You have been warned.
11 *
12 * Timeout handling:
13 *
14 * Uses the depricated alarm() syntax for simple compatability between
15 * BSD systems.
16 *
17 * Copyright (C) 1998 David G. Andersen <angio@aros.net>
18 *
19 * This information is subject to change without notice and does not
20 * represent a commitment on the part of David G. Andersen
21 * This software is furnished under a license and a nondisclosure agreement.
22 * This software may be used or copied only in accordance with the terms of
23 * this agreement.  It is against Federal Law to copy this software on magnetic
24 * tape, disk, or any other medium for any purpose other than the purchaser's
25 * use.  David G. Andersen makes no warranty of any kind, expressed
26 * or implied, with regard to these programs or documentation.  David G.
27 * Andersen shall not be liable in any event for incidental or consequential
28 * damages in connection with, or arising out of, the furnishing, performance,
29 * or use of these programs.
30 */
31
32#include <unistd.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <sys/types.h>
37#include <sys/time.h>
38#include <sys/socket.h>
39#include <netinet/in.h>
40#include <netdb.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <signal.h>
44#include <time.h>
45
46#define ERR_SUCCESS 0
47#define ERR_REFUSED 1
48#define ERR_TIMEOUT 2
49#define ERR_OTHER   3
50
51#define MAXTRIES 220;  /* Max number of connection attempts we can
52			  try at once.  Must be lower than the
53			  max number of sockets we can have open... */
54
55#define DEFAULT_TIMEOUT 20
56static int numcons;
57static int consused; /* Number actually used */
58static int timeout = DEFAULT_TIMEOUT; /* 20 second default timeout */
59static struct connectinfo **cons;
60
61struct connectinfo {
62  char *hostname;
63  char *portname;
64  short port;
65  int status;
66  int socket;
67  struct sockaddr_in *sockaddr;
68  int socklen;
69};
70
71/*
72 * setupsocket:  Configures a socket to make it go away quickly:
73 *
74 * SO_REUSEADDR - allow immediate reuse of the address
75 * not SO_LINGER - Don't wait for data to be delivered.
76 */
77static int setupsocket(int sock) {
78  int flags;
79  struct linger nolinger;
80
81  nolinger.l_onoff = 0;
82  if (setsockopt(sock, SOL_SOCKET,
83	     SO_LINGER, (char *) &nolinger, sizeof(nolinger)) == -1)
84    return -1;
85
86  flags = 1;
87  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&flags,
88		 sizeof(flags)) == -1)
89    return -1;
90
91  /* Last thing we do:  Set it nonblocking */
92
93  flags = O_NONBLOCK | fcntl(sock, F_GETFL);
94  fcntl(sock, F_SETFL, flags);
95
96  return 0;
97}
98
99/*
100 * setuphost:  Configures a struct connectinfo for
101 *             connecting to a host.  Opens a socket for it.
102 *
103 * returns 0 on success, -1 on failure
104 */
105static int setuphost(struct connectinfo *cinfo, char *hostport)
106{
107  struct hostent *tmp;
108  char *port;
109  struct servent *serv;
110
111  if (!hostport) {
112    fprintf(stderr, "null hostname passed to setuphost.  bailing\n");
113    exit(-1);
114  }
115
116  port = strchr(hostport, ':');
117  if (!port || *(++port) == 0) {
118    return -1;
119  }
120
121  if (port[0] < '0' || port[0] > '9') {
122    serv = getservbyname(port, "tcp");
123    if (!serv) {
124      return -1;
125    }
126    cinfo->port = ntohs(serv->s_port);
127  } else {
128    cinfo->port = atoi(port);
129  }
130  cinfo->portname = strdup(port);
131
132  cinfo->hostname = strdup(hostport);
133  port = strchr(cinfo->hostname, ':');
134  *port = 0;
135
136  tmp = gethostbyname(cinfo->hostname);
137
138  /* If DNS fails, skip this puppy */
139  if (tmp == NULL) {
140    cinfo->status = -1;
141    return -1;
142  }
143
144  cinfo->status = 0;
145
146  /* Groovy.  DNS stuff worked, now open a socket for it */
147  cinfo->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
148  if (cinfo->socket == -1) {
149    return -1;
150  }
151
152  /* Set the socket stuff */
153  setupsocket(cinfo->socket);
154
155  cinfo->socklen = sizeof(struct sockaddr_in);
156  cinfo->sockaddr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
157  bzero(cinfo->sockaddr, cinfo->socklen);
158
159  bcopy(tmp->h_addr_list[0],
160	&(cinfo->sockaddr->sin_addr),
161	sizeof(cinfo->sockaddr->sin_addr));
162#ifdef DA_HAS_STRUCT_SINLEN
163  cinfo->sockaddr->sin_len = cinfo->socklen;
164#endif
165  cinfo->sockaddr->sin_family = AF_INET;
166  cinfo->sockaddr->sin_port = htons(cinfo->port);
167
168  return 0;
169}
170
171static void usage()
172{
173  fprintf(stderr, "usage:  tcpcheck <timeout> <host:port> [host:port]\n");
174  exit(1);
175}
176
177static void wakeme(int sig)
178{
179  if (sig == SIGTERM)
180		exit(0);
181
182  printf("Got a timeout.  Will fail all outstanding connections.\n");
183  exit(ERR_TIMEOUT);
184}
185
186static void goodconnect(int i) {
187  printf("%s:%s is alive\n", cons[i]->hostname, cons[i]->portname);
188  cons[i]->status = 1;
189  close(cons[i]->socket);
190}
191
192static void failedconnect(int i) {
193  printf("%s:%s failed\n", cons[i]->hostname, cons[i]->portname);
194  cons[i]->status = -1;
195}
196
197/*
198 * utility function to set res = a - b
199 * for timeval structs
200 * Assumes that a > b
201 */
202static void subtime(struct timeval *a, struct timeval *b, struct timeval *res)
203{
204  res->tv_sec = a->tv_sec - b->tv_sec;
205  if (b->tv_usec > a->tv_usec) {
206    a->tv_usec += 1000000;
207    res->tv_sec -= 1;
208  }
209  res->tv_usec = a->tv_usec - b->tv_usec;
210}
211
212/*
213 * set up our fd sets
214 */
215
216static void setupset(fd_set *theset, int *numfds)
217{
218  int i;
219  int fds_used = 0;
220
221  *numfds = 0;
222  for (i = 0; i < numcons; i++) {
223    if (cons[i] == NULL) continue;
224    if (cons[i]->status == 0 && cons[i]->socket != -1) {
225      FD_SET(cons[i]->socket, theset);
226      fds_used++;
227      if (cons[i]->socket > *numfds)
228	*numfds = cons[i]->socket;
229    }
230  }
231  if (!fds_used) {
232    exit(0);			/* Success! */
233  }
234}
235
236/*
237 * We've initiated all connection attempts.
238 * Here we sit and wait for them to complete.
239 */
240static void waitforconnects()
241{
242  fd_set writefds, exceptfds;
243  struct timeval timeleft;
244  struct timeval starttime;
245  struct timeval curtime;
246  struct timeval timeoutval;
247  struct timeval timeused;
248  struct sockaddr_in dummysock;
249  int dummyint = sizeof(dummysock);
250  int numfds;
251  int res;
252  int i;
253
254  gettimeofday(&starttime, NULL);
255
256  timeoutval.tv_sec = timeout;
257  timeoutval.tv_usec = 0;
258
259  timeleft = timeoutval;
260
261  while (1)
262  {
263    FD_ZERO(&writefds);
264    FD_ZERO(&exceptfds);
265    setupset(&writefds, &numfds);
266    setupset(&exceptfds, &numfds);
267    res = select(numfds+1, NULL, &writefds, &exceptfds, &timeleft);
268
269    if (res == -1) {
270      perror("select barfed, bailing");
271      exit(-1);
272    }
273
274    if (res == 0)		/* We timed out */
275      break;
276
277    /* Oooh.  We have some successes */
278    /* First test the exceptions */
279
280    for (i = 0; i < numcons; i++)
281    {
282      if (cons[i] == NULL) continue;
283      if (FD_ISSET(cons[i]->socket, &exceptfds)) {
284				failedconnect(i);
285      }
286      else if (FD_ISSET(cons[i]->socket, &writefds)) {
287				/* Boggle.  It's not always good.  select() is weird. */
288				if (getpeername(cons[i]->socket, (struct sockaddr *)&dummysock, (socklen_t *)&dummyint))
289					failedconnect(i);
290				else
291					goodconnect(i);
292			}
293		}
294
295		/* now, timeleft = timeoutval - timeused */
296
297		gettimeofday(&curtime, NULL);
298		subtime(&curtime, &starttime, &timeused);
299		subtime(&timeoutval, &timeused, &timeleft);
300	}
301
302  /* Now clean up the remainder... they timed out. */
303  for (i = 0; i < numcons; i++) {
304    if (cons[i]->status == 0) {
305      printf("%s:%d failed:  timed out\n",
306	     cons[i]->hostname, cons[i]->port);
307    }
308  }
309}
310
311int tcpcheck_main(int argc, char *argv[])
312{
313  struct connectinfo *pending;
314  int i;
315  int res;
316
317  signal(SIGALRM, wakeme);
318  signal(SIGTERM, wakeme);
319
320  if (argc <= 2) usage();
321  if (argv == NULL || argv[1] == NULL || argv[2] == NULL) usage();
322
323  timeout = atoi(argv[1]);
324
325  numcons = argc-2;
326  cons = malloc(sizeof(struct connectinfo *) * (numcons+1));
327  bzero((char *)cons, sizeof(struct connectinfo *) * (numcons+1));
328
329  pending = NULL;
330  consused = 0;
331
332  /* Create a bunch of connection management structs */
333  for (i = 2; i < argc; i++) {
334    if (pending == NULL)
335      pending = malloc(sizeof(struct connectinfo));
336    if (setuphost(pending, (char *)argv[i])) {
337      printf("%s failed.  could not resolve address\n", pending->hostname);
338    } else {
339      cons[consused++]  = pending;
340      pending = NULL;
341    }
342  }
343
344  for (i = 0; i < consused; i++) {
345    if (cons[i] == NULL) continue;
346    res = connect(cons[i]->socket, (struct sockaddr *)(cons[i]->sockaddr),
347		  cons[i]->socklen);
348
349    if (res && errno != EINPROGRESS) {
350      failedconnect(i);
351    }
352  }
353
354  /* Okay, we've initiated all of our connection attempts.
355   * Now we just have to wait for them to timeout or complete
356   */
357
358  waitforconnects();
359
360  exit(0);
361}
362