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