Fixed include files to eliminate compile warnings.
[icmpmonitor] / icmpmonitor.c
CommitLineData
c5de9e27
AT
1/*
2 * Copyright (c) 1989 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Mike Muuss.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37/*
38 * ICMPMONITOR.C
39 *
40 * Using the InterNet Control Message Protocol (ICMP) "ECHO" facility,
41 * monitor several hosts, and notify admin if some of them are down.
42 *
43 * Author -
44 * Vadim Zaliva <lord@crocodile.org>
45 *
46 * Status -
47 * Public Domain. Distribution Unlimited.
48 */
49
50char copyright[] =
51"@(#) Copyright (c) 1989 The Regents of the University of California.\n"
52"All rights reserved.\n";
53
54char rcsid[] = "$Id: icmpmonitor.c,v 1.8 2004/05/28 01:33:07 lord Exp $";
55
56#include <sys/param.h>
57#include <stdio.h>
58#include <stdlib.h>
59#ifdef HAVE_SYSLOG_H
60# include <syslog.h>
61#endif
62#include <stdarg.h>
63#include <signal.h>
64#include <string.h>
65#ifdef HAVE_SYS_TIME_H
66# include <sys/time.h>
b50b99d4 67# include <sys/wait.h>
c5de9e27
AT
68#endif
69#include <sys/socket.h>
70#include <sys/types.h>
71#ifdef HAVE_FCNTL_H
72# include <fcntl.h>
73#endif
74#ifdef HAVE_SYS_FCNTL_H
75# include <sys/fcntl.h>
76#endif
77#ifdef HAVE_UNISTD_H
78# include <unistd.h>
79#endif
80
b50b99d4
AT
81#include <sys/stat.h>
82
c5de9e27
AT
83#include <netinet/in.h>
84#include <arpa/inet.h>
85#include <netdb.h>
86#include <netinet/in_systm.h>
87#include <netinet/ip.h>
88#include <netinet/ip_icmp.h>
89
90/* Workaround for broken ICMP header on Slackware 4.x */
91#ifdef _LINUX_ICMP_H
92# warning "Broken Slackware 4.x 'netinet/ip_icmp.h' header detected. Using replacement 'struct icmp' definition."
93# define ICMP_MINLEN 8
94struct icmp
95{
96 u_int8_t icmp_type;
97 u_int8_t icmp_code;
98 u_int16_t icmp_cksum;
99 union
100 {
101 struct ih_idseq
102 {
103 u_int16_t icd_id;
104 u_int16_t icd_seq;
105 } ih_idseq;
106 } icmp_hun;
107
108# define icmp_id icmp_hun.ih_idseq.icd_id
109# define icmp_seq icmp_hun.ih_idseq.icd_seq
110
111 union {
112 u_int8_t id_data[1];
113 } icmp_dun;
114
115# define icmp_data icmp_dun.id_data
116
117};
118#endif /* _LINUX_ICMP_H */
119
120#include <stddef.h>
121#include <errno.h>
122
123#include "cfg.h"
124
125/* defines */
126
127/* #define DEBUG */
128
129#ifndef nil
130# define nil NULL
131#endif
132
133/* return codes */
134#define RET_OK 0
135#define RET_NO_HOSTS 1
136#define RET_INIT_ERROR 2
137#define RET_BAD_CFG 3
138#define RET_BAD_OPT 4
139
140#define MAXPACKET (65536 - 60 - 8) /* max packet size */
141#define DEFDATALEN (64 - 8) /* default data length */
142
143#define VERSION "ICMPmonitor v1.2 by lord@crocodile.org"
144#define MAX_LOG_MSG_SIZE 4096
145
146# define icmphdr icmp
147
148/* typedefs */
149typedef struct monitor_host
150{
151 /* following are coming from cfg */
152 char *name;
153 int ping_interval;
154 int max_delay;
155 char *upcmd;
156 char *downcmd;
157
158 /* following values are calculated */
159 int socket;
160 struct timeval last_ping_received;
161 struct timeval last_ping_sent;
162 int up;
163 int down;
164 struct sockaddr_in dest;
165
166 unsigned int sentpackets ;
167 unsigned int recvdpackets;
168
169 /* linked list */
170 struct monitor_host *next;
171} monitor_host_t;
172
173/* protos */
174static void logopen(void);
175static void logclose(void);
176static void log(int type, char *format, ...);
177static int gethostaddr(const char *name);
178static void read_hosts(const char *cfg_file_name);
179static void init_hosts(void);
180static void get_response(void);
181static void pinger(int);
182static int in_cksum(u_short *addr, int len);
183static void read_icmp_data(monitor_host_t *p);
184static void tvsub(struct timeval *out, struct timeval *in);
185static void done(int code);
186static void start_daemon(void);
187static int gcd(int x, int y);
188
189/* globals */
190
191static monitor_host_t **hosts = nil;
192static int isDaemon = 0;
193static int isVerbose = 0;
194static int keepBanging = 0;
195static unsigned short ident;
196static int send_delay = 1;
197
198int main(int ac, char **av)
199{
200 extern char* optarg;
201 extern int optind;
202 char *cfgfile=nil;
203 int param;
204
205 logopen();
206 log(LOG_INFO, VERSION " is starting.");
207
208 while((param = getopt(ac, av, "rvdf:")) != -1)
209 switch(param)
210 {
211 case 'v':
212 isVerbose = 1;
213 break;
214 case 'd':
215 isDaemon = 1;
216 break;
217 case 'r':
218 keepBanging = 1;
219 break;
220 case 'f':
221 cfgfile=strdup(optarg);
222 break;
223 default:
224 fprintf(stderr,"Usage: icmpmonitor [-d] [-v] [-r] [-f cfgfile]\n");
225 done(RET_BAD_OPT);
226 }
227
228 if(!cfgfile)
229 {
230 log(LOG_WARNING,"No cfg file specified. Assuming 'icmpmonitor.cfg'");
231 cfgfile="icmpmonitor.cfg";
232 }
233
234 read_hosts(cfgfile); /* we do this before becoming daemon,
235 to be able process relative path */
236
237 if(isDaemon)
238 start_daemon();
239
240 init_hosts();
241
242 ident=getpid() & 0xFFFF;
243
244 (void)signal(SIGALRM, pinger);
245 alarm(send_delay);
246
247 get_response();
248
249 done(RET_OK);
250}
251
252
253/*
254 * in_cksum --
255 * Checksum routine for Internet Protocol family headers (C Version)
256 */
257static int
258in_cksum(u_short *addr, int len)
259{
260 register int nleft = len;
261 register u_short *w = addr;
262 register int sum = 0;
263 u_short answer = 0;
264
265 /*
266 * Our algorithm is simple, using a 32 bit accumulator (sum), we add
267 * sequential 16 bit words to it, and at the end, fold back all the
268 * carry bits from the top 16 bits into the lower 16 bits.
269 */
270 while (nleft > 1) {
271 sum += *w++;
272 nleft -= 2;
273 }
274
275 /* mop up an odd byte, if necessary */
276 if (nleft == 1) {
277 *(u_char *)(&answer) = *(u_char *)w ;
278 sum += answer;
279 }
280
281 /* add back carry outs from top 16 bits to low 16 bits */
282 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
283 sum += (sum >> 16); /* add carry */
284 answer = ~sum; /* truncate to 16 bits */
285 return(answer);
286}
287
288/*
289 * pinger --
290 * Compose and transmit an ICMP ECHO REQUEST packet. The IP packet
291 * will be added on by the kernel. The ID field is our UNIX process ID,
292 * and the sequence number is an ascending integer. The first 8 bytes
293 * of the data portion are used to hold a UNIX "timeval" struct in VAX
294 * byte-order, to compute the round-trip time.
295 */
296static void pinger(int ignore)
297{
298 register struct icmphdr *icp;
299 register int cc;
300 int i;
301 monitor_host_t *p;
302 u_char outpack[MAXPACKET];
303
304 p=hosts[0];
305 while(p)
306 {
307 if(p->socket!=-1)
308 {
309 struct timeval now;
310
311 (void)gettimeofday(&now,(struct timezone *)NULL);
312 tvsub(&now, &p->last_ping_received);
313
314 if(now.tv_sec > (p->max_delay+p->ping_interval))
315 {
316 p->up=0;
317 if((!p->down) || keepBanging)
318 {
319 p->down = 1;
320
321 if(isVerbose)
322 log(LOG_INFO,"Host %s in down. Executing DOWN command",p->name);
323 if(!fork())
324 {
325 system(p->downcmd);
326 exit(0);
327 } else
328 {
329 wait(nil);
330 }
331 }
332 }
333
334 (void)gettimeofday(&now,(struct timezone *)NULL);
335 tvsub(&now, &p->last_ping_sent);
336
337 if(now.tv_sec > p->ping_interval)
338 {
339 /* Time to send ping */
340
341 icp = (struct icmphdr *)outpack;
342 icp->icmp_type = ICMP_ECHO;
343 icp->icmp_code = 0;
344 icp->icmp_cksum = 0;
345 icp->icmp_seq = p->socket;
346 icp->icmp_id = ident;
347
348 if(isVerbose)
349 log(LOG_INFO,"Sending ICMP packet to %s.",p->name);
350
351 (void)gettimeofday((struct timeval *)&outpack[8],
352 (struct timezone *)NULL);
353
354 cc = DEFDATALEN + 8; /* skips ICMP portion */
355
356 /* compute ICMP checksum here */
357 icp->icmp_cksum = in_cksum((u_short *)icp, cc);
358
359 i = sendto(p->socket, (char *)outpack, cc, 0, (const struct sockaddr *)(&p->dest),
360 sizeof(struct sockaddr));
361
362 (void)gettimeofday(&p->last_ping_sent,
363 (struct timezone *)NULL);
364
365 if(i<0 || i!=cc)
366 {
367 if(i<0)
368 log(LOG_WARNING,"Sending ICMP packet to %s failed.",p->name);
369 }
370 p->sentpackets++;
371 }
372 }
373 p=p->next;
374
375 }
376
377 (void)signal(SIGALRM, pinger); /* restore handler */
378 alarm(send_delay);
379}
380
381static void get_response(void)
382{
383 fd_set rfds;
384 int retval;
385 monitor_host_t *p;
386 int maxd=-1;
387
388 while(1)
389 {
390 p=hosts[0];
391 FD_ZERO(&rfds);
392 while(p)
393 {
394 if(p->socket != -1)
395 {
396 if(p->socket > maxd)
397 maxd=p->socket;
398 FD_SET(p->socket, &rfds);
399 }
400 p=p->next;
401 }
402
403 retval = select(maxd+1, &rfds, nil, nil, nil);
404 if(retval<0)
405 {
406 /* we get her in case we are interrupted by signal.
407 it's ok. */
408 }
409 else
410 {
411 if(retval>0)
412 {
413 /* log(LOG_DEBUG,"ICMP data is available now."); */
414 p=hosts[0];
415 while(p)
416 {
417 if(p->socket!=-1 && FD_ISSET(p->socket, &rfds))
418 {
419 /* Read data */
420 read_icmp_data(p);
421 }
422 p=p->next;
423 }
424 } else
425 {
426 log(LOG_DEBUG,"select returns 0."); /* TODO */
427 }
428 }
429 }
430}
431
432static void read_icmp_data(monitor_host_t *p)
433{
434 socklen_t fromlen ;
435 struct sockaddr_in from ;
436 int cc ;
437 struct ip *ip ;
438 struct icmp *icmp ;
439 int iphdrlen ;
440 int delay ;
441 struct timeval tv ;
442 unsigned char buf[MAXPACKET]; /* read buffer */
443
444 (void)gettimeofday(&tv, (struct timezone *)NULL);
445
446 fromlen = sizeof(from);
447 if((cc = recvfrom(p->socket, buf, sizeof(buf), 0,
448 (struct sockaddr *)&from, &fromlen)) < 0)
449 {
450 if(errno != EINTR)
451 log(LOG_WARNING,"Error reading ICMP data from %s.",p->name);
452 return;
453 }
454
455 /* log(LOG_DEBUG,"Got %d bytes of ICMP data from %s.",cc, p->name); */
456
457 /* check IP header actual len */
458 ip = (struct ip *)buf ;
459 iphdrlen = ip->ip_hl<<2 ;
460 icmp = (struct icmp *) (buf+iphdrlen) ;
461
462 if(cc < iphdrlen+ICMP_MINLEN)
463 {
464 log(LOG_WARNING,"Received short packet from %s.",p->name);
465 return;
466 }
467
468 if(icmp->icmp_type == ICMP_ECHOREPLY &&
469 icmp->icmp_id == ident &&
470 icmp->icmp_seq == p->socket)
471 {
472 p->recvdpackets++;
473
474 memcpy(&p->last_ping_received, &tv, sizeof(tv));
475
476 tvsub(&tv, (struct timeval *) &icmp->icmp_data[0]);
477 delay=tv.tv_sec*1000+(tv.tv_usec/1000);
478
479 if(isVerbose)
480 log(LOG_INFO,"Got ICMP reply from %s in %d ms.",p->name,delay);
481 p->down=0;
482 if(!p->up)
483 {
484 p->up=1;
485 if(isVerbose)
486 log(LOG_INFO,"Host %s in now up. Executing UP command",p->name);
487 if(!fork())
488 {
489 system(p->upcmd);
490 exit(0);
491 } else
492 {
493 wait(nil);
494 }
495 }
496 } else
497 {
498 /*
499 log(LOG_DEBUG,"ICMP packet of type %d from %s. Ident=%d",icmp->icmp_type,
500 p->name,
501 icmp->icmp_id
502 );
503 */
504 }
505}
506
507static void read_hosts(const char *cfg_file_name)
508{
509 int i,n=0;
510 struct Cfg *cfg;
511
512 if((cfg=readcfg(cfg_file_name))==NULL)
513 {
514 log(LOG_ERR,"Error reading cfg. Exiting.");
515 done(RET_BAD_CFG);
516 }
517
518 if(cfg->nelements)
519 {
520 hosts=malloc(sizeof(monitor_host_t *)*cfg->nelements);
521 for(i=0;i<cfg->nelements;i++)
522 {
523 if(cfg->dict[i]->nvalues<4)
524 {
525 log(LOG_ERR,"Not enough fields in record %d of cfg file. Got %d.",n, cfg->dict[i]->nvalues+1);
526 done(RET_BAD_CFG);
527 } else if(cfg->dict[i]->nvalues>5)
528 {
529 log(LOG_ERR,"Too many fields in record %d of cfg file. Got %d.",n, cfg->dict[i]->nvalues+1);
530 done(RET_BAD_CFG);
531 }
532
533 hosts[n]=malloc(sizeof(monitor_host_t));
534 hosts[n]->name = strdup(cfg->dict[i]->name);
535 hosts[n]->ping_interval = atoi (cfg->dict[i]->value[0]);
536 hosts[n]->max_delay = atoi (cfg->dict[i]->value[1]);
537 hosts[n]->upcmd = strdup(cfg->dict[i]->value[2]);
538 hosts[n]->downcmd = strdup(cfg->dict[i]->value[3]);
539
540 if(cfg->dict[i]->nvalues==4)
541 {
542 hosts[n]->down = 0;
543 hosts[n]->up = 1;
544 } else if(strcmp(cfg->dict[i]->value[4], "up")==0)
545 {
546 hosts[n]->down = 0;
547 hosts[n]->up = 1;
548 } else if(strcmp(cfg->dict[i]->value[4], "down")==0)
549 {
550 hosts[n]->down = 1;
551 hosts[n]->up = 0;
552 } else if(strcmp(cfg->dict[i]->value[4], "auto")==0)
553 {
554 hosts[n]->down = 1;
555 hosts[n]->up = 1;
556 } else if(strcmp(cfg->dict[i]->value[4], "none")==0)
557 {
558 hosts[n]->down = 0;
559 hosts[n]->up = 0;
560 } else
561 {
562 log(LOG_ERR,"Illegal value %s in record %n for startup condition.", cfg->dict[i]->value[4], n);
563 done(RET_BAD_CFG);
564 }
565 hosts[n]->sentpackets = 0;
566 hosts[n]->recvdpackets = 0;
567
568 hosts[n]->socket = -1;
569 hosts[n]->next = nil;
570 if(n>0)
571 hosts[n-1]->next=hosts[n];
572 (void)gettimeofday(&(hosts[n]->last_ping_received), (struct timezone *)NULL);
573
574 n++;
575 }
576 }
577
578 freecfg(cfg);
579
580 if(n<=0)
581 {
582 log(LOG_ERR,"No hosts defined in cfg file, exiting.");
583 done(RET_NO_HOSTS);
584 }
585 else
586 log(LOG_DEBUG,"%d host(s) found in cfg file,", n);
587
588}
589
590static int gethostaddr(const char *name)
591{
592 static int res;
593 struct hostent *he;
594
595 if((res=inet_addr(name))<0)
596 {
597 he=gethostbyname(name);
598 if(!he)
599 return -1;
600 memcpy( &res , he->h_addr , he->h_length );
601 }
602 return(res);
603}
604
605static void init_hosts(void)
606{
607 monitor_host_t *p=hosts[0];
608 struct protoent *proto;
609 int ok=0;
610
611 if((proto=getprotobyname("icmp"))==nil)
612 {
613 log(LOG_ERR,"Unknown protocol: icmp. Exiting.");
614 done(RET_INIT_ERROR);
615 }
616
617 while(p)
618 {
619 log(LOG_DEBUG,"resolving host %s", p->name);
620
621 bzero(&p->dest,sizeof(p->dest));
622 p->dest.sin_family=AF_INET;
623 if((p->dest.sin_addr.s_addr=gethostaddr(p->name))<=0)
624 {
625 log(LOG_ERR,"Can't resolve host. Skipping client %s.",p->name);
626 p->socket=-1;
627 } else
628 {
629 if((p->socket=socket(AF_INET,SOCK_RAW,proto->p_proto))<0)
630 {
631 log(LOG_ERR,"Can't create socket. Skipping client %s.",p->name);
632 p->socket=-1;
633 } else
634 {
635 if(ok==0)
636 send_delay = p->ping_interval;
637 else
638 send_delay = gcd(send_delay, p->ping_interval);
639 ok++;
640 }
641 }
642 p=p->next;
643 }
644
645 if(!ok)
646 {
647 log(LOG_ERR,"No hosts left to process, exiting.");
648 done(RET_NO_HOSTS);
649 }
650}
651
652/*
653 * tvsub --
654 * Subtract 2 timeval structs: out = out - in. Out is assumed to
655 * be >= in.
656 */
657static void
658tvsub(register struct timeval *out, register struct timeval *in)
659{
660 if ((out->tv_usec -= in->tv_usec) < 0) {
661 --out->tv_sec;
662 out->tv_usec += 1000000;
663 }
664 out->tv_sec -= in->tv_sec;
665}
666
667void done(int code)
668{
669 logclose();
670 exit(code);
671}
672
673void start_daemon(void)
674{
675 if(fork())
676 exit(0);
677
678 chdir("/");
679 umask(0);
680 (void) close(0);
681 (void) close(1);
682 (void) close(2);
683 (void) open("/", O_RDONLY);
684 (void) dup2(0, 1);
685 (void) dup2(0, 2);
686 setsid();
687}
688
689static void logopen(void)
690{
691#if HAVE_OPENLOG
692 if(isDaemon)
693 openlog("icmpmonitor", LOG_PID| LOG_CONS|LOG_NOWAIT, LOG_USER);
694#else
695 log(LOG_WARNING,"Compiled without syslog. Syslog can't be used.");
696#endif
697}
698
699static void logclose(void)
700{
701#if HAVE_CLOSELOG
702 if(isDaemon)
703 closelog();
704#endif
705}
706
707/**
708 * This function should be used as central logging facility.
709 * 'type' argument should be one of following:
710 *
711 * LOG_EMERG system is unusable
712 * LOG_ALERT action must be taken immediately
713 * LOG_CRIT critical conditions
714 * LOG_ERR error conditions
715 * LOG_WARNING warning conditions
716 * LOG_NOTICE normal but significant condition
717 * LOG_INFO informational
718 * LOG_DEBUG debug-level messages
719 */
720static void log(int type, char *format, ...)
721{
722 va_list ap;
723
724#ifndef DEBUG
725 if(type==LOG_DEBUG)
726 return;
727#endif
728
729 va_start(ap, format);
730
731 if(isDaemon)
732 {
733 char buffer[MAX_LOG_MSG_SIZE];
734
735#if HAVE_VSNPRINTF
736 (void)vsnprintf(buffer, MAX_LOG_MSG_SIZE, format, ap);
737#else
738# if HAVE_VSPRINTF
739# warning "Using VSPRINTF. Buffer overflow could happen!"
740 (void)vsprintf(buffer, format, ap);
741# else
742# error "Your standard libabry have neither vsnprintf nor vsprintf defined. One of them is reqired!"
743# endif
744#endif
745#if HAVE_SYSLOG
746 syslog(type,buffer);
747#endif
748 } else
749 {
750 (void) fprintf(stderr, "icmpmonitor[%d]:", (int)getpid());
751 (void) vfprintf(stderr, format, ap);
752 (void) fprintf(stderr, "\n");
753 }
754 va_end(ap);
755}
756
757static int gcd(int x, int y)
758{
759 while(x!=y)
760 {
761 if(x<y)
762 y-=x;
763 else
764 x-=y;
765 }
766 return x;
767}