1 /******************************************************************************
3 * Nagios check_icmp plugin
6 * Copyright (c) 2005-2007 nagios-plugins team
8 * Original Author : Andreas Ericsson <ae@op5.se>
10 * Last Modified: $Date$
14 * This file contains the check_icmp plugin
16 * Relevant RFC's: 792 (ICMP), 791 (IP)
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
25 * License Information:
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.
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.
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.
43 *****************************************************************************/
45 /* progname may change */
46 /* char *progname = "check_icmp"; */
48 const char *revision
= "$Revision$";
49 const char *copyright
= "2005-2007";
50 const char *email
= "nagiosplug-devel@lists.sourceforge.net";
52 /** nagios plugins basic includes */
58 #include <sys/sockio.h>
60 #include <sys/ioctl.h>
62 #include <sys/types.h>
72 #include <sys/socket.h>
74 #include <netinet/in_systm.h>
75 #include <netinet/in.h>
76 #include <netinet/ip.h>
77 #include <netinet/ip_icmp.h>
78 #include <arpa/inet.h>
82 /** sometimes undefined system macros (quite a few, actually) **/
87 # define INADDR_NONE (in_addr_t)(-1)
94 /* we bundle these in one #ifndef, since they're all from BSD
95 * Put individual #ifndef's around those that bother you */
96 #ifndef ICMP_UNREACH_NET_UNKNOWN
97 # define ICMP_UNREACH_NET_UNKNOWN 6
98 # define ICMP_UNREACH_HOST_UNKNOWN 7
99 # define ICMP_UNREACH_ISOLATED 8
100 # define ICMP_UNREACH_NET_PROHIB 9
101 # define ICMP_UNREACH_HOST_PROHIB 10
102 # define ICMP_UNREACH_TOSNET 11
103 # define ICMP_UNREACH_TOSHOST 12
105 /* tru64 has the ones above, but not these */
106 #ifndef ICMP_UNREACH_FILTER_PROHIB
107 # define ICMP_UNREACH_FILTER_PROHIB 13
108 # define ICMP_UNREACH_HOST_PRECEDENCE 14
109 # define ICMP_UNREACH_PRECEDENCE_CUTOFF 15
113 typedef unsigned short range_t
; /* type for get_range() -- unimplemented */
115 typedef struct rta_host
{
116 unsigned short id
; /* id in **table, and icmp pkts */
117 char *name
; /* arg used for adding this host */
118 char *msg
; /* icmp error message, if any */
119 struct sockaddr_in saddr_in
; /* the address of this host */
120 struct in_addr error_addr
; /* stores address of error replies */
121 unsigned long long time_waited
; /* total time waited, in usecs */
122 unsigned int icmp_sent
, icmp_recv
, icmp_lost
; /* counters */
123 unsigned char icmp_type
, icmp_code
; /* type and code from errors */
124 unsigned short flags
; /* control/status flags */
125 double rta
; /* measured RTA */
126 unsigned char pl
; /* measured packet loss */
127 struct rta_host
*next
; /* linked list */
130 #define FLAG_LOST_CAUSE 0x01 /* decidedly dead target. */
132 /* threshold structure. all values are maximum allowed, exclusive */
133 typedef struct threshold
{
134 unsigned char pl
; /* max allowed packet loss in percent */
135 unsigned int rta
; /* roundtrip time average, microseconds */
138 /* the data structure */
139 typedef struct icmp_ping_data
{
140 struct timeval stime
; /* timestamp (saved in protocol struct as well) */
141 unsigned short ping_id
;
144 /* the different modes of this program are as follows:
145 * MODE_RTA: send all packets no matter what (mimic check_icmp and check_ping)
146 * MODE_HOSTCHECK: Return immediately upon any sign of life
147 * In addition, sends packets to ALL addresses assigned
148 * to this host (as returned by gethostbyname() or
149 * gethostbyaddr() and expects one host only to be checked at
150 * a time. Therefore, any packet response what so ever will
151 * count as a sign of life, even when received outside
152 * crit.rta limit. Do not misspell any additional IP's.
153 * MODE_ALL: Requires packets from ALL requested IP to return OK (default).
154 * MODE_ICMP: implement something similar to check_icmp (MODE_RTA without
155 * tcp and udp args does this)
158 #define MODE_HOSTCHECK 1
162 /* the different ping types we can do
163 * TODO: investigate ARP ping as well */
169 #define MIN_PING_DATA_SIZE sizeof(struct icmp_ping_data)
170 #define MAX_IP_PKT_SIZE 65536 /* (theoretical) max IP packet size */
171 #define IP_HDR_SIZE 20
172 #define MAX_PING_DATA (MAX_IP_PKT_SIZE - IP_HDR_SIZE - ICMP_MINLEN)
173 #define DEFAULT_PING_DATA_SIZE (MIN_PING_DATA_SIZE + 44)
175 /* various target states */
176 #define TSTATE_INACTIVE 0x01 /* don't ping this host anymore */
177 #define TSTATE_WAITING 0x02 /* unanswered packets on the wire */
178 #define TSTATE_ALIVE 0x04 /* target is alive (has answered something) */
179 #define TSTATE_UNREACH 0x08
182 void print_help (void);
183 void print_usage (void);
184 static u_int
get_timevar(const char *);
185 static u_int
get_timevaldiff(struct timeval
*, struct timeval
*);
186 static in_addr_t
get_ip_address(const char *);
187 static int wait_for_reply(int, u_int
);
188 static int recvfrom_wto(int, char *, unsigned int, struct sockaddr
*, u_int
*);
189 static int send_icmp_ping(int, struct rta_host
*);
190 static int get_threshold(char *str
, threshold
*th
);
191 static void run_checks(void);
192 static void set_source_ip(char *);
193 static int add_target(char *);
194 static int add_target_ip(char *, struct in_addr
*);
195 static int handle_random_icmp(char *, struct sockaddr_in
*);
196 static unsigned short icmp_checksum(unsigned short *, int);
197 static void finish(int);
198 static void crash(const char *, ...);
201 extern int optind
, opterr
, optopt
;
203 extern char **environ
;
205 /** global variables **/
206 static struct rta_host
**table
, *cursor
, *list
;
207 static threshold crit
= {80, 500000}, warn
= {40, 200000};
208 static int mode
, protocols
, sockets
, debug
= 0, timeout
= 10;
209 static unsigned short icmp_pkt_size
, icmp_data_size
= DEFAULT_PING_DATA_SIZE
;
210 static unsigned int icmp_sent
= 0, icmp_recv
= 0, icmp_lost
= 0;
211 #define icmp_pkts_en_route (icmp_sent - (icmp_recv + icmp_lost))
212 static unsigned short targets_down
= 0, targets
= 0, packets
= 0;
213 #define targets_alive (targets - targets_down)
214 static unsigned int retry_interval
, pkt_interval
, target_interval
;
215 static int icmp_sock
, tcp_sock
, udp_sock
, status
= STATE_OK
;
217 static struct timezone tz
;
218 static struct timeval prog_start
;
219 static unsigned long long max_completion_time
= 0;
220 static unsigned char ttl
= 0; /* outgoing ttl */
221 static unsigned int warn_down
= 1, crit_down
= 1; /* host down threshold values */
222 static int min_hosts_alive
= -1;
223 float pkt_backoff_factor
= 1.5;
224 float target_backoff_factor
= 1.5;
228 crash(const char *fmt
, ...)
232 printf("%s: ", progname
);
238 if(errno
) printf(": %s", strerror(errno
));
246 get_icmp_error_msg(unsigned char icmp_type
, unsigned char icmp_code
)
248 const char *msg
= "unreachable";
250 if(debug
> 1) printf("get_icmp_error_msg(%u, %u)\n", icmp_type
, icmp_code
);
254 case ICMP_UNREACH_NET
: msg
= "Net unreachable"; break;
255 case ICMP_UNREACH_HOST
: msg
= "Host unreachable"; break;
256 case ICMP_UNREACH_PROTOCOL
: msg
= "Protocol unreachable (firewall?)"; break;
257 case ICMP_UNREACH_PORT
: msg
= "Port unreachable (firewall?)"; break;
258 case ICMP_UNREACH_NEEDFRAG
: msg
= "Fragmentation needed"; break;
259 case ICMP_UNREACH_SRCFAIL
: msg
= "Source route failed"; break;
260 case ICMP_UNREACH_ISOLATED
: msg
= "Source host isolated"; break;
261 case ICMP_UNREACH_NET_UNKNOWN
: msg
= "Unknown network"; break;
262 case ICMP_UNREACH_HOST_UNKNOWN
: msg
= "Unknown host"; break;
263 case ICMP_UNREACH_NET_PROHIB
: msg
= "Network denied (firewall?)"; break;
264 case ICMP_UNREACH_HOST_PROHIB
: msg
= "Host denied (firewall?)"; break;
265 case ICMP_UNREACH_TOSNET
: msg
= "Bad TOS for network (firewall?)"; break;
266 case ICMP_UNREACH_TOSHOST
: msg
= "Bad TOS for host (firewall?)"; break;
267 case ICMP_UNREACH_FILTER_PROHIB
: msg
= "Prohibited by filter (firewall)"; break;
268 case ICMP_UNREACH_HOST_PRECEDENCE
: msg
= "Host precedence violation"; break;
269 case ICMP_UNREACH_PRECEDENCE_CUTOFF
: msg
= "Precedence cutoff"; break;
270 default: msg
= "Invalid code"; break;
275 /* really 'out of reach', or non-existant host behind a router serving
276 * two different subnets */
278 case ICMP_TIMXCEED_INTRANS
: msg
= "Time to live exceeded in transit"; break;
279 case ICMP_TIMXCEED_REASS
: msg
= "Fragment reassembly time exceeded"; break;
280 default: msg
= "Invalid code"; break;
284 case ICMP_SOURCEQUENCH
: msg
= "Transmitting too fast"; break;
285 case ICMP_REDIRECT
: msg
= "Redirect (change route)"; break;
286 case ICMP_PARAMPROB
: msg
= "Bad IP header (required option absent)"; break;
288 /* the following aren't error messages, so ignore */
290 case ICMP_TSTAMPREPLY
:
295 default: msg
= ""; break;
302 handle_random_icmp(char *packet
, struct sockaddr_in
*addr
)
304 struct icmp p
, sent_icmp
;
305 struct rta_host
*host
= NULL
;
307 memcpy(&p
, packet
, sizeof(p
));
308 if(p
.icmp_type
== ICMP_ECHO
&& p
.icmp_id
== pid
) {
309 /* echo request from us to us (pinging localhost) */
313 if(debug
) printf("handle_random_icmp(%p, %p)\n", (void *)&p
, (void *)addr
);
315 /* only handle a few types, since others can't possibly be replies to
316 * us in a sane network (if it is anyway, it will be counted as lost
317 * at summary time, but not as quickly as a proper response */
318 /* TIMXCEED can be an unreach from a router with multiple IP's which
319 * serves two different subnets on the same interface and a dead host
320 * on one net is pinged from the other. The router will respond to
321 * itself and thus set TTL=0 so as to not loop forever. Even when
322 * TIMXCEED actually sends a proper icmp response we will have passed
323 * too many hops to have a hope of reaching it later, in which case it
324 * indicates overconfidence in the network, poor routing or both. */
325 if(p
.icmp_type
!= ICMP_UNREACH
&& p
.icmp_type
!= ICMP_TIMXCEED
&&
326 p
.icmp_type
!= ICMP_SOURCEQUENCH
&& p
.icmp_type
!= ICMP_PARAMPROB
)
331 /* might be for us. At least it holds the original package (according
332 * to RFC 792). If it isn't, just ignore it */
333 memcpy(&sent_icmp
, packet
+ 28, sizeof(sent_icmp
));
334 if(sent_icmp
.icmp_type
!= ICMP_ECHO
|| sent_icmp
.icmp_id
!= pid
||
335 sent_icmp
.icmp_seq
>= targets
)
337 if(debug
) printf("Packet is no response to a packet we sent\n");
341 /* it is indeed a response for us */
342 host
= table
[sent_icmp
.icmp_seq
];
344 printf("Received \"%s\" from %s for ICMP ECHO sent to %s.\n",
345 get_icmp_error_msg(p
.icmp_type
, p
.icmp_code
),
346 inet_ntoa(addr
->sin_addr
), host
->name
);
351 /* don't spend time on lost hosts any more */
352 if(host
->flags
& FLAG_LOST_CAUSE
) return 0;
354 /* source quench means we're sending too fast, so increase the
355 * interval and mark this packet lost */
356 if(p
.icmp_type
== ICMP_SOURCEQUENCH
) {
357 pkt_interval
*= pkt_backoff_factor
;
358 target_interval
*= target_backoff_factor
;
362 host
->flags
|= FLAG_LOST_CAUSE
;
364 host
->icmp_type
= p
.icmp_type
;
365 host
->icmp_code
= p
.icmp_code
;
366 host
->error_addr
.s_addr
= addr
->sin_addr
.s_addr
;
372 main(int argc
, char **argv
)
377 int icmp_sockerrno
, udp_sockerrno
, tcp_sockerrno
;
379 struct rta_host
*host
;
381 setlocale (LC_ALL
, "");
382 bindtextdomain (PACKAGE
, LOCALEDIR
);
383 textdomain (PACKAGE
);
385 /* print a helpful error message if geteuid != 0 */
386 np_warn_if_not_root();
388 /* we only need to be setsuid when we get the sockets, so do
389 * that before pointer magic (esp. on network data) */
390 icmp_sockerrno
= udp_sockerrno
= tcp_sockerrno
= sockets
= 0;
392 if((icmp_sock
= socket(PF_INET
, SOCK_RAW
, IPPROTO_ICMP
)) != -1)
393 sockets
|= HAVE_ICMP
;
394 else icmp_sockerrno
= errno
;
396 /* if((udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) */
397 /* sockets |= HAVE_UDP; */
398 /* else udp_sockerrno = errno; */
400 /* if((tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) */
401 /* sockets |= HAVE_TCP; */
402 /* else tcp_sockerrno = errno; */
404 /* now drop privileges (no effect if not setsuid or geteuid() == 0) */
407 /* POSIXLY_CORRECT might break things, so unset it (the portable way) */
410 /* use the pid to mark packets as ours */
411 /* Some systems have 32-bit pid_t so mask off only 16 bits */
412 pid
= getpid() & 0xffff;
413 /* printf("pid = %u\n", pid); */
415 /* get calling name the old-fashioned way for portability instead
416 * of relying on the glibc-ism __progname */
417 ptr
= strrchr(argv
[0], '/');
418 if(ptr
) progname
= &ptr
[1];
419 else progname
= argv
[0];
421 /* now set defaults. Use progname to set them initially (allows for
422 * superfast check_host program when target host is up */
423 cursor
= list
= NULL
;
431 protocols
= HAVE_ICMP
| HAVE_UDP
| HAVE_TCP
;
432 pkt_interval
= 80000; /* 80 msec packet interval by default */
435 if(!strcmp(progname
, "check_icmp") || !strcmp(progname
, "check_ping")) {
437 protocols
= HAVE_ICMP
;
439 else if(!strcmp(progname
, "check_host")) {
440 mode
= MODE_HOSTCHECK
;
441 pkt_interval
= 1000000;
443 crit
.rta
= warn
.rta
= 1000000;
444 crit
.pl
= warn
.pl
= 100;
446 else if(!strcmp(progname
, "check_rta_multi")) {
449 pkt_interval
= 50000;
453 /* parse the arguments */
454 for(i
= 1; i
< argc
; i
++) {
455 while((arg
= getopt(argc
, argv
, "vhVw:c:n:p:t:H:s:i:b:I:l:m:")) != EOF
) {
461 /* silently ignored for now */
464 pkt_interval
= get_timevar(optarg
);
467 target_interval
= get_timevar(optarg
);
470 get_threshold(optarg
, &warn
);
473 get_threshold(optarg
, &crit
);
477 packets
= strtoul(optarg
, NULL
, 0);
480 timeout
= strtoul(optarg
, NULL
, 0);
481 if(!timeout
) timeout
= 10;
487 ttl
= (unsigned char)strtoul(optarg
, NULL
, 0);
490 min_hosts_alive
= (int)strtoul(optarg
, NULL
, 0);
492 case 'd': /* implement later, for cluster checks */
493 warn_down
= (unsigned char)strtoul(optarg
, &ptr
, 0);
495 crit_down
= (unsigned char)strtoul(ptr
+ 1, NULL
, 0);
498 case 's': /* specify source IP address */
499 set_source_ip(optarg
);
501 case 'V': /* version */
502 /*print_revision (progname, revision);*/ /* FIXME: Why? */
511 argv
= &argv
[optind
];
518 crash("No hosts to check");
523 if(icmp_sock
== -1) {
524 errno
= icmp_sockerrno
;
525 crash("Failed to obtain ICMP socket");
528 /* if(udp_sock == -1) { */
529 /* errno = icmp_sockerrno; */
530 /* crash("Failed to obtain UDP socket"); */
533 /* if(tcp_sock == -1) { */
534 /* errno = icmp_sockerrno; */
535 /* crash("Failed to obtain TCP socker"); */
542 result
= setsockopt(icmp_sock
, SOL_IP
, IP_TTL
, &ttl
, sizeof(ttl
));
544 if(result
== -1) printf("setsockopt failed\n");
545 else printf("ttl set to %u\n", ttl
);
549 /* stupid users should be able to give whatever thresholds they want
550 * (nothing will break if they do), but some anal plugin maintainer
551 * will probably add some printf() thing here later, so it might be
552 * best to at least show them where to do it. ;) */
553 if(warn
.pl
> crit
.pl
) warn
.pl
= crit
.pl
;
554 if(warn
.rta
> crit
.rta
) warn
.rta
= crit
.rta
;
555 if(warn_down
> crit_down
) crit_down
= warn_down
;
557 signal(SIGINT
, finish
);
558 signal(SIGHUP
, finish
);
559 signal(SIGTERM
, finish
);
560 signal(SIGALRM
, finish
);
561 if(debug
) printf("Setting alarm timeout to %u seconds\n", timeout
);
564 /* make sure we don't wait any longer than necessary */
565 gettimeofday(&prog_start
, &tz
);
566 max_completion_time
=
567 ((targets
* packets
* pkt_interval
) + (targets
* target_interval
)) +
568 (targets
* packets
* crit
.rta
) + crit
.rta
;
571 printf("packets: %u, targets: %u\n"
572 "target_interval: %0.3f, pkt_interval %0.3f\n"
574 "max_completion_time: %0.3f\n",
576 (float)target_interval
/ 1000, (float)pkt_interval
/ 1000,
577 (float)crit
.rta
/ 1000,
578 (float)max_completion_time
/ 1000);
582 if(max_completion_time
> (u_int
)timeout
* 1000000) {
583 printf("max_completion_time: %llu timeout: %u\n",
584 max_completion_time
, timeout
);
585 printf("Timout must be at lest %llu\n",
586 max_completion_time
/ 1000000 + 1);
590 icmp_pkt_size
= icmp_data_size
+ ICMP_MINLEN
;
591 if(debug
> 2) printf("icmp_pkt_size = %u\n", icmp_pkt_size
);
592 if(icmp_pkt_size
< sizeof(struct icmp
) + sizeof(struct icmp_ping_data
)) {
593 icmp_pkt_size
= sizeof(struct icmp
) + sizeof(struct icmp_ping_data
);
595 if(debug
> 2) printf("icmp_pkt_size = %u\n", icmp_pkt_size
);
598 printf("crit = {%u, %u%%}, warn = {%u, %u%%}\n",
599 crit
.rta
, crit
.pl
, warn
.rta
, warn
.pl
);
600 printf("pkt_interval: %u target_interval: %u retry_interval: %u\n",
601 pkt_interval
, target_interval
, retry_interval
);
602 printf("icmp_pkt_size: %u timeout: %u\n",
603 icmp_pkt_size
, timeout
);
608 crash("packets is > 20 (%d)", packets
);
611 if(min_hosts_alive
< -1) {
613 crash("minimum alive hosts is negative (%i)", min_hosts_alive
);
617 table
= malloc(sizeof(struct rta_host
**) * (argc
- 1));
638 u_int final_wait
, time_passed
;
640 /* this loop might actually violate the pkt_interval or target_interval
641 * settings, but only if there aren't any packets on the wire which
642 * indicates that the target can handle an increased packet rate */
643 for(i
= 0; i
< packets
; i
++) {
644 for(t
= 0; t
< targets
; t
++) {
645 /* don't send useless packets */
646 if(!targets_alive
) finish(0);
647 if(table
[t
]->flags
& FLAG_LOST_CAUSE
) {
648 if(debug
) printf("%s is a lost cause. not sending any more\n",
653 /* we're still in the game, so send next packet */
654 (void)send_icmp_ping(icmp_sock
, table
[t
]);
655 result
= wait_for_reply(icmp_sock
, target_interval
);
657 result
= wait_for_reply(icmp_sock
, pkt_interval
* targets
);
660 if(icmp_pkts_en_route
&& targets_alive
) {
661 time_passed
= get_timevaldiff(NULL
, NULL
);
662 final_wait
= max_completion_time
- time_passed
;
665 printf("time_passed: %u final_wait: %u max_completion_time: %llu\n",
666 time_passed
, final_wait
, max_completion_time
);
668 if(time_passed
> max_completion_time
) {
669 if(debug
) printf("Time passed. Finishing up\n");
673 /* catch the packets that might come in within the timeframe, but
675 if(debug
) printf("Waiting for %u micro-seconds (%0.3f msecs)\n",
676 final_wait
, (float)final_wait
/ 1000);
677 result
= wait_for_reply(icmp_sock
, final_wait
);
681 /* response structure:
682 * ip header : 20 bytes
683 * icmp header : 28 bytes
684 * icmp echo reply : the rest
687 wait_for_reply(int sock
, u_int t
)
690 static char buf
[4096];
691 struct sockaddr_in resp_addr
;
694 struct rta_host
*host
;
695 struct icmp_ping_data data
;
696 struct timeval wait_start
, now
;
697 u_int tdiff
, i
, per_pkt_wait
;
699 /* if we can't listen or don't have anything to listen to, just return */
700 if(!t
|| !icmp_pkts_en_route
) return 0;
702 gettimeofday(&wait_start
, &tz
);
705 per_pkt_wait
= t
/ icmp_pkts_en_route
;
706 while(icmp_pkts_en_route
&& get_timevaldiff(&wait_start
, NULL
) < i
) {
709 /* wrap up if all targets are declared dead */
711 get_timevaldiff(&prog_start
, NULL
) >= max_completion_time
||
712 (mode
== MODE_HOSTCHECK
&& targets_down
))
717 /* reap responses until we hit a timeout */
718 n
= recvfrom_wto(sock
, buf
, sizeof(buf
),
719 (struct sockaddr
*)&resp_addr
, &t
);
722 printf("recvfrom_wto() timed out during a %u usecs wait\n",
725 continue; /* timeout for this one, so keep trying */
728 if(debug
) printf("recvfrom_wto() returned errors\n");
732 ip
= (struct ip
*)buf
;
733 if(debug
> 1) printf("received %u bytes from %s\n",
734 ntohs(ip
->ip_len
), inet_ntoa(resp_addr
.sin_addr
));
736 /* obsolete. alpha on tru64 provides the necessary defines, but isn't broken */
737 /* #if defined( __alpha__ ) && __STDC__ && !defined( __GLIBC__ ) */
738 /* alpha headers are decidedly broken. Using an ansi compiler,
739 * they provide ip_vhl instead of ip_hl and ip_v, so we mask
740 * off the bottom 4 bits */
741 /* hlen = (ip->ip_vhl & 0x0f) << 2; */
743 hlen
= ip
->ip_hl
<< 2;
746 if(n
< (hlen
+ ICMP_MINLEN
)) {
747 crash("received packet too short for ICMP (%d bytes, expected %d) from %s\n",
748 n
, hlen
+ icmp_pkt_size
, inet_ntoa(resp_addr
.sin_addr
));
750 /* else if(debug) { */
751 /* printf("ip header size: %u, packet size: %u (expected %u, %u)\n", */
752 /* hlen, ntohs(ip->ip_len) - hlen, */
753 /* sizeof(struct ip), icmp_pkt_size); */
756 /* check the response */
757 memcpy(&icp
, buf
+ hlen
, sizeof(icp
));
759 if(icp
.icmp_id
!= pid
) {
760 handle_random_icmp(buf
+ hlen
, &resp_addr
);
764 if(icp
.icmp_type
!= ICMP_ECHOREPLY
|| icp
.icmp_seq
>= targets
) {
765 if(debug
> 2) printf("not a proper ICMP_ECHOREPLY\n");
766 handle_random_icmp(buf
+ hlen
, &resp_addr
);
770 /* this is indeed a valid response */
771 memcpy(&data
, icp
.icmp_data
, sizeof(data
));
773 host
= table
[icp
.icmp_seq
];
774 gettimeofday(&now
, &tz
);
775 tdiff
= get_timevaldiff(&data
.stime
, &now
);
777 host
->time_waited
+= tdiff
;
782 printf("%0.3f ms rtt from %s, outgoing ttl: %u, incoming ttl: %u\n",
783 (float)tdiff
/ 1000, inet_ntoa(resp_addr
.sin_addr
),
787 /* if we're in hostcheck mode, exit with limited printouts */
788 if(mode
== MODE_HOSTCHECK
) {
789 printf("OK - %s responds to ICMP. Packet %u, rta %0.3fms|"
790 "pkt=%u;;0;%u rta=%0.3f;%0.3f;%0.3f;;\n",
791 host
->name
, icmp_recv
, (float)tdiff
/ 1000,
792 icmp_recv
, packets
, (float)tdiff
/ 1000,
793 (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000);
801 /* the ping functions */
803 send_icmp_ping(int sock
, struct rta_host
*host
)
806 char *buf
; /* re-use so we prevent leaks */
811 struct icmp_ping_data data
;
813 struct sockaddr
*addr
;
817 crash("Attempt to send on bogus socket");
820 addr
= (struct sockaddr
*)&host
->saddr_in
;
823 if (!(packet
.buf
= malloc(icmp_pkt_size
))) {
824 crash("send_icmp_ping(): failed to malloc %d bytes for send buffer",
826 return -1; /* might be reached if we're in debug mode */
829 memset(packet
.buf
, 0, icmp_pkt_size
);
831 if((gettimeofday(&tv
, &tz
)) == -1) return -1;
833 data
.ping_id
= 10; /* host->icmp.icmp_sent; */
834 memcpy(&data
.stime
, &tv
, sizeof(tv
));
835 memcpy(&packet
.icp
->icmp_data
, &data
, sizeof(data
));
836 packet
.icp
->icmp_type
= ICMP_ECHO
;
837 packet
.icp
->icmp_code
= 0;
838 packet
.icp
->icmp_cksum
= 0;
839 packet
.icp
->icmp_id
= pid
;
840 packet
.icp
->icmp_seq
= host
->id
;
841 packet
.icp
->icmp_cksum
= icmp_checksum(packet
.cksum_in
, icmp_pkt_size
);
843 len
= sendto(sock
, packet
.buf
, icmp_pkt_size
, 0, (struct sockaddr
*)addr
,
844 sizeof(struct sockaddr
));
846 if(len
< 0 || (unsigned int)len
!= icmp_pkt_size
) {
847 if(debug
) printf("Failed to send ping to %s\n",
848 inet_ntoa(host
->saddr_in
.sin_addr
));
859 recvfrom_wto(int sock
, char *buf
, unsigned int len
, struct sockaddr
*saddr
,
864 struct timeval to
, then
, now
;
868 if(debug
) printf("*timo is not\n");
872 to
.tv_sec
= *timo
/ 1000000;
873 to
.tv_usec
= (*timo
- (to
.tv_sec
* 1000000));
879 gettimeofday(&then
, &tz
);
880 n
= select(sock
+ 1, &rd
, &wr
, NULL
, &to
);
881 if(n
< 0) crash("select() in recvfrom_wto");
882 gettimeofday(&now
, &tz
);
883 *timo
= get_timevaldiff(&then
, &now
);
885 if(!n
) return 0; /* timeout */
887 slen
= sizeof(struct sockaddr
);
889 return recvfrom(sock
, buf
, len
, 0, saddr
, &slen
);
898 struct rta_host
*host
;
899 const char *status_string
[] =
900 {"OK", "WARNING", "CRITICAL", "UNKNOWN", "DEPENDENT"};
905 if(debug
> 1) printf("finish(%d) called\n", sig
);
907 if(icmp_sock
!= -1) close(icmp_sock
);
908 if(udp_sock
!= -1) close(udp_sock
);
909 if(tcp_sock
!= -1) close(tcp_sock
);
912 printf("icmp_sent: %u icmp_recv: %u icmp_lost: %u\n",
913 icmp_sent
, icmp_recv
, icmp_lost
);
914 printf("targets: %u targets_alive: %u\n", targets
, targets_alive
);
917 /* iterate thrice to calculate values, give output, and print perfparse */
920 if(!host
->icmp_recv
) {
921 /* rta 0 is ofcourse not entirely correct, but will still show up
922 * conspicuosly as missing entries in perfparse and cacti */
925 status
= STATE_CRITICAL
;
926 /* up the down counter if not already counted */
927 if(!(host
->flags
& FLAG_LOST_CAUSE
) && targets_alive
) targets_down
++;
930 pl
= ((host
->icmp_sent
- host
->icmp_recv
) * 100) / host
->icmp_sent
;
931 rta
= (double)host
->time_waited
/ host
->icmp_recv
;
935 if(pl
>= crit
.pl
|| rta
>= crit
.rta
) {
936 status
= STATE_CRITICAL
;
938 else if(!status
&& (pl
>= warn
.pl
|| rta
>= warn
.rta
)) {
939 status
= STATE_WARNING
;
948 /* this is inevitable */
949 if(!targets_alive
) status
= STATE_CRITICAL
;
950 if(min_hosts_alive
> -1) {
951 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
952 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
954 printf("%s - ", status_string
[status
]);
960 if(i
< targets
) printf(" :: ");
964 if(!host
->icmp_recv
) {
965 status
= STATE_CRITICAL
;
966 if(host
->flags
& FLAG_LOST_CAUSE
) {
967 printf("%s: %s @ %s. rta nan, lost %d%%",
969 get_icmp_error_msg(host
->icmp_type
, host
->icmp_code
),
970 inet_ntoa(host
->error_addr
),
973 else { /* not marked as lost cause, so we have no flags for it */
974 printf("%s: rta nan, lost 100%%", host
->name
);
977 else { /* !icmp_recv */
978 printf("%s: rta %0.3fms, lost %u%%",
979 host
->name
, host
->rta
/ 1000, host
->pl
);
985 /* iterate once more for pretty perfparse output */
991 printf("%srta=%0.3fms;%0.3f;%0.3f;0; %spl=%u%%;%u;%u;; ",
992 (targets
> 1) ? host
->name
: "",
993 host
->rta
/ 1000, (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000,
994 (targets
> 1) ? host
->name
: "",
995 host
->pl
, warn
.pl
, crit
.pl
);
1000 if(min_hosts_alive
> -1) {
1001 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
1002 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
1005 /* finish with an empty line */
1007 if(debug
) printf("targets: %u, targets_alive: %u, hosts_ok: %u, hosts_warn: %u, min_hosts_alive: %i\n",
1008 targets
, targets_alive
, hosts_ok
, hosts_warn
, min_hosts_alive
);
1014 get_timevaldiff(struct timeval
*early
, struct timeval
*later
)
1020 gettimeofday(&now
, &tz
);
1023 if(!early
) early
= &prog_start
;
1025 /* if early > later we return 0 so as to indicate a timeout */
1026 if(early
->tv_sec
> early
->tv_sec
||
1027 (early
->tv_sec
== later
->tv_sec
&& early
->tv_usec
> later
->tv_usec
))
1032 ret
= (later
->tv_sec
- early
->tv_sec
) * 1000000;
1033 ret
+= later
->tv_usec
- early
->tv_usec
;
1039 add_target_ip(char *arg
, struct in_addr
*in
)
1041 struct rta_host
*host
;
1043 /* disregard obviously stupid addresses */
1044 if(in
->s_addr
== INADDR_NONE
|| in
->s_addr
== INADDR_ANY
)
1047 /* no point in adding two identical IP's, so don't. ;) */
1050 if(host
->saddr_in
.sin_addr
.s_addr
== in
->s_addr
) {
1051 if(debug
) printf("Identical IP already exists. Not adding %s\n", arg
);
1057 /* add the fresh ip */
1058 host
= malloc(sizeof(struct rta_host
));
1060 crash("add_target_ip(%s, %s): malloc(%d) failed",
1061 arg
, inet_ntoa(*in
), sizeof(struct rta_host
));
1063 memset(host
, 0, sizeof(struct rta_host
));
1065 /* set the values. use calling name for output */
1066 host
->name
= strdup(arg
);
1068 /* fill out the sockaddr_in struct */
1069 host
->saddr_in
.sin_family
= AF_INET
;
1070 host
->saddr_in
.sin_addr
.s_addr
= in
->s_addr
;
1072 if(!list
) list
= cursor
= host
;
1073 else cursor
->next
= host
;
1081 /* wrapper for add_target_ip */
1083 add_target(char *arg
)
1087 struct in_addr
*in
, ip
;
1089 /* don't resolve if we don't have to */
1090 if((ip
.s_addr
= inet_addr(arg
)) != INADDR_NONE
) {
1091 /* don't add all ip's if we were given a specific one */
1092 return add_target_ip(arg
, &ip
);
1093 /* he = gethostbyaddr((char *)in, sizeof(struct in_addr), AF_INET); */
1094 /* if(!he) return add_target_ip(arg, in); */
1098 he
= gethostbyname(arg
);
1101 crash("Failed to resolve %s", arg
);
1106 /* possibly add all the IP's as targets */
1107 for(i
= 0; he
->h_addr_list
[i
]; i
++) {
1108 in
= (struct in_addr
*)he
->h_addr_list
[i
];
1109 add_target_ip(arg
, in
);
1111 /* this is silly, but it works */
1112 if(mode
== MODE_HOSTCHECK
|| mode
== MODE_ALL
) {
1113 if(debug
> 2) printf("mode: %d\n", mode
);
1123 set_source_ip(char *arg
)
1125 struct sockaddr_in src
;
1127 memset(&src
, 0, sizeof(src
));
1128 src
.sin_family
= AF_INET
;
1129 if((src
.sin_addr
.s_addr
= inet_addr(arg
)) == INADDR_NONE
)
1130 src
.sin_addr
.s_addr
= get_ip_address(arg
);
1131 if(bind(icmp_sock
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
1132 crash("Cannot bind to IP address %s", arg
);
1135 /* TODO: Move this to netutils.c and also change check_dhcp to use that. */
1137 get_ip_address(const char *ifname
)
1139 #if defined(SIOCGIFADDR)
1141 struct sockaddr_in ip
;
1143 strncpy(ifr
.ifr_name
, ifname
, sizeof(ifr
.ifr_name
) - 1);
1144 ifr
.ifr_name
[sizeof(ifr
.ifr_name
) - 1] = '\0';
1145 if(ioctl(icmp_sock
, SIOCGIFADDR
, &ifr
) == -1)
1146 crash("Cannot determine IP address of interface %s", ifname
);
1147 memcpy(&ip
, &ifr
.ifr_addr
, sizeof(ip
));
1148 return ip
.sin_addr
.s_addr
;
1151 crash("Cannot get interface IP address on this platform.");
1159 * return value is in microseconds
1162 get_timevar(const char *str
)
1166 u_int i
, d
; /* integer and decimal, respectively */
1167 u_int factor
= 1000; /* default to milliseconds */
1173 /* unit might be given as ms|m (millisec),
1174 * us|u (microsec) or just plain s, for seconds */
1177 if(len
>= 2 && !isdigit((int)str
[len
- 2])) p
= str
[len
- 2];
1178 if(p
&& u
== 's') u
= p
;
1180 if(debug
> 2) printf("evaluating %s, u: %c, p: %c\n", str
, u
, p
);
1182 if(u
== 'u') factor
= 1; /* microseconds */
1183 else if(u
== 'm') factor
= 1000; /* milliseconds */
1184 else if(u
== 's') factor
= 1000000; /* seconds */
1185 if(debug
> 2) printf("factor is %u\n", factor
);
1187 i
= strtoul(str
, &ptr
, 0);
1188 if(!ptr
|| *ptr
!= '.' || strlen(ptr
) < 2 || factor
== 1)
1191 /* time specified in usecs can't have decimal points, so ignore them */
1192 if(factor
== 1) return i
;
1194 d
= strtoul(ptr
+ 1, NULL
, 0);
1196 /* d is decimal, so get rid of excess digits */
1197 while(d
>= factor
) d
/= 10;
1199 /* the last parenthesis avoids floating point exceptions. */
1200 return ((i
* factor
) + (d
* (factor
/ 10)));
1203 /* not too good at checking errors, but it'll do (main() should barfe on -1) */
1205 get_threshold(char *str
, threshold
*th
)
1207 char *p
= NULL
, i
= 0;
1209 if(!str
|| !strlen(str
) || !th
) return -1;
1211 /* pointer magic slims code by 10 lines. i is bof-stop on stupid libc's */
1212 p
= &str
[strlen(str
) - 1];
1213 while(p
!= &str
[1]) {
1214 if(*p
== '%') *p
= '\0';
1215 else if(*p
== ',' && i
) {
1216 *p
= '\0'; /* reset it so get_timevar(str) works nicely later */
1217 th
->pl
= (unsigned char)strtoul(p
+1, NULL
, 0);
1223 th
->rta
= get_timevar(str
);
1225 if(!th
->rta
) return -1;
1227 if(th
->rta
> MAXTTL
* 1000000) th
->rta
= MAXTTL
* 1000000;
1228 if(th
->pl
> 100) th
->pl
= 100;
1234 icmp_checksum(unsigned short *p
, int n
)
1236 register unsigned short cksum
;
1237 register long sum
= 0;
1244 /* mop up the occasional odd byte */
1245 if(n
== 1) sum
+= (unsigned char)*p
;
1247 sum
= (sum
>> 16) + (sum
& 0xffff); /* add hi 16 to low 16 */
1248 sum
+= (sum
>> 16); /* add carry */
1249 cksum
= ~sum
; /* ones-complement, trunc to 16 bits */
1258 /*print_revision (progname, revision);*/ /* FIXME: Why? */
1260 printf ("Copyright (c) 2005 Andreas Ericsson <ae@op5.se>\n");
1261 printf (COPYRIGHT
, copyright
, email
);
1267 printf (_(UT_HELP_VRSN
));
1269 printf (" %s\n", "-H");
1270 printf (" %s\n", _("specify a target"));
1271 printf (" %s\n", "-w");
1272 printf (" %s", _("warning threshold (currently "));
1273 printf ("%0.3fms,%u%%)\n", (float)warn
.rta
/ 1000 , warn
.pl
/ 1000);
1274 printf (" %s\n", "-c");
1275 printf (" %s", _("critical threshold (currently "));
1276 printf ("%0.3fms,%u%%)\n", (float)crit
.rta
, crit
.pl
);
1277 printf (" %s\n", "-s");
1278 printf (" %s\n", _("specify a source IP address or device name"));
1279 printf (" %s\n", "-n");
1280 printf (" %s", _("number of packets to send (currently "));
1281 printf ("%u)\n",packets
);
1282 printf (" %s\n", "-i");
1283 printf (" %s", _("max packet interval (currently "));
1284 printf ("%0.3fms)\n",(float)pkt_interval
/ 1000);
1285 printf (" %s\n", "-I");
1286 printf (" %s", _("max target interval (currently "));
1287 printf ("%0.3fms)\n", (float)target_interval
/ 1000);
1288 printf (" %s\n", "-m");
1289 printf (" %s",_("number of alive hosts required for success"));
1291 printf (" %s\n", "-l");
1292 printf (" %s", _("TTL on outgoing packets (currently "));
1293 printf ("%u)", ttl
);
1294 printf (" %s\n", "-t");
1295 printf (" %s",_("timeout value (seconds, currently "));
1296 printf ("%u)\n", timeout
);
1297 printf (" %s\n", "-b");
1298 printf (" %s\n", _("icmp packet size (currenly ignored)"));
1299 printf (" %s\n", "-v");
1300 printf (" %s\n", _("verbose"));
1303 printf ("%s\n\n", _("The -H switch is optional. Naming a host (or several) to check is not."));
1304 printf ("%s\n", _("Threshold format for -w and -c is 200.25,60% for 200.25 msec RTA and 60%"));
1305 printf ("%s\n", _("packet loss. The default values should work well for most users."));
1306 printf ("%s\n", _("You can specify different RTA factors using the standardized abbreviations"));
1307 printf ("%s\n\n", _("us (microseconds), ms (milliseconds, default) or just plain s for seconds."));
1308 /* -d not yet implemented */
1309 /* printf ("%s\n", _("Threshold format for -d is warn,crit. 12,14 means WARNING if >= 12 hops"));
1310 printf ("%s\n", _("are spent and CRITICAL if >= 14 hops are spent."));
1311 printf ("%s\n\n", _("NOTE: Some systems decrease TTL when forming ICMP_ECHOREPLY, others do not."));*/
1312 printf ("%s\n\n", _("The -v switch can be specified several times for increased verbosity."));
1314 /* printf ("%s\n", _("Long options are currently unsupported."));
1315 printf ("%s\n", _("Options marked with * require an argument"));
1317 printf (_(UT_SUPPORT
));
1319 printf (_(UT_NOWARRANTY
));
1327 printf (_("Usage:"));
1328 printf(" %s [options] [-H] host1 host2 hostn\n", progname
);