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