Code

306113b51869b355755e7c294e12eeb2c95c610e
[nagiosplug.git] / plugins-root / check_icmp.c
1  /******************************************************************************
2 *
3 * Nagios check_icmp plugin
4 *
5 * License: GPL
6 * Copyright (c) 2005-2006 nagios-plugins team
7 *
8 * Original Author : Andreas Ericsson <ae@op5.se>
9 *
10 * Last Modified: $Date$
11 *
12 * Description:
13 *
14 * This file contains the check_icmp plugin
15 *
16 *  Relevant RFC's: 792 (ICMP), 791 (IP)
17 *
18 *  This program was modeled somewhat after the check_icmp program,
19 *  which was in turn a hack of fping (www.fping.org) but has been
20 *  completely rewritten since to generate higher precision rta values,
21 *  and support several different modes as well as setting ttl to control.
22 *  redundant routes. The only remainders of fping is currently a few
23 *  function names.
24 *
25 * License Information:
26 *
27 * This program is free software; you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation; either version 2 of the License, or
30 * (at your option) any later version.
31 *
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35 * GNU General Public License for more details.
36 *
37 * You should have received a copy of the GNU General Public License
38 * along with this program; if not, write to the Free Software
39 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
40 *
41 * $Id$
42
43 *****************************************************************************/
45 /* progname may be check_ldaps */
46 //char *progname = "check_ldap";
47 const char *revision = "$Revision$";
48 const char *copyright = "2005-2006";
49 const char *email = "nagiosplug-devel@lists.sourceforge.net";
51 /** nagios plugins basic includes */
52 #include "common.h"
53 #include "netutils.h"
54 #include "utils.h"
56 #include <sys/time.h>
57 #include <sys/types.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <stdarg.h>
61 #include <unistd.h>
62 #include <stddef.h>
63 #include <errno.h>
64 #include <string.h>
65 #include <ctype.h>
66 #include <netdb.h>
67 #include <sys/socket.h>
68 #include <netinet/in_systm.h>
69 #include <netinet/in.h>
70 #include <netinet/ip.h>
71 #include <netinet/ip_icmp.h>
72 #include <arpa/inet.h>
73 #include <signal.h>
76 /** sometimes undefined system macros (quite a few, actually) **/
77 #ifndef MAXTTL
78 # define MAXTTL 255
79 #endif
80 #ifndef INADDR_NONE
81 # define INADDR_NONE 0xffffffU
82 #endif
84 #ifndef SOL_IP
85 #define SOL_IP 0
86 #endif
88 /* we bundle these in one #ifndef, since they're all from BSD
89  * Put individual #ifndef's around those that bother you */
90 #ifndef ICMP_UNREACH_NET_UNKNOWN
91 # define ICMP_UNREACH_NET_UNKNOWN 6
92 # define ICMP_UNREACH_HOST_UNKNOWN 7
93 # define ICMP_UNREACH_ISOLATED 8
94 # define ICMP_UNREACH_NET_PROHIB 9
95 # define ICMP_UNREACH_HOST_PROHIB 10
96 # define ICMP_UNREACH_TOSNET 11
97 # define ICMP_UNREACH_TOSHOST 12
98 #endif
99 /* tru64 has the ones above, but not these */
100 #ifndef ICMP_UNREACH_FILTER_PROHIB
101 # define ICMP_UNREACH_FILTER_PROHIB 13
102 # define ICMP_UNREACH_HOST_PRECEDENCE 14
103 # define ICMP_UNREACH_PRECEDENCE_CUTOFF 15
104 #endif
107 typedef unsigned short range_t;  /* type for get_range() -- unimplemented */
109 typedef struct rta_host {
110         unsigned short id;           /* id in **table, and icmp pkts */
111         char *name;                  /* arg used for adding this host */
112         char *msg;                   /* icmp error message, if any */
113         struct sockaddr_in saddr_in; /* the address of this host */
114         struct in_addr error_addr;   /* stores address of error replies */
115         unsigned long long time_waited; /* total time waited, in usecs */
116         unsigned int icmp_sent, icmp_recv, icmp_lost; /* counters */
117         unsigned char icmp_type, icmp_code; /* type and code from errors */
118         unsigned short flags;        /* control/status flags */
119         double rta;                  /* measured RTA */
120         unsigned char pl;            /* measured packet loss */
121         struct rta_host *next;       /* linked list */
122 } rta_host;
124 #define FLAG_LOST_CAUSE 0x01  /* decidedly dead target. */
126 /* threshold structure. all values are maximum allowed, exclusive */
127 typedef struct threshold {
128         unsigned char pl;    /* max allowed packet loss in percent */
129         unsigned int rta;  /* roundtrip time average, microseconds */
130 } threshold;
132 /* the data structure */
133 typedef struct icmp_ping_data {
134         struct timeval stime;   /* timestamp (saved in protocol struct as well) */
135         unsigned short ping_id;
136 } icmp_ping_data;
138 /* the different modes of this program are as follows:
139  * MODE_RTA: send all packets no matter what (mimic check_icmp and check_ping)
140  * MODE_HOSTCHECK: Return immediately upon any sign of life
141  *                 In addition, sends packets to ALL addresses assigned
142  *                 to this host (as returned by gethostbyname() or
143  *                 gethostbyaddr() and expects one host only to be checked at
144  *                 a time.  Therefore, any packet response what so ever will
145  *                 count as a sign of life, even when received outside
146  *                 crit.rta limit. Do not misspell any additional IP's.
147  * MODE_ALL:  Requires packets from ALL requested IP to return OK (default).
148  * MODE_ICMP: implement something similar to check_icmp (MODE_RTA without
149  *            tcp and udp args does this)
150  */
151 #define MODE_RTA 0
152 #define MODE_HOSTCHECK 1
153 #define MODE_ALL 2
154 #define MODE_ICMP 3
156 /* the different ping types we can do
157  * TODO: investigate ARP ping as well */
158 #define HAVE_ICMP 1
159 #define HAVE_UDP 2
160 #define HAVE_TCP 4
161 #define HAVE_ARP 8
163 #define MIN_PING_DATA_SIZE sizeof(struct icmp_ping_data)
164 #define MAX_IP_PKT_SIZE 65536   /* (theoretical) max IP packet size */
165 #define IP_HDR_SIZE 20
166 #define MAX_PING_DATA (MAX_IP_PKT_SIZE - IP_HDR_SIZE - ICMP_MINLEN)
167 #define DEFAULT_PING_DATA_SIZE (MIN_PING_DATA_SIZE + 44)
169 /* various target states */
170 #define TSTATE_INACTIVE 0x01    /* don't ping this host anymore */
171 #define TSTATE_WAITING 0x02             /* unanswered packets on the wire */
172 #define TSTATE_ALIVE 0x04       /* target is alive (has answered something) */
173 #define TSTATE_UNREACH 0x08
175 /** prototypes **/
176 void print_help (void);
177 void print_usage (void);
178 static u_int get_timevar(const char *);
179 static u_int get_timevaldiff(struct timeval *, struct timeval *);
180 static int wait_for_reply(int, u_int);
181 static int recvfrom_wto(int, char *, unsigned int, struct sockaddr *, u_int *);
182 static int send_icmp_ping(int, struct rta_host *);
183 static int get_threshold(char *str, threshold *th);
184 static void run_checks(void);
185 static int add_target(char *);
186 static int add_target_ip(char *, struct in_addr *);
187 static int handle_random_icmp(struct icmp *, struct sockaddr_in *);
188 static unsigned short icmp_checksum(unsigned short *, int);
189 static void finish(int);
190 static void crash(const char *, ...);
192 /** external **/
193 extern int optind, opterr, optopt;
194 extern char *optarg;
195 extern char **environ;
197 /** global variables **/
198 static char *progname;
199 static struct rta_host **table, *cursor, *list;
200 static threshold crit = {80, 500000}, warn = {40, 200000};
201 static int mode, protocols, sockets, debug = 0, timeout = 10;
202 static unsigned short icmp_pkt_size, icmp_data_size = DEFAULT_PING_DATA_SIZE;
203 static unsigned int icmp_sent = 0, icmp_recv = 0, icmp_lost = 0;
204 #define icmp_pkts_en_route (icmp_sent - (icmp_recv + icmp_lost))
205 static unsigned short targets_down = 0, targets = 0, packets = 0;
206 #define targets_alive (targets - targets_down)
207 static unsigned int retry_interval, pkt_interval, target_interval;
208 static int icmp_sock, tcp_sock, udp_sock, status = STATE_OK;
209 static pid_t pid;
210 static struct timezone tz;
211 static struct timeval prog_start;
212 static unsigned long long max_completion_time = 0;
213 static unsigned char ttl = 0;   /* outgoing ttl */
214 static unsigned int warn_down = 1, crit_down = 1; /* host down threshold values */
215 float pkt_backoff_factor = 1.5;
216 float target_backoff_factor = 1.5;
218 /** code start **/
219 static void
220 crash(const char *fmt, ...)
222         va_list ap;
224         printf("%s: ", progname);
226         va_start(ap, fmt);
227         vprintf(fmt, ap);
228         va_end(ap);
230         if(errno) printf(": %s", strerror(errno));
231         puts("");
233         exit(3);
237 static char *
238 get_icmp_error_msg(unsigned char icmp_type, unsigned char icmp_code)
240         char *msg = "unreachable";
242         if(debug > 1) printf("get_icmp_error_msg(%u, %u)\n", icmp_type, icmp_code);
243         switch(icmp_type) {
244         case ICMP_UNREACH:
245                 switch(icmp_code) {
246                 case ICMP_UNREACH_NET: msg = "Net unreachable"; break;
247                 case ICMP_UNREACH_HOST: msg = "Host unreachable"; break;
248                 case ICMP_UNREACH_PROTOCOL: msg = "Protocol unreachable (firewall?)"; break;
249                 case ICMP_UNREACH_PORT: msg = "Port unreachable (firewall?)"; break;
250                 case ICMP_UNREACH_NEEDFRAG: msg = "Fragmentation needed"; break;
251                 case ICMP_UNREACH_SRCFAIL: msg = "Source route failed"; break;
252                 case ICMP_UNREACH_ISOLATED: msg = "Source host isolated"; break;
253                 case ICMP_UNREACH_NET_UNKNOWN: msg = "Unknown network"; break;
254                 case ICMP_UNREACH_HOST_UNKNOWN: msg = "Unknown host"; break;
255                 case ICMP_UNREACH_NET_PROHIB: msg = "Network denied (firewall?)"; break;
256                 case ICMP_UNREACH_HOST_PROHIB: msg = "Host denied (firewall?)"; break;
257                 case ICMP_UNREACH_TOSNET: msg = "Bad TOS for network (firewall?)"; break;
258                 case ICMP_UNREACH_TOSHOST: msg = "Bad TOS for host (firewall?)"; break;
259                 case ICMP_UNREACH_FILTER_PROHIB: msg = "Prohibited by filter (firewall)"; break;
260                 case ICMP_UNREACH_HOST_PRECEDENCE: msg = "Host precedence violation"; break;
261                 case ICMP_UNREACH_PRECEDENCE_CUTOFF: msg = "Precedence cutoff"; break;
262                 default: msg = "Invalid code"; break;
263                 }
264                 break;
266         case ICMP_TIMXCEED:
267                 /* really 'out of reach', or non-existant host behind a router serving
268                  * two different subnets */
269                 switch(icmp_code) {
270                 case ICMP_TIMXCEED_INTRANS: msg = "Time to live exceeded in transit"; break;
271                 case ICMP_TIMXCEED_REASS: msg = "Fragment reassembly time exceeded"; break;
272                 default: msg = "Invalid code"; break;
273                 }
274                 break;
276         case ICMP_SOURCEQUENCH: msg = "Transmitting too fast"; break;
277         case ICMP_REDIRECT: msg = "Redirect (change route)"; break;
278         case ICMP_PARAMPROB: msg = "Bad IP header (required option absent)"; break;
280                 /* the following aren't error messages, so ignore */
281         case ICMP_TSTAMP:
282         case ICMP_TSTAMPREPLY:
283         case ICMP_IREQ:
284         case ICMP_IREQREPLY:
285         case ICMP_MASKREQ:
286         case ICMP_MASKREPLY:
287         default: msg = ""; break;
288         }
290         return msg;
293 static int
294 handle_random_icmp(struct icmp *p, struct sockaddr_in *addr)
296         struct icmp *sent_icmp = NULL;
297         struct rta_host *host = NULL;
298         unsigned char *ptr;
300         if(p->icmp_type == ICMP_ECHO && p->icmp_id == pid) {
301                 /* echo request from us to us (pinging localhost) */
302                 return 0;
303         }
305         ptr = (unsigned char *)p;
306         if(debug) printf("handle_random_icmp(%p, %p)\n", (void *)p, (void *)addr);
308         /* only handle a few types, since others can't possibly be replies to
309          * us in a sane network (if it is anyway, it will be counted as lost
310          * at summary time, but not as quickly as a proper response */
311         /* TIMXCEED can be an unreach from a router with multiple IP's which
312          * serves two different subnets on the same interface and a dead host
313          * on one net is pinged from the other. The router will respond to
314          * itself and thus set TTL=0 so as to not loop forever.  Even when
315          * TIMXCEED actually sends a proper icmp response we will have passed
316          * too many hops to have a hope of reaching it later, in which case it
317          * indicates overconfidence in the network, poor routing or both. */
318         if(p->icmp_type != ICMP_UNREACH && p->icmp_type != ICMP_TIMXCEED &&
319            p->icmp_type != ICMP_SOURCEQUENCH && p->icmp_type != ICMP_PARAMPROB)
320         {
321                 return 0;
322         }
324         /* might be for us. At least it holds the original package (according
325          * to RFC 792). If it isn't, just ignore it */
326         sent_icmp = (struct icmp *)(ptr + 28);
327         if(sent_icmp->icmp_type != ICMP_ECHO || sent_icmp->icmp_id != pid ||
328            sent_icmp->icmp_seq >= targets)
329         {
330                 if(debug) printf("Packet is no response to a packet we sent\n");
331                 return 0;
332         }
334         /* it is indeed a response for us */
335         host = table[sent_icmp->icmp_seq];
336         if(debug) {
337                 printf("Received \"%s\" from %s for ICMP ECHO sent to %s.\n",
338                            get_icmp_error_msg(p->icmp_type, p->icmp_code),
339                            inet_ntoa(addr->sin_addr), host->name);
340         }
342         icmp_lost++;
343         host->icmp_lost++;
344         /* don't spend time on lost hosts any more */
345         if(host->flags & FLAG_LOST_CAUSE) return 0;
347         /* source quench means we're sending too fast, so increase the
348          * interval and mark this packet lost */
349         if(p->icmp_type == ICMP_SOURCEQUENCH) {
350                 pkt_interval *= pkt_backoff_factor;
351                 target_interval *= target_backoff_factor;
352         }
353         else {
354                 targets_down++;
355                 host->flags |= FLAG_LOST_CAUSE;
356         }
357         host->icmp_type = p->icmp_type;
358         host->icmp_code = p->icmp_code;
359         host->error_addr.s_addr = addr->sin_addr.s_addr;
361         return 0;
364 int
365 main(int argc, char **argv)
367         int i;
368         char *ptr;
369         long int arg;
370         int icmp_sockerrno, udp_sockerrno, tcp_sockerrno;
371         int result;
372         struct rta_host *host;
373         
374         /* we only need to be setsuid when we get the sockets, so do
375          * that before pointer magic (esp. on network data) */
376         icmp_sockerrno = udp_sockerrno = tcp_sockerrno = sockets = 0;
378         if((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) != -1)
379                 sockets |= HAVE_ICMP;
380         else icmp_sockerrno = errno;
382         /* if((udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) */
383         /*      sockets |= HAVE_UDP; */
384         /* else udp_sockerrno = errno; */
386         /* if((tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) */
387         /*      sockets |= HAVE_TCP; */
388         /* else tcp_sockerrno = errno; */
390         /* now drop privileges (no effect if not setsuid or geteuid() == 0) */
391         setuid(getuid());
393         /* POSIXLY_CORRECT might break things, so unset it (the portable way) */
394         environ = NULL;
396         /* use the pid to mark packets as ours */
397         pid = getpid();
398         /* printf("pid = %u\n", pid); */
400         /* get calling name the old-fashioned way for portability instead
401          * of relying on the glibc-ism __progname */
402         ptr = strrchr(argv[0], '/');
403         if(ptr) progname = &ptr[1];
404         else progname = argv[0];
406         /* now set defaults. Use progname to set them initially (allows for
407          * superfast check_host program when target host is up */
408         cursor = list = NULL;
409         table = NULL;
411         mode = MODE_RTA;
412         crit.rta = 500000;
413         crit.pl = 80;
414         warn.rta = 200000;
415         warn.pl = 40;
416         protocols = HAVE_ICMP | HAVE_UDP | HAVE_TCP;
417         pkt_interval = 80000;  /* 80 msec packet interval by default */
418         packets = 5;
420         if(!strcmp(progname, "check_icmp") || !strcmp(progname, "check_ping")) {
421                 mode = MODE_ICMP;
422                 protocols = HAVE_ICMP;
423         }
424         else if(!strcmp(progname, "check_host")) {
425                 mode = MODE_HOSTCHECK;
426                 pkt_interval = 1000000;
427                 packets = 5;
428                 crit.rta = warn.rta = 1000000;
429                 crit.pl = warn.pl = 100;
430         }
431         else if(!strcmp(progname, "check_rta_multi")) {
432                 mode = MODE_ALL;
433                 target_interval = 0;
434                 pkt_interval = 50000;
435                 packets = 5;
436         }
438         /* parse the arguments */
439         for(i = 1; i < argc; i++) {
440                 while((arg = getopt(argc, argv, "vhVw:c:n:p:t:H:i:b:I:l:")) != EOF) {
441                         switch(arg) {
442                         case 'v':
443                                 debug++;
444                                 break;
445                         case 'b':
446                                 /* silently ignored for now */
447                                 break;
448                         case 'i':
449                                 pkt_interval = get_timevar(optarg);
450                                 break;
451                         case 'I':
452                                 target_interval = get_timevar(optarg);
453                                 break;
454                         case 'w':
455                                 get_threshold(optarg, &warn);
456                                 break;
457                         case 'c':
458                                 get_threshold(optarg, &crit);
459                                 break;
460                         case 'n':
461                         case 'p':
462                                 packets = strtoul(optarg, NULL, 0);
463                                 break;
464                         case 't':
465                                 timeout = strtoul(optarg, NULL, 0);
466                                 if(!timeout) timeout = 10;
467                                 break;
468                         case 'H':
469                                 add_target(optarg);
470                                 break;
471                         case 'l':
472                                 ttl = (unsigned char)strtoul(optarg, NULL, 0);
473                                 break;
474                         case 'd': /* implement later, for cluster checks */
475                                 warn_down = (unsigned char)strtoul(optarg, &ptr, 0);
476                                 if(ptr) {
477                                         crit_down = (unsigned char)strtoul(ptr + 1, NULL, 0);
478                                 }
479                                 break;
480       case 'V':                 /* version */
481         //print_revision (progname, revision);
482         exit (STATE_OK);
483       case 'h':                 /* help */
484         print_help ();
485         exit (STATE_OK);
486                         }
487                 }
488         }
490         argv = &argv[optind];
491         while(*argv) {
492                 add_target(*argv);
493                 argv++;
494         }
495         if(!targets) {
496                 errno = 0;
497                 crash("No hosts to check");
498                 exit(3);
499         }
501         if(!sockets) {
502                 if(icmp_sock == -1) {
503                         errno = icmp_sockerrno;
504                         crash("Failed to obtain ICMP socket");
505                         return -1;
506                 }
507                 /* if(udp_sock == -1) { */
508                 /*      errno = icmp_sockerrno; */
509                 /*      crash("Failed to obtain UDP socket"); */
510                 /*      return -1; */
511                 /* } */
512                 /* if(tcp_sock == -1) { */
513                 /*      errno = icmp_sockerrno; */
514                 /*      crash("Failed to obtain TCP socker"); */
515                 /*      return -1; */
516                 /* } */
517         }
518         if(!ttl) ttl = 64;
520         if(icmp_sock) {
521                 result = setsockopt(icmp_sock, SOL_IP, IP_TTL, &ttl, sizeof(ttl));
522                 if(debug) {
523                         if(result == -1) printf("setsockopt failed\n");
524                         else printf("ttl set to %u\n", ttl);
525                 }
526         }
528         /* stupid users should be able to give whatever thresholds they want
529          * (nothing will break if they do), but some anal plugin maintainer
530          * will probably add some printf() thing here later, so it might be
531          * best to at least show them where to do it. ;) */
532         if(warn.pl > crit.pl) warn.pl = crit.pl;
533         if(warn.rta > crit.rta) warn.rta = crit.rta;
534         if(warn_down > crit_down) crit_down = warn_down;
536         signal(SIGINT, finish);
537         signal(SIGHUP, finish);
538         signal(SIGTERM, finish);
539         signal(SIGALRM, finish);
540         if(debug) printf("Setting alarm timeout to %u seconds\n", timeout);
541         alarm(timeout);
543         /* make sure we don't wait any longer than necessary */
544         gettimeofday(&prog_start, &tz);
545         max_completion_time =
546                 ((targets * packets * pkt_interval) + (targets * target_interval)) +
547                 (targets * packets * crit.rta) + crit.rta;
549         if(debug) {
550                 printf("packets: %u, targets: %u\n"
551                            "target_interval: %0.3f, pkt_interval %0.3f\n"
552                            "crit.rta: %0.3f\n"
553                            "max_completion_time: %0.3f\n",
554                            packets, targets,
555                            (float)target_interval / 1000, (float)pkt_interval / 1000,
556                            (float)crit.rta / 1000,
557                            (float)max_completion_time / 1000);
558         }
560         if(debug) {
561                 if(max_completion_time > (u_int)timeout * 1000000) {
562                         printf("max_completion_time: %llu  timeout: %u\n",
563                                    max_completion_time, timeout);
564                         printf("Timout must be at lest %llu\n",
565                                    max_completion_time / 1000000 + 1);
566                 }
567         }
569         icmp_pkt_size = icmp_data_size + ICMP_MINLEN;
570         if(debug > 2) printf("icmp_pkt_size = %u\n", icmp_pkt_size);
571         if(icmp_pkt_size < sizeof(struct icmp) + sizeof(struct icmp_ping_data)) {
572                 icmp_pkt_size = sizeof(struct icmp) + sizeof(struct icmp_ping_data);
573         }
574         if(debug > 2) printf("icmp_pkt_size = %u\n", icmp_pkt_size);
576         if(debug) {
577                 printf("crit = {%u, %u%%}, warn = {%u, %u%%}\n",
578                            crit.rta, crit.pl, warn.rta, warn.pl);
579                 printf("pkt_interval: %u  target_interval: %u  retry_interval: %u\n",
580                            pkt_interval, target_interval, retry_interval);
581                 printf("icmp_pkt_size: %u  timeout: %u\n",
582                            icmp_pkt_size, timeout);
583         }
585         if(packets > 20) {
586                 errno = 0;
587                 crash("packets is > 20 (%d)", packets);
588         }
590         host = list;
591         table = malloc(sizeof(struct rta_host **) * (argc - 1));
592         i = 0;
593         while(host) {
594                 host->id = i;
595                 table[i] = host;
596                 host = host->next;
597                 i++;
598         }
600         run_checks();
602         errno = 0;
603         finish(0);
605         return(0);
608 static void
609 run_checks()
611         u_int i, t, result;
612         u_int final_wait, time_passed;
614         /* this loop might actually violate the pkt_interval or target_interval
615          * settings, but only if there aren't any packets on the wire which
616          * indicates that the target can handle an increased packet rate */
617         for(i = 0; i < packets; i++) {
618                 for(t = 0; t < targets; t++) {
619                         /* don't send useless packets */
620                         if(!targets_alive) finish(0);
621                         if(table[t]->flags & FLAG_LOST_CAUSE) {
622                                 if(debug) printf("%s is a lost cause. not sending any more\n",
623                                                                  table[t]->name);
624                                 continue;
625                         }
626                         
627                         /* we're still in the game, so send next packet */
628                         (void)send_icmp_ping(icmp_sock, table[t]);
629                         result = wait_for_reply(icmp_sock, target_interval);
630                 }
631                 result = wait_for_reply(icmp_sock, pkt_interval * targets);
632         }
634         if(icmp_pkts_en_route && targets_alive) {
635                 time_passed = get_timevaldiff(NULL, NULL);
636                 final_wait = max_completion_time - time_passed;
638                 if(debug) {
639                         printf("time_passed: %u  final_wait: %u  max_completion_time: %llu\n",
640                                    time_passed, final_wait, max_completion_time);
641                 }
642                 if(time_passed > max_completion_time) {
643                         if(debug) printf("Time passed. Finishing up\n");
644                         finish(0);
645                 }
647                 /* catch the packets that might come in within the timeframe, but
648                  * haven't yet */
649                 if(debug) printf("Waiting for %u micro-seconds (%0.3f msecs)\n",
650                                                  final_wait, (float)final_wait / 1000);
651                 result = wait_for_reply(icmp_sock, final_wait);
652         }
655 /* response structure:
656  * ip header   : 20 bytes
657  * icmp header : 28 bytes
658  * icmp echo reply : the rest
659  */
660 static int
661 wait_for_reply(int sock, u_int t)
663         int n, hlen;
664         static char buf[4096];
665         struct sockaddr_in resp_addr;
666         struct ip *ip;
667         struct icmp *icp, *sent_icmp;
668         struct rta_host *host;
669         struct icmp_ping_data *data;
670         struct timeval wait_start, now;
671         u_int tdiff, i, per_pkt_wait;
673         /* if we can't listen or don't have anything to listen to, just return */
674         if(!t || !icmp_pkts_en_route) return 0;
676         gettimeofday(&wait_start, &tz);
678         i = t;
679         per_pkt_wait = t / icmp_pkts_en_route;
680         while(icmp_pkts_en_route && get_timevaldiff(&wait_start, NULL) < i) {
681                 t = per_pkt_wait;
683                 /* wrap up if all targets are declared dead */
684                 if(!targets_alive ||
685                    get_timevaldiff(&prog_start, NULL) >= max_completion_time ||
686                    (mode == MODE_HOSTCHECK && targets_down))
687                 {
688                         finish(0);
689                 }
691                 /* reap responses until we hit a timeout */
692                 n = recvfrom_wto(sock, buf, sizeof(buf),
693                                                  (struct sockaddr *)&resp_addr, &t);
694                 if(!n) {
695                         if(debug > 1) {
696                                 printf("recvfrom_wto() timed out during a %u usecs wait\n",
697                                            per_pkt_wait);
698                         }
699                         continue;       /* timeout for this one, so keep trying */
700                 }
701                 if(n < 0) {
702                         if(debug) printf("recvfrom_wto() returned errors\n");
703                         return n;
704                 }
706                 ip = (struct ip *)buf;
707                 if(debug > 1) printf("received %u bytes from %s\n",
708                                                  ntohs(ip->ip_len), inet_ntoa(resp_addr.sin_addr));
710 /* obsolete. alpha on tru64 provides the necessary defines, but isn't broken */
711 /* #if defined( __alpha__ ) && __STDC__ && !defined( __GLIBC__ ) */
712                 /* alpha headers are decidedly broken. Using an ansi compiler,
713                  * they provide ip_vhl instead of ip_hl and ip_v, so we mask
714                  * off the bottom 4 bits */
715 /*              hlen = (ip->ip_vhl & 0x0f) << 2; */
716 /* #else */
717                 hlen = ip->ip_hl << 2;
718 /* #endif */
720                 if(n < (hlen + ICMP_MINLEN)) {
721                         crash("received packet too short for ICMP (%d bytes, expected %d) from %s\n",
722                                   n, hlen + icmp_pkt_size, inet_ntoa(resp_addr.sin_addr));
723                 }
724                 /* else if(debug) { */
725                 /*      printf("ip header size: %u, packet size: %u (expected %u, %u)\n", */
726                 /*                 hlen, ntohs(ip->ip_len) - hlen, */
727                 /*                 sizeof(struct ip), icmp_pkt_size); */
728                 /* } */
730                 /* check the response */
731                 icp = (struct icmp *)(buf + hlen);
732                 sent_icmp = (struct icmp *)(buf + hlen + ICMP_MINLEN);
733                 /* printf("buf: %p, icp: %p, distance: %u (expected %u)\n", */
734                 /*         buf, icp, */
735                 /*         (u_int)icp - (u_int)buf, hlen); */
736                 /* printf("buf: %p, sent_icmp: %p, distance: %u (expected %u)\n", */
737                 /*         buf, sent_icmp, */
738                 /*         (u_int)sent_icmp - (u_int)buf, hlen + ICMP_MINLEN); */
740                 if(icp->icmp_id != pid) {
741                         handle_random_icmp(icp, &resp_addr);
742                         continue;
743                 }
745                 if(icp->icmp_type != ICMP_ECHOREPLY || icp->icmp_seq >= targets) {
746                         if(debug > 2) printf("not a proper ICMP_ECHOREPLY\n");
747                         handle_random_icmp(icp, &resp_addr);
748                         continue;
749                 }
751                 /* this is indeed a valid response */
752                 data = (struct icmp_ping_data *)(icp->icmp_data);
754                 host = table[icp->icmp_seq];
755                 gettimeofday(&now, &tz);
756                 tdiff = get_timevaldiff(&data->stime, &now);
758                 host->time_waited += tdiff;
759                 host->icmp_recv++;
760                 icmp_recv++;
762                 if(debug) {
763                         printf("%0.3f ms rtt from %s, outgoing ttl: %u, incoming ttl: %u\n",
764                                    (float)tdiff / 1000, inet_ntoa(resp_addr.sin_addr),
765                                    ttl, ip->ip_ttl);
766                 }
768                 /* if we're in hostcheck mode, exit with limited printouts */
769                 if(mode == MODE_HOSTCHECK) {
770                         printf("OK - %s responds to ICMP. Packet %u, rta %0.3fms|"
771                                    "pkt=%u;;0;%u rta=%0.3f;%0.3f;%0.3f;;\n",
772                                    host->name, icmp_recv, (float)tdiff / 1000,
773                                    icmp_recv, packets, (float)tdiff / 1000,
774                                    (float)warn.rta / 1000, (float)crit.rta / 1000);
775                         exit(STATE_OK);
776                 }
777         }
779         return 0;
782 /* the ping functions */
783 static int
784 send_icmp_ping(int sock, struct rta_host *host)
786         static char *buf = NULL; /* re-use so we prevent leaks */
787         long int len;
788         struct icmp *icp;
789         struct icmp_ping_data *data;
790         struct timeval tv;
791         struct sockaddr *addr;
793         
794         if(sock == -1) {
795                 errno = 0;
796                 crash("Attempt to send on bogus socket");
797                 return -1;
798         }
799         addr = (struct sockaddr *)&host->saddr_in;
801         if(!buf) {
802                 buf = (char *)malloc(icmp_pkt_size + sizeof(struct ip));
803                 if(!buf) {
804                         crash("send_icmp_ping(): failed to malloc %d bytes for send buffer",
805                                   icmp_pkt_size);
806                         return -1;      /* might be reached if we're in debug mode */
807                 }
808         }
809         memset(buf, 0, icmp_pkt_size + sizeof(struct ip));
811         if((gettimeofday(&tv, &tz)) == -1) return -1;
813         icp = (struct icmp *)buf;
814         icp->icmp_type = ICMP_ECHO;
815         icp->icmp_code = 0;
816         icp->icmp_cksum = 0;
817         icp->icmp_id = pid;
818         icp->icmp_seq = host->id;
819         data = (struct icmp_ping_data *)icp->icmp_data;
820         data->ping_id = 10; /* host->icmp.icmp_sent; */
821         memcpy(&data->stime, &tv, sizeof(struct timeval));
822         icp->icmp_cksum = icmp_checksum((u_short *)icp, icmp_pkt_size);
824         len = sendto(sock, buf, icmp_pkt_size, 0, (struct sockaddr *)addr,
825                                  sizeof(struct sockaddr));
827         if(len < 0 || (unsigned int)len != icmp_pkt_size) {
828                 if(debug) printf("Failed to send ping to %s\n",
829                                                  inet_ntoa(host->saddr_in.sin_addr));
830                 return -1;
831         }
833         icmp_sent++;
834         host->icmp_sent++;
836         return 0;
839 static int
840 recvfrom_wto(int sock, char *buf, unsigned int len, struct sockaddr *saddr,
841                          u_int *timo)
843         u_int slen;
844         int n;
845         struct timeval to, then, now;
846         fd_set rd, wr;
848         if(!*timo) {
849                 if(debug) printf("*timo is not\n");
850                 return 0;
851         }
853         to.tv_sec = *timo / 1000000;
854         to.tv_usec = (*timo - (to.tv_sec * 1000000));
856         FD_ZERO(&rd);
857         FD_ZERO(&wr);
858         FD_SET(sock, &rd);
859         errno = 0;
860         gettimeofday(&then, &tz);
861         n = select(sock + 1, &rd, &wr, NULL, &to);
862         if(n < 0) crash("select() in recvfrom_wto");
863         gettimeofday(&now, &tz);
864         *timo = get_timevaldiff(&then, &now);
866         if(!n) return 0;                                /* timeout */
868         slen = sizeof(struct sockaddr);
870         return recvfrom(sock, buf, len, 0, saddr, &slen);
873 static void
874 finish(int sig)
876         u_int i = 0;
877         unsigned char pl;
878         double rta;
879         struct rta_host *host;
880         char *status_string[] =
881         {"OK", "WARNING", "CRITICAL", "UNKNOWN", "DEPENDENT"};
883         alarm(0);
884         if(debug > 1) printf("finish(%d) called\n", sig);
886         if(icmp_sock != -1) close(icmp_sock);
887         if(udp_sock != -1) close(udp_sock);
888         if(tcp_sock != -1) close(tcp_sock);
890         if(debug) {
891                 printf("icmp_sent: %u  icmp_recv: %u  icmp_lost: %u\n",
892                            icmp_sent, icmp_recv, icmp_lost);
893                 printf("targets: %u  targets_alive: %u\n", targets, targets_alive);
894         }
896         /* iterate thrice to calculate values, give output, and print perfparse */
897         host = list;
898         while(host) {
899                 if(!host->icmp_recv) {
900                         /* rta 0 is ofcourse not entirely correct, but will still show up
901                          * conspicuosly as missing entries in perfparse and cacti */
902                         pl = 100;
903                         rta = 0;
904                         status = STATE_CRITICAL;
905                         /* up the down counter if not already counted */
906                         if(!(host->flags & FLAG_LOST_CAUSE) && targets_alive) targets_down++;
907                 }
908                 else {
909                         pl = ((host->icmp_sent - host->icmp_recv) * 100) / host->icmp_sent;
910                         rta = (double)host->time_waited / host->icmp_recv;
911                 }
912                 host->pl = pl;
913                 host->rta = rta;
914                 if(!status && (pl >= warn.pl || rta >= warn.rta)) status = STATE_WARNING;
915                 if(pl >= crit.pl || rta >= crit.rta) status = STATE_CRITICAL;
917                 host = host->next;
918         }
919         /* this is inevitable */
920         if(!targets_alive) status = STATE_CRITICAL;
921         printf("%s - ", status_string[status]);
923         host = list;
924         while(host) {
925                 if(debug) puts("");
926                 if(i) {
927                         if(i < targets) printf(" :: ");
928                         else printf("\n");
929                 }
930                 i++;
931                 if(!host->icmp_recv) {
932                         status = STATE_CRITICAL;
933                         if(host->flags & FLAG_LOST_CAUSE) {
934                                 printf("%s: %s @ %s. rta nan, lost %d%%",
935                                            host->name,
936                                            get_icmp_error_msg(host->icmp_type, host->icmp_code),
937                                            inet_ntoa(host->error_addr),
938                                            100);
939                         }
940                         else { /* not marked as lost cause, so we have no flags for it */
941                                 printf("%s: rta nan, lost 100%%", host->name);
942                         }
943                 }
944                 else {  /* !icmp_recv */
945                         printf("%s: rta %0.3fms, lost %u%%",
946                                    host->name, host->rta / 1000, host->pl);
947                 }
949                 host = host->next;
950         }
952         /* iterate once more for pretty perfparse output */
953         printf("|");
954         i = 0;
955         host = list;
956         while(host) {
957                 if(debug) puts("");
958                 printf("%srta=%0.3fms;%0.3f;%0.3f;0; %spl=%u%%;%u;%u;; ",
959                            (targets > 1) ? host->name : "",
960                            host->rta / 1000, (float)warn.rta / 1000, (float)crit.rta / 1000,
961                            (targets > 1) ? host->name : "",
962                            host->pl, warn.pl, crit.pl);
964                 host = host->next;
965         }
967         /* finish with an empty line */
968         puts("");
969         if(debug) printf("targets: %u, targets_alive: %u\n",
970                                          targets, targets_alive);
972         exit(status);
975 static u_int
976 get_timevaldiff(struct timeval *early, struct timeval *later)
978         u_int ret;
979         struct timeval now;
981         if(!later) {
982                 gettimeofday(&now, &tz);
983                 later = &now;
984         }
985         if(!early) early = &prog_start;
987         /* if early > later we return 0 so as to indicate a timeout */
988         if(early->tv_sec > early->tv_sec ||
989            (early->tv_sec == later->tv_sec && early->tv_usec > later->tv_usec))
990         {
991                 return 0;
992         }
994         ret = (later->tv_sec - early->tv_sec) * 1000000;
995         ret += later->tv_usec - early->tv_usec;
997         return ret;
1000 static int
1001 add_target_ip(char *arg, struct in_addr *in)
1003         struct rta_host *host;
1005         /* disregard obviously stupid addresses */
1006         if(in->s_addr == INADDR_NONE || in->s_addr == INADDR_ANY)
1007                 return -1;
1009         /* no point in adding two identical IP's, so don't. ;) */
1010         host = list;
1011         while(host) {
1012                 if(host->saddr_in.sin_addr.s_addr == in->s_addr) {
1013                         if(debug) printf("Identical IP already exists. Not adding %s\n", arg);
1014                         return -1;
1015                 }
1016                 host = host->next;
1017         }
1019         /* add the fresh ip */
1020         host = malloc(sizeof(struct rta_host));
1021         if(!host) {
1022                 crash("add_target_ip(%s, %s): malloc(%d) failed",
1023                           arg, inet_ntoa(*in), sizeof(struct rta_host));
1024         }
1025         memset(host, 0, sizeof(struct rta_host));
1027         /* set the values. use calling name for output */
1028         host->name = strdup(arg);
1030         /* fill out the sockaddr_in struct */
1031         host->saddr_in.sin_family = AF_INET;
1032         host->saddr_in.sin_addr.s_addr = in->s_addr;
1034         if(!list) list = cursor = host;
1035         else cursor->next = host;
1037         cursor = host;
1038         targets++;
1040         return 0;
1043 /* wrapper for add_target_ip */
1044 static int
1045 add_target(char *arg)
1047         int i;
1048         struct hostent *he;
1049         struct in_addr *in, ip;
1051         /* don't resolve if we don't have to */
1052         if((ip.s_addr = inet_addr(arg)) != INADDR_NONE) {
1053                 /* don't add all ip's if we were given a specific one */
1054                 return add_target_ip(arg, &ip);
1055                 /* he = gethostbyaddr((char *)in, sizeof(struct in_addr), AF_INET); */
1056                 /* if(!he) return add_target_ip(arg, in); */
1057         }
1058         else {
1059                 errno = 0;
1060                 he = gethostbyname(arg);
1061                 if(!he) {
1062                         errno = 0;
1063                         crash("Failed to resolve %s", arg);
1064                         return -1;
1065                 }
1066         }
1068         /* possibly add all the IP's as targets */
1069         for(i = 0; he->h_addr_list[i]; i++) {
1070                 in = (struct in_addr *)he->h_addr_list[i];
1071                 add_target_ip(arg, in);
1073                 /* this is silly, but it works */
1074                 if(mode == MODE_HOSTCHECK || mode == MODE_ALL) {
1075                         printf("mode: %d\n", mode);
1076                         continue;
1077                 }
1078                 break;
1079         }
1081         return 0;
1083 /*
1084  * u = micro
1085  * m = milli
1086  * s = seconds
1087  * return value is in microseconds
1088  */
1089 static u_int
1090 get_timevar(const char *str)
1092         char p, u, *ptr;
1093         unsigned int len;
1094         u_int i, d;                 /* integer and decimal, respectively */
1095         u_int factor = 1000;    /* default to milliseconds */
1097         if(!str) return 0;
1098         len = strlen(str);
1099         if(!len) return 0;
1101         /* unit might be given as ms|m (millisec),
1102          * us|u (microsec) or just plain s, for seconds */
1103         u = p = '\0';
1104         u = str[len - 1];
1105         if(len >= 2 && !isdigit((int)str[len - 2])) p = str[len - 2];
1106         if(p && u == 's') u = p;
1107         else if(!p) p = u;
1108         if(debug > 2) printf("evaluating %s, u: %c, p: %c\n", str, u, p);
1110         if(u == 'u') factor = 1;            /* microseconds */
1111         else if(u == 'm') factor = 1000;        /* milliseconds */
1112         else if(u == 's') factor = 1000000;     /* seconds */
1113         if(debug > 2) printf("factor is %u\n", factor);
1115         i = strtoul(str, &ptr, 0);
1116         if(!ptr || *ptr != '.' || strlen(ptr) < 2 || factor == 1)
1117                 return i * factor;
1119         /* time specified in usecs can't have decimal points, so ignore them */
1120         if(factor == 1) return i;
1122         d = strtoul(ptr + 1, NULL, 0);
1124         /* d is decimal, so get rid of excess digits */
1125         while(d >= factor) d /= 10;
1127         /* the last parenthesis avoids floating point exceptions. */
1128         return ((i * factor) + (d * (factor / 10)));
1131 /* not too good at checking errors, but it'll do (main() should barfe on -1) */
1132 static int
1133 get_threshold(char *str, threshold *th)
1135         char *p = NULL, i = 0;
1137         if(!str || !strlen(str) || !th) return -1;
1139         /* pointer magic slims code by 10 lines. i is bof-stop on stupid libc's */
1140         p = &str[strlen(str) - 1];
1141         while(p != &str[1]) {
1142                 if(*p == '%') *p = '\0';
1143                 else if(*p == ',' && i) {
1144                         *p = '\0';      /* reset it so get_timevar(str) works nicely later */
1145                         th->pl = (unsigned char)strtoul(p+1, NULL, 0);
1146                         break;
1147                 }
1148                 i = 1;
1149                 p--;
1150         }
1151         th->rta = get_timevar(str);
1153         if(!th->rta) return -1;
1155         if(th->rta > MAXTTL * 1000000) th->rta = MAXTTL * 1000000;
1156         if(th->pl > 100) th->pl = 100;
1158         return 0;
1161 unsigned short
1162 icmp_checksum(unsigned short *p, int n)
1164         register unsigned short cksum;
1165         register long sum = 0;
1167         while(n > 1) {
1168                 sum += *p++;
1169                 n -= 2;
1170         }
1172         /* mop up the occasional odd byte */
1173         if(n == 1) sum += (unsigned char)*p;
1175         sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
1176         sum += (sum >> 16);                     /* add carry */
1177         cksum = ~sum;                           /* ones-complement, trunc to 16 bits */
1179         return cksum;
1182 /* make core plugin developers happy (silly, really) */
1183 void
1184 print_help(void)
1187   //print_revision (progname, revision);
1188   
1189   printf ("Copyright (c) 2005 Andreas Ericsson <ae@op5.se>\n");
1190   printf (COPYRIGHT, copyright, email);
1191   
1192   printf ("\n\n");
1193   
1194   print_usage ();
1195   
1196   printf (_(UT_HELP_VRSN));
1197    
1198         printf("Where options are any combination of:\n"
1199                    " * -H | --host        specify a target\n"
1200                    " * -w | --warn        warning threshold (currently %0.3fms,%u%%)\n"
1201                    " * -c | --crit        critical threshold (currently %0.3fms,%u%%)\n"
1202                    " * -n | --packets     number of packets to send (currently %u)\n"
1203                    " * -i | --interval    max packet interval (currently %0.3fms)\n"
1204                    " * -I | --hostint     max target interval (currently %0.3fms)\n"
1205                    " * -l | --ttl         TTL on outgoing packets (currently %u)\n"
1206                    " * -t | --timeout     timeout value (seconds, currently  %u)\n"
1207                    " * -b | --bytes       icmp packet size (currenly ignored)\n"
1208                    "   -v | --verbose     verbosity++\n"
1209                    "   -h | --help        this cruft\n",
1210                    (float)warn.rta / 1000, warn.pl, (float)crit.rta / 1000, crit.pl,
1211                    packets,
1212                    (float)pkt_interval / 1000, (float)target_interval / 1000,
1213                    ttl, timeout);
1215         printf("\nThe -H switch is optional. Naming a host (or several) to check is not.\n\n"
1216                  "Threshold format for -w and -c is 200.25,60% for 200.25 msec RTA and 60%\n"
1217                  "packet loss.  The default values should work well for most users.\n"
1218                  "You can specify different RTA factors using the standardized abbreviations\n"
1219                  "us (microseconds), ms (milliseconds, default) or just plain s for seconds.\n\n"
1220                  "Threshold format for -d is warn,crit.  12,14 means WARNING if >= 12 hops\n"
1221                  "are spent and CRITICAL if >= 14 hops are spent.\n"
1222                  "NOTE: Some systems decrease TTL when forming ICMP_ECHOREPLY, others do not.\n\n"
1223                  "The -v switch can be specified several times for increased verbosity.\n\n"
1224                  "Long options are currently unsupported.\n\n"
1225                  "Options marked with * require an argument\n");
1227 /*      printf("The latest version of this plugin can be found at http://oss.op5.se/nagios\n"
1228                  "or https://devel.op5.se/oss until the day it is included in the official\n"
1229                  "plugin distribution.\n");
1230 */
1231   
1232   printf (_(UT_SUPPORT));
1233   
1234   printf (_(UT_NOWARRANTY));
1235   
1236 //      exit(3);
1241 void
1242 print_usage (void)
1244   printf (_("Usage:"));
1245   printf("Usage: %s [options] [-H] host1 host2 hostn\n\n", progname);