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